Right aligned UITextField spacebar does not advance cursor in iOS 7
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:
Somewhere add an action for the
UIControlEventEditingChanged
event on your textfield:[myTextField addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces) forControlEvents:UIControlEventEditingChanged];
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
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)..<oldString.startIndex.advancedBy(range.location + range.length)
let newString = oldString.stringByReplacingCharactersInRange(newRange, withString: string)
textField.text = newString.stringByReplacingOccurrencesOfString(" ", withString: "\u{00a0}");
return false;
} else {
return true;
}
}
--
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..<newEnd, with: string)
textField.text = newString.replacingOccurrences(of: " ", with: "\u{00a0}")
return false;
} else {
return true;
}
}
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.
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.