Can you please help me.
In UITextField
when we provide a placeholder text its placeholder string will be gone when we enter any character. How can I achieve that only entered character will be gone not a full string? Meaning that if I type in 3 characters, only the first 3 characters of placeholder will be gone.
#EDIT 1
Also, newly entered character text color will change and other remaining character text color remains same.
Thanks in advance.
I don't believe the default behavior of the placeholder is editable, but what you are trying to accomplish can be done using NSAttributedString
to simulate the placeholder value.
I'm sure this can be optimized, but here I have created a handler class that acts as the delegate for a given UITextField
, manipulating the string the user inputs to achieve the desired effect. You init the handler with your desired placeholder string, so you can make any text field work this way.
import UIKit
class CustomPlaceholderTextFieldHandler: NSObject {
let placeholderText: String
let placeholderAttributes = [NSForegroundColorAttributeName : UIColor.lightGray]
let inputAttributes = [NSForegroundColorAttributeName : UIColor(red: 255/255, green: 153/255, blue: 0, alpha: 1.0)]
var input = ""
init(placeholder: String) {
self.placeholderText = placeholder
super.init()
}
func resetPlaceholder(for textField: UITextField) {
input = ""
setCombinedText(for: textField)
}
fileprivate func setCursorPosition(for textField: UITextField) {
guard let cursorPosition = textField.position(from: textField.beginningOfDocument, offset: input.characters.count)
else { return }
textField.selectedTextRange = textField.textRange(from: cursorPosition, to: cursorPosition)
}
fileprivate func setCombinedText(for textField: UITextField) {
let placeholderSubstring = placeholderText.substring(from: input.endIndex)
let attributedString = NSMutableAttributedString(string: input + placeholderSubstring, attributes: placeholderAttributes)
attributedString.addAttributes(inputAttributes, range: NSMakeRange(0, input.characters.count))
textField.attributedText = attributedString
}
}
extension CustomPlaceholderTextFieldHandler: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "" {
if input.characters.count > 0 {
input = input.substring(to: input.index(before: input.endIndex))
}
} else {
input += string
}
if input.characters.count <= placeholderText.characters.count {
setCombinedText(for: textField)
setCursorPosition(for: textField)
return false
}
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
setCursorPosition(for: textField)
}
}
Here's a the way I initialized the gif above:
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let placeholderHandler = CustomPlaceholderTextFieldHandler(placeholder: "_2_-__-__A")
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = placeholderHandler
placeholderHandler.resetPlaceholder(for: textField)
}
}
This could be expanded to take color parameters, fonts, etc. at initialization, or you may find it cleaner to subclass UITextField
and make it its own delegate. I also haven't really tested this for selecting/deleting/replacing multiple characters.
The input
variable will return the text the user has input at any given point. Also, using a fixed-width font would remove the jitteriness as the user types and replaces the placeholder text.
Instead of using placeholder text, use a UILabel below your textfield and give the same font style to both. the label text should be like "- - - - -"
And when user starts typing on textfield give a space after each character press.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text?.characters.count == 0 && string.characters.count != 0 {
textField.text = textField.text! + " "
}
else {
return false
}
if textField.text?.characters.count == 1 && string.characters.count != 0 {
textField.text = textField.text! + " "
}
else {
return false
}
if textField.text?.characters.count == 2 && string.characters.count != 0 {
textField.text = textField.text! + " "
}
else {
return false
}
if textField.text?.characters.count == 3 && string.characters.count != 0 {
textField.text = textField.text! + " "
}
else {
return false
}
return true
}
There is my solution using UITextField text property
+(BOOL)shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
textField:(UITextField *)textField
mask:(NSString *)mask withMaskTemplate:(NSString *)maskTemplate{
NSString * alreadyExistString = @"";
if (string.length == 0) {
alreadyExistString = textField.text;
for (int i = range.location; i >= 0; i--) {
unichar currentCharMask = [maskTemplate characterAtIndex:i];
unichar currentChar = [alreadyExistString characterAtIndex:i];
if (currentCharMask == currentChar) {// fixed value and _
continue;
}else{
alreadyExistString = [alreadyExistString stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:@"_"];
break;
}
}
textField.text = alreadyExistString;
return NO;
}else{
alreadyExistString = [textField.text stringByReplacingCharactersInRange:NSMakeRange(range.location, 1) withString:string];
}
NSMutableString * validText = [[NSMutableString alloc] init];
int last = 0;
BOOL append = NO;
for (int i = 0; i < alreadyExistString.length; i++) {
unichar currentCharMask = [mask characterAtIndex:i];
unichar currentChar = [alreadyExistString characterAtIndex:i];
BOOL isLetter = [[NSCharacterSet alphanumericCharacterSet] characterIsMember: currentChar];
BOOL isDigit = [[NSCharacterSet decimalDigitCharacterSet] characterIsMember: currentChar];
if ((isLetter && currentCharMask == '#') || (isDigit && currentCharMask == '9')) {
[validText appendString:[NSString stringWithFormat:@"%c",currentChar]];
}else{
if (currentCharMask == '#' || currentCharMask == '9') {
break;
}
if ((isLetter && currentCharMask!= currentChar)|| (isDigit && currentCharMask!= currentChar)) {
append = YES;
}
[validText appendString:[NSString stringWithFormat:@"%c",currentCharMask]];
}
last = i;
}
for (int i = last+1; i < mask.length; i++) {
unichar currentCharMask = [mask characterAtIndex:i];
if (currentCharMask != '#' && currentCharMask != '9') {
[validText appendString:[NSString stringWithFormat:@"%c",currentCharMask]];
}
if (currentCharMask == '#' || currentCharMask == '9') {
break;
}
}
if (append) {
[validText appendString:string];
}
NSString *placeHolderMask = textField.text;
NSString *sub = [validText substringWithRange:NSMakeRange(range.location, 1)];
placeHolderMask = [placeHolderMask stringByReplacingCharactersInRange:NSMakeRange(range.location, 1) withString:sub];
textField.text = placeHolderMask;
return NO;
}
@property (nonatomic,strong) NSString * maskTemplate;// like: _2_-__-__A
@property (nonatomic,strong) NSString * mask;// like: #2#-99-##A
#Edit 1 There is also some more code I have implemented which move the cursor to the underscore location. If someone need help. Please comment I will help you.
#Edit 2
Problems I have facing using this approached
Thanks, Still waiting for if there is any other workaround using Placeholder String.
I guess this is just what you are looking for. Create your UITextField
object with the text _2_-__-__A
(not placeholder text). Then, use its view controller as delegate, and add that to the view controller:
-(BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string{
if (range.length>1) return NO; // Avoids removing multiple characters at once
if (range.location==1) range.location++; // '2' index
if (range.location==3) range.location++; // '-' index
if (range.location==6) range.location++; // '-' index
if (range.location==9) return NO; // 'A' index
if (range.location==10) return NO; // String end
if ([string isEqualToString:@""]) return NO; //Avoids removing characters
if (range.length==0) {
range.length++;
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *start = [textField positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textField positionFromPosition:start offset:range.length];
UITextRange *textRange = [textField textRangeFromPosition:start toPosition:end];
[textField setSelectedTextRange:textRange];
}
return YES;
}
-(void)textFieldDidBeginEditing:(UITextField*)textField{
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *start = [textField positionFromPosition:beginning offset:0];
UITextPosition *end = [textField positionFromPosition:start offset:0];
UITextRange *textRange = [textField textRangeFromPosition:start toPosition:end];
[textField setSelectedTextRange:textRange];
}
-(BOOL)textFieldShouldReturn:(UITextField*)textField{
[passwordInput resignFirstResponder];
return YES;
}
It should work as intended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With