Right aligned UITextField spacebar does not advance cursor in iOS 7

匿名 (未验证) 提交于 2019-12-03 01:11:01

问题:

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

I create the UITextField as follows:

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]; 

In iOS 6, when I type "hello world" the cursor advances a blank space when I hit the spacebar after "hello."

In iOS 7, the cursor does not advance when I hit the spacebar. However, when I type the "w" in "world," it shows the space and the w.

How can I advance the cursor when the spacebar is hit in iOS 7?

Update:

If I change the textField.textAlignment to UITextAlignmentLeft, then the space appears in iOS 7. I would like to keep it right aligned, if possible.

回答1:

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.



回答2:

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   @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 


回答3:

All the answers above are awesome and very indicative! Especially big thanks to meaning-matters's answer below. Here's a tested Swift 2.0 version. Remember to assign the delegate of the UITextField to your ViewController! Happy coding.

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)..

--

And here is 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..


回答4:

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.



回答5:

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:@" "]; } 


回答6:

Old question but all the above solutions seem overly complicated. Here is how I solved the issue:

I subscribed to two textfield events ->

  • TextFieldEditingDidBegin
  • TextFieldEditingEnded

On TextFieldEditingDidBegin, I simple set textField.textAlignment to UITextAlignmentLeft. On TextFieldEditingEnded, I set textField.textAlignment back to UITextAlignmentRight.

This worked flawlessly for me and I feel like its not a hack. Hope it helps!



回答7:

Transformed triazotan's answer into Swift3.

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{      if (range.location == textField.text?.characters.count && string == " ") {         let noBreakSpace: Character = "\u{00a0}"         textField.text = textField.text?.append(noBreakSpace)         return false     }     return true } 


回答8:

Fix right aligned text space removing by replacing space with non-breaking space

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

And vice versa

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


回答9:

Here is Swift 3 from @Jack Song's answer

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..


回答10:

My following solution also takes care of the problem with the cursor jumping to the end when typing a space in the middle or beginning of the string. Also pasting a string is now processed correctly too.

I also put in a check for email address fields and other checks, but the interesting part is the last part. It works perfectly for me, have yet to find a problem with it.

You can directly copy/paste this in your project. Don't forget to implement the didBeginEditing and didEndEditing to replace the spaces with non-breaking spaces and back!

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


回答11:

I solved this issue in my app using an by using a left-aligned text field, and then used AutoLayout to align the entire text field to the right. This simulates a right-aligned text field and handles trailing spaces without messing around with space characters etc.

The main hurdle in this approach is that UITextField does not update it's intrinsic content size as the text changes. To get around this I subclassed UITextField to automatically calculate intrinsic content size as the text changes. Here's my subclass:

@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 

And here's a fragment of my auto layout code, using the PureLayout library:

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

Important points to note here:

  1. set content hugging priority on the text field
  2. use a NSLayoutRelationGreaterThanOrEqual relation between the left edge of text field and the view to the left of it (or superview's left edge).


回答12:

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)..


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!