右对齐 UITextField 空格键在 iOS 7 中不前进光标

Posted

技术标签:

【中文标题】右对齐 UITextField 空格键在 iOS 7 中不前进光标【英文标题】:Right aligned UITextField spacebar does not advance cursor in iOS 7 【发布时间】:2013-11-03 09:10:17 【问题描述】:

在我的 iPad 应用中,我注意到 ios 6 和 iOS 7 之间的 UITextFields 行为不同。

我创建 UITextField 如下:

UIButton *theButton = (UIButton*)sender;
UITextField *textField = [[UITextField alloc] initWithFrame:[theButton frame]];

[textField setDelegate:self];
[textField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
[textField setContentHorizontalAlignment:UIControlContentHorizontalAlignmentRight];

textField.textAlignment = UITextAlignmentRight;
textField.keyboardType = UIKeyboardTypeDefault;

...

[textField becomeFirstResponder];

在 iOS 6 中,当我键入“hello world”时,当我在“hello”之后点击空格键时,光标会前进一个空格。

在 iOS 7 中,当我按下空格键时光标不会前进。但是,当我在“world”中键入“w”时,它会显示空格和 w。

在 iOS 7 中按空格键时如何前进光标?

更新:

如果我将 textField.textAlignment 更改为 UITextAlignmentLeft,则在 iOS 7 中会出现空格。如果可能,我希望保持右对齐。

【问题讨论】:

【参考方案1】:

这有点小题大做,但如果你真的需要它来查看 iOS6 的方式,你可以用 non-breaking space 替换空格,因为它是写的。它被区别对待。示例代码可能如下所示:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 
    // only when adding on the end of textfield && it's a space
    if (range.location == textField.text.length && [string isEqualToString:@" "]) 
        // ignore replacement string and add your own
        textField.text = [textField.text stringByAppendingString:@"\u00a0"];
        return NO;
    
    // for all other cases, proceed with replacement
    return YES;

如果不清楚,textField:shouldChangeCharactersInRange:replacementString:UITextFieldDelegate 协议方法,所以在您的示例中,上述方法将在[textField setDelegate:self] 指定的视图控制器中。

如果您想要恢复常规空格,显然还需要记住在从文本字段中取出字符串时,通过将出现的 @"\u00a0" 替换为 @" " 来将文本转换回来。

【讨论】:

这仅适用于一次添加/删除一个字符;所以在粘贴或删除带有多个空格的文本时不会。这可以做得更简单一些;看我的回答。 如果你从textField:shouldChangeCharactersInRange:replacementString:返回NO,你可能会破坏事情。请参阅my answer 了解更安全的方法。 对我来说无法正常工作,我必须按两次空格键才能开始在字符串末尾添加空格,并且在退格按钮删除最后一个空格字符的情况下也会发生同样的情况。 你能解释一下为什么Apple在iOS7+中会这样做吗?我现在在 8.1 中看到它,所以我认为这不是错误。更改背后是否存在我们应该考虑不规避的 UI 原理? 我没有这方面的官方信息,但 IMO 很容易猜到。 UITextFields 最常见的用例可能是输入数据的各种形式。明确要求数据中带有尾随空格的情况极为罕见。所以这可能是一种无声的内置防白痴措施。【参考方案2】:

这是一个始终有效的解决方案,也适用于粘贴和编辑(即当您可以添加/删除具有多个空格的文本时)。

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

    textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
    textField.text = [textField.text stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"];

    return NO;

不用担心每次做stringByReplacingOccurrencesOfString的性能;相对于 CPU 速度而言,UI 中的文本非常短。

那么当你真正想从文本字段中获取值时:

NSString* text = [textField.text stringByReplacingOccurrencesOfString:@"\u00a0" withString:@" "];

所以这是一个很好的对称。

【讨论】:

-textField:shouldChangeCharactersInRange:replacementString 中返回 NO 抑制 UITextFieldTextDidChangeNotification。所以你可以在你的方法[[NSNotificationCenter defaultCenter] postNotificationName:UITextFieldTextDidChangeNotification object:textField]; 中发送它来返回默认行为【参考方案3】:

您必须将普通空格替换为non-breaking spaces。最好为此触发更改事件的操作:

    在某个地方为您的文本字段上的UIControlEventEditingChanged 事件添加一个操作:

    [myTextField addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces)
                      forControlEvents:UIControlEventEditingChanged];
    

    然后实现replaceNormalSpacesWithNonBreakingSpaces方法:

    - (void)replaceNormalSpacesWithNonBreakingSpaces
    
        self.text = [self.text stringByReplacingOccurrencesOfString:@" "
                                                         withString:@"\u00a0"];
    
    

这比使用textField:shouldChangeCharactersInRange:replacementString: 更安全,因为如果你从这个方法返回NO,你实际上是在说不应该改变指定的文本。这将导致更改事件(如 IBActions textFieldEditingChanged: 或 UITextField 的 UIControlEventEditingChanged 事件)不会被触发。

到处修复它:

如果您希望对所有 UITextFields 进行此修复,您可以创建一个 category 在启动 UITextField 时添加这些事件操作。在下面的示例中,我还将在编辑结束时将不间断空格改回正常空格,以便在其他地方使用数据时不会出现不间断空格的可能问题。请注意,此示例使用method swizzling,因此可能看起来有点奇怪,但它是正确的。

头文件:

//  UITextField+RightAlignedNoSpaceFix.h

#import <UIKit/UIKit.h>

@interface UITextField (RightAlignedNoSpaceFix)
@end

实现文件:

//  UITextField+RightAlignedNoSpaceFix.m

#import "UITextField+RightAlignedNoSpaceFix.h"

@implementation UITextField (RightAlignedNoSpaceFix)

static NSString *normal_space_string = @" ";
static NSString *non_breaking_space_string = @"\u00a0";

+(void)load

    [self overrideSelector:@selector(initWithCoder:)
              withSelector:@selector(initWithCoder_override:)];

    [self overrideSelector:@selector(initWithFrame:)
              withSelector:@selector(initWithFrame_override:)];


/**
 * Method swizzles the initWithCoder method and adds the space fix
 * actions.
 */
-(instancetype)initWithCoder_override:(NSCoder*)decoder

    self = [self initWithCoder_override:decoder];
    [self addSpaceFixActions];
    return self;


/**
 * Method swizzles the initWithFrame method and adds the space fix
 * actions.
 */
-(instancetype)initWithFrame_override:(CGRect)frame

    self = [self initWithFrame_override:frame];
    [self addSpaceFixActions];
    return self;


/**
 * Will add actions on the text field that will replace normal 
 * spaces with non-breaking spaces, and replaces them back after
 * leaving the textfield.
 *
 * On iOS 7 spaces are not shown if they're not followed by another
 * character in a text field where the text is right aligned. When we
 * use non-breaking spaces this issue doesn't occur.
 *
 * While editing, the normal spaces will be replaced with non-breaking
 * spaces. When editing ends, the non-breaking spaces are replaced with
 * normal spaces again, so that possible problems with non-breaking
 * spaces won't occur when the data is used somewhere else.
 */
- (void)addSpaceFixActions


    [self addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces)
               forControlEvents:UIControlEventEditingDidBegin];

    [self addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces)
               forControlEvents:UIControlEventEditingChanged];

    [self addTarget:self action:@selector(replaceNonBreakingSpacesWithNormalSpaces)
               forControlEvents:UIControlEventEditingDidEnd];



/**
 * Will replace normal spaces with non-breaking spaces.
 */
- (void)replaceNormalSpacesWithNonBreakingSpaces

    self.text = [self.text stringByReplacingOccurrencesOfString:normal_space_string
                                                     withString:non_breaking_space_string];


/**
 * Will replace non-breaking spaces with normal spaces.
 */
- (void)replaceNonBreakingSpacesWithNormalSpaces

    self.text = [self.text stringByReplacingOccurrencesOfString:non_breaking_space_string
                                                     withString:normal_space_string];


@end

【讨论】:

我喜欢使用目标/动作,但它在编辑文本时会干扰(例如删除字符串中间的一个字母会导致插入符号跳到字符串的末尾)【参考方案4】:

老问题,但上述所有解决方案似乎都过于复杂。以下是我解决问题的方法:

我订阅了两个文本字段事件 ->

TextFieldEditingDidBegin TextFieldEditingEnded

在 TextFieldEditingDidBegin 上,我简单地将 textField.textAlignment 设置为 UITextAlignmentLeft。 在 TextFieldEditingEnded 上,我将 textField.textAlignment 设置回 UITextAlignmentRight。

这对我来说完美无缺,我觉得它不是黑客。希望对您有所帮助!

【讨论】:

这在 ios 6.0 上已弃用。 textField.textAlignment = UITextAlignmentLeft;【参考方案5】:

我的以下解决方案还解决了在字符串中间或开头键入空格时光标跳到末尾的问题。现在也可以正确处理粘贴字符串。

我还检查了电子邮件地址字段和其他检查,但有趣的部分是最后一部分。它对我来说非常有效,还没有发现问题。

您可以直接将其复制/粘贴到您的项目中。不要忘记实现 didBeginEditing 和 didEndEditing 以用不间断空格替换空格并返回!

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

    if (textField.textAlignment != NSTextAlignmentRight) //the whole issue only applies to right aligned text
        return YES;

    if (!([string isEqualToString:@" "] || string.length > 1)) //string needs to be a space or paste action (>1) to get special treatment
        return YES;

    if (textField.keyboardType == UIKeyboardTypeEmailAddress) //keep out spaces from email address field
    
        if (string.length == 1)
            return NO;
        //remove spaces and nonbreaking spaces from paste action in email field:
        string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
        string = [string stringByReplacingOccurrencesOfString:@"\u00a0" withString:@""];
    

    //special treatment starts here
    string = [string stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"];
    UITextPosition *beginning = textField.beginningOfDocument;
    textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
    UITextPosition *start = [textField positionFromPosition:beginning offset:range.location+string.length];
    UITextPosition *end = [textField positionFromPosition:start offset:range.length];
    UITextRange *textRange = [textField textRangeFromPosition:start toPosition:end];
    [textField setSelectedTextRange:textRange];

    return NO;

【讨论】:

【参考方案6】:

通过用不间断空格替换空格来修复右对齐文本空格

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

    if (textField.textAlignment == NSTextAlignmentRight) 
        NSString *text = [textField.text stringByReplacingCharactersInRange:range withString:string];
        textField.text = [text stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"];

        UITextPosition *startPos = [textField positionFromPosition:textField.beginningOfDocument offset:range.location + string.length];
        UITextRange *textRange = [textField textRangeFromPosition:startPos toPosition:startPos];
        textField.selectedTextRange = textRange;

        return NO;
    

    return YES;

反之亦然

- (void)textFieldDidEndEditing:(UITextField *)textField

    // Replacing non-breaking spaces with spaces and remove obsolete data
    NSString *textString = [[textField.text stringByReplacingOccurrencesOfString:@"\u00a0" withString:@" "] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    textField.text = textString;

【讨论】:

【参考方案7】:

我想出了一个解决方案,它继承 UITextField 类并执行交换,而不需要到处复制和粘贴代码。这也避免了使用方法嘶嘶声来解决这个问题。

@implementation CustomTextField

-(id) initWithCoder:(NSCoder *)aDecoder 
    self = [super initWithCoder:aDecoder];

    if( self ) 

        [self addSpaceFixActions];
    

    return self;


- (void)addSpaceFixActions 
    [self addTarget:self action:@selector(replaceNormalSpaces) forControlEvents:UIControlEventEditingChanged];
    [self addTarget:self action:@selector(replaceBlankSpaces) forControlEvents:UIControlEventEditingDidEnd];



//replace normal spaces with non-breaking spaces.
- (void)replaceNormalSpaces 
    if (self.textAlignment == NSTextAlignmentRight) 
        UITextRange *textRange = self.selectedTextRange;
        self.text = [self.text stringByReplacingOccurrencesOfString:@" " withString:@"\u00a0"];
        [self setSelectedTextRange:textRange];
    


//replace non-breaking spaces with normal spaces.
- (void)replaceBlankSpaces 
    self.text = [self.text stringByReplacingOccurrencesOfString:@"\u00a0" withString:@" "];

【讨论】:

【参考方案8】:

我在我的应用程序中使用左对齐文本字段解决了这个问题,然后使用 AutoLayout 将整个文本字段向右对齐。这模拟了一个右对齐的文本字段并处理尾随空格,而不会弄乱空格字符等。

这种方法的主要障碍是 UITextField 不会随着文本的变化而更新其固有的内容大小。为了解决这个问题,我将 UITextField 子类化为在文本更改时自动计算内在内容大小。这是我的子类:

@implementation PLResizingTextField

- (instancetype)init 
    self = [super init];
    if(self) 
        [self addTarget:self action:@selector(invalidateIntrinsicContentSize) forControlEvents:UIControlEventEditingChanged];
    
    return self;


- (CGSize)intrinsicContentSize 
    CGSize size = [super intrinsicContentSize];
    NSString *text = self.text.length ? self.text : self.placeholder;

    CGRect rect = [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX,CGFLOAT_MAX)
                                     options:NSStringDrawingUsesLineFragmentOrigin
                                  attributes:@NSFontAttributeName:self.font
                                     context:nil];
    size.width = CGRectGetWidth(rect);

    return size;


@end

这是我使用 PureLayout 库的自动布局代码片段:

[textField autoPinEdgeToSuperviewEdge:ALEdgeTrailing
                            withInset:10];
[textField autoPinEdge:ALEdgeLeading
                toEdge:ALEdgeTrailing
                ofView:cell.textLabel
            withOffset:10
              relation:NSLayoutRelationGreaterThanOrEqual];
[textField setContentHuggingPriority:UILayoutPriorityDefaultHigh
                             forAxis:UILayoutConstraintAxisHorizontal];

这里需要注意的重点:

    在文本字段上设置内容拥抱优先级 在文本字段的左边缘与其左侧的视图(或超级视图的左边缘)之间使用NSLayoutRelationGreaterThanOrEqual 关系。

【讨论】:

【参考方案9】:

上面所有的答案都很棒,而且很有指示性!特别感谢meaning-matters 的answer below。这是一个经过测试的 Swift 2.0 版本。 记住分配 UITextField 的delegate 到您的ViewController!快乐编码。

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 

    if (textField == self.desiredTextField) 
        var oldString = textField.text!
        let newRange = oldString.startIndex.advancedBy(range.location)..<oldString.startIndex.advancedBy(range.location + range.length)
        let newString = oldString.stringByReplacingCharactersInRange(newRange, withString: string)
        textField.text = newString.stringByReplacingOccurrencesOfString(" ", withString: "\u00a0");
        return false;
     else 
        return true;
    


--

这里是 Swift 3!

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 
    if (textField == self.textfield) 
        let oldString = textField.text!
        let newStart = oldString.index(oldString.startIndex, offsetBy: range.location)
        let newEnd = oldString.index(oldString.startIndex, offsetBy: range.location + range.length)
        let newString = oldString.replacingCharacters(in: newStart..<newEnd, with: string)
        textField.text = newString.replacingOccurrences(of: " ", with: "\u00a0")
        return false;
     else 
        return true;
    

【讨论】:

如果用户插入多个表情符号,这崩溃。有什么办法解决吗?【参考方案10】:

我已经将Jack Song's answer 用于 Swift 2 有一段时间了,直到我意识到在其他地方以 html 呈现时,非制动空格会出现问题,并且 UITextView 中的换行变得混乱本身。因此,我改进了解决方案,立即清除非括号字符。

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 
    if (textField == self.desiredTextField) 
       var oldString = textView.text!
       oldString = oldString.stringByReplacingOccurrencesOfString("\u00a0", withString: " ");
       let newRange = oldString.startIndex.advancedBy(range.location)..<oldString.startIndex.advancedBy(range.location + range.length)
       let alteredText = text.stringByReplacingOccurrencesOfString(" ", withString: "\u00a0")
       textView.text = oldString.stringByReplacingCharactersInRange(newRange, withString: alteredText)
       return false;
     else 
       return true;
    

【讨论】:

【参考方案11】:

三唑坦的答案转换为 Swift3。

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool

    if (range.location == textField.text?.characters.count && string == " ") 
        let noBreakSpace: Character = "\u00a0"
        textField.text = textField.text?.append(noBreakSpace)
        return false
    
    return true

【讨论】:

【参考方案12】:

这是来自@Jack Song 的回答中的 Swift 3

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 
    if (textField == self.textfield) 
        let oldString = textField.text!
        let newStart = oldString.index(oldString.startIndex, offsetBy: range.location)
        let newEnd = oldString.index(oldString.startIndex, offsetBy: range.location + range.length)
        let newString = oldString.replacingCharacters(in: newStart..<newEnd, with: string)
        textField.text = newString.replacingOccurrences(of: " ", with: "\u00a0")
        return false;
     else 
        return true;
    

【讨论】:

【参考方案13】:

Swift 4 版本:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
    if var text = textField.text, range.location == text.count, string == " " 
        let noBreakSpace: Character = "\u00a0"
        text.append(noBreakSpace)
        textField.text = text
        return false
    
    return true

【讨论】:

完美的解决方案!【参考方案14】:
extension UITextField 

    /// runtime key
    private struct AssociatedKeys 

        ///
        static var toggleState: UInt8 = 0
    

    /// prevent multiple fix
    private var isFixedRightSpace: Bool 
        get 
            return objc_getAssociatedObject(self, &AssociatedKeys.toggleState) as? Bool ?? false
        
        set 
            objc_setAssociatedObject(self, &AssociatedKeys.toggleState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        
    

    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 

        if self.textAlignment == .right && !isFixedRightSpace 
            self.isFixedRightSpace = true
            self.addTarget(self, action: #selector(replaceNormalSpacesWithNonBreakingSpaces(textFiled:)), for: UIControl.Event.editingChanged)
        

        return super.hitTest(point, with: event)
    

    /// replace space to \u00a0
    @objc private func replaceNormalSpacesWithNonBreakingSpaces(textFiled: UITextField) 

        if textFiled.markedTextRange == nil && textFiled.text?.contains(" ") ?? false 

            /// keep current range
            let editRange = selectedTextRange
            textFiled.text = textFiled.text?.replacingOccurrences(of: " ", with: "\u00a0")
            /// reset this range
            selectedTextRange = editRange
        
    


【讨论】:

以上是关于右对齐 UITextField 空格键在 iOS 7 中不前进光标的主要内容,如果未能解决你的问题,请参考以下文章

占位符右对齐的 UITextField 不能正确向右移动

在 UITextField 中右对齐 PlaceHolder 文本

如何在UITextView中以编程方式移动光标?

如何对齐 UITextField 中的文本?

iOS----------输入框UITextField禁止输入空格

将 uitextfield 3 空间中的光标向右移动