it-swarm.com.ru

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

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

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

Обновление:

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

40
jkh

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

- (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" на @" " при извлечении строки из текстового поля.

41
triazotan

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

  1. Где-нибудь добавьте действие для события UIControlEventEditingChanged в ваше текстовое поле:

    [myTextField addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces)
                      forControlEvents:UIControlEventEditingChanged];
    
  2. Затем реализуйте метод replaceNormalSpacesWithNonBreakingSpaces:

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

Это безопаснее, чем использование textField:shouldChangeCharactersInRange:replacementString:, потому что если вы возвращаете NO из этого метода, вы фактически говорите, что указанный текст не должен изменяться. Это приведет к тому, что события изменения (такие как textFieldEditingChanged: IBActions или событие UIControlEventEditingChanged UITextField) не будут инициированы.

Исправьте это везде:

Если вы хотите, чтобы это исправление для всех ваших UITextFields, вы можете создать категорию , куда вы добавите эти события при запуске 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
13
gitaarik

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

}

-

А вот и Свифт 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;
    }
}
11
Jack Song

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

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

- (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
meaning-matters

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

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

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

  • TextFieldEditingDidBegin
  • TextFieldEditingEnded

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

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

2
pnavk

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

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

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

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

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

Важные моменты, на которые следует обратить внимание:

  1. установить приоритет размещения содержимого в текстовом поле
  2. используйте отношение NSLayoutRelationGreaterThanOrEqual между левым краем текстового поля и видом слева от него (или левым краем суперпредставления).
0
James

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

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

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

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
        }
    }
}
0
Z King

Я использовал ответ Джека Сонга для 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;
    }
}
0
Vitalii