2013-10-24 18 views
35

Nella mia app per iPad, ho notato un comportamento diverso tra iOS 6 e iOS 7 con UITextFields.Barra spaziatrice UITextField allineata a destra non fa avanzare il cursore in iOS 7

creo l'UITextField come segue: "ciao"

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, quando si digita "ciao mondo", il cursore avanza uno spazio vuoto quando ho colpito la barra spaziatrice dopo

In iOS 7, il cursore non avanza quando premo la barra spaziatrice. Tuttavia, quando digito "w" in "world", mostra lo spazio e il w.

Come si può far avanzare il cursore quando si colpisce la barra spaziatrice in iOS 7?

Aggiornamento:

Se cambio la textField.textAlignment a UITextAlignmentLeft, allora lo spazio appare in iOS 7. Vorrei tenerlo allineamento a destra, se possibile.

risposta

37

Sarebbe un po 'un trucco, ma se hai davvero bisogno di questo per guardare in modo iOS6, puoi sostituire lo spazio con non-breaking space come è scritto. È trattato in modo diverso. Esempio di codice potrebbe essere la seguente:

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

Nel caso in cui non è chiaro, textField:shouldChangeCharactersInRange:replacementString: è un metodo UITextFieldDelegate protocollo, quindi nel tuo esempio, il metodo di cui sopra sarebbe nel viewcontroller designato da [textField setDelegate:self].

Se volete che i vostri spazi regolari indietro, si sarà ovviamente anche bisogno di ricordare per convertire il testo di nuovo, sostituendo le occorrenze di @"\u00a0" con @" " quando ottiene la stringa di fuori del campo di testo.

+0

Grazie per questo! – Morrowless

+0

Funziona solo per aggiungere/eliminare un carattere alla volta; quindi non quando si incolla o si elimina del testo con più spazi al loro interno. E questo può essere fatto un po 'più semplice; guarda la mia risposta –

+0

Se si restituisce 'NO' da' textField: shouldChangeCharactersInRange: replacementString: 'si potrebbe rompere le cose. Per favore vedi [la mia risposta] (http://stackoverflow.com/a/22512184/1248175) per un metodo più sicuro. – rednaw

5

Ecco una soluzione che funziona sempre, anche per incollare e modificare (ad esempio quando è possibile aggiungere/eliminare testi con più spazi).

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

Non preoccuparti per le prestazioni di fare stringByReplacingOccurrencesOfString ogni volta; i testi nelle interfacce utente sono molto molto brevi rispetto alla velocità della CPU.

Poi, quando si vuole realmente ottenere il valore dal campo di testo:

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

Quindi questo è un ben simmetrica.

+1

Restituzione di NO in '-textField: shouldChangeCharactersInRange: replacementString' suppres' UITextFieldTextDidChangeNotification'. Quindi puoi inviarlo nel tuo metodo '[[NSNotificationCenter defaultCenter] postNotificationName: UITextFieldTextDidChangeNotification oggetto: textField];' per restituire il comportamento predefinito – Wisors

11

È necessario sostituire gli spazi normali con non-breaking spaces.E 'meglio attivare un'azione su un evento di modifica per questo:

  1. Da qualche parte aggiungere un'azione per l'evento UIControlEventEditingChanged sul campo di testo:

    [myTextField addTarget:self action:@selector(replaceNormalSpacesWithNonBreakingSpaces) 
            forControlEvents:UIControlEventEditingChanged]; 
    
  2. quindi implementare il metodo replaceNormalSpacesWithNonBreakingSpaces:

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

Questo è più sicuro dell'utilizzo di textField:shouldChangeCharactersInRange:replacementString:, b perché se si restituisce NO da questo metodo, si sta effettivamente dicendo che il testo specificato non deve essere modificato. Ciò causerà la non attivazione degli eventi di modifica (come le IBActions textFieldEditingChanged: o l'evento UIControlEventEditingChanged di UITextField).

Fix ovunque:

Se si desidera che questa correzione per tutti i vostri UITextFields è possibile creare un category cui si aggiungono queste azioni di eventi quando viene avviata un'UITextField. Nell'esempio seguente, inoltre, quando si termina la modifica, gli spazi non interrompibili tornano agli spazi normali, in modo che eventuali problemi con gli spazi non interrotti non si verifichino quando i dati vengono utilizzati altrove. Nota che questo esempio usa method swizzling quindi potrebbe sembrare un po 'strano, ma è corretto.

Il file di intestazione:

// UITextField+RightAlignedNoSpaceFix.h 

#import <UIKit/UIKit.h> 

@interface UITextField (RightAlignedNoSpaceFix) 
@end 

Il file di implementazione:

// 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 
+1

Mi piace l'utilizzo di target/action ma interferisce quando si modifica il testo (ad esempio eliminando una lettera in il centro della stringa fa saltare il cursore alla fine della stringa) – Clafou

2

vecchia questione, ma tutte le soluzioni di cui sopra sembrano eccessivamente complicato. Ecco come ho risolto il problema:

Mi sono iscritto a due eventi TextField ->

  • TextFieldEditingDidBegin
  • TextFieldEditingEnded

Su TextFieldEditingDidBegin, io semplice set textField.textAlignment a UITextAlignmentLeft. In TextFieldEditingEnded, ho impostato textField.textAlignment su UITextAlignmentRight.

Questo ha funzionato perfettamente per me e mi sento come se non fosse un trucco. Spero che sia d'aiuto!

+0

Ciò è deprecato su iOS 6.0. textField.textAlignment = UITextAlignmentLeft; –

0

La mia soluzione successiva si occupa anche del problema con il cursore che salta alla fine quando si digita uno spazio nel mezzo o all'inizio della stringa. Anche incollare una stringa ora viene elaborata correttamente.

Ho anche inserito un assegno per i campi dell'indirizzo e-mail e altri assegni, ma la parte interessante è l'ultima parte. Funziona perfettamente per me, deve ancora trovare un problema con esso.

È possibile copiare/incollare direttamente questo nel progetto. Non dimenticare di implementare didobeginEditing e didEndEditing per sostituire gli spazi con spazi non interrotti e tornare indietro!

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

Fix allineata a destra lo spazio del testo rimozione sostituendo spazio con spazio unificatore

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

E viceversa

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

ho si avvicinò con una soluzione che sottoclassi l'UITextField classe e esegue lo scambio, senza la necessità di copiare e incollare il codice ovunque. Questo evita anche l'uso del metodo sizzle per risolvere questo problema.

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

soluzione davvero bella – gbk

0

Ho risolto questo problema nella mia app utilizzando un utilizzando un campo di testo allineato a sinistra, e poi utilizzato AutoLayout per allineare l'intero campo di testo a destra. Questo simula un campo di testo allineato a destra e gestisce gli spazi finali, senza fare in giro con caratteri di spazio ecc

L'ostacolo principale di questo approccio è che UITextField non aggiorna la sua dimensione contenuto intrinseco, come le modifiche al testo. Per aggirare questo ho sottostimato UITextField per calcolare automaticamente le dimensioni del contenuto intrinseco mentre il testo cambia. Ecco il mio sottoclasse:

@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 

Ed ecco un frammento del mio codice layout automatico, utilizzando la libreria PureLayout:

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

punti importanti da notare qui:

  1. serie di priorità contenuti Abbracciare il campo di testo
  2. utilizza una relazione NSLayoutRelationGreaterThanOrEqual tra il bordo sinistro del campo di testo e la vista a sinistra di esso (o superv il bordo sinistro di iew).
10

Tutte le risposte sopra sono fantastiche e molto indicative! Particolarmente grande grazie allo meaning-mattersanswer below. Ecco una versione aggiornata di Swift 2.0. Ricordare a assegnare il delegato di UITextField al ViewController! Buona programmazione.

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

} 

-

E qui è 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; 
    } 
} 
+0

Questo * si blocca * se l'utente inserisce più di un'emoji. Qualche modo per aggiustarlo? – Cesare

0

Ho usato Jack Song's answer per Swift 2 per un po 'fino a quando mi sono reso conto che gli spazi di frenata non fanno problemi se fusi in HTML altrove, così come linea di rottura diventa disordinato nel UITextView stesso. Quindi, ho migliorato la soluzione per fare in modo che i caratteri senza parentesi fossero puliti immediatamente.

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

Trasformata risposta di triazotan in 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 
} 
1

Ecco Swift 3 dalla risposta di @Jack canzone

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