Прямая выровненная UITextField пробел не продвигает курсор в iOS 7

В моем приложении для 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", курсор продвигает пустое место, когда я нажимаю пробел после "привет".

В iOS 7 курсор не продвигается, когда я нажимаю пробел. Однако, когда я набираю "w" в "мире", он показывает пробел и w.

Как я могу продвигать курсор, когда пробел попал в iOS 7?

Update:

Если я изменил textField.textAlignment на UITextAlignmentLeft, тогда пространство появится в iOS 7. Я бы хотел, чтобы это было правильно выровнено, если это возможно.

Ответ 1

Это будет немного взломать, но если вам действительно нужно, чтобы посмотреть путь iOS6, вы можете заменить пространство неразрывным пространством, поскольку оно написано, Он относился по-разному. Пример кода может выглядеть следующим образом:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    // only when adding on the end of textfield && it 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" на @" " при получении строки из текстового поля.

Ответ 2

Вам нужно будет заменить обычные пробелы неразрывными пробелами. Для этого лучше всего вызвать действие для события изменения:

  • Где-то добавьте действие для события 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. В приведенном ниже примере я также изменяю неиспользуемые пробелы обратно в нормальные пространства, когда редактирование заканчивается, так что возможные проблемы с неразрывными пробелами не будут возникать, когда данные будут использоваться где-то в другом месте. Обратите внимание, что в этом примере используется метод 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

Ответ 3

Все приведенные выше ответы являются удивительными и очень показательными! Особенно большое спасибо meaning-matters ответить ниже. Здесь протестирована версия Swift 2.0. Запомнить назначить делегат 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;
    }
}

Ответ 4

Здесь всегда работает решение для вставки и редактирования (т.е. когда вы можете добавлять/удалять тексты с несколькими пробелами).

- (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 каждый раз; тексты в пользовательских интерфейсах очень короткие относительно скорости процессора.

Затем, когда вы действительно хотите получить значение из текстового поля:

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

Итак, это симпатично симметрично.

Ответ 5

Я придумал решение, которое подклассифицирует класс 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:@" "];
}

Ответ 6

Преобразованный ответ триазотана в 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
}

Ответ 7

Старый вопрос, но все вышеперечисленные решения кажутся чрезмерно сложными. Вот как я решил проблему:

Я подписался на два события в текстовом поле →

  • TextFieldEditingDidBegin
  • TextFieldEditingEnded

В TextFieldEditingDidBegin я просто устанавливаю textField.textAlignment в UITextAlignmentLeft. В TextFieldEditingEnded я устанавливаю textField.textAlignment обратно в UITextAlignmentRight.

Это работало безупречно для меня, и я чувствую, что это не хак. Надеюсь, это поможет!

Ответ 8

Исправить удаление выровненного правого выравнивания текста путем замены пространства на неиспользуемое пространство

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

Ответ 9

Вот 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;
    }
}

Ответ 10

Версия 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
}

Ответ 11

Мое следующее решение также заботится о проблеме, когда курсор прыгает до конца при вводе пробела в середине или в начале строки. Кроме того, вставка строки также обрабатывается правильно.

Я также проверил поля для адресов электронной почты и другие проверки, но интересная часть - последняя часть. Он отлично работает для меня, еще не нашел проблемы с ним.

Вы можете напрямую скопировать/вставить это в свой проект. Не забудьте реализовать 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;
}

Ответ 12

Я решил эту проблему в своем приложении, используя текстовое поле с выравниванием по левому краю, а затем использовал 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 между левым краем текстового поля и представлением слева от него (или просмотрите левый край).

Ответ 13

Я использовал ответ 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;
    }
}

Ответ 14

swift4

//
//  UITextFiled+fixspace.swift
//  anbaobao
//
//  Created by 荆文征 on 2018/11/9.
//  Copyright © 2018 com.baimaodai. All rights reserved.
//

import UIKit

extension UITextField {
    /// Runtime 键
    private struct AssociatedKeys {

        static var toggleState: UInt8 = 0
    }

    /// 是否已经修复 右侧问题
    private var isFixedRightSpace: Bool {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.toggleState) as? Bool ?? false
        }
        set(newValue) {
            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)
    }

    @objc func replaceNormalSpacesWithNonBreakingSpaces(textFiled: UITextField) {

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

            let editRange = selectedTextRange
            textFiled.text = textFiled.text?.replacingOccurrences(of: " ", with: "\u{00a0}")
            selectedTextRange = editRange
        }
    }
}