Right aligned UITextField spacebar does not advance cursor in iOS 7

前端 未结 14 1682
执笔经年
执笔经年 2020-12-07 23:04

In my iPad app, I noticed different behavior between iOS 6 and iOS 7 with UITextFields.

I create the UITextField as follows:

UIButton *theButton = (U         


        
相关标签:
14条回答
  • 2020-12-07 23:29

    It would be a bit of a hack, but if you really need that to look the iOS6 way, you can replace space with non-breaking space as it's written. It's treated differently. Example code could look like this:

    - (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;
    }
    

    In case it's not clear, textField:shouldChangeCharactersInRange:replacementString: is a UITextFieldDelegate protocol method, so in your example, the above method would be in the viewcontroller designated by [textField setDelegate:self].

    If you want your regular spaces back, you will obviously also need to remember to convert the text back by replacing occurrences of @"\u00a0" with @" " when getting the string out of the textfield.

    0 讨论(0)
  • 2020-12-07 23:30

    Here's a solution that always works, also for pasting and editing (i.e. when you may add/delete texts with multiple spaces).

    - (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;
    }
    

    Don't worry about performance of doing stringByReplacingOccurrencesOfString every time; texts in UIs are very very short relative to CPU speed.

    Then when you actually want to get the value from the text field:

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

    So this is a nicely symmetrical.

    0 讨论(0)
  • 2020-12-07 23:31

    You'll have to replace the normal spaces with non-breaking spaces. It's best to trigger an action on a change event for this:

    1. Somewhere add an action for the UIControlEventEditingChanged event on your textfield:

      [myTextField addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces)
                        forControlEvents:UIControlEventEditingChanged];
      
    2. Then implement the replaceNormalSpacesWithNonBreakingSpaces method:

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

    This is safer than using textField:shouldChangeCharactersInRange:replacementString:, because if you return NO from this method, you're actually saying that the specified text should not be changed. This will cause change events (like the IBActions textFieldEditingChanged: or the UITextField's UIControlEventEditingChanged event) to not be triggered.

    Fix it everywhere:

    If you want this fix for all your UITextFields you can create a category where you add these event actions when a UITextField is initiated. In the example below I also change the non-breaking spaces back to normal spaces when editing ended, so that possible problems with the non-breaking spaces won't occur when the data used somewhere else. Note that this example uses method swizzling so it might look a bit weird, but it's correct.

    The header file:

    //  UITextField+RightAlignedNoSpaceFix.h
    
    #import <UIKit/UIKit.h>
    
    @interface UITextField (RightAlignedNoSpaceFix)
    @end
    

    The implementation file:

    //  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
    
    0 讨论(0)
  • 2020-12-07 23:32

    I've came up with a solution that subclasses the UITextField class and performs the swap, without the need of copying and pasting code everywhere. This also avoids using method sizzle to fix this.

    @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:@" "];
    }
    
    0 讨论(0)
  • 2020-12-07 23:33
    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 \u{00a0}
        @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: "\u{00a0}")
                /// reset this range
                selectedTextRange = editRange
            }
        }
    }
    
    
    0 讨论(0)
  • 2020-12-07 23:38

    I've used Jack Song's answer for Swift 2 for a while until I realized that the non-braking spaces make problems when rendered in HTML elsewhere, as well as line breaking gets messy in the UITextView itself. So, I've improved the solution to have the non-bracking characters cleaned right away.

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        if (textField == self.desiredTextField) {
           var oldString = textView.text!
           oldString = oldString.stringByReplacingOccurrencesOfString("\u{00a0}", withString: " ");
           let newRange = oldString.startIndex.advancedBy(range.location)..<oldString.startIndex.advancedBy(range.location + range.length)
           let alteredText = text.stringByReplacingOccurrencesOfString(" ", withString: "\u{00a0}")
           textView.text = oldString.stringByReplacingCharactersInRange(newRange, withString: alteredText)
           return false;
        } else {
           return true;
        }
    }
    
    0 讨论(0)
提交回复
热议问题