安全なエントリを保持しているUITextField
を編集しようとすると、常にクリアされるという奇妙な問題があります。 3 characters
をフィールドに追加し、別のフィールドに移動して戻ってきました。カーソルは4番目のcharacter
位置にありますが、別の文字を追加しようとすると、フィールド内のテキスト全体が取得されます新しいキャラクターによってクリアされます。ペン先で[編集開始時にクリア]のチェックを外しています。では、問題は何でしょうか?セキュアエントリプロパティを削除すると、すべてが正常に機能しているので、これはセキュアエントリtextfields
のプロパティですか?この動作を防ぐ方法はありますか?
セットする、
textField.clearsOnBeginEditing = NO;
注:secureTextEntry = YES。の場合、これは機能しません。デフォルトでは、iOSはセキュアエントリのテキストをクリアするようです編集前のテキストフィールド、clearsOnBeginEditingはYESまたはNOです。
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;
}
サインアップビューにパスワードテキストの表示/非表示機能を追加すると、同様の問題が発生しました。
Swiftを使用している場合、このサブクラスを試してください。
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
の値が変更されると(たとえば、ユーザーがパスワードを入力し、「パスワードを表示」ボタンを前後に切り替え、入力を続ける)、テキストフィールドは次のようにリセットされますいつも。
このシナリオで入力テキストを保持するために、このサブクラスは、テキストフィールドが最初のレスポンダーである場合にのみbecomeFirstResponder
をトリガーします。このステップは、他の回答では欠落しているようです。
@Patrick Riddが訂正してくれてありがとう!
@Ericの答えは機能しますが、2つの問題がありました。
私の最終的なコード
- (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;
}
同様の問題がありました。ログイン(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を返します。これは、(奇妙なことに)フィールドがクリアされるのを見る唯一の時間です。
私はあちこちですべてのソリューションを試してみましたが、最終的にそのオーバーライドを使用しました:
- (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のクリアをトリガーし、元のテキストを復元します。
Swift 3/Swift 4
yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false
注:secureTextEntry = YESの場合、これは機能しません。デフォルトでは、clearsOnBeginEditingがYESでもNOでも、iOSは編集前にセキュアエントリテキストフィールドのテキストをクリアするようです。
簡単な使い方のクラスとその仕事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
}
}
@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()
に置き換えました。それは魅力のように機能しました。
トーマスの元の解決策がなければ、そこまでは得られなかったでしょう。トーマスに感謝します!
Alekseyのソリューションに基づいて、このサブクラスを使用しています。
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
}
}
範囲内の置換文字を変更しても機能しないことがありますが、それを使用して他のことを行っている場合があり、さらに追加するとバグが発生しやすくなります。
完璧に機能するため、これは素晴らしいことです。唯一奇妙なのは、フィールドに戻ったときに最後の文字が再び表示されることです。それが一種のプレースホルダーとして機能するので、私は実際にそれが好きです。
@malexのソリューションで遊んだ後、私はこれに来ましたSwift version:
1)View Controllerを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)
}
}
これはSwift私のプロジェクトからのコードがテストされ、対処されている、バックスペースおよびセキュアエントリの変更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
}
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
}
}
UITextFieldのサブクラスを作成し、以下の2つのメソッドをオーバーライドします。
-(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;
}
このクラス名をテキストフィールドクラス名として使用します。
dwsolberg's answer に基づいて解決し、2つの修正を加えました。
deleteBackward
およびinsertText
により、値変更イベントが発生しますそこで、私たちはこれを思いつきました(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
}
}
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;
}
私の解決策(バグが修正されるまで)は、以前(またはiOS 6)のようにクリアするのではなく、既存のテキストに追加するようにUITextFieldをサブクラス化することです。 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
私はこれが少し古いことを認識していますが、iOS 6では、UITextFieldの「テキスト」はデフォルトでInterface Builderの「属性」になっています。これを「プレーン」に切り替えると、iOS 5のようになり、この問題が修正されます。
@Craigがリンクした質問にもこの同じ回答を投稿しました。
私のプロジェクトが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;
}
私はdwsolbergとfluidsonicの答えを実験しましたが、これはうまくいくようです
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
}
パスワードテキストフィールド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を返し、ユーザーが文字を削除するために削除ボタンをクリックしてもこのコードが機能するように操作しました。
私は同じ問題を抱えていたが、解決策を得た。
-(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;
}
私の前の答えに感謝します。行う必要があるのは、isSecureTextEntryが設定された直後に同じ文字列オブジェクトのテキストを削除および挿入することだけであるようです。そこで、以下に拡張機能を追加しました。
extension UITextField {
func setSecureTextEntry(_ on: Bool, clearOnBeginEditing: Bool = true) {
isSecureTextEntry = on
guard on,
!clearOnBeginEditing,
let textCopy = text
else { return }
text?.removeAll()
insertText(textCopy)
}
}
@ 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
}
}