How to set letter spacing of UITextField

IosObjective CSwiftUitextfield

Ios Problem Overview


I have an app in which the user has to type a four digit pin code. All digits have to be at a set distance from each other.

Is there a way to do this if the PinTextField is a subclass of UIView? I know in a ViewController you could use UITextFieldTextDidChangeNotification and set the attributed text for each change. Notifications don't seem to work in a UIView though.

Also I was wondering if there isn't a simpler way than making an attributed string for every update if you want to set the letter spacing of a UITextField text ?

Correct spacing:

TextField with correct spacing

Wrong spacing:

TextField with wrong spacing

Ios Solutions


Solution 1 - Ios

No need to go for attributedText, which to be honest, was a mess implementing with modified spacing. As soon as I closed the keyboard the spacing disappeared, which prompted me to dig further.

Every UITextField has a property called defaultTextAttributes, which according to Apple "returns a dictionary of text attributes with default values.". The Apple document also says that "this property applies the specified attributes to the entire text of the text field"

Just find a suitable place in your code, usually where the textfield is being initialized and then copy and paste the following.

Answered in Swift 3.0

textfield.defaultTextAttributes.updateValue(spacing, forKey: NSKernAttributeName)

where spacing is of CGFloat type. For example 2.0

This works for different fonts as well.

Cheers!!


The latest syntax seems to be:

 yourField.defaultTextAttributes.updateValue(36.0, 
     forKey: NSAttributedString.Key.kern)

Solution 2 - Ios

This is what eventually worked to set the kern for every change

    textField.addTarget(self, action: "textFieldDidChange", forControlEvents: .EditingChanged)

    func textFieldDidChange () {    
        let attributedString = NSMutableAttributedString(string: textField.text)
        attributedString.addAttribute(NSKernAttributeName, value: 5, range: NSMakeRange(0, count(textField.text)))
        attributedString.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, count(textField.text)))
        attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: NSMakeRange(0, count(textField.text)))
    
        textField.attributedText = attributedString
    }

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    
        if count(textField.text) < 4 {
            return true
            // Else if 4 and delete is allowed
        }else if count(string) == 0 {
            return true
            // Else limit reached
        }else{
            return false
        }
    }

The problem however remains because different numbers have different widths, I will just resort back to making a UITextField for every digit.

Solution 3 - Ios

Use the defaultTextAttributes property of UITextField. It will handle the conversion to NSAttributedString for you and apply the attributes you set. For example:

    NSMutableDictionary *attrs = [self.textField.defaultTextAttributes mutableCopy];
[attrs addEntriesFromDictionary:@{
    NSKernAttributeName: @18,
    NSUnderlineColorAttributeName: [UIColor grayColor],
    NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDash)
}];
self.textField.defaultTextAttributes = attrs;

Solution 4 - Ios

Not really sure about any other solution instead of using attributed string.

But for the notification part, you can set the textFields delegate to UIView and define below method in the view.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 

The above method is called every time the text entered in the text field changes.

Solution 5 - Ios

Try this code After setting the delegate to the textfield.Hope it will work.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:textField.text];
    [attributedString addAttribute:NSKernAttributeName
                             value:@(5.4)
                             range:NSMakeRange(0, textField.text.length)];
    textField.attributedText = attributedString;
    return YES;
}

Solution 6 - Ios

This is working fine in Swift 2.2. Hope this will help you for letter spacing in text field

override func viewDidLoad() {
 // Do any additional setup after loading the view.
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SignupVC.limitTextField(_:)), name: "UITextFieldTextDidChangeNotification", object: txtContactNumber)
}
 func limitTextField(Notif:NSNotification) {
    
    let limit=10;
    
    let attributedString = NSMutableAttributedString(string: txtContactNumber.text!)
    attributedString.addAttribute(NSKernAttributeName, value: 7, range: NSMakeRange(0, (txtContactNumber.text?.characters.count)!))
   // attributedString.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, count(textField.text)))
    attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: NSMakeRange(0,(txtContactNumber.text?.characters.count)!))
    
    txtContactNumber.attributedText = attributedString
    if(txtContactNumber.text?.characters.count>limit)
    {
        txtContactNumber.text=txtContactNumber.text?.substringToIndex(limit)
    }
}

Solution 7 - Ios

Need to count the kern for each character and remove it for the last character. There is example on Swift 5.3

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    let maxLength = 6
    let symbolWidth = CGFloat(43)
    let font = UIFont.systemFont(ofSize: 30)
    
    if string == "" { // when user remove text
        return true
    }
        
    if textField.text!.count + string.count - range.length > maxLength { // when user type extra text
        return false
    }
    
    let currentText = NSMutableAttributedString(attributedString: textField.attributedText ?? NSMutableAttributedString())
    currentText.deleteCharacters(in: range) // delete selected text
    var newStringLength = 0 
    
    for char in string{
        let newSymbol = NSMutableAttributedString(string: String(char))
        newSymbol.addAttribute(.font, value: font, range: NSMakeRange(0, 1))
        let currentSymbolWidth = newSymbol.size().width
        let kern = symbolWidth - currentSymbolWidth
        newSymbol.addAttribute(.kern, value: kern, range: NSMakeRange(0,1))
        
        currentText.insert(newSymbol, at: range.location + newStringLength)
        newStringLength += 1
    }
    
    if currentText.length == maxLength{
        currentText.addAttribute(.kern, value: 0, range: NSMakeRange(maxLength - 1, 1))
    }
    
    textField.attributedText = currentText
    
    return false

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKMVView Question on Stackoverflow
Solution 1 - IosiOSerView Answer on Stackoverflow
Solution 2 - IosKMVView Answer on Stackoverflow
Solution 3 - IosAviView Answer on Stackoverflow
Solution 4 - IosScarletView Answer on Stackoverflow
Solution 5 - IosVishnuvardhanView Answer on Stackoverflow
Solution 6 - IosArvind KumarView Answer on Stackoverflow
Solution 7 - IosBodja4xView Answer on Stackoverflow