具有安全条目的UITextField,在编辑之前始终被清除

时间:2011-09-05 08:26:39

标签: iphone uitextfield

我遇到一个奇怪的问题,当我尝试编辑它时,我的UITextField持有一个安全条目总是被清除。我在字段中添加了3个字符,转到另一个字段并返回,光标位于第4个字符位置,但是当我尝试添加另一个字符时,字段中的整个文本被新字符清除。我在笔尖中取消选中“在编辑开始时清除”。那么问题是什么?如果我删除了安全入口属性,一切正常,那么,这是安全入口文本字段的属性吗?有没有办法阻止这种行为?

26 个答案:

答案 0 :(得分:45)

设置,

textField.clearsOnBeginEditing = NO;

注意:如果 secureTextEntry = YES ,这将无效。默认情况下,iOS会在编辑前清除安全条目文本字段的文本,无论< strong> clearsOnBeginEditing 是YES或NO。

答案 1 :(得分:44)

如果您不希望字段清除,即使在secureTextEntry = YES时,请使用:

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

    NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];

    textField.text = updatedString;

    return NO;
}

在注册视图中添加显示/隐藏密码文本功能时,我遇到了类似的问题。

答案 2 :(得分:18)

如果你使用的是 Swift 3 ,那就给这个子类吧。

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            self.text?.removeAll()
            insertText(text)
        }
        return success
    }

}

为什么这样做?

TL; DR:如果您在切换isSecureTextEntry时编辑字段,请务必致电becomeFirstResponder

切换isSecureTextEntry的值工作正常,直到用户编辑文本字段 - 文本字段在放置新字符之前清除。这个暂定的结算似乎发生在becomeFirstResponder UITextField来电期间。如果此调用与deleteBackward / insertText技巧相结合(如 @Aleksey @dwsolberg 的答案中所示),则输入文本为保留,似乎取消了试探性的清理。

但是,当isSecureTextEntry的值发生变化而textfield是第一个响应者时(例如,用户输入他们的密码,来回切换'show password'按钮,然后继续输入),textfield将像往常一样重置。

要保留此方案中的输入文本,仅当文本字段是第一个响应者时,此子类才会触发becomeFirstResponder。其他答案中似乎缺少此步骤。

感谢@Patrick Ridd的纠正!

答案 3 :(得分:15)

@ Eric的答案有效,但我有两个问题。

  1. 正如@malex指出的那样,中间文本的任何更改都会将文本放在文本的末尾。
  2. 我使用ReactiveCocoa中的rac_textSignal并直接更改文字不会触发信号。
  3. 我的最终代码

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        //Setting the new text.
        NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
        textField.text = updatedString;
    
        //Setting the cursor at the right place
        NSRange selectedRange = NSMakeRange(range.location + string.length, 0);
        UITextPosition* from = [textField positionFromPosition:textField.beginningOfDocument offset:selectedRange.location];
        UITextPosition* to = [textField positionFromPosition:from offset:selectedRange.length];
        textField.selectedTextRange = [textField textRangeFromPosition:from toPosition:to];
    
        //Sending an action
        [textField sendActionsForControlEvents:UIControlEventEditingChanged];
    
        return NO;
    }
    

    @Mars的Swift3补充:

      func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let nsString:NSString? = textField.text as NSString?
        let updatedString = nsString?.replacingCharacters(in:range, with:string);
    
        textField.text = updatedString;
    
    
        //Setting the cursor at the right place
        let selectedRange = NSMakeRange(range.location + string.length, 0)
        let from = textField.position(from: textField.beginningOfDocument, offset:selectedRange.location)
        let to = textField.position(from: from!, offset:selectedRange.length)
        textField.selectedTextRange = textField.textRange(from: from!, to: to!)
    
        //Sending an action
        textField.sendActions(for: UIControlEvents.editingChanged)
    
        return false;
    }
    

答案 4 :(得分:5)

我有类似的问题。我在表视图中嵌入了登录(secureEntry = NO)和密码(secureEntry = YES)文本字段。我试过设置

textField.clearsOnBeginEditing = NO;

在两个相关的委托方法(textFieldDidBeginEditing和textFieldShouldBeginEditing)中,这些都没有用。从密码字段移动到登录字段后,如果我尝试删除单个字符,整个登录字段将被清除。我使用textFieldShouldChangeCharactersInRange来解决我的问题,如下所示:

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (range.length == 1 && textField.text.length > 0) 
    {
        //reset text manually
        NSString *firstPart = [textField.text substringToIndex:range.location]; //current text minus one character
        NSString *secondPart = [textField.text substringFromIndex:range.location + 1]; //everything after cursor
        textField.text = [NSString stringWithFormat:@"%@%@", firstPart, secondPart];

        //reset cursor position, in case character was not deleted from end of 
        UITextRange *endRange = [textField selectedTextRange];
        UITextPosition *correctPosition = [textField positionFromPosition:endRange.start offset:range.location - textField.text.length];
        textField.selectedTextRange = [textField textRangeFromPosition:correctPosition toPosition:correctPosition];

        return NO;
    }
    else
    {
        return YES;
    }
}

当用户输入退格时,range.length == 1会返回true,这是(奇怪)唯一一次我会看到该字段被清除。

答案 5 :(得分:5)

我已经在这里和那里尝试了所有解决方案,并最终带来了压倒一切:

- (BOOL)becomeFirstResponder
{
    BOOL became = [super becomeFirstResponder];
    if (became) {
        NSString *originalText = [self text];
        //Triggers UITextField to clear text as first input
        [self deleteBackward];

        //Requires setting text via 'insertText' to fire all associated events
        [self setText:@""];
        [self insertText:originalText];
    }
    return became;
}

它触发UITextField的清除然后恢复原始文本。

答案 6 :(得分:5)

@Thomas Verbeek的回答给了我很多帮助:

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            deleteBackward()
            insertText(text)
        }
        return success
    }

}

除了我确实在我的代码中发现了一个错误。在实现他的代码之后,如果textField中有文本并点击textField框,它将只删除第一个字符,然后再次插入所有文本。基本上再次粘贴文本。

为了解决此问题,我将deleteBackward()替换为self.text?.removeAll(),它就像一个魅力。

如果没有托马斯的话,我就不会走得那么远。原始解决方案,谢谢托马斯!

答案 7 :(得分:5)

Swift 3 / Swift 4

yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false

注意:如果secureTextEntry = YES,则无效。看来,默认情况下,iOS会在编辑之前清除安全条目文本字段的文本,无论clearsOnBeginEditing是YES还是NO。

轻松使用班级及其工作100%

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            self.text?.removeAll()
            insertText(text)
        }
         return success
    }

}

答案 8 :(得分:2)

Based on Aleksey's solution, I'm using this subclass.

class SecureNonDeleteTextField: UITextField {

    override func becomeFirstResponder() -> Bool {
        guard super.becomeFirstResponder() else { return false }
        guard self.secureTextEntry == true else { return true }
        guard let existingText = self.text else { return true }
        self.deleteBackward() // triggers a delete of all text, does NOT call delegates
        self.insertText(existingText) // does NOT call delegates
        return true
    }
}

Changing the replace characters in range doesn't work for me because I'm doing other things with that at times, and adding more makes it more likely to be buggy.

This is nice because it pretty much works perfectly. The only oddity is that the last character is shown again when you tap back into the field. I actually like that because it acts as a sort of placeholder.

答案 9 :(得分:1)

如果你想使用secureTextEntry = YES和适当的视觉行为进行运输,你需要这样:

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

    if (!string.length) {
        UITextPosition *start = [self positionFromPosition:self.beginningOfDocument offset:range.location];
        UITextPosition *end = [self positionFromPosition:start offset:range.length];
        UITextRange *textRange = [self textRangeFromPosition:start toPosition:end];
        [self replaceRange:textRange withText:string];
    }
    else {
       [self replaceRange:self.selectedTextRange withText:string];
    }

    return NO;
}

答案 10 :(得分:1)

在使用@malex的解决方案后,我来到了 Swift 版本:

1)不要忘记让你的视图控制器成为UITextFieldDelegate:

class LoginViewController: UIViewController, UITextFieldDelegate {
...
}

override func viewDidLoad() {
   super.viewDidLoad()
   textfieldPassword.delegate = self
}

2)使用此:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if let start: UITextPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: range.location),
       let end: UITextPosition = textField.positionFromPosition(start, offset: range.length),
       let textRange: UITextRange = textField.textRangeFromPosition(start, toPosition: end) {
           textField.replaceRange(textRange, withText: string)
    }
    return false
}

...如果您为“显示/隐藏密码”功能执行此操作,则最有可能在打开/关闭secureTextEntry时需要保存和恢复插入位置。这是如何(在切换方法中做到):

var startPosition: UITextPosition?
var endPosition: UITextPosition?

// Remember the place where cursor was placed before switching secureTextEntry
if let selectedRange = textfieldPassword.selectedTextRange {
   startPosition = selectedRange.start
   endPosition = selectedRange.end
}

...

// After secureTextEntry has been changed
if let start = startPosition {
   // Restoring cursor position
   textfieldPassword.selectedTextRange = textfieldPassword.textRangeFromPosition(start, toPosition: start)
   if let end = endPosition {
       // Restoring selection (if there was any)
       textfieldPassword.selectedTextRange = textfield_password.textRangeFromPosition(start, toPosition: end)
       }
}

答案 11 :(得分:1)

我们根据dwsolberg's answer解决了两个问题:

  • 在点击已经集中的密码字段时,密码文本被复制了
  • 点击密码字段时显示密码的最后一个字符
  • deleteBackwardinsertText会导致触发值更改事件

所以我们想出了这个(Swift 2.3):

class PasswordTextField: UITextField {

    override func becomeFirstResponder() -> Bool {
        guard !isFirstResponder() else {
            return true
        }
        guard super.becomeFirstResponder() else {
            return false
        }
        guard secureTextEntry, let text = self.text where !text.isEmpty else {
            return true
        }

        self.text = ""
        self.text = text

        // make sure that last character is not revealed
        secureTextEntry = false
        secureTextEntry = true

        return true
    }
}

答案 12 :(得分:1)

这是我的项目中的快速代码经过测试和处理,退格和安全条目更改false / true

//获取用户输入并调用验证方法

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

    if (textField == passwordTextFields) {

        let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)

        // prevent backspace clearing the password
        if (range.location > 0 && range.length == 1 && string.characters.count == 0) {
            // iOS is trying to delete the entire string
            textField.text = newString
            choosPaswwordPresenter.validatePasword(text: newString as String)

            return false
        }

        // prevent typing clearing the pass
        if range.location == textField.text?.characters.count {
            textField.text = newString
            choosPaswwordPresenter.validatePasword(text: newString as String)

            return false
        }

        choosPaswwordPresenter.validatePasword(text: newString as String)
    }

    return true
}

答案 13 :(得分:0)

创建UITextField的子类,并覆盖以下两个方法:

-(void)setSecureTextEntry:(BOOL)secureTextEntry {
    [super setSecureTextEntry:secureTextEntry];

    if ([self isFirstResponder]) {
        [self becomeFirstResponder];
    }
}

-(BOOL)becomeFirstResponder {
    BOOL became = [super becomeFirstResponder];
    if (became) {
        NSString *originalText = [self text];
        //Triggers UITextField to clear text as first input
        [self deleteBackward];

        //Requires setting text via 'insertText' to fire all associated events
        [self setText:@""];
        [self insertText:originalText];
    }
    return became;
}

使用该类名称作为您的文本字段类名称。

答案 14 :(得分:0)

IOS 12,Swift 4

  • 当文本字段再次成为第一响应者时,不显示最后一个字符。
  • 当您重复触摸文本字段时,不复制现有文本。

如果您不仅要在安全文本输入中使用此解决方案,请添加isSecureTextEntry检查。

class PasswordTextField: UITextField {
    override func becomeFirstResponder() -> Bool {
        let wasFirstResponder = isFirstResponder
        let success = super.becomeFirstResponder()
        if !wasFirstResponder, let text = self.text {
            insertText("\(text)+")
            deleteBackward()
        }
        return success
    }
}

答案 15 :(得分:0)

当我的项目升级到iOS 12.1并出现此问题时。这是我对此的解决方案。没关系。



    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
        NSMutableString *checkString = [textField.text mutableCopy];
        [checkString replaceCharactersInRange:range withString:string];
        textField.text = checkString;
        NSRange selectedRange = NSMakeRange(range.location + string.length, 0);
        UITextPosition* from = [textField positionFromPosition:textField.beginningOfDocument offset:selectedRange.location];
        UITextPosition* to = [textField positionFromPosition:from offset:selectedRange.length];
        textField.selectedTextRange = [textField textRangeFromPosition:from toPosition:to];
        [textField sendActionsForControlEvents:UIControlEventEditingChanged];
        return NO;
    }

答案 16 :(得分:0)

Swift5版本的https://stackoverflow.com/a/29195723/1979953

func textField(
    _ textField: UITextField,
    shouldChangeCharactersIn range: NSRange,
    replacementString string: String
) -> Bool {
    let nsString = textField.text as NSString?
    let updatedString = nsString?.replacingCharacters(
        in: range,
        with: string
    )
    textField.text = updatedString

    // Setting the cursor at the right place
    let selectedRange = NSRange(
        location: range.location + string.count,
        length: 0
    )

    guard let from = textField.position(
        from: textField.beginningOfDocument,
        offset: selectedRange.location
    ) else {
        assertionFailure()
        return false
    }

    guard let to = textField.position(
        from: from,
        offset: selectedRange.length
    ) else {
        assertionFailure()
        return false
    }

    textField.selectedTextRange = textField.textRange(
        from: from,
        to: to
    )

    // Sending an action
    textField.sendActions(for: UIControl.Event.editingChanged)

    return false
}

答案 17 :(得分:0)

我在密码文本字段textField.clearsOnBeginEditing = NO;上使用了@EmptyStack回复passwordTextField.secureTextEntry = YES;但是在带有Xcode 9.3的iOS11 SDK中没有用到,所以我完成了以下代码。实际上,如果用户在不同的字段之间切换,我想保留文本(在文本字段中)。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (textField.tag == 2) {
        if ([string isEqualToString:@""] && textField.text.length >= 1) {
            textField.text = [textField.text substringToIndex:[textField.text length] - 1];
        } else{
            textField.text = [NSString stringWithFormat:@"%@%@",textField.text,string];
        }
        return false;
    } else {
        return true;
    }
}

我在shouldChangeCharactersInRange中返回false并操作,因为如果用户点击删除按钮删除字符,此代码也可以使用。

答案 18 :(得分:0)

感谢我面前的答案。似乎需要做的就是在设置isSecureTextEntry之后立即在同一字符串对象上删除并插入文本。所以我在下面添加了扩展名:

extension UITextField {
    func setSecureTextEntry(_ on: Bool, clearOnBeginEditing: Bool = true)   {
        isSecureTextEntry = on

        guard on,
              !clearOnBeginEditing,
              let textCopy = text
        else { return }

        text?.removeAll()
        insertText(textCopy)
    }
}

答案 19 :(得分:0)

我需要通过添加一个属性来调整@ thomas-verbeek解决方案,该属性处理用户尝试将任何文本粘贴到字段(文本已被复制)时的情况

class PasswordTextField: UITextField {

    private var barier = true

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {
        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text, barier {
            deleteBackward()
            insertText(text)
        }
        barier = !isSecureTextEntry
        return success
    }

}

答案 20 :(得分:0)

我尝试了dwsolberg和fluidonic的答案,这似乎是可行的

override func becomeFirstResponder() -> Bool {

    guard !isFirstResponder else { return true }
    guard super.becomeFirstResponder() else { return false }
    guard self.isSecureTextEntry == true else { return true }
    guard let existingText = self.text else { return true }
    self.deleteBackward() // triggers a delete of all text, does NOT call delegates
    self.insertText(existingText) // does NOT call delegates

    return true
}

答案 21 :(得分:0)

我的解决方案(直到修复错误)是将UITextField子类化,使其附加到现有文本而不是像以前那样(或在iOS 6中)清除。如果未在iOS 7上运行,则尝试保留原始行为:

@interface ZTTextField : UITextField {
    BOOL _keyboardJustChanged;
}
@property (nonatomic) BOOL keyboardJustChanged;
@end

@implementation ZTTextField
@synthesize keyboardJustChanged = _keyboardJustChanged;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _keyboardJustChanged = NO;
    }
    return self;
}

- (void)insertText:(NSString *)text {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
    if (self.keyboardJustChanged == YES) {
        BOOL isIOS7 = NO;
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(backgroundRefreshStatus)]) {
            isIOS7 = YES;
        }
        NSString *currentText = [self text];
        // only mess with editing in iOS 7 when the field is masked, wherein our problem lies
        if (isIOS7 == YES && self.secureTextEntry == YES && currentText != nil && [currentText length] > 0) {
            NSString *newText = [currentText stringByAppendingString: text];
            [super insertText: newText];
        } else {
            [super insertText:text];
        }
        // now that we've handled it, set back to NO
        self.keyboardJustChanged = NO;
    } else {
        [super insertText:text];
    }
#else
    [super insertText:text];
#endif
}

- (void)setKeyboardType:(UIKeyboardType)keyboardType {
    [super setKeyboardType:keyboardType];
    [self setKeyboardJustChanged:YES];
}

@end

答案 22 :(得分:0)

我有同样的问题,但得到了解决方案;

-(BOOL)textFieldShouldReturn:(UITextField *)textField
    {

        if(textField==self.m_passwordField)
        {
            text=self.m_passwordField.text;  
        }

        [textField resignFirstResponder];

        if(textField==self.m_passwordField)
        {
            self.m_passwordField.text=text;
        }
        return YES;
    }

答案 23 :(得分:0)

我意识到这有点旧,但是在iOS 6中,UITextField“text”现在默认为“Attributed”在Interface Builder中。将此切换为“Plain”,就像在iOS 5中一样,解决了这个问题。

在@Craig链接的问题上也发布了相同的答案。

答案 24 :(得分:-1)

还有另一个stackoverflow问题帖子:Question

阅读该帖子看起来你需要在textFieldShouldBeginEditing委托方法中设置textField.clearsOnBeginEditing = NO

答案 25 :(得分:-2)

保存在属性中输入的文本。例如:

NSString *_password;

然后在代表中:

textFieldDidBeginEditing:(UITextField *)textField
assign textField.text = _password;