Reputation: 759
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:
Wrong spacing:
Upvotes: 52
Views: 24249
Reputation: 1
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
}
Upvotes: 0
Reputation: 2336
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)
Upvotes: 75
Reputation: 866
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;
Upvotes: 4
Reputation: 759
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.
Upvotes: 12
Reputation: 2411
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)
}
}
Upvotes: 0
Reputation: 56
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.
Upvotes: 0
Reputation: 5107
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;
}
Upvotes: 0