Come posso spostare UITextField verso l'alto quando è presente la tastiera - all'avvio della modifica?


1692

Con iOS SDK:

Ho un UIViewcon UITextFields che portano una tastiera. Ne ho bisogno per essere in grado di:

  1. Consenti lo scorrimento del contenuto di UIScrollViewper visualizzare gli altri campi di testo una volta visualizzata la tastiera

  2. "Salta" automaticamente (scorrendo verso l'alto) o accorciando

So che ho bisogno di un UIScrollView. Ho provato a cambiare la mia classe UIViewin a UIScrollViewma non riesco ancora a scorrere le caselle di testo verso l'alto o verso il basso.

Ho bisogno sia di una UIViewche di una UIScrollView? Uno va dentro l'altro?

Cosa deve essere implementato per scorrere automaticamente fino al campo di testo attivo?

Idealmente, gran parte della configurazione dei componenti sarà eseguita in Interface Builder. Vorrei solo scrivere il codice per ciò di cui ha bisogno.

Nota: il UIView(o UIScrollView) con cui sto lavorando è richiamato da una tabbar ( UITabBar), che deve funzionare normalmente.


Modifica: sto aggiungendo la barra di scorrimento solo per quando viene visualizzata la tastiera. Anche se non è necessario, mi sembra che fornisca un'interfaccia migliore perché, ad esempio, l'utente può scorrere e modificare le caselle di testo.

Ho funzionato dove cambio la dimensione del frame UIScrollViewquando la tastiera va su e giù. Sto semplicemente usando:

-(void)textFieldDidBeginEditing:(UITextField *)textField { 
    //Keyboard becomes visible
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
                     scrollView.frame.origin.y, 
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50);   //resize
}

-(void)textFieldDidEndEditing:(UITextField *)textField {
   //keyboard will hide
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
       scrollView.frame.origin.y, 
     scrollView.frame.size.width,
      scrollView.frame.size.height + 215 - 50); //resize
}

Tuttavia, ciò non "sposta in alto" automaticamente o centra i campi di testo inferiori nell'area visibile, che è quello che mi piacerebbe davvero.


6
Controllalo. Nessun problema per te. TPKeyboardAvoiding
Aruna

21
È documentato da Apple, penso che sia il modo migliore: developer.apple.com/library/ios/#documentation/StringsTextFonts/…
Maik639

58
Usa questo codice. Hai solo bisogno di 1 riga nel file appdelegate.m e funziona. github.com/hackiftekhar/IQKeyboardManager
Pradeep Mittal

9
Il modo migliore che ho trovato finora è questo TPKeyboard
Mongi Zaidi,

2
Un altro modo è quello di aggiungere tali campi di testo di contenuto e tutti in TableViewController e lasciare che tableview gestisca questo.
Vicky Dhas,

Risposte:


1036
  1. Ti servirà solo ScrollViewse i contenuti che hai ora non si adattano allo schermo dell'iPhone. (Se stai aggiungendo ScrollViewcome superview dei componenti solo per far TextFieldscorrere verso l'alto quando viene visualizzata la tastiera, non è necessario.)

  2. Il modo standard per evitare che TextFields sia coperto dalla tastiera è spostare la vista su / giù ogni volta che viene mostrata la tastiera.

Ecco un po 'di codice di esempio:

#define kOFFSET_FOR_KEYBOARD 80.0

-(void)keyboardWillShow {
    // Animate the current view out of the way
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)keyboardWillHide {
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    if ([sender isEqual:mailTf])
    {
        //move the main view, so that the keyboard does not hide it.
        if  (self.view.frame.origin.y >= 0)
        {
            [self setViewMovedUp:YES];
        }
    }
}

//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.3]; // if you want to slide up the view

    CGRect rect = self.view.frame;
    if (movedUp)
    {
        // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
        // 2. increase the size of the view so that the area behind the keyboard is covered up.
        rect.origin.y -= kOFFSET_FOR_KEYBOARD;
        rect.size.height += kOFFSET_FOR_KEYBOARD;
    }
    else
    {
        // revert back to the normal state.
        rect.origin.y += kOFFSET_FOR_KEYBOARD;
        rect.size.height -= kOFFSET_FOR_KEYBOARD;
    }
    self.view.frame = rect;

    [UIView commitAnimations];
}


- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

3
Cosa significa _textField? L'ho copiato nel mio codice, dice che _textField non è dichiarato.
Cocoa Dev,

È il campo che usi per dire "quando l'utente sta modificando qui la vista dovrebbe scorrere verso l'alto" o qualcosa del genere ... Comunque puoi rimuoverlo se, se hai più campi.
patrick,

non è una pastella da chiamare - (void) setViewMovedUp: (BOOL) movedUp negli eventi keyBoardWillSHow e KeyBoardWillHide !!
Abduliam Rehmanius,

4
Non particolarmente utile se si supportano le rotazioni della vista principale.
FractalDoctor

2
Per farlo funzionare, ho dovuto commentare la textFieldDidBeginEditingsezione.
avance

445

Avevo anche molti problemi con la UIScrollViewcomposizione di multipli UITextFields, dei quali uno o più di loro sarebbero stati oscurati dalla tastiera quando venivano modificati.

Ecco alcune cose da considerare se il tuo UIScrollViewnon scorre correttamente.

1) Assicurarsi che ContentSize sia maggiore della UIScrollViewdimensione del frame. Il modo per capire UIScrollViewsè che UIScrollViewè come una finestra di visualizzazione sul contenuto definito in contentSize. Quindi, per UIScrollviewpoter scorrere ovunque, contentSize deve essere maggiore di UIScrollView. Altrimenti, non è necessario lo scorrimento poiché tutto ciò che è definito in contentSize è già visibile. A proposito, contentSize predefinito = CGSizeZero.

2) Ora che hai capito che UIScrollViewc'è davvero una finestra nel tuo "contenuto", il modo per garantire che la tastiera non oscuri la tua UIScrollView's"finestra" di visualizzazione sarebbe ridimensionare in UIScrollViewmodo che quando la tastiera è presente, hai la UIScrollViewfinestra ridimensionato al solo UIScrollViewframe.size.height originale meno l'altezza della tastiera. Questo assicurerà che la tua finestra sia solo quella piccola area visibile.

3) Ecco il trucco: quando l'ho implementato per la prima volta ho pensato che avrei dovuto ottenere il campo CGRectdi testo modificato e chiamare il UIScrollView'smetodo scrollRecToVisible. Ho implementato il UITextFieldDelegatemetodo textFieldDidBeginEditingcon la chiamata al scrollRecToVisiblemetodo. Questo in realtà ha lavorato con un effetto collaterale strano che lo scorrimento sarebbe scattare l' UITextFieldin posizione. Per molto tempo non sono riuscito a capire cosa fosse. Quindi ho commentato il textFieldDidBeginEditingmetodo Delegate e tutto funziona !! (???). Come si è scoperto, credo che in UIScrollViewrealtà implicitamente porti implicitamente UITextFieldla finestra correntemente modificata nella finestra visualizzabile. La mia implementazione del UITextFieldDelegatemetodo e la successiva chiamata a scrollRecToVisibleera ridondante ed era la causa dello strano effetto collaterale.

Così qui sono i passi per scorrere correttamente il vostro UITextFieldin un UIScrollViewin posizione quando le appare tastiera.

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad 
{
    [super viewDidLoad];

    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillShow:) 
                                                 name:UIKeyboardWillShowNotification 
                                               object:self.view.window];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillHide:) 
                                                 name:UIKeyboardWillHideNotification 
                                               object:self.view.window];
    keyboardIsShown = NO;
    //make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
    CGSize scrollContentSize = CGSizeMake(320, 345);
    self.scrollView.contentSize = scrollContentSize;
}

- (void)keyboardWillHide:(NSNotification *)n
{
    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;


    // resize the scrollview
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height += (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    keyboardIsShown = NO;
}

- (void)keyboardWillShow:(NSNotification *)n
{
    // This is an ivar I'm using to ensure that we do not do the frame size adjustment on the `UIScrollView` if the keyboard is already shown.  This can happen if the user, after fixing editing a `UITextField`, scrolls the resized `UIScrollView` to another `UITextField` and attempts to edit the next `UITextField`.  If we were to resize the `UIScrollView` again, it would be disastrous.  NOTE: The keyboard notification will fire even when the keyboard is already shown.
    if (keyboardIsShown) {
        return;
    }

    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    // resize the noteView
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];
    keyboardIsShown = YES;
}
  1. Registrati per le notifiche della tastiera all'indirizzo viewDidLoad
  2. Annullare la registrazione per le nofitications della tastiera a viewDidUnload
  3. Assicurarsi che contentSizesia impostato e maggiore di UIScrollViewaviewDidLoad
  4. Riduci la presenza UIScrollViewdella tastiera
  5. Ripristina il UIScrollViewmomento in cui la tastiera scompare.
  6. Utilizzare un ivar per rilevare se la tastiera è già visualizzata sullo schermo poiché le notifiche della tastiera vengono inviate ogni volta che UITextFieldviene visualizzata una scheda anche se la tastiera è già presente per evitare di ridurre il UIScrollViewquando è già ridotta

Una cosa da notare è che UIKeyboardWillShowNotificationsparerà anche quando la tastiera è già sullo schermo quando si tocca un'altra UITextField. Mi sono occupato di questo usando un ivar per evitare di ridimensionare UIScrollViewquando la tastiera è già sullo schermo. Ridimensionare inavvertitamente il UIScrollViewmomento in cui la tastiera è già lì sarebbe disastroso!

Spero che questo codice ti risparmi un sacco di mal di testa.


3
Grande, ma due problemi: 1. UIKeyboardBoundsUserInfoKeyè deprecato. 2. keyboardSize è in "coordinate dello schermo", quindi i calcoli viewFrame falliranno se il frame viene ruotato o ridimensionato.
Martin Wickman,

21
@Martin Wickman - Usa CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;invece del deprecatoUIKeyboardBoundsUserInfoKey
sottenad

1
Ciao, ho fatto lo stesso, ma la visualizzazione del testo si sposta solo quando l'utente inizia a digitare? È il comportamento previsto o mi manca qualcosa?

3
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].sizedovrebbe essere [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size. Ottima soluzione però!
j7nn7k,

1
Mi piace la tua soluzione ma penso di poterla rendere ancora più semplice: non preoccuparti delle cose di Notification Observer; chiama invece le giuste routine di animazione all'interno dei metodi delegati appropriati: per UITextView sono textViewDidBeginEditing e textViewDidEndEditing.
AlexChaffee,

270

In realtà è meglio solo usare l'implementazione di Apple, come previsto nei documenti . Tuttavia, il codice che forniscono è difettoso. Sostituisci la seguente porzione keyboardWasShown:appena sotto i commenti a quanto segue:

NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);

CGPoint origin =  activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
    CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
    [backScrollView setContentOffset:scrollPoint animated:YES];
}

I problemi con il codice di Apple sono questi: (1) Calcolano sempre se il punto si trova all'interno della cornice della vista, ma è un ScrollView, quindi potrebbe già averlo fatto scorrere e devi tenere conto di tale offset:

origin.y -= scrollView.contentOffset.y

(2) Spostano il contentOffset dall'altezza della tastiera, ma vogliamo il contrario (vogliamo spostare contentOffsetl'altezza visibile sullo schermo, non ciò che non lo è):

activeField.frame.origin.y-(aRect.size.height)

1
In situazioni in cui la vista di scorrimento non riempie lo schermo, aRect deve essere impostato sul riquadro della vista di scorrimento
mblackwell8

2
Non dovresti voler CGPoint origin = activeField.frame.origin + activeField.frame.size.height?, Perché vuoi che venga visualizzato l'intero campo di testo e se capita di avere solo alcuni pixel visibili, il codice non entrerà nel condizione.
htafoya,

1
Questa soluzione non funziona con l'orientamento orizzontale: il campo di testo vola dalla parte superiore della porta di visualizzazione. iPad con iOS 7.1.
Andrew,

4
Per un migliore supporto di iOS 8, suggerirei di utilizzare UIKeyboardFrameEndUserInfoKeyanziché UIKeyboardFrameBeginUserInfoKeyquando si ottengono le dimensioni della tastiera, poiché ciò rileverà cose come le modifiche della tastiera personalizzate e l'attivazione / disattivazione del testo predittivo.
Endareth,

1
@Egor: La tua correzione lo fa funzionare meglio - ma l'ultima riga deve essere inversa:self.scrollView.contentOffset = self.currentSVoffset;
Morten Holmgaard,

244

In textFieldDidBeginEdittinge in textFieldDidEndEditingcall la funzione in questo [self animateTextField:textField up:YES]modo:

-(void)textFieldDidBeginEditing:(UITextField *)textField 
{ 
    [self animateTextField:textField up:YES]; 
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField:textField up:NO];
}

-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
    const int movementDistance = -130; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? movementDistance : -movementDistance); 

    [UIView beginAnimations: @"animateTextField" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

Spero che questo codice ti possa aiutare.

In Swift 2

func animateTextField(textField: UITextField, up: Bool) 
{
     let movementDistance:CGFloat = -130
     let movementDuration: Double = 0.3

     var movement:CGFloat = 0
     if up 
     {
         movement = movementDistance
     }
     else 
     {
         movement = -movementDistance
     }
     UIView.beginAnimations("animateTextField", context: nil)
     UIView.setAnimationBeginsFromCurrentState(true)
     UIView.setAnimationDuration(movementDuration)
     self.view.frame = CGRectOffset(self.view.frame, 0, movement)
     UIView.commitAnimations()
}


func textFieldDidBeginEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:true)
}

func textFieldDidEndEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:false)
}

SWIFT 3

 func animateTextField(textField: UITextField, up: Bool)
    {
        let movementDistance:CGFloat = -130
        let movementDuration: Double = 0.3

        var movement:CGFloat = 0
        if up
        {
            movement = movementDistance
        }
        else
        {
            movement = -movementDistance
        }
        UIView.beginAnimations("animateTextField", context: nil)
        UIView.setAnimationBeginsFromCurrentState(true)
        UIView.setAnimationDuration(movementDuration)
        self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
        UIView.commitAnimations()
    }


    func textFieldDidBeginEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:true)
    }

    func textFieldDidEndEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:false)
    }

1
perché non usare [UIView animateWithDuration: animations:^{ }];?
Andre Cytryn,

2
funziona bene, sebbene const int movementDistance = -130; // modificare in base alle esigenze deve essere modificato in più flessibile
Hammer,

7
Incredibilmente semplice su piccole implementazioni. Nessun problema con ScrollViews e ambigui problemi di layout automatico.
James Perih,

4
Ehm ... non usi affatto il parametro textField. Perché quindi averlo come parametro di funzione? Inoltre, è possibile utilizzare l'operatore ternario anche in Swift. Rende il codice meno chiacchierone.
stk

1
Se il colore di sfondo della vista è diverso dal nero, assicurati di impostare il colore della finestra in modo che corrisponda alla vista in modo che l'utente non veda dietro di essa. ovvero self.window.backgroundColor = [UIColor whiteColor];
bvmobileapps

134

Basta usare TextFields:

1a) Utilizzo Interface Builder: selezionare Tutti i campi di testo => Modifica => Incorpora in>> ScrollView

1b) Incorporare manualmente TextFields in UIScrollView chiamato scrollView

2) Set UITextFieldDelegate

3) Impostare ciascuno textField.delegate = self;(o effettuare connessioni Interface Builder)

4) Copia / Incolla:

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
    [scrollView setContentOffset:scrollPoint animated:YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    [scrollView setContentOffset:CGPointZero animated:YES];
}

8
Ma aumenta anche la vista quando textFieldè già visibile.
TheTiger

1
Devi cambiare CGPointMake(0, textField.frame.origin.y);inCGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top);
Fury l'

@Egor Anche dopo il tuo commento non funziona. Come menzionato "TheTiger", la vista si sposta in alto anche dopo che il campo di testo è visibile.
rak appdev,

Modifica per XCode 10: "Seleziona tutto TextFields => Editor => Incorpora in => Scorri vista"
tibalt

116

Per Universal Solution , ecco il mio approccio per l'implementazione di IQKeyboardManager .

inserisci qui la descrizione dell'immagine

Fase 1: - ho aggiunto le notifiche globali di UITextField, UITextViewe UIKeyboardin una classe Singleton. Lo chiamo IQKeyboardManager .

Step2: - Se trovato UIKeyboardWillShowNotification, UITextFieldTextDidBeginEditingNotificationo UITextViewTextDidBeginEditingNotificationnotifiche, cerco di ottenere topMostViewControlleristanza dalla UIWindow.rootViewControllergerarchia. Per scoprire correttamente UITextField/ UITextViewsu di esso, topMostViewController.viewla cornice deve essere regolata.

Step3: - Ho calcolato la distanza di spostamento prevista topMostViewController.viewrispetto alla prima risposta UITextField/ UITextView.

Step4: - Mi sono spostato topMostViewController.view.framesu / giù in base alla distanza di spostamento prevista.

Passaggio 5: - Se trovato UIKeyboardWillHideNotification, UITextFieldTextDidEndEditingNotificationo UITextViewTextDidEndEditingNotificationnotifica, provo di nuovo a ottenere topMostViewControlleristanza dalla UIWindow.rootViewControllergerarchia.

Passaggio 6: - Ho calcolato la distanza disturbata di topMostViewController.viewcui deve essere ripristinata la posizione originale.

Step7: - Ho ripristinato topMostViewController.view.framein base alla distanza disturbata.

Step8: - Ho istanziato l' istanza della classe IQKeyboardManager singleton al caricamento dell'app, quindi ogni UITextField/ UITextViewnell'app si regolerà automaticamente in base alla distanza di spostamento prevista.

Questo è tutto ciò che IQKeyboardManager fa per te SENZA LINEA DI CODICE davvero !! è sufficiente trascinare e rilasciare il file sorgente correlato al progetto. IQKeyboardManager supporta anche l' orientamento del dispositivo , la gestione automatica della barra degli strumenti UIT , KeybkeyboardDistanceFromTextField e molto altro.


Aggiungi la directory IQKeyBoardManagerSwift al mio progetto e non funziona. Impossibile abilitare perché non lo riconosce in AppDelegate ...
user3722523

2
sembra che il phishing non abbia mostrato la soluzione reale, ma invece vediamo una pubblicità per questo account GitHub di ragazzi.
Brian,

101

Ho messo insieme un universale, drop-in UIScrollView, UITableViewe anche UICollectionViewsottoclasse che si occupa di spostare tutti i campi di testo all'interno di esso fuori del modo della tastiera.

Quando la tastiera sta per apparire, la sottoclasse troverà la sottoview che sta per essere modificata e regola la cornice e l'offset del contenuto per assicurarsi che la vista sia visibile, con un'animazione che si adatti al pop-up della tastiera. Quando la tastiera scompare, ripristina le dimensioni precedenti.

Dovrebbe funzionare praticamente con qualsiasi configurazione, UITableViewun'interfaccia basata su, o una composta da viste posizionate manualmente.

Ecco qui: soluzione per spostare i campi di testo lontano dalla tastiera


Questo è! Questa è la soluzione migliore, più efficiente e perfetta! Gestisce inoltre correttamente le rotazioni per le viste di scorrimento. Se si ruota, assicurarsi di ridimensionare automaticamente in verticale ma non ancorare sul fondo. Nel mio caso ho aggiunto un UITextView alla vista di scorrimento. Grazie mazzi!
Christopher,

Ottimo lavoro! Certo, sto diventando pigro usando la tua soluzione invece di quella fai-da-te, ma il mio capo è più felice, quindi sì! Anche se qualcuno vuole fare da solo, mi piace il tuo approccio alla sottoclasse, invece di aggiungere codice a ciascun controller. Ero scioccato che iOS non lo facesse di default come fece Android - poi di nuovo, sto trovando molte cose che mancano in iOS e MacOS :(
eselk,

Fa cose strane come la mia scrollview si adatta tutte allo schermo, quindi non può essere fatto scorrere. Dopo aver aperto e chiuso la tastiera, il contenuto è ora più grande (sembra che sia stato aggiunto qualcosa di invisibile e non rimosso nella parte inferiore della pagina), e può essere fatto scorrere.
Almo,

91

Per programmatori Swift :

Questo farà tutto per te, inseriscili nella classe del controller di visualizzazione e implementa il UITextFieldDelegatecontroller di visualizzazione e imposta il delegato di textField suself

textField.delegate = self // Setting delegate of your UITextField to self

Implementare i metodi di callback delegato:

func textFieldDidBeginEditing(textField: UITextField) {
    animateViewMoving(true, moveValue: 100)
}

func textFieldDidEndEditing(textField: UITextField) {
    animateViewMoving(false, moveValue: 100)
}

// Lifting the view up
func animateViewMoving (up:Bool, moveValue :CGFloat){
    let movementDuration:NSTimeInterval = 0.3
    let movement:CGFloat = ( up ? -moveValue : moveValue)
    UIView.beginAnimations( "animateView", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(movementDuration )
    self.view.frame = CGRectOffset(self.view.frame, 0,  movement)
    UIView.commitAnimations()
}

Per Swift 4, 4.2, 5: Modifica

self.view.frame = CGRectOffset(self.view.frame, 0,  movement)

per

self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)

Ultima nota su questa implementazione: se si spinge un altro controller di visualizzazione nello stack mentre viene mostrata la tastiera, ciò creerà un errore in cui la vista viene riportata al suo frame centrale ma l'offset della tastiera non viene ripristinato. Ad esempio, la tua tastiera è la prima risposta per nameField, ma poi premi un pulsante che spinge il tuo controller di visualizzazione della guida sullo stack. Per correggere l'errore di offset, assicurarsi di chiamare nameField.resignFirstResponder () prima di lasciare il controller di visualizzazione, assicurandosi che venga chiamato anche il metodo delegato textFieldDidEndEditing. Faccio questo nel metodo viewWillDisappear.


3
SwiftLint non mi è piaciuto, self.view.frame = CGRectOffset(self.view.frame, 0, movement)quindi ho cambiato quella riga inself.view.frame.offsetInPlace(dx: 0, dy: movement)
levibostian

2
Swift 4 cambia self.view.frame = CGRectOffset (self.view.frame, 0, movimento) in self.view.frame.offsetBy (dx: 0, dy: movimento)
Asinox

Cordiali saluti, perché questo funzioni, devi mettere. self.view.frame = self.view.frame.offsetBy (dx: 0, dy: movimento)
Josh Wolff,

64

Ci sono già molte risposte, ma nessuna delle soluzioni precedenti aveva tutte le fantasiose funzionalità di posizionamento richieste per un'animazione "perfetta" senza errori, retrocompatibile e senza sfarfallio. (bug durante l'animazione di frame / limiti e contentOffset insieme, diversi orientamenti dell'interfaccia, tastiera divisa per iPad, ...)
Fammi condividere la mia soluzione:
(supponendo che tu abbia impostato UIKeyboardWill(Show|Hide)Notification)

// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = [notification userInfo];

    CGRect keyboardFrameInWindow;
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];

    // the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
    CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];

    CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
    UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);

    // this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    _scrollView.contentInset = newContentInsets;
    _scrollView.scrollIndicatorInsets = newContentInsets;

    /*
     * Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
     * that should be visible, e.g. a purchase button below an amount text field
     * it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
     */
    if (_focusedControl) {
        CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
        controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.

        CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
        CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;

        // this is the visible part of the scroll view that is not hidden by the keyboard
        CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;

        if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
            // scroll up until the control is in place
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);

            // make sure we don't set an impossible offset caused by the "nice visual offset"
            // if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
            newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        } else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
            // if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y = controlFrameInScrollView.origin.y;

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        }
    }

    [UIView commitAnimations];
}


// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = notification.userInfo;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    // undo all that keyboardWillShow-magic
    // the scroll view will adjust its contentOffset apropriately
    _scrollView.contentInset = UIEdgeInsetsZero;
    _scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;

    [UIView commitAnimations];
}

Grandi miglioramenti della risposta @Shiun. Ma dopo che la tastiera è scomparsa, la vista non torna in prima posizione. È ancora un ottimo lavoro :)
Lucien,

2
Grazie, questa è la soluzione migliore per me nel 2017. Nota che non devi tenere traccia di FocusControl da solo, puoi determinarlo con UIApplication.shared.sendAction(...). Ecco la versione Swift 3 della tua risposta (meno la parte saràHide), con l' sendActionimplementazione: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d
xaphod

@xaphod nel mio caso avevo bisogno di focalizzare più controlli, ad esempio un pulsante sotto un campo di input. ma sì, quel codice ora ha 4 anni e potrebbe beneficiare di miglioramenti.
Martin Ullrich,

Questa è probabilmente la soluzione corretta. La notifica da tastiera trasporta dati di animazione, le delegazioni dei campi di testo non conoscono la durata dell'animazione, sarebbe solo un'ipotesi.
XY,

62

Shiun ha detto "A quanto pare, UIScrollView in realtà porta implicitamente UITextField nella finestra visualizzabile in modo implicito" Ciò sembra essere vero per iOS 3.1.3, ma non 3.2, 4.0 o 4.1. Ho dovuto aggiungere uno scrollRectToVisible esplicito per rendere UITextField visibile su iOS> = 3.2.


Non è UIScrollView che fa scorrere implicitamente UITextField modificato in vista, è UITextField che chiama un [UITextField scrollTextFieldToVisibleIfNecessary]metodo privato che a sua volta chiama [UIScrollView scrollRectToVisible]quando [UITextField becomeFirstResponder]viene chiamato. Vedi github.com/leopatras/ios_textfields_on_scrollview . Se i vincoli e i controller di visualizzazione sono impostati correttamente, in realtà non è necessario chiamare scrollRectToVisibleesplicitamente (almeno da IOS 11).
Leone,

48

Una cosa da considerare è se vuoi mai usare un UITextFieldda solo. Non ho mai trovato app per iPhone ben progettate che effettivamente usano UITextFieldsal di fuori di UITableViewCells.

Ci vorrà un po 'di lavoro extra, ma ti consiglio di implementare tutte le viste di immissione dati e viste di tabella. Aggiungi a UITextViewal tuo UITableViewCells.


1
Una delle mie app deve consentire agli utenti di aggiungere note a mano libera, quindi sì, a volte è utile usare un UITextField.
Peter Johnson,

1
Sono d'accordo con questo metodo. Zero lavoro o codice in questo modo. Anche se hai bisogno di una nota in formato libero puoi ancora con una cella da tavolo
RJH,

UITableViewè purtroppo l'unica strada da percorrere. Le notifiche della tastiera sono fragili e hanno cambiato gli straordinari. Codice di esempio su Stack Overflow: stackoverflow.com/a/32390936/218152
SwiftArchitect

Questa risposta è obsoleta da cinque anni . L'unica soluzione moderna è qualcosa di simile ... stackoverflow.com/a/41808338/294884
Fattie

47

Questo documento descrive in dettaglio una soluzione a questo problema. Guarda il codice sorgente in "Spostamento del contenuto che si trova sotto la tastiera". È abbastanza semplice.

EDIT: Notato che c'è un piccolo errore nell'esempio. Probabilmente vorrai ascoltare UIKeyboardWillHideNotificationinvece di UIKeyboardDidHideNotification. In caso contrario, la vista di scorrimento dietro la tastiera verrà troncata per la durata dell'animazione di chiusura della tastiera.


32

La soluzione più semplice trovata

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}


- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = 80; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

Lo schermo si sposta verso l'alto anche se non è in fondo. cioè, se il campo di testo è in alto, si sposta fuori dallo schermo. Come controllare quel caso?
MELWIN,

@MELWIN Basta aggiungere dopo questa riga: int movement = (up ? -movementDistance : movementDistance); if (textField.frame.origin.y < self.view.frame.size.height - keyboard.height) { movementDistance = 0 }Per favore, non che la keyboardvariabile sia il CGRect della tastiera che si apre che si ottiene facendo:let keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
CapturedTree

31

Piccola correzione che funziona per molti UITextFields

#pragma mark UIKeyboard handling

#define kMin 150

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
   if (currTextField) {
      [currTextField release];
   }
   currTextField = [sender retain];
   //move the main view, so that the keyboard does not hide it.
   if (self.view.frame.origin.y + currTextField.frame.origin. y >= kMin) {
        [self setViewMovedUp:YES]; 
   }
}



//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
   [UIView beginAnimations:nil context:NULL];
   [UIView setAnimationDuration:0.3]; // if you want to slide up the view

   CGRect rect = self.view.frame;
   if (movedUp)
   {
      // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
      // 2. increase the size of the view so that the area behind the keyboard is covered up.
      rect.origin.y = kMin - currTextField.frame.origin.y ;
   }
   else
   {
      // revert back to the normal state.
      rect.origin.y = 0;
   }
   self.view.frame = rect;

   [UIView commitAnimations];
}


- (void)keyboardWillShow:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately

   if ([currTextField isFirstResponder] && currTextField.frame.origin.y + self.view.frame.origin.y >= kMin)
   {
      [self setViewMovedUp:YES];
   }
   else if (![currTextField isFirstResponder] && currTextField.frame.origin.y  + self.view.frame.origin.y < kMin)
   {
      [self setViewMovedUp:NO];
   }
}

- (void)keyboardWillHide:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately
   if (self.view.frame.origin.y < 0 ) {
      [self setViewMovedUp:NO];
   }

}


- (void)viewWillAppear:(BOOL)animated
{
   // register for keyboard notifications
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) 
                                                name:UIKeyboardWillShowNotification object:self.view.window]; 
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) 
                                                name:UIKeyboardWillHideNotification object:self.view.window]; 
}

- (void)viewWillDisappear:(BOOL)animated
{
   // unregister for keyboard notifications while not visible.
   [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 
}

rect.origin.y=+currTextField.frame.origin.yfunziona bene grazie
u.gen

30

Il codice di RPDP sposta correttamente il campo di testo dalla tastiera. Ma quando si scorre verso l'alto dopo aver utilizzato e eliminato la tastiera, l'alto è stato fatto scorrere fuori dalla vista. Questo è vero per il simulatore e il dispositivo. Per leggere il contenuto nella parte superiore di quella vista, è necessario ricaricare la vista.

Il suo codice seguente non dovrebbe riportare indietro la vista?

else
{
    // revert back to the normal state.
    rect.origin.y += kOFFSET_FOR_KEYBOARD;
    rect.size.height -= kOFFSET_FOR_KEYBOARD;
}

23

Non sono sicuro che spostare la vista verso l'alto sia l'approccio corretto, l'ho fatto in un modo diverso, ridimensionando UIScrollView. L'ho spiegato in dettaglio su un piccolo articolo


Il link all'articolo è morto.
Teo,

22

Per riportare allo stato di visualizzazione originale, aggiungere:

-(void)textFieldDidEndEditing:(UITextField *)sender

{
    //move the main view, so that the keyboard does not hide it.
    if  (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

20

Prova questo breve trucco.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = textField.frame.origin.y / 2; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

19

Ci sono così tante soluzioni, ma ho trascorso alcune ore prima che inizi a funzionare. Quindi, ho inserito questo codice qui (basta incollarlo nel progetto, non è necessario apportare modifiche):

@interface RegistrationViewController : UIViewController <UITextFieldDelegate>{
    UITextField* activeField;
    UIScrollView *scrollView;
}
@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];

    //scrool view must be under main view - swap it
    UIView* natView = self.view;
    [self setView:scrollView];
    [self.view addSubview:natView];

    CGSize scrollViewContentSize = self.view.frame.size;
    [scrollView setContentSize:scrollViewContentSize];

    [self registerForKeyboardNotifications];
}

- (void)viewDidUnload {
    activeField = nil;
    scrollView = nil;
    [self unregisterForKeyboardNotifications];
    [super viewDidUnload];
}

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

}

-(void)unregisterForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];
}

- (void)keyboardWillShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    CGRect frame = self.view.frame;
    frame.size.height -= kbSize.height;
    CGPoint fOrigin = activeField.frame.origin;
    fOrigin.y -= scrollView.contentOffset.y;
    fOrigin.y += activeField.frame.size.height;
    if (!CGRectContainsPoint(frame, fOrigin) ) {
        CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y + activeField.frame.size.height - frame.size.height);
        [scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
     [scrollView setContentOffset:CGPointZero animated:YES];
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    activeField = textField;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    activeField = nil;
}

-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

PS: spero che il codice aiuti qualcuno a ottenere rapidamente l'effetto desiderato. (Xcode 4.5)


Ciao Hotjard, sto ricevendo EXE_BAD_ACCESS in [self.view addSubview: natView];
Bala

18

@ user271753

Per riportare la vista all'aggiunta originale:

-(BOOL)textFieldShouldReturn:(UITextField *)textField{
   [textField resignFirstResponder];
   [self setViewMovedUp:NO];
   return YES;
}

16

Non richiede una vista di scorrimento per poter spostare il riquadro della vista. È possibile modificare la cornice di una viewcontroller'svista in modo che l'intera vista si sposti abbastanza in alto da posizionare il campo di testo del firstresponder sopra la tastiera. Quando ho riscontrato questo problema ho creato una sottoclasse UIViewControllerche fa ciò. Osserva che la tastiera apparirà notifica e trova la prima sottoview del risponditore e (se necessario) anima la vista principale verso l'alto abbastanza da far sì che il primo risponditore sia sopra la tastiera. Quando la tastiera si nasconde, anima la vista indietro dov'era.

Per utilizzare questa sottoclasse, rendere il controller di visualizzazione personalizzato una sottoclasse di GMKeyboardVC ed eredita questa funzione (accertarsi solo se si implementa viewWillAppeare viewWillDisappeardevono chiamare super). La classe è su github .


Quale licenza? Alcuni dei tuoi file hanno una licenza open source e altri no.
Jaime

Attenzione: questo codice non è compatibile con i progetti ARC.
Almo,

Devi solo aggiungere l'opzione build per specificare che si tratta di file non ARC o benvenuto per convertirlo in ARC e inviare una richiesta pull.
progr

14

Swift 4 .

È possibile scorrere facilmente su e giù UITextFieldO UIViewCon UIKeyBoardConAnimation inserisci qui la descrizione dell'immagine

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var textField: UITextField!
    @IBOutlet var chatView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: .UIKeyboardWillChangeFrame, object: nil)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.resignFirstResponder()
    }

    @objc func keyboardWillChange(notification: NSNotification) {

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y
        print("deltaY",deltaY)

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
            self.chatView.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
        },completion: nil)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

}

2
Quasi perfetto. Su iPhone X hai uno strano divario tra tastiera e campo di testo.
Houman,

12

Ecco la soluzione di hacking che ho ideato per un layout specifico. Questa soluzione è simile alla soluzione Matt Gallagher in quanto scorre una sezione in vista. Sono ancora nuovo nello sviluppo di iPhone e non ho familiarità con il funzionamento dei layout. Quindi, questo trucco.

La mia implementazione doveva supportare lo scorrimento quando si fa clic in un campo e anche lo scorrimento quando l'utente seleziona la tastiera successiva.

Ho avuto un UIView con un'altezza di 775. I controlli sono distribuiti sostanzialmente in gruppi di 3 su un ampio spazio. Ho finito con il seguente layout IB.

UIView -> UIScrollView -> [UI Components]

Ecco che arriva l'hack

Ho impostato l'altezza di UIScrollView su 500 unità più grandi del layout effettivo (1250). Ho quindi creato un array con le posizioni assolute su cui devo scorrere e una semplice funzione per ottenerle in base al numero del tag IB.

static NSInteger stepRange[] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 140, 140, 140, 140, 410
};

NSInteger getScrollPos(NSInteger i) {
    if (i < TXT_FIELD_INDEX_MIN || i > TXT_FIELD_INDEX_MAX) {
        return 0 ;
    return stepRange[i] ;
}

Ora tutto ciò che devi fare è utilizzare le seguenti due righe di codice in textFieldDidBeginEditing e textFieldShouldReturn (quest'ultima se stai creando una navigazione nel campo successivo)

CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
[self.scrollView setContentOffset:point animated:YES] ;

Un esempio.

- (void) textFieldDidBeginEditing:(UITextField *)textField
{
    CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
    [self.scrollView setContentOffset:point animated:YES] ;
}


- (BOOL)textFieldShouldReturn:(UITextField *)textField {

    NSInteger nextTag = textField.tag + 1;
    UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];

    if (nextResponder) {
        [nextResponder becomeFirstResponder];
        CGPoint point = CGPointMake(0, getScrollPos(nextTag)) ;
        [self.scrollView setContentOffset:point animated:YES] ;
    }
    else{
        [textField resignFirstResponder];
    }

    return YES ;
}

Questo metodo non "scorre indietro" come altri metodi. Questo non era un requisito. Ancora una volta questo era per un UIView abbastanza "alto", e non avevo giorni per imparare i motori di layout interni.


12

Secondo i documenti , a partire da iOS 3.0, la UITableViewControllerclasse ridimensiona e riposiziona automaticamente la sua vista tabella quando c'è una modifica in linea dei campi di testo. Penso che non sia sufficiente inserire il campo di testo all'interno di una UITableViewCellcome alcuni hanno indicato.

Dai documenti :

Un controller vista tabella supporta la modifica in linea delle righe della vista tabella; se, ad esempio, le righe hanno campi di testo incorporati in modalità di modifica, scorre la riga che si sta modificando sopra la tastiera virtuale visualizzata.


Ho trovato lo stesso commento. Si è vero. La cosa strana è che sta funzionando in un UITabelViewController e in un secondo no. Ma non sono riuscito a trovare differenze nella mia implementazione.
Morpheus78,

11

Qui ho trovato la soluzione più semplice per gestire la tastiera.

Devi solo copiare e incollare sotto il codice di esempio e modificare il campo di testo o qualsiasi vista che desideri spostare in alto.

Passo 1

Basta copiare e incollare sotto due metodi nel controller

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];
}

- (void)deregisterFromKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Passo 2

registra e cancella le notifiche della tastiera rispettivamente nei metodi viewWillAppear e viewWillDisappear .

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self registerForKeyboardNotifications];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self deregisterFromKeyboardNotifications];
    [super viewWillDisappear:animated];
}

Step-3

Arriva la parte dell'anima, basta sostituire il campo di testo e modificare l'altezza di quanto vuoi spostare al rialzo.

- (void)keyboardWasShown:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGSize currentKeyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    //you need replace your textfield instance here
    CGPoint textFieldOrigin = self.tokenForPlaceField.frame.origin;
    CGFloat textFieldHeight = self.tokenForPlaceField.frame.size.height;

    CGRect visibleRect = self.view.frame;
    visibleRect.size.height -= currentKeyboardSize.height;

    if (!CGRectContainsPoint(visibleRect, textFieldOrigin))
    {
        //you can add yor desired height how much you want move keypad up, by replacing "textFieldHeight" below

        CGPoint scrollPoint = CGPointMake(0.0, textFieldOrigin.y - visibleRect.size.height  + textFieldHeight); //replace textFieldHeight to currentKeyboardSize.height, if you want to move up with more height
        [self.scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification *)notification
{
    [self.scrollView setContentOffset:CGPointZero animated:YES];
}

Riferimento : beh, per favore , apprezza questo ragazzo , che ha condiviso questo bellissimo codice, soluzione pulita.

Spero che questo sarebbe sicuramente utile qualcuno là fuori.


Non penso che questo sia il migliore. Pensa che @Dheeraj VS ha ragione: può essere fatto facilmente e automaticamente se quel campo di testo è nella cella di una tabella (anche quando table.scrollable = NO). NOTA : la posizione e le dimensioni della tabella devono essere ragionevoli. ad es .: - se la posizione y della tabella viene contata 100 dal fondo della vista, la tastiera con altezza 300 si sovrapporrà all'intera tabella. - se l'altezza della tabella = 10 e il campo di testo al suo interno deve essere fatto scorrere verso l'alto di 100 quando appare la tastiera per essere visibile, quel campo di testo sarà fuori dal limite della tabella.
samthui7,

@ samthui7 La risposta Dheeraj funziona solo se stai usando un TableViewController, non solo una vista da tavolo. Lo rende un vincolo che a volte non è adatto.
Ben G,

10

Sono stato alla ricerca di un buon tutorial per principianti sull'argomento, ho trovato il miglior tutorial qui .

Nel MIScrollView.hesempio, al fondo del tutorial assicurarsi di inserire uno spazio a

@property (nonatomic, retain) id backgroundTapDelegate;

come vedi.


Ciao Savagenoob, grazie per il link fornito e benvenuto su StackOverflow. Cerca di fornire quante più informazioni possibili quando rispondi alle (future) domande: i collegamenti semplici sono un po 'fragili. Detto questo, se la risposta è un collegamento a un buon tutorial che potrebbe essere trascurato.
Maarten Bodewes,

10

Quando UITextFieldè in uno UITableViewCellscorrimento dovrebbe essere impostato automaticamente.

In caso contrario, è probabilmente a causa di codice / impostazione errati della vista tabella.

Ad esempio, quando ho ricaricato il mio lungo tavolo con uno UITextFieldin basso come segue,

-(void) viewWillAppear:(BOOL)animated
{
   [self.tableview reloadData];
}

quindi il mio campo di testo in basso è stato oscurato dalla tastiera che è apparso quando ho cliccato all'interno del campo di testo.

Per risolvere questo ho dovuto fare questo -

-(void) viewWillAppear:(BOOL)animated
{
    //add the following line to fix issue
    [super viewWillAppear:animated];
    [self.tableview reloadData];
}

Sono confuso a cosa serve questo codice? Quando viene visualizzata la tastiera, viewWillAppearnon viene chiamata. E reloadDatanon rende visibili le file oscurate.
Adam Johns,

10

Usa questa terza parte che non è necessario scrivere nemmeno una riga

https://github.com/hackiftekhar/IQKeyboardManager

scarica il progetto e trascina la selezione IQKeyboardManagernel tuo progetto. Se riscontri problemi, leggi il READMEdocumento.

Ragazzi è davvero il suo mal di testa per gestire la tastiera.


8

Nota : questa risposta presuppone che textField sia in scrollView.

Preferisco occuparmene usando scrollContentInset e scrollContentOffset invece di scherzare con i frame della mia vista.

Per prima cosa ascoltiamo le notifiche della tastiera

//call this from viewWillAppear
-(void)addKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];
}
//call this from viewWillDisappear
-(void)removeKeyboardNotifications{
    [[NSNotificationCenter default
    Center] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Il prossimo passo è mantenere una proprietà che rappresenti l'attuale primo risponditore (UITextfield / UITextVIew che attualmente ha la tastiera).

Utilizziamo i metodi delegati per impostare questa proprietà. Se stai usando un altro componente, avrai bisogno di qualcosa di simile.

Nota che per textfield lo abbiamo impostato in didBeginEditing e per textView in shouldBeginEditing. Questo perché textViewDidBeginEditing viene chiamato dopo UIKeyboardWillShowNotification per qualche motivo.

-(BOOL)textViewShouldBeginEditing:(UITextView * )textView{
    self.currentFirstResponder = textView;
    return YES;
}

-(void)textFieldDidBeginEditing:(UITextField *)textField{
    self.currentFirstResponder = textField;
}

Infine, ecco la magia

- (void)keyboardWillShow:(NSNotification*)aNotification{
    NSDictionary* info = [aNotification userInfo];
    CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];


    /*if currentFirstResponder is overlayed by the keyboard, move it so it bottom ends where the keyboard begins*/
    if(self.currentFirstResponder){

        //keyboard origin in currentFirstResponderFrame
        CGPoint keyboardOrigin = [self.currentFirstResponder convertPoint:kbFrame.origin fromView:nil];

        float spaceBetweenFirstResponderAndKeyboard = abs(self.currentFirstResponder.frame.size.height-keyboardOrigin.y);

        //only scroll the scrollview if keyboard overlays the first responder
        if(spaceBetweenFirstResponderAndKeyboard>0){
            //if i call setContentOffset:animate:YES it behaves differently, not sure why
            [UIView animateWithDuration:0.25 animations:^{
                [self.scrollView setContentOffset:CGPointMake(0,self.scrollView.contentOffset.y+spaceBetweenFirstResponderAndKeyboard)];
            }];
        }
    }

    //set bottom inset to the keyboard height so you can still scroll the whole content

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbFrame.size.height, 0.0);
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;

}

- (void)keyboardWillHide:(NSNotification*)aNotification{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;
}

8

Questa è la soluzione che utilizza Swift.

import UIKit

class ExampleViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var scrollView: UIScrollView!

    @IBOutlet var textField1: UITextField!
    @IBOutlet var textField2: UITextField!
    @IBOutlet var textField3: UITextField!
    @IBOutlet var textField4: UITextField!
    @IBOutlet var textField5: UITextField!

    var activeTextField: UITextField!

    // MARK: - View
    override func viewDidLoad() {
        super.viewDidLoad()
        self.textField1.delegate = self
        self.textField2.delegate = self
        self.textField3.delegate = self
        self.textField4.delegate = self
        self.textField5.delegate = self
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.registerForKeyboardNotifications()
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        self.unregisterFromKeyboardNotifications()
    }

    // MARK: - Keyboard

    // Call this method somewhere in your view controller setup code.
    func registerForKeyboardNotifications() {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
        center.addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
    }

    func unregisterFromKeyboardNotifications () {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.removeObserver(self, name: UIKeyboardDidShowNotification, object: nil)
        center.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    }

    // Called when the UIKeyboardDidShowNotification is sent.
    func keyboardWasShown (notification: NSNotification) {
        let info : NSDictionary = notification.userInfo!
        let kbSize = (info.objectForKey(UIKeyboardFrameBeginUserInfoKey)?.CGRectValue() as CGRect!).size

        let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;

        // If active text field is hidden by keyboard, scroll it so it's visible
        // Your app might not need or want this behavior.
        var aRect = self.view.frame
        aRect.size.height -= kbSize.height;
        if (!CGRectContainsPoint(aRect, self.activeTextField.frame.origin) ) {
            self.scrollView.scrollRectToVisible(self.activeTextField.frame, animated: true)
        }
    }

    // Called when the UIKeyboardWillHideNotification is sent
    func keyboardWillBeHidden (notification: NSNotification) {
        let contentInsets = UIEdgeInsetsZero;
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;
    }

    // MARK: -  Text Field

    func textFieldDidBeginEditing(textField: UITextField) {
        self.activeTextField = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        self.activeTextField = nil
    }

}

Risposta corretta, ma sto riscontrando un problema nullo quando si utilizzano sia TextField che TextView. Qualsiasi aiuto?
Thiha Aung,

@Thiha Aung, Le tue variabili IBOutlet nel tuo codice sorgente sono collegate all'IB?
Homam

Sì, sono anche connessi. Ho solo quell'errore quando usi UITextView su quella linea: if (! CGRectContainsPoint (aRect, self.activeTextField.frame.origin)) {
Thiha Aung

Significa self.activeTextField è zero
Thiha Aung
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.