pravin nagargoje
pravin nagargoje

Reputation: 70

While entering text in UITextField placeholder should disappear one letter at at time

I have created a UITextfield where i want to add 4 digit password, but once I enter the first digit whole placeholder disappear. how can I keep next three placeholder letters in UITextfield while entering next password letters.

here is my screenshot:

enter image description here

Upvotes: 1

Views: 1363

Answers (3)

A. Welch
A. Welch

Reputation: 356

Swift 4

This is a modified version of @Denislava Shentova's answer that I believe simplifies into less lines of code, fixes issues, and makes the code more readable.

Not Fully Tested

import UIKit

class YourClass: UIViewController {

    //It is also a good idea to deny users the ability to paste into this textField.

    //set up textField
    let textField : UITextField = {
        let textField = UITextField()
        // These are 'long dashes' you can replace them with whatever you would like.
        // These look best IMO.
        textField.text = "——————" //No spaces needed!
        textField.textColor = .black
        textField.textAlignment = .center
        textField.tintColor = .clear //this will hide the cursor.

        return textField
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        textField.delegate = self
        //This sets the spacing or 'Kern' of the textfield. Adjust the value: 10.0 and the fontSize to get the desired output.
        textField.defaultTextAttributes.updateValue(10.0, forKey: NSAttributedStringKey.kern.rawValue)
    }
}
extension YourClass : UITextFieldDelegate {
        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        //get the current text of the textField.
        guard let text = textField.text else { return false }

        //handle backspace event.
        if string == "" {
            guard let indexToReplace = text.index(text.startIndex, offsetBy: range.location, limitedBy: text.endIndex) else { return false }
            textField.text?.remove(at: indexToReplace)
            textField.text?.insert("—", at: indexToReplace)
            //adjust cursor position
            if let newPostion = textField.position(from: textField.beginningOfDocument, offset: range.location) {
                textField.selectedTextRange = textField.textRange(from: newPostion, to: newPostion)
                return false
            }
        }
        //handle character entered event.
        if range.location + 1 <= text.count,
            let end = text.index(text.startIndex, offsetBy: range.location + 1, limitedBy: text.endIndex),
            let start = text.index(text.startIndex, offsetBy: range.location, limitedBy: text.endIndex) {
            textField.text = textField.text?.replacingOccurrences(of: "—", with: string, options: .caseInsensitive, range: Range(uncheckedBounds: (lower: start, upper: end)))
        }
        //adjust cursor position.
        if range.location + 1 < text.count {
            if let newPosition = textField.position(from: textField.beginningOfDocument, offset: range.location + 1){
                textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
            }
        }
        return false
    }
    //make sure to start at the begining of the textField.
    func textFieldDidBeginEditing(_ textField: UITextField) {
        let newPosition = textField.beginningOfDocument
        textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
    }
}

Upvotes: 0

Denislava Shentova
Denislava Shentova

Reputation: 741

I wanted to try SeanLintern88's solution because it sounded like a little challange. And it is in the case where the text field should have spaces between the underscore.

textField.text = "_ _ _ _"

This is the solution I came with and while it was fun to write, this is something I do not recommend using in real projects. Better try the 4 separate text fields approach :)

extension ViewController: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        guard let text = textField.text else { return false }

        var location = range.location

        //is deleting
        if string == "" {

            guard let indexToReplace = text.index(text.startIndex, offsetBy: location, limitedBy: text.endIndex) else {
                return false
            }

            let isCharDeleted = location % 2 == 0
            //place " " or "_" depending on the position that was deleted
            let stringToReplaceWith = isCharDeleted ? "_" : " "
            let charachter = stringToReplaceWith[stringToReplaceWith.startIndex]
            textField.text?.remove(at: indexToReplace)
            textField.text?.insert(charachter, at: indexToReplace)

            var newCursorPositionOffset = location

            //deletetion occured on space " "
            if !isCharDeleted {

                guard let previousIndex = text.index(text.startIndex, offsetBy: location-1, limitedBy: text.endIndex) else {
                    return false
                }
                //delete the previous charachter
                textField.text?.remove(at: previousIndex)
                let dash = "_"
                let char = dash[dash.startIndex]

                textField.text?.insert(char, at: previousIndex)

                //correct cursor position
                newCursorPositionOffset -= 1
            }

            //move cursor position
            let newPosition = textField.position(from: textField.beginningOfDocument, offset: newCursorPositionOffset)
            textField.selectedTextRange = textField.textRange(from: newPosition!, to: newPosition!)

            return false
        }

        //is typing
        if range.location + 1 <= text.characters.count,
            let end = text.index(text.startIndex, offsetBy: location+1, limitedBy: text.endIndex),
            let start = text.index(text.startIndex, offsetBy: location, limitedBy: text.endIndex) {
            textField.text = textField.text?.replacingOccurrences(of: "_", with: string, options: .caseInsensitive, range: Range(uncheckedBounds: (lower: start, upper: end)))

            //correct the cursor position if placed on " " index
            if range.location % 2 != 0 {
                location -= 1
            }
        }

        //skip " " and move cursor to the next "_"
        if location+2 < text.characters.count {
            let newPosition = textField.position(from: textField.beginningOfDocument, offset: location+2)
            textField.selectedTextRange = textField.textRange(from: newPosition!, to: newPosition!)

        }

        return false
    }

    func textFieldDidBeginEditing(_ textField: UITextField) {
        let newPosition = textField.beginningOfDocument
        textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)


    }
}

PS: Probably it can be made shorter with better choosing of functions for string operations, but still a pretty .. unfriendly solution.

Conclusion: DON'T DO THIS AT HOME :D

Upvotes: 1

Sean Lintern
Sean Lintern

Reputation: 3141

Instead of using placeHolder, just override the shouldChangeCharactersInRange method and append the characters to the string until the string is 4 characters long, you can also use attributed string if you want the _ to look different to the dots.

Upvotes: 1

Related Questions