Text like PlaceHolder

Hi I have a textField to provide Date for Birth(Requirement). Initially the textField should have the text "DD MM YYYY" . And if I key in the date of birth, need to remove characters (DD MM YYYY) one by one.

Scenario:

DD MM YYYY is the text displayed in TextField initially.

After typing first digit(for example 3), it should be like " 3D MM YYYY "

After typing second digit(for example 1, it should be like " 31 MM YYYY "

After typing third digit(for example 0, it should be like " 31 0M YYYY "

After typing fourth digit(for example 8, it should be like " 31 08 YYYY " and so on.... Is this possible?

Accepted Reply

In previous code, there are some issues with darkmode:

  • placeholder hardly readable (a known bug in iOS in dark mode)
  • when switching mode, colors did not immediately adjust.

Here is the improved code:

class TextFieldDynPlaceHolder: UITextField, UITextFieldDelegate {
     
     let defaultPlaceHolder = "DD MM YYYY" // Could set it as IBInspectable property
     var thePlaceHolder : String!     // To be set at call
     
     // --------------------- commonInit ----------------------------------------------------
     //  Description: set the placeholder from text
     //  Parameters
     //  Comments:
     //     Need to set borderStyle (bug in iOS): https://stackoverflow.com/questions/64057501/how-to-fix-uitextfield-background-image-not-displayed-after-ios14
     //  -------------------------------------------------------------------------------------------------

     func commonInit() {
          
          delegate = self
          thePlaceHolder = defaultPlaceHolder // "DD MM YYYY"
          self.background = image(from: thePlaceHolder)
          self.borderStyle = .line
     }
     
     override init(frame: CGRect) {
          
          super.init(frame: frame)
          commonInit()
     }
     
     required init?(coder: NSCoder) {
          super.init(coder: coder)
          commonInit()
     }
     
     // --------------------- traitCollectionDidChange -------------------------------------------
     //  Description: Adjust colors when switching mode light / dark.
     //  Parameters
     //        previousTraitCollection: UITraitCollection?
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
             super.traitCollectionDidChange(previousTraitCollection)

          adaptPlaceHolder(self)
         }

     // --------------------- image -------------------------------------------
     //  Description: Create image from a text.
     //  Parameters
     //        from text: String?
     //  Comments:
     //     https://stackoverflow.com/questions/51100121/how-to-generate-an-uiimage-from-custom-text-in-swift
     //     In fact, placeholderText unreadable in darkMode. https://stackoverflow.com/questions/58478744/uitextfield-placeholder-text-is-unreadable-in-ios13-dark-mode
     //  -------------------------------------------------------------------------------------------------

     func image(from text: String?) -> UIImage? {
          
          let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
          let nameLabel = UILabel(frame: frame)
          nameLabel.textAlignment = .left
          nameLabel.backgroundColor = .systemBackground
          if traitCollection.userInterfaceStyle == .light {
               nameLabel.textColor = .placeholderText // .darkGray if .placeholderText is too light
          } else {
               nameLabel.textColor = .systemGray
          }

          nameLabel.font = self.font
          nameLabel.text = text
          UIGraphicsBeginImageContext(frame.size)
           if let currentContext = UIGraphicsGetCurrentContext() {
              nameLabel.layer.render(in: currentContext)
              let nameImage = UIGraphicsGetImageFromCurrentImageContext()
              return nameImage
           }
           return nil
     }
     
     // --------------------- adaptPlaceHolder --------------------------------------
     //  Description: Trims placeHolder text when first characters typed ; rebuilds image.
     //  Parameters
     //        textField: UITextField
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     func adaptPlaceHolder(_ textField: UITextField) {
          
          var trimmedPlaceHolder = defaultPlaceHolder

          if let typed = textField.text?.count, typed > 0 {
               trimmedPlaceHolder = String(defaultPlaceHolder.dropFirst(typed))
               // And we replace with spaces
               for _ in 0..<typed {
                    trimmedPlaceHolder = " " + trimmedPlaceHolder
               }
               self.background = image(from: trimmedPlaceHolder)
          } else {
               self.background = image(from: trimmedPlaceHolder)
          }
     }
     
     // --------------------- adaptPlaceHolder --------------------------------------
     //  Description: redraws placeHolder image when characters typed and checks characters are decimals
     //  Parameters
     //        textField: UITextField
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     func textFieldDidChangeSelection(_ textField: UITextField) {
          
          if let typedText = textField.text {
               for c in typedText.unicodeScalars {
                    if !CharacterSet.decimalDigits.contains(c) && !CharacterSet.whitespaces.contains(c) {
                         // And play a beep
                         textField.text = String(textField.text!.dropLast())
                    }
               }
          }

          adaptPlaceHolder(textField)
     }
     
     // --------------------- textField --------------------------------------
     //  Description: test the validity of entry (as a Date for instance)
     //  Parameters
     //        textField: UITextField
     //        shouldChangeCharactersIn range: NSRange
     //        replacementString string: String
     //  Comments:
     //        To be implemented
     //  -------------------------------------------------------------------------------------------------

     func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          
          return true
     }
     
}
  • Hi Claude31, Thank you very much.

Add a Comment

Replies

As Beatles would have song, yes, you can display anything you want…

More seriously, you have to consider:

  • 1 that "placeholder" part must be gray: hence need attributedString
  • 2 that you have to type text at the place of the first placeHolder char : need to force the insertion point
  • 3 at each character typed, substitute in the String if valid ; use :
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  • 4 to insert space at appropriate places
  • 5 that user must understand what is expected.

But it is possible. You'll probably have to subclass UITextField.

Another way to do this is

  • add a label (smaller fonts) atop of the textField.
  • make this label appear as soon as you've started to type.
  • display " 31 0M YYYY " only in the label; in textField, you would have " 31 0"

That would avoid step 2 and make step 5 easier

Yet another way:

  • create 3 textFields for Day, Month and year
  • have placeholders in each
  • automatically jump to next textField once one is complete.
  • Hi Claude31, Thanks for your points. It will be more helpful if you share the code for the First Method. Thank u

  • Sure that would be helpfu… So, start writing and ask when there is something you cannot make working.

    To help you start I will prepare something and post later.

Add a Comment

Well, I think I found a much simpler solution:

  • create a background image with the text for placeholder
  • then, when you type, the new text will appear just over the placeholder.

I've left some links to additional information if needed

Here is a sample code:

class TextFieldDynPlaceHolder: UITextField, UITextFieldDelegate {
     
     let defaultPlaceHolder = "DD MM YYYY"
     var thePlaceHolder : String!     // To be set at call
     
     func commonInit() {
          
          delegate = self
          thePlaceHolder = defaultPlaceHolder // "DD MM YYYY"
          self.background = image(from: thePlaceHolder)
          // Need to set borderStyle: https://stackoverflow.com/questions/64057501/how-to-fix-uitextfield-background-image-not-displayed-after-ios14
          self.borderStyle = .line
     }
     
     // https://stackoverflow.com/questions/759658/uitextview-background-image
     override init(frame: CGRect) {
          
          super.init(frame: frame)
          commonInit()
     }
     
     required init?(coder: NSCoder) {
          super.init(coder: coder)
          commonInit()
     }
     
     // https://stackoverflow.com/questions/51100121/how-to-generate-an-uiimage-from-custom-text-in-swift
     func image(from text: String?) -> UIImage? {
          let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)     
          let nameLabel = UILabel(frame: frame)
          nameLabel.textAlignment = .left
          nameLabel.backgroundColor = .systemBackground 
          nameLabel.textColor = .placeholderText // .darkGray if .placeholderText is too light
          nameLabel.font = self.font 
          nameLabel.text = text
          UIGraphicsBeginImageContext(frame.size)
           if let currentContext = UIGraphicsGetCurrentContext() {
              nameLabel.layer.render(in: currentContext)
              let nameImage = UIGraphicsGetImageFromCurrentImageContext()
              return nameImage
           }
           return nil
     }
     
    // Check we typed digits or space or backspace
     func textFieldDidChangeSelection(_ textField: UITextField) {
          
          var trimmedPlaceHolder = defaultPlaceHolder
          
          if let typedText = textField.text {
               for c in typedText.unicodeScalars {
                    if !CharacterSet.decimalDigits.contains(c) && !CharacterSet.whitespaces.contains(c) {
                         // And play a beep
                         textField.text = String(textField.text!.dropLast())
                    }
               }
          }

          if let typed = textField.text?.count, typed > 0 {
               trimmedPlaceHolder = String(defaultPlaceHolder.dropFirst(typed))
               // And we replace with spaces
               for _ in 0..<typed {
                    trimmedPlaceHolder = " " + trimmedPlaceHolder
               }
               self.background = image(from: trimmedPlaceHolder)
          } else {
               self.background = image(from: trimmedPlaceHolder)
          }
     }
     
    // Could check here the complete date Validity
     func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          
          return true // Here you could test the validity of entry as a Date
     }
     
}

To test it:

  • in storyboard, create a UITextField and declare as TextFieldDynPlaceHolder
  • select a fixed width font (otherwise, date will nor superpose placeholder)
  • set its width to what you need (don't let it free, otherwise placeholder may not show)
  • in the controller, set thePlaceHolder to the date you need
  • May set thePlaceHolder in commonInit() (change from = "DD MM YYYY")

If that works, thanks to tell as well as to post any improvement you may find, like better testing the validity of what was typed. And then don't forget to close the thread.

In previous code, there are some issues with darkmode:

  • placeholder hardly readable (a known bug in iOS in dark mode)
  • when switching mode, colors did not immediately adjust.

Here is the improved code:

class TextFieldDynPlaceHolder: UITextField, UITextFieldDelegate {
     
     let defaultPlaceHolder = "DD MM YYYY" // Could set it as IBInspectable property
     var thePlaceHolder : String!     // To be set at call
     
     // --------------------- commonInit ----------------------------------------------------
     //  Description: set the placeholder from text
     //  Parameters
     //  Comments:
     //     Need to set borderStyle (bug in iOS): https://stackoverflow.com/questions/64057501/how-to-fix-uitextfield-background-image-not-displayed-after-ios14
     //  -------------------------------------------------------------------------------------------------

     func commonInit() {
          
          delegate = self
          thePlaceHolder = defaultPlaceHolder // "DD MM YYYY"
          self.background = image(from: thePlaceHolder)
          self.borderStyle = .line
     }
     
     override init(frame: CGRect) {
          
          super.init(frame: frame)
          commonInit()
     }
     
     required init?(coder: NSCoder) {
          super.init(coder: coder)
          commonInit()
     }
     
     // --------------------- traitCollectionDidChange -------------------------------------------
     //  Description: Adjust colors when switching mode light / dark.
     //  Parameters
     //        previousTraitCollection: UITraitCollection?
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
             super.traitCollectionDidChange(previousTraitCollection)

          adaptPlaceHolder(self)
         }

     // --------------------- image -------------------------------------------
     //  Description: Create image from a text.
     //  Parameters
     //        from text: String?
     //  Comments:
     //     https://stackoverflow.com/questions/51100121/how-to-generate-an-uiimage-from-custom-text-in-swift
     //     In fact, placeholderText unreadable in darkMode. https://stackoverflow.com/questions/58478744/uitextfield-placeholder-text-is-unreadable-in-ios13-dark-mode
     //  -------------------------------------------------------------------------------------------------

     func image(from text: String?) -> UIImage? {
          
          let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
          let nameLabel = UILabel(frame: frame)
          nameLabel.textAlignment = .left
          nameLabel.backgroundColor = .systemBackground
          if traitCollection.userInterfaceStyle == .light {
               nameLabel.textColor = .placeholderText // .darkGray if .placeholderText is too light
          } else {
               nameLabel.textColor = .systemGray
          }

          nameLabel.font = self.font
          nameLabel.text = text
          UIGraphicsBeginImageContext(frame.size)
           if let currentContext = UIGraphicsGetCurrentContext() {
              nameLabel.layer.render(in: currentContext)
              let nameImage = UIGraphicsGetImageFromCurrentImageContext()
              return nameImage
           }
           return nil
     }
     
     // --------------------- adaptPlaceHolder --------------------------------------
     //  Description: Trims placeHolder text when first characters typed ; rebuilds image.
     //  Parameters
     //        textField: UITextField
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     func adaptPlaceHolder(_ textField: UITextField) {
          
          var trimmedPlaceHolder = defaultPlaceHolder

          if let typed = textField.text?.count, typed > 0 {
               trimmedPlaceHolder = String(defaultPlaceHolder.dropFirst(typed))
               // And we replace with spaces
               for _ in 0..<typed {
                    trimmedPlaceHolder = " " + trimmedPlaceHolder
               }
               self.background = image(from: trimmedPlaceHolder)
          } else {
               self.background = image(from: trimmedPlaceHolder)
          }
     }
     
     // --------------------- adaptPlaceHolder --------------------------------------
     //  Description: redraws placeHolder image when characters typed and checks characters are decimals
     //  Parameters
     //        textField: UITextField
     //  Comments:
     //  -------------------------------------------------------------------------------------------------

     func textFieldDidChangeSelection(_ textField: UITextField) {
          
          if let typedText = textField.text {
               for c in typedText.unicodeScalars {
                    if !CharacterSet.decimalDigits.contains(c) && !CharacterSet.whitespaces.contains(c) {
                         // And play a beep
                         textField.text = String(textField.text!.dropLast())
                    }
               }
          }

          adaptPlaceHolder(textField)
     }
     
     // --------------------- textField --------------------------------------
     //  Description: test the validity of entry (as a Date for instance)
     //  Parameters
     //        textField: UITextField
     //        shouldChangeCharactersIn range: NSRange
     //        replacementString string: String
     //  Comments:
     //        To be implemented
     //  -------------------------------------------------------------------------------------------------

     func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          
          return true
     }
     
}
  • Hi Claude31, Thank you very much.

Add a Comment