¿Cómo puedo hacer que un UITextField se mueva hacia arriba cuando el teclado está presente, al comenzar a editar?

1692

Con el SDK de iOS:

Tengo una UIViewcon UITextFields que muestra un teclado. Lo necesito para poder:

  1. Permita el desplazamiento del contenido del UIScrollViewpara ver los otros campos de texto una vez que aparezca el teclado

  2. Automáticamente "salta" (desplazándose hacia arriba) o acorta

Sé que necesito a UIScrollView. Intenté cambiar la clase de mi UIViewa a UIScrollViewpero todavía no puedo desplazar los cuadros de texto hacia arriba o hacia abajo.

¿Necesito tanto a UIViewcomo a UIScrollView? ¿Uno va dentro del otro?

¿Qué debe implementarse para desplazarse automáticamente al campo de texto activo?

Idealmente, la mayor parte posible de la configuración de los componentes se realizará en Interface Builder. Me gustaría escribir solo código para lo que lo necesita.

Nota: el UIView(o UIScrollView) con el que estoy trabajando aparece en una barra de pestañas ( UITabBar), que debe funcionar normalmente.


Editar: estoy agregando la barra de desplazamiento solo cuando aparece el teclado. Aunque no es necesario, creo que proporciona una mejor interfaz porque, por ejemplo, el usuario puede desplazarse y cambiar cuadros de texto.

Lo tengo funcionando donde cambio el tamaño del cuadro UIScrollViewcuando el teclado sube y baja. Simplemente estoy 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
}

Sin embargo, esto no "sube" o centra automáticamente los campos de texto inferiores en el área visible, que es lo que realmente me gustaría.

philfreo
fuente
66
Mira esto. Sin problemas para ti. TPKeyboardAvoiding
Aruna
21
Está documentado por Apple, creo que es la mejor manera: developer.apple.com/library/ios/#documentation/StringsTextFonts/…
Maik639
58
Use este código. Solo necesita 1 línea en el archivo appdelegate.m y funciona. github.com/hackiftekhar/IQKeyboardManager
Pradeep Mittal
99
La mejor manera que he encontrado hasta ahora es este TPKeyboard de
Mongi Zaidi
2
Otra forma es agregar dichos campos de texto de contenido y todo en TableViewController y dejar que tableview maneje esto.
Vicky Dhas

Respuestas:

1036
  1. Solo necesitará un ScrollViewsi los contenidos que tiene ahora no caben en la pantalla del iPhone. (Si está agregando el ScrollViewcomo la supervista de los componentes solo para TextFielddesplazarse hacia arriba cuando aparece el teclado, entonces no es necesario).

  2. La forma estándar de evitar que el TextFieldteclado cubra los correos electrónicos es mover la vista hacia arriba / abajo cada vez que se muestra el teclado.

Aquí hay un código de muestra:

#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];
}
RPDP
fuente
3
¿Qué significa _textField? Lo copié en mi código, dice _textField no está declarado.
Cocoa Dev
Es el campo que usa para decir "cuando el usuario está editando aquí, la vista debería deslizarse hacia arriba" o algo así ... Sin embargo, puede eliminar eso si, si tiene más campos.
patrick
¿No es difícil llamar - (vacío) setViewMovedUp: (BOOL) se movió en los eventos keyBoardWillSHow y KeyBoardWillHide !!
Abduliam Rehmanius
44
No es particularmente útil si admite rotaciones de la vista principal.
FractalDoctor
2
Para que esto funcione, tuve que comentar la textFieldDidBeginEditingsección.
avance
445

También tenía muchos problemas con la UIScrollViewcomposición de múltiples UITextFields, de los cuales, uno o más de ellos se oscurecían con el teclado cuando se editaban.

Aquí hay algunas cosas a tener en cuenta si su UIScrollViewdesplazamiento no es correcto.

1) Asegúrese de que su contentSize sea mayor que el UIScrollViewtamaño del marco. La forma de entender UIScrollViewses que UIScrollViewes como una ventana de visualización del contenido definido en contentSize. Por lo tanto, cuando se UIScrollviewdesplaza a cualquier lugar, contentSize debe ser mayor que UIScrollView. De lo contrario, no se requiere desplazamiento ya que todo lo definido en contentSize ya está visible. Por cierto, por defecto contentSize = CGSizeZero.

2) Ahora que comprende que UIScrollViewrealmente es una ventana a su "contenido", la forma de asegurarse de que el teclado no oculte su UIScrollView's"ventana" de visualización sería cambiar el tamaño de UIScrollViewmodo que cuando el teclado esté presente, tenga la UIScrollViewventana dimensionado solo al UIScrollViewframe.size.height original menos la altura del teclado. Esto asegurará que su ventana sea solo esa pequeña área visible.

3) Aquí está el truco: cuando implementé esto por primera vez, pensé que tendría que obtener el campo CGRectde texto editado y llamar UIScrollView'sal método scrollRecToVisible. Implementé el UITextFieldDelegatemétodo textFieldDidBeginEditingcon la llamada al scrollRecToVisiblemétodo. En realidad, esto trabajó con un efecto secundario raro que el desplazamiento sería romper la UITextFielden su posición. Durante mucho tiempo no pude entender qué era. ¡Entonces comenté el textFieldDidBeginEditingmétodo Delegate y todo funciona! (???). Al final resultó que, creo que la UIScrollViewrealidad trae implícitamente lo editado actualmente UITextFielden la ventana visible de forma implícita. Mi implementación del UITextFieldDelegatemétodo y la posterior llamada al scrollRecToVisiblefue redundante y fue la causa del extraño efecto secundario.

Así que aquí están los pasos para desplazarlo correctamente UITextFielden su UIScrollViewlugar cuando aparezca el teclado.

// 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. Regístrese para las notificaciones del teclado en viewDidLoad
  2. Anule el registro de las notificaciones de teclado en viewDidUnload
  3. Asegúrese de que el contentSizese establece y mayor que el UIScrollViewdeviewDidLoad
  4. Encoge el UIScrollViewcuando el teclado está presente
  5. Revierta el UIScrollViewcuando el teclado desaparece.
  6. Use un ivar para detectar si el teclado ya se muestra en la pantalla, ya que las notificaciones del teclado se envían cada vez que UITextFieldse presiona a incluso si el teclado ya está presente para evitar reducir el tamañoUIScrollView cuando ya está encogido.

Una cosa a tener en cuenta es que se UIKeyboardWillShowNotificationactivará incluso cuando el teclado ya esté en la pantalla cuando presionas otro UITextField. Me ocupé de esto usando un ivar para evitar cambiar el tamaño UIScrollViewcuando el teclado ya está en la pantalla. ¡Cambiar de tamaño inadvertidamente UIScrollViewcuando el teclado ya está allí sería desastroso!

Espero que este código les ahorre muchos dolores de cabeza.

Shiun
fuente
3
Genial, pero dos problemas: 1. UIKeyboardBoundsUserInfoKeyestá en desuso. 2. keyboardSize está en "coordenadas de pantalla", por lo que sus cálculos de viewFrame fallarán si el marco se gira o se escala.
Martin Wickman el
21
@Martin Wickman - Uso en CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;lugar de obsoletoUIKeyboardBoundsUserInfoKey
sottenad
1
Hola, hice lo mismo, pero la vista de texto solo se mueve hacia arriba cuando el usuario comienza a escribir. ¿Es el comportamiento esperado o me falta algo?
3
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].sizedebería ser [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size. ¡Gran solución sin embargo!
j7nn7k
1
Me gusta su solución, pero creo que puedo hacerlo aún más simple: no se moleste con las cosas del Observador de notificaciones; en su lugar, llame a las rutinas de animación correctas dentro de los métodos delegados apropiados: para UITextView son textViewDidBeginEditing y textViewDidEndEditing.
AlexChaffee
270

En realidad, es mejor usar la implementación de Apple, como se proporciona en los documentos . Sin embargo, el código que proporcionan es defectuoso. Reemplace la parte que se encuentra keyboardWasShown:justo debajo de los comentarios a lo siguiente:

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

Los problemas con el código de Apple son los siguientes: (1) Siempre calculan si el punto está dentro del marco de la vista, pero es un ScrollView, por lo que es posible que ya se haya desplazado y que deba tener en cuenta ese desplazamiento:

origin.y -= scrollView.contentOffset.y

(2) Cambian el contenido Offset por la altura del teclado, pero queremos lo contrario (queremos cambiar contentOffsetla altura que es visible en la pantalla, no lo que no es):

activeField.frame.origin.y-(aRect.size.height)
DK_
fuente
1
En situaciones en las que la vista de desplazamiento no llena la pantalla, aRect debe establecerse en el marco de la vista de desplazamiento
mblackwell8
2
¿No debería querer el CGPoint origin = activeField.frame.origin + activeField.frame.size.height ?, porque desea que se muestre todo el campo de texto y si tiene solo algunos píxeles visibles, entonces el código no ingresará el condición.
htafoya
1
Esta solución no funciona en orientación horizontal: el campo de texto sale de la parte superior del puerto de vista. iPad con iOS 7.1.
Andrew
44
Para una mejor compatibilidad con iOS 8, sugeriría usar en UIKeyboardFrameEndUserInfoKeylugar de UIKeyboardFrameBeginUserInfoKeyobtener el tamaño del teclado, ya que esto recogerá cosas como los cambios personalizados del teclado y la activación / desactivación del texto predictivo.
Endareth
1
@Egor: Su solución hace que funcione mejor manera - pero la última línea debe ser inversa:self.scrollView.contentOffset = self.currentSVoffset;
Morten Holmgaard
244

In textFieldDidBeginEdittingy in textFieldDidEndEditingcall la función [self animateTextField:textField up:YES]así:

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

Espero que este código te ayude.

En 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)
    }
sumanthkodi
fuente
1
¿Por qué no usar [UIView animateWithDuration: animations:^{ }];?
Andre Cytryn
2
esto funciona bien, aunque const int movementDistance = -130; // ajustar según sea necesario necesita ser cambiado para ser más flexible
Martillo
77
Increíblemente simple en implementaciones pequeñas. Sin perder el tiempo con ScrollViews y problemas ambiguos de diseño automático.
James Perih
44
Erm ... no usas el parámetro textField en absoluto. ¿Por qué entonces tenerlo como parámetro de función? Además, puede usar el operador ternario también en Swift. Hace que el código sea menos hablador.
stk
1
Si el color de fondo de la Vista no es negro, asegúrese de establecer el color de la Ventana para que coincida con su vista para que el usuario no vea detrás de ella. es decir, self.window.backgroundColor = [UIColor whiteColor];
bvmobileapps
134

Solo usando TextFields:

1a) Uso Interface Builder: Seleccione All TextFields => Edit => Embed In => ScrollView

1b) Incruste manualmente TextFields en UIScrollView llamado scrollView

2) Establecer UITextFieldDelegate

3) Establecer cada uno textField.delegate = self;(o hacer conexiones Interface Builder)

4) Copiar / Pegar:

- (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];
}
DAS
fuente
8
Pero también sube la vista cuando textFieldya es visible.
TheTiger
1
Necesito cambiar CGPointMake(0, textField.frame.origin.y);aCGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top);
Fury
@Egor Incluso después de tu comentario, no funciona. Como mencionó "TheTiger", sube la vista incluso después de que el campo de texto es visible.
rak appdev
Cambiar para XCode 10: "Seleccionar todos los campos de texto => Editor => Incrustar en => Vista de desplazamiento"
tibalt
116

Para Universal Solution , aquí estaba mi enfoque para implementar IQKeyboardManager .

ingrese la descripción de la imagen aquí

Paso 1: - añadí notificaciones globales de UITextField, UITextViewy UIKeyboarden una clase Singleton. Lo llamo IQKeyboardManager .

Paso 2: - Si se encuentran UIKeyboardWillShowNotification, UITextFieldTextDidBeginEditingNotificationo UITextViewTextDidBeginEditingNotificationnotificaciones, trato de obtener una topMostViewControllerinstancia de la UIWindow.rootViewControllerjerarquía. Con el fin de destapar adecuadamente UITextField/ UITextViewen él, topMostViewController.view's marco necesita ser ajustado.

Paso 3: - Calculé la distancia de movimiento esperada topMostViewController.viewcon respecto a la primera respuesta UITextField/ UITextView.

Paso 4: - Me moví topMostViewController.view.framehacia arriba / abajo según la distancia de movimiento esperada.

Paso 5: - Si se encuentra UIKeyboardWillHideNotification, UITextFieldTextDidEndEditingNotificationo UITextViewTextDidEndEditingNotificationnotificación, nuevamente intento obtener una topMostViewControllerinstancia de la UIWindow.rootViewControllerjerarquía.

Paso 6: - Calculé la distancia perturbada de la topMostViewController.viewcual debe restaurarse a su posición original.

Paso 7: - Me restaure de topMostViewController.view.frameacuerdo con la distancia perturbada.

Paso 8: - Ejecuté la instancia de clase IQKeyboardManager singleton en la carga de la aplicación, por lo que cada UITextField/ UITextViewen la aplicación se ajustará automáticamente de acuerdo con la distancia de movimiento esperada.

¡Eso es todo lo que IQKeyboardManager hace por usted SIN NINGUNA LÍNEA DE CÓDIGO realmente! solo necesita arrastrar y soltar el archivo fuente relacionado al proyecto. IQKeyboardManager también es compatible con la Orientación del dispositivo , la Gestión automática de UIToolbar , KeybkeyboardDistanceFromTextField y mucho más de lo que piensas.

Mohd Iftekhar Qurashi
fuente
Agregue el directorio IQKeyBoardManagerSwift a mi proyecto y no funcione. No se puede habilitar porque no se reconoce en AppDelegate ...
user3722523
2
esto se siente como phishing, la solución real no se muestra, sino que vemos un comercial para esta cuenta de GitHub.
Brian
101

He reunido un universal, sin cita UIScrollView, UITableViewe inclusoUICollectionView subclase que se encarga de mover todos los campos de texto dentro de ella fuera del camino del teclado.

Cuando el teclado está a punto de aparecer, la subclase encontrará la subvista que está a punto de editarse, y ajustará el marco y el desplazamiento del contenido para asegurarse de que la vista sea visible, con una animación que coincida con la ventana emergente del teclado. Cuando el teclado desaparece, restaura su tamaño anterior.

Debería funcionar básicamente con cualquier configuración, ya sea una UITableView interfaz basada en o una que consista en vistas colocadas manualmente.

Aquí está: solución para mover campos de texto fuera del camino del teclado

Michael Tyson
fuente
¡Eso es todo! ¡Esta es la mejor, más eficiente y perfecta solución! También maneja las rotaciones correctamente para las vistas de desplazamiento. Si está girando, asegúrese de autorizar verticalmente pero no ancle en la parte inferior. Agregué un UITextView a la vista de desplazamiento en mi caso. Gracias racimos!
Christopher
¡Muy buen trabajo! Claro, estoy siendo flojo usando su solución en lugar de la de bricolaje, pero mi jefe está más feliz, ¡así que sí! Incluso si alguien quiere hacerlo por sí mismo, me gusta su enfoque de subclase, en lugar de agregar código a cada controlador. Me sorprendió que iOS no hiciera esto de manera predeterminada como lo hizo Android; luego, nuevamente, encuentro muchas cosas que faltan en iOS y MacOS :(
eselk
¿Hay cosas raras como mi vista de desplazamiento? Todo encaja en la pantalla, por lo que no se puede desplazar. Después de abrir y cerrar el teclado, el contenido ahora es más grande (parece que se agregó algo invisible y no se eliminó en la parte inferior de la página) y se puede desplazar.
Almo
91

Para programadores Swift :

Esto hará todo por usted, simplemente UITextFieldDelegatecolóquelos en su clase de controlador de vista e implemente el en su controlador de vista y configure el delegado de textField enself

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

Implemente los métodos de devolución de llamada delegados:

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

Para Swift 4, 4.2, 5: Cambiar

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

a

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

Última nota sobre esta implementación: si inserta otro controlador de vista en la pila mientras se muestra el teclado, esto creará un error en el que la vista vuelve a su marco central pero el desplazamiento del teclado no se restablece. Por ejemplo, su teclado es el primer respondedor para nameField, pero luego presiona un botón que empuja su Controlador de vista de ayuda a su pila. Para corregir el error de desplazamiento, asegúrese de llamar a nameField.resignFirstResponder () antes de abandonar el controlador de vista, asegurándose de que también se llame al método delegado textFieldDidEndEditing. Hago esto en el método viewWillDisappear.

Satnam Sync
fuente
3
A SwiftLint no le gustó, self.view.frame = CGRectOffset(self.view.frame, 0, movement)así que cambié esa línea aself.view.frame.offsetInPlace(dx: 0, dy: movement)
levibostian
2
Swift 4 cambia self.view.frame = CGRectOffset (self.view.frame, 0, movimiento) a self.view.frame.offsetBy (dx: 0, dy: movimiento)
Asinox
FYI, para que esto funcione, tienes que poner. self.view.frame = self.view.frame.offsetBy (dx: 0, dy: movimiento)
Josh Wolff
64

Ya hay muchas respuestas, pero ninguna de las soluciones anteriores tenía todo el material de posicionamiento necesario para una animación "perfecta" sin errores, compatible con versiones anteriores y sin parpadeos. (error al animar el marco / límites y el contenido Offset juntos, diferentes orientaciones de interfaz, teclado dividido iPad, ...)
Permítanme compartir mi solución:
(suponiendo que haya configurado 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];
}
Martin Ullrich
fuente
Grandes mejoras de la respuesta de @Shiun. Pero después de que el teclado se ha ido, la vista no vuelve a la primera posición. Sigue siendo un gran trabajo :)
Lucien
2
Gracias, esta es la mejor solución para mí en 2017. Tenga en cuenta que no necesita rastrear usted mismo el FocusControl, puede determinarlo con UIApplication.shared.sendAction(...). Aquí está la versión de Swift 3 de su respuesta (menos la porción deHideHide), con la sendActionimplementación: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d
xaphod
@xaphod en mi caso necesitaba enfocar más controles, por ejemplo, un botón debajo de un campo de entrada. pero sí, ese código tiene ahora 4 años y puede beneficiarse de las mejoras.
Martin Ullrich
Esta es probablemente la solución adecuada. La notificación del teclado lleva datos de animación, las delegaciones de campo de texto no saben acerca de la duración de la animación, solo sería una suposición.
XY
62

Shiun dijo "Al final resultó que, creo que UIScrollView en realidad trae implícitamente el UITextField actualmente editado a la ventana visible" Esto parece ser cierto para iOS 3.1.3, pero no 3.2, 4.0 o 4.1. Tuve que agregar un scrollRectToVisible explícito para hacer visible el UITextField en iOS> = 3.2.

cbranch
fuente
No es el UIScrollView que desplaza implícitamente el UITextField editado a la vista, es el UITextField que llama a un [UITextField scrollTextFieldToVisibleIfNecessary]método privado que a su vez llama [UIScrollView scrollRectToVisible]cuando [UITextField becomeFirstResponder]se llama. Ver github.com/leopatras/ios_textfields_on_scrollview . Si las restricciones y los controladores de vista están configurados correctamente, en realidad no hay necesidad de llamar scrollRectToVisibleexplícitamente (al menos desde IOS 11).
Leo
48

Una cosa a tener en cuenta es si alguna vez quieres usar un UITextFieldsolo. No he encontrado ninguna aplicación para iPhone bien diseñada que realmente use UITextFieldsfuera de UITableViewCells.

Será un trabajo adicional, pero le recomiendo que implemente todas las vistas de entrada de datos y vistas de tabla. Agrega un UITextViewa tu UITableViewCells.

Jonathan Sterling
fuente
1
Una de mis aplicaciones debe permitir a los usuarios agregar notas de forma libre, por lo que sí, a veces es útil usar un UITextField.
Peter Johnson
1
Estoy de acuerdo con este método Cero trabajo o código de esta manera. Incluso si necesita una nota de forma gratuita, puede
hacerlo
UITableViewEs tristemente el único camino a seguir. Las notificaciones del teclado son frágiles y han cambiado las horas extraordinarias. Código de muestra en Stack Overflow: stackoverflow.com/a/32390936/218152
SwiftArchitect
Esta respuesta tiene unos cinco años de retraso. La única solución moderna es algo como esto ... stackoverflow.com/a/41808338/294884
Fattie
47

Este documento detalla una solución a este problema. Mire el código fuente en 'Mover contenido que se encuentra debajo del teclado'. Es bastante sencillo.

EDITAR: Noté que hay un pequeño error en el ejemplo. Probablemente querrá escuchar en UIKeyboardWillHideNotificationlugar de UIKeyboardDidHideNotification. De lo contrario, la vista de desplazamiento detrás del teclado se recortará mientras dure la animación de cierre del teclado.

Mihai Damian
fuente
32

La solución más fácil encontrada

- (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];
}
Jawad Zeb
fuente
La pantalla se mueve hacia arriba incluso si no está en la parte inferior. es decir, si el campo de texto está en la parte superior, se mueve fuera de la pantalla. ¿Cómo controlar ese caso?
MELWIN
@MELWIN Simplemente agregue después de esta línea: int movement = (up ? -movementDistance : movementDistance); if (textField.frame.origin.y < self.view.frame.size.height - keyboard.height) { movementDistance = 0 }Por favor, no es que la keyboardvariable sea el CGRect del teclado que aparece al hacerlo:let keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
CapturedTree
31

Pequeña solución que funciona para muchos 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]; 
}
tt.Kilew
fuente
rect.origin.y=+currTextField.frame.origin.ytrabajando bien gracias
u.gen
30

El código RPDP mueve con éxito el campo de texto fuera del camino del teclado. Pero cuando se desplaza hacia la parte superior después de usar y cerrar el teclado, la parte superior se ha desplazado hacia arriba fuera de la vista. Esto es cierto para el simulador y el dispositivo. Para leer el contenido en la parte superior de esa vista, uno tiene que volver a cargar la vista.

¿No se supone que su siguiente código reducirá la vista?

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

No estoy seguro de si mover la vista hacia arriba es el enfoque correcto, lo hice de una manera diferente, cambiando el tamaño del UIScrollView. Lo expliqué en detalle en un pequeño artículo.

Jose Muanis
fuente
El enlace al artículo está muerto.
Teo
22

Para volver al estado de vista original, agregue:

-(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];
    }
}
Nicolas Marchand
fuente
20

Prueba este pequeño truco.

- (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];
}
Sourabh Sharma
fuente
19

Hay tantas soluciones, pero he pasado algunas horas antes de que comience a funcionar. Entonces, puse este código aquí (solo pegue en el proyecto, no es necesario realizar ninguna modificación):

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

PD: Espero que el código ayude a alguien a lograr el efecto deseado rápidamente. (Xcode 4.5)

HotJard
fuente
Hola Hotjard, estoy obteniendo EXE_BAD_ACCESS en [self.view addSubview: natView];
Bala
18

@ user271753

Para que su vista vuelva al original, agregue:

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

No requiere una vista de desplazamiento para poder mover el marco de vista. Puede cambiar el marco de una viewcontroller'svista para que toda la vista se mueva hacia arriba lo suficiente como para colocar el campo de texto del primer respondedor sobre el teclado. Cuando me encontré con este problema, creé una subclase deUIViewController que hace esto. Observa que el teclado aparecerá notificación y encuentra la primera subvista del respondedor y (si es necesario) anima la vista principal hacia arriba lo suficiente para que el primer respondedor esté por encima del teclado. Cuando el teclado se oculta, anima la vista donde estaba.

Para usar esta subclase, haga que su controlador de vista personalizado sea una subclase de GMKeyboardVC y herede esta característica (solo asegúrese de implementar viewWillAppeary viewWillDisappeardeben llamar a super). La clase está en github .

progrmr
fuente
Que licencia Algunos de sus archivos tienen una licencia de código abierto y otros no.
jaime
Advertencia: este código no es compatible con proyectos ARC.
Almo
Simplemente agrega la opción de compilación para especificar que esos archivos no son ARC o son bienvenidos para convertirlos a ARC y enviar una solicitud de extracción.
programa el
14

Rápido 4 .

Fácilmente puede moverse arriba y abajo UITextFieldO UIViewCon UIKeyBoardConAnimation ingrese la descripción de la imagen aquí

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
    }

}
ZAFAR007
fuente
2
Casi perfecto. En el iPhone X, aunque obtienes una extraña brecha entre el teclado y el campo de texto.
Houman
12

Aquí está la solución de hackeo que se me ocurrió para un diseño específico. Esta solución es similar a la solución de Matt Gallagher, ya que desplaza una sección a la vista. Todavía soy nuevo en el desarrollo de iPhone y no estoy familiarizado con el funcionamiento de los diseños. Por lo tanto, este truco.

Mi implementación necesitaba admitir el desplazamiento al hacer clic en un campo, y también el desplazamiento cuando el usuario selecciona el siguiente en el teclado.

Tuve una UIView con una altura de 775. Los controles se distribuyen básicamente en grupos de 3 en un espacio grande. Terminé con el siguiente diseño de IB.

UIView -> UIScrollView -> [UI Components]

Aquí viene el truco

Configuré la altura UIScrollView en 500 unidades más grande que el diseño real (1250). Luego creé una matriz con las posiciones absolutas a las que necesito desplazarme, y una función simple para obtenerlas en función del número de etiqueta 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] ;
}

Ahora todo lo que necesita hacer es usar las siguientes dos líneas de código en textFieldDidBeginEditing y textFieldShouldReturn (este último si está creando un próximo campo de navegación)

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

Un ejemplo.

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

Este método no se 'desplaza hacia atrás' como lo hacen otros métodos. Esto no fue un requisito. Nuevamente, esto fue para una UIView bastante 'alta', y no tuve días para aprender los motores de diseño interno.

Steve McFarlin
fuente
12

Según los documentos , a partir de iOS 3.0, la UITableViewControllerclase cambia automáticamente el tamaño y vuelve a colocar su vista de tabla cuando hay una edición en línea de los campos de texto. Creo que no es suficiente poner el campo de texto dentro de unUITableViewCell como algunos han indicado.

De los documentos :

Un controlador de vista de tabla admite la edición en línea de filas de vista de tabla; si, por ejemplo, las filas tienen campos de texto incrustados en el modo de edición, desplaza la fila que se está editando sobre el teclado virtual que se muestra.

Dheeraj VS
fuente
Encontré el mismo comentario. Si es cierto. Lo extraño es que está funcionando en un UITabelViewController y en un segundo no. Pero no pude encontrar ninguna diferencia en mi implementación.
Morpheus78
11

Aquí encontré la solución más simple para manejar el teclado.

Solo necesita copiar y pegar debajo del código de muestra y cambiar su campo de texto o cualquier vista que desee subir.

Paso 1

Simplemente copie y pegue debajo de dos métodos en su controlador

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

Paso 2

registrarse y cancelar el registro de notificaciones del teclado en los métodos viewWillAppear y viewWillDisappear respectivamente.

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

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

Paso 3

Aquí viene la parte del alma, simplemente reemplace su campo de texto y cambie la altura cuánto desea mover al alza.

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

Referencia : bueno, aprecia a este chico , que compartió este hermoso recorte de código, solución limpia.

Espero que esto sea muy útil para alguien por ahí.

swiftBoy
fuente
No creo que sea lo mejor. Ithink @Dheeraj VS tiene razón: se puede hacer fácil y automáticamente si ese campo de texto está en la celda de una tabla (incluso cuando table.scrollable = NO). TENGA EN CUENTA que: la posición y el tamaño de la mesa deben ser razonables. por ejemplo: - si la posición y de la tabla se cuenta 100 desde la parte inferior de la vista, entonces el teclado de 300 alturas se superpondrá a toda la tabla. - si la altura de la tabla = 10, y el campo de texto debe desplazarse hacia arriba 100 cuando aparece el teclado para que sea visible, entonces ese campo de texto estará fuera del límite de la tabla.
samthui7
@ samthui7 La respuesta Dheeraj solo funciona si está utilizando un TableViewController, no solo una vista de tabla. Lo convierte en una restricción que a veces no es adecuada.
Ben G
10

He estado buscando un buen tutorial para principiantes sobre el tema, encontré el mejor tutorial aquí .

En el MIScrollView.hejemplo al final del tutorial, asegúrese de poner un espacio en

@property (nonatomic, retain) id backgroundTapDelegate;

como ves.

savagenoob
fuente
Hola savagenoob, gracias por el enlace proporcionado y bienvenido a stackoverflow. Intente proporcionar tanta información como sea posible al responder (futuras) preguntas: los enlaces simples son un poco frágiles. Dicho esto, si la respuesta es un enlace a un buen tutorial que podría pasarse por alto.
Maarten Bodewes
10

Cuando UITextFieldestá en un UITableViewCelldesplazamiento debe configurarse automáticamente.

Si no es así, probablemente se deba a un código / configuración incorrectos de la vista de tabla.

Por ejemplo, cuando volví a cargar mi tabla larga con una UITextFielden la parte inferior de la siguiente manera,

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

entonces mi campo de texto en la parte inferior estaba oscurecido por el teclado que apareció cuando hice clic dentro del campo de texto.

Para solucionar esto, tuve que hacer esto:

-(void) viewWillAppear:(BOOL)animated
{
    //add the following line to fix issue
    [super viewWillAppear:animated];
    [self.tableview reloadData];
}
lakersgonna3peat
fuente
¿Estoy confundido para qué sirve este código? Cuando se muestra el teclado, viewWillAppearno se llama. Y reloadDatano hace que las filas oscuras se vuelvan visibles.
Adam Johns
10

Use este tercero, no necesita escribir ni una sola línea

https://github.com/hackiftekhar/IQKeyboardManager

descargue el proyecto y arrastre y suelte IQKeyboardManagersu proyecto. Si encuentra algún problema, lea el READMEdocumento.

Chicos realmente es eliminar el dolor de cabeza para administrar el teclado.

Arvind Kumar
fuente
8

Nota : esta respuesta asume que su textField está en scrollView.

Prefiero lidiar con esto usando scrollContentInset y scrollContentOffset en lugar de jugar con los marcos de mi vista.

Primero escuchemos las notificaciones del teclado

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

El siguiente paso es mantener una propiedad que represente el primer respondedor actual (UITextfield / UITextVIew que actualmente tiene el teclado).

Usamos los métodos delegados para establecer esta propiedad. Si está utilizando otro componente, necesitará algo similar.

Tenga en cuenta que para textfield lo configuramos en didBeginEditing y para textView en shouldBeginEditing. Esto se debe a que se llama a textViewDidBeginEditing después de UIKeyboardWillShowNotification por algún motivo.

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

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

Finalmente, aquí está 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;
}
Juraj Petrik
fuente
8

Esta es la solución usando 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
    }

}
Homam
fuente
Respuesta correcta, pero tengo un problema nulo cuando utilizo TextField y TextView. ¿Alguna ayuda?
Thiha Aung
@Thiha Aung, ¿Están sus variables IBOutlet en su código fuente conectadas al IB?
Homam
Sí, también están conectados. Solo tienen ese error al usar UITextView en esa línea: if (! CGRectContainsPoint (aRect, self.activeTextField.frame.origin)) {
Thiha Aung
Significa self.activeTextField es nulo
Thiha Aung