私のiPadアプリでは、UITextFieldsを使用したiOS 6とiOS 7の動作が異なることに気付きました。
次のように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では、スペースバーを押してもカーソルが進まない。しかし、「ワールド」に「w」と入力すると、スペースとwが表示されます。
IOS 7でスペースバーを押したときにカーソルを進めるにはどうすればよいですか?
更新:
TextField.textAlignmentをUITextAlignmentLeftに変更すると、iOS 7にスペースが表示されます。可能であれば、スペースを正しく揃えておきます。
少しハックになるかもしれませんが、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"
を@" "
で置き換えることにより、テキストを元に戻すことも忘れないでください。
通常のスペースを 非改行スペース に置き換える必要があります。これには、変更イベントでアクションをトリガーするのが最善です。
どこかに、テキストフィールドに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
イベントなど)がトリガーされなくなります。
すべてのUITextFieldに対してこの修正が必要な場合は、UITextFieldが開始されたときにこれらのイベントアクションを追加する category を作成できます。以下の例では、編集が終了したときに改行しないスペースを通常のスペースに戻します。これにより、データが別の場所で使用されたときに改行しないスペースに関する問題が発生しなくなります。この例では 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
上記のすべての答えは素晴らしく、非常に示唆的です! 意味の意味 の 以下の回答 に特に感謝します。テスト済みSwift 2.0バージョンです。 RemembertoassignデリゲートUITextFieldを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: "\u{00a0}");
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: "\u{00a0}")
return false;
} else {
return true;
}
}
どこにでもコードをコピーして貼り付ける必要なしに、UITextFieldクラスをサブクラス化してスワップを実行するソリューションを思いつきました。これにより、メソッドsizzleを使用してこれを修正することも回避されます。
@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:@" "];
}
これは、貼り付けや編集(つまり、複数のスペースを持つテキストを追加/削除する場合)でも常に機能するソリューションです。
- (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
を実行するパフォーマンスについて心配する必要はありません。 UIのテキストは、CPU速度に比べて非常に短いです。
次に、実際にテキストフィールドから値を取得したい場合:
NSString* text = [textField.text stringByReplacingOccurrencesOfString:@"\u00a0" withString:@" "];
したがって、これは対称性に優れています。
変換トリアゾタン 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
}
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 = "\u{00a0}"
text.append(noBreakSpace)
textField.text = text
return false
}
return true
}
古い質問ですが、上記のすべてのソリューションは非常に複雑に見えます。これが私が問題を解決した方法です:
2つのテキストフィールドイベントに登録しました->
TextFieldEditingDidBeginでは、textField.textAlignmentをUITextAlignmentLeftに設定しました。 TextFieldEditingEndedで、textField.textAlignmentをUITextAlignmentRightに戻しました。
これは完璧に機能し、ハックではないように感じます。それが役に立てば幸い!
スペースを改行しないスペースで置き換えることにより、右揃えのテキストスペースの削除を修正
- (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;
}
Swift 3 @Jack Songの回答から
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;
}
}
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
}
}
}
アプリでこの問題を解決するには、左揃えのテキストフィールドを使用し、次に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
関係を使用します。次の解決策では、文字列の途中または先頭にスペースを入力すると、カーソルが最後にジャンプする問題も処理します。また、文字列の貼り付けも正しく処理されるようになりました。
メールアドレスのチェックやその他のチェックも入れましたが、興味深い部分は最後の部分です。それは私にとって完璧に機能しますが、まだ問題を見つけていません。
これをプロジェクトに直接コピー/貼り付けできます。スペースを改行しないスペースに置き換えて戻すために、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;
}
私は Jack Songの答え を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("\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;
}
}