Hacer un desplazamiento UITableView cuando se selecciona el campo de texto


251

Después de mucho ensayo y error, me doy por vencido y hago la pregunta. He visto a muchas personas con problemas similares pero no puedo obtener todas las respuestas para que funcionen correctamente.

tengo un UITableView que se compone de celdas personalizadas. Las celdas están formadas por 5 campos de texto uno al lado del otro (algo así como una cuadrícula).

Cuando trato de desplazarme y editar las celdas en la parte inferior del UITableView , no puedo lograr que mis celdas se coloquen correctamente sobre el teclado.

He visto muchas respuestas hablando sobre cambiar el tamaño de las vistas, etc. pero ninguna de ellas ha funcionado bien hasta ahora.

¿Alguien podría aclarar la forma "correcta" de hacer esto con un ejemplo de código concreto?


11
Esta documentación de Applle describe los pasos para implementar una solución para esta pregunta. http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
ChrisP

@ChrisP Ese enlace indica que no se ha actualizado para iOS 4.0
Bae

Este código puede ser útil: gist.github.com/TimMedcalf/9505416
landonandrey

Siga a continuación la URL, funcionará: stackoverflow.com/questions/48922266/…
Venkatesh G

Respuestas:


126

Si usa UITableViewController en lugar de UIViewController, lo hará automáticamente.


13
¿Intentaste y descubriste que no funcionaba? ¿O es la solución demasiado simple para que creas? Simplemente extienda el UITableViewController en lugar del UIViewController y la celda que contiene los campos de texto se desplazará por encima del teclado cada vez que los campos de texto se conviertan en el primer respondedor. No se necesita código adicional.
Sam Ho

3
Sí, pero especialmente en el iPad, necesitamos una forma de hacerlo que no implique el UITableViewController.
Bob Spryn

13
Para aclarar, no es una respuesta razonable decir que cada vez que usa una vista de tabla debe estar en pantalla completa, especialmente en un iPad. Hay hordas de ejemplos de excelentes aplicaciones que no hacen eso. Por ejemplo, muchos de los de Apple, incluida la aplicación Contactos en el iPad.
Bob Spryn

32
No funcionará si anula [super viewWillAppear: YES]. Aparte de eso, debería funcionar.
Rambatino

18
Si anula viewWillAppear: (BOOL) animado, no olvide llamar a [super viewWillAppear: animado]; :)
Médéric Petit

93

La función que realiza el desplazamiento podría ser mucho más simple:

- (void) textFieldDidBeginEditing:(UITextField *)textField {
    UITableViewCell *cell;

    if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
    // Load resources for iOS 6.1 or earlier
        cell = (UITableViewCell *) textField.superview.superview;

    } else {
        // Load resources for iOS 7 or later
        cell = (UITableViewCell *) textField.superview.superview.superview; 
       // TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Cell!
    }
    [tView scrollToRowAtIndexPath:[tView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

Eso es. No hay cálculos en absoluto.


2
¡¿Y por qué no?! Simplemente reemplace UITableViewScrollPositionTop con UITableViewScrollPositionMiddle. Solo necesita reescalar UITableView para ajustar el área visible, por supuesto.
Mihai Damian

3
No parece funcionar si un UITableViewController se ha ocupado del cambio de tamaño de la vista de tabla cuando se muestra el teclado: el controlador reduce el tamaño visible con a contentInset, que aparentemente no se tiene en cuenta al solicitar visibleRowso indexPathsForVisibleRows.
Julian D.

16
No funciona para las últimas filas de la vista de tabla. El teclado seguirá oscureciendo todas las filas que no se pueden desplazar por encima del teclado.
Alex Zavatone

3
Para que el comportamiento de desplazamiento automático funcione en las últimas filas de la tabla, detecte cuándo comienzan a editar estas filas y agregue un pie de página al final de la vista de tabla con una vista en blanco de cierta altura. Esto permitirá que la vista de tabla desplace las celdas al lugar correcto.
Sammio2

10
Llegar a la celda a través de una cadena de llamadas a la supervista no es confiable, a menos que se asegure de llegar a la celda. Ver stackoverflow.com/a/17757851/1371070 y stackoverflow.com/a/17758021/1371070
Cezar el

70

Estoy haciendo algo muy similar, es genérico, no es necesario calcular algo específico para su código. Simplemente verifique los comentarios en el código:

En MyUIViewController.h

@interface MyUIViewController: UIViewController <UITableViewDelegate, UITableViewDataSource>
{
     UITableView *myTableView;
     UITextField *actifText;
}

@property (nonatomic, retain) IBOutlet UITableView *myTableView;
@property (nonatomic, retain) IBOutlet UITextField *actifText;

- (IBAction)textFieldDidBeginEditing:(UITextField *)textField;
- (IBAction)textFieldDidEndEditing:(UITextField *)textField;

-(void) keyboardWillHide:(NSNotification *)note;
-(void) keyboardWillShow:(NSNotification *)note;

@end

En MyUIViewController.m

@implementation MyUIViewController

@synthesize myTableView;
@synthesize actifText;

- (void)viewDidLoad 
{
    // Register notification when the keyboard will be show
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(keyboardWillShow:)
                                          name:UIKeyboardWillShowNotification
                                          object:nil];

    // Register notification when the keyboard will be hide
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(keyboardWillHide:)
                                          name:UIKeyboardWillHideNotification
                                          object:nil];
}

// To be link with your TextField event "Editing Did Begin"
//  memoryze the current TextField
- (IBAction)textFieldDidBeginEditing:(UITextField *)textField
{
    self.actifText = textField;
}

// To be link with your TextField event "Editing Did End"
//  release current TextField
- (IBAction)textFieldDidEndEditing:(UITextField *)textField
{
    self.actifText = nil;
}

-(void) keyboardWillShow:(NSNotification *)note
{
    // Get the keyboard size
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];

    // Detect orientation
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect frame = self.myTableView.frame;

    // Start animation
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3f];

    // Reduce size of the Table view 
    if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
        frame.size.height -= keyboardBounds.size.height;
    else 
        frame.size.height -= keyboardBounds.size.width;

    // Apply new size of table view
    self.myTableView.frame = frame;

    // Scroll the table view to see the TextField just above the keyboard
    if (self.actifText)
      {
        CGRect textFieldRect = [self.myTableView convertRect:self.actifText.bounds fromView:self.actifText];
        [self.myTableView scrollRectToVisible:textFieldRect animated:NO];
      }

    [UIView commitAnimations];
}

-(void) keyboardWillHide:(NSNotification *)note
{
    // Get the keyboard size
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];

    // Detect orientation
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect frame = self.myTableView.frame;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3f];

    // Increase size of the Table view 
    if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
        frame.size.height += keyboardBounds.size.height;
    else 
        frame.size.height += keyboardBounds.size.width;

    // Apply new size of table view
    self.myTableView.frame = frame;

    [UIView commitAnimations];
}

@end

Versión Swift 1.2+:

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var activeText: UITextField!
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: Selector("keyboardWillShow:"),
            name: UIKeyboardWillShowNotification,
            object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: Selector("keyboardWillHide:"),
            name: UIKeyboardWillHideNotification,
            object: nil)
    }

    func textFieldDidBeginEditing(textField: UITextField) {
        activeText = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        activeText = nil
    }

    func keyboardWillShow(note: NSNotification) {
        if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            var frame = tableView.frame
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationBeginsFromCurrentState(true)
            UIView.setAnimationDuration(0.3)
            frame.size.height -= keyboardSize.height
            tableView.frame = frame
            if activeText != nil {
                let rect = tableView.convertRect(activeText.bounds, fromView: activeText)
                tableView.scrollRectToVisible(rect, animated: false)
            }
            UIView.commitAnimations()
        }
    }

    func keyboardWillHide(note: NSNotification) {
        if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            var frame = tableView.frame
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationBeginsFromCurrentState(true)
            UIView.setAnimationDuration(0.3)
            frame.size.height += keyboardSize.height
            tableView.frame = frame
            UIView.commitAnimations()
        }
    }
}

usar las notificaciones y obtener la altura del teclado mientras se incorpora la orientación del dispositivo fue increíble, ¡gracias por eso! la parte de desplazamiento no funcionó para mí por alguna razón, así que tuve que usar esto:[tableView scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionMiddle animated: YES];
Taber

77
Esta es la mejor respuesta aquí, creo. Muy limpio. Solo dos cosas: 1) su viewDidLoad no está llamando a [super viewDidLoad] y 2) tuve que tener algunas matemáticas de tabulación en las líneas frame.size.height. De lo contrario perfecto! Gracias.
toxaq

3
Aquí está la modificación que describe toxaq: MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate]; CGFloat tabBarHeight = appDelegate.tabBarController.tabBar.frame.size.height; luego reste tabBarHeight de la altura del teclado donde sea que use la altura del teclado.
Steve N

1
Si el usuario toca el campo de texto, funciona perfectamente. pero si el usuario toca otro campo de texto sin presionar la tecla de retorno, entonces reduce el tamaño de la vista de tabla.
Bhavin Ramani

1
@BhavinRamani estuvo de acuerdo. Agregué una propiedad booleana simple para recordar si el teclado ya se está mostrando o no, y omitir la ejecución de código cuando no sea necesario.
Dirty Henry

46

La solución más simple para Swift 3 , basada en la solución Bartłomiej Semańczyk :

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

// MARK: Keyboard Notifications

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
        tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    UIView.animate(withDuration: 0.2, animations: {
        // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
        self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
    })
}

Un detalle menor ... Usar en Notificationlugar de NSNotificationsería más "Swift 3-y" :-)
Nicolas Miari

Esto ayudará con el reposicionamiento si hay una barra de navegación: rodee UIView.animate con esto si let - if let frame = self.navigationController? .NavigationBar.frame {let y = frame.size.height + frame.origin.y}
Sean Dev

cuando ocurre la rotación, hay un problema en la carga y algunas celdas desaparecen cuando la vista de la mesa se desplaza manualmente
jothikenpachi

Buena solución gracias! Nota: ya no es necesario eliminar removeObserver.
Nick McConnell

44

Tuve el mismo problema pero noté que aparece solo en una vista. Entonces comencé a buscar las diferencias en los controladores.

Descubrí que el comportamiento de desplazamiento se establece en - (void)viewWillAppear:(BOOL)animatedla súper instancia.

Así que asegúrese de implementar así:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // your code
}

Y no importa si usa UIViewControllero UITableViewController; lo verificó colocando un UITableViewcomo subvista de self.view en el UIViewController. Fue el mismo comportamiento. La vista no permitía desplazarse si [super viewWillAppear:animated];faltaba la llamada .


1
Esto funcionó excelentemente. Me preguntaba por qué la gente decía que UITableView lo haría por mí y esto lo resolvió. ¡Gracias!
olivaresF

55
También tuve este problema, ¡esta respuesta debería llegar a la cima!
Amiel Martin

Perdí mucho tiempo tratando de resolverlo por mi cuenta ... gracias;)
budidino

+1 estaba empezando a llorar un poco, tenía esa línea pero también necesitaba [tableViewController viewWillAppear: animated]; porque estoy agregando un UITableViewController a un UIViewController. no más lágrimas :)
colin lamarre

41

Puede que me haya perdido esto, ya que no leí toda la publicación aquí, pero lo que se me ocurrió parece engañosamente simple. No lo he pasado por alto, probando en todas las situaciones, pero parece que debería funcionar bien.

simplemente ajuste el contentInset de la vista de tabla por la altura del teclado y luego desplace la celda hacia abajo:

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

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    self.myTableView.contentInset = contentInsets;
    self.myTableView.scrollIndicatorInsets = contentInsets;

    [self.myTableView scrollToRowAtIndexPath:self.currentField.indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

y por supuesto

- (void)keyboardWasHidden:(NSNotification *)aNotification
{
    [UIView animateWithDuration:.3 animations:^(void) 
    {
        self.myTableView.contentInset = UIEdgeInsetsZero;
        self.myTableView.scrollIndicatorInsets = UIEdgeInsetsZero;
    }];
}

¿Es esto demasiado simple? ¿Me estoy perdiendo de algo? hasta ahora está funcionando bien para mí, pero como dije, no lo he exprimido ...


OMI, esta es la mejor solución. Lo único que cambiaría es tu duración codificada a[aNotification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
Andy

Es muy simple. Pero un problema que encuentro es que no animará el cambio contentInsetni cambiará bruscamente los límites de desplazamiento.
Geek

Este funcionó mejor para mí, sin embargo, algunos problemas. 1) No sé dónde podría obtener "currentField.indexPath", así que tuve que guardar indexPath.row como la etiqueta del campo y crear indexPath más tarde. 2) No funciona para las filas en la parte superior de la tabla, las desplaza fuera de la pantalla. Tuve que agregar un código para desplazarme solo si indexPath de currentField es mayor de lo que cabe en la pantalla. 3) tenía que usar kbSize.Width (en lugar de altura) en iPad si es horizontal
Travis M.

lo siento, nos acostumbramos tanto a nuestro propio código que a veces lo olvidamos, ¿eh? currentField es el campo de texto actual con el que estoy trabajando, y indexPath es una extensión que agregué a la clase que simplemente agrega un NSIndexPath para saber en qué celda está.
mickm

Este es el camino a seguir, no mover marcos solo modificando las propiedades de la tabla.
Nextorlg

35

Creo que he encontrado la solución para que coincida con el comportamiento de las aplicaciones de Apple.

Primero, en su viewWillAppear: suscríbase a las notificaciones del teclado, para que sepa cuándo se mostrará y se ocultará el teclado, y el sistema le dirá el tamaño del teclado, pero no se olvide de anular el registro en su viewWillDisappear :.

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

Implemente los métodos similares a los siguientes para que ajuste el tamaño de su tableView para que coincida con el área visible una vez que se muestra el teclado. Aquí estoy rastreando el estado del teclado por separado para poder elegir cuándo volver a configurar la tabla Vista a la altura completa, ya que obtienes estas notificaciones en cada cambio de campo. No olvide implementar keyboardWillHide: y elija un lugar apropiado para arreglar su tamaño tableView.

-(void) keyboardWillShow:(NSNotification *)note
{
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keyboardBounds];
    keyboardHeight = keyboardBounds.size.height;
    if (keyboardIsShowing == NO)
    {
        keyboardIsShowing = YES;
        CGRect frame = self.view.frame;
        frame.size.height -= keyboardHeight;

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationBeginsFromCurrentState:YES];
        [UIView setAnimationDuration:0.3f];
        self.view.frame = frame;
        [UIView commitAnimations];
    }
}

Ahora aquí está el bit de desplazamiento, primero trabajamos algunos tamaños, luego vemos dónde estamos en el área visible y establecemos el rectángulo al que queremos desplazar para que sea la vista media arriba o debajo del centro del campo de texto basado en donde está en la vista. En este caso, tenemos una matriz de UITextFields y una enumeración que los rastrea, por lo que multiplicar la altura de fila por el número de fila nos da el desplazamiento real del marco dentro de esta vista externa.

- (void) textFieldDidBeginEditing:(UITextField *)textField
{
    CGRect frame = textField.frame;
    CGFloat rowHeight = self.tableView.rowHeight;
    if (textField == textFields[CELL_FIELD_ONE])
    {
        frame.origin.y += rowHeight * CELL_FIELD_ONE;
    }
    else if (textField == textFields[CELL_FIELD_TWO])
    {
        frame.origin.y += rowHeight * CELL_FIELD_TWO;
    }
    else if (textField == textFields[CELL_FIELD_THREE])
    {
        frame.origin.y += rowHeight * CELL_FIELD_THREE;
    }
    else if (textField == textFields[CELL_FIELD_FOUR])
    {
        frame.origin.y += rowHeight * CELL_FIELD_FOUR;
    }
    CGFloat viewHeight = self.tableView.frame.size.height;
    CGFloat halfHeight = viewHeight / 2;
    CGFloat midpoint = frame.origin.y + (textField.frame.size.height / 2);
    if (midpoint < halfHeight)
    {
        frame.origin.y = 0;
        frame.size.height = midpoint;
    }
    else
    {
        frame.origin.y = midpoint;
        frame.size.height = midpoint;
    }
    [self.tableView scrollRectToVisible:frame animated:YES];
}

Esto parece funcionar bastante bien.


Buena solución Gracias por publicarlo.
Alex Reynolds

2
UIKeyboardBoundsUserInfoKeyestá en desuso a partir de iOS 3.2. Vea mi solución a continuación que funciona en todas las versiones actuales de iOS ≥ 3.0. / @ iPhoneDev
Ortwin Gentz

Esto fue más complicado de lo necesario. La respuesta de @ user91083 fue simple y funciona.
Richard Brightwell

1
Hay un pequeño problema en esta solución. keyboardWillShow se llama DESPUÉS de textFieldDidBeginEditing, por lo que cuando queremos desplazarnos a alguna celda, el marco de tableView aún no ha cambiado, por lo que no funcionará
HiveHicks

35

Si puede usar UITableViewController, obtiene la funcionalidad de forma gratuita. A veces, sin embargo, esta no es una opción, específicamente si necesita múltiples vistas, no solo la UITableView.

Algunas de las soluciones presentadas aquí no funcionan en iOS ≥4, algunas no funcionan en iPad o en modo horizontal, algunas no funcionan para teclados Bluetooth (donde no queremos ningún desplazamiento), algunas no funciona al cambiar entre múltiples campos de texto. Entonces, si elige cualquier solución, asegúrese de probar estos casos. Esta es la solución que utilizamos utilizamos en InAppSettingsKit :

- (void)_keyboardWillShow:(NSNotification*)notification {
    if (self.navigationController.topViewController == self) {
        NSDictionary* userInfo = [notification userInfo];

        // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
        NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
        if (!keyboardFrameValue) {
            keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
        }

        // Reduce the tableView height by the part of the keyboard that actually covers the tableView
        CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
        if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
            windowRect = IASKCGRectSwap(windowRect);
        }
        CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
        if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
            viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute);
        }
        CGRect frame = _tableView.frame;
        frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
        [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        _tableView.frame = frame;
        [UIView commitAnimations];

        UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
        NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];

        // iOS 3 sends hide and show notifications right after each other
        // when switching between textFields, so cancel -scrollToOldPosition requests
        [NSObject cancelPreviousPerformRequestsWithTarget:self];

        [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
    }
}

- (void) scrollToOldPosition {
  [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)_keyboardWillHide:(NSNotification*)notification {
    if (self.navigationController.topViewController == self) {
        NSDictionary* userInfo = [notification userInfo];

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
        [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        _tableView.frame = self.view.bounds;
        [UIView commitAnimations];

        [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
    }
}   

Aquí está el código completo de la clase en InAppSettingsKit. Para probarlo, use el panel secundario "Lista completa" donde puede probar los escenarios mencionados anteriormente.


No sé si es útil usar cadenas en lugar de constantes, porque si Apple tiene la idea de cambiar la cadena internamente por alguna razón, su solución ya no funciona. Del mismo modo, no recibió una advertencia cuando se desaprobó. Creo

@iPortable: no es ideal, lo sé. ¿Puede sugerir una mejor solución que se ejecute en todas las versiones ≥3.0?
Ortwin Gentz

1
Funciona como encanto, pero no para UIInterfaceOrientationPortraitUpsideDown. Luego, el cálculo de la reducción de altura también debe basarse al revés: CGFloat reduceHeight = keyboardRect.size.height - (CGRectGetMinY (viewRectAbsolute) - CGRectGetMinY (windowRect));
Klaas

Esto tiene fallas visuales muy notables en mi iPad y el Simulador (4.3). Demasiado notable para usar. :(
Bob Spryn

Me gusta que esta solución tenga en cuenta una barra de herramientas en la parte inferior de la pantalla.
pdemarest

24

La solución más simple para Swift :

override func viewDidLoad() {
    super.viewDidLoad()

    searchBar?.becomeFirstResponder()
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillHide(_:)), name: UIKeyboardDidHideNotification, object: nil)
}

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        if let keyboardHeight = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue.size.height {
            tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
        }
    }
}

func keyboardWillHide(notification: NSNotification) {
    UIView.animateWithDuration(0.2, animations: { self.table_create_issue.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) })
    // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
    }

Funciona perfectamente, se necesitan cálculos mínimos. Agregué un código que restaura las inserciones de la tabla para que esta respuesta finalice.
Vitalii

La mejor solución gracias. He publicado una versión de Swift 3 aquí: stackoverflow.com/a/41040630/1064438
squall2022

Solución súper perfecta jamás vista, probé otras pero tiene algunos problemas. Su solución funciona perfectamente en iOS 10.2.
Wangdu Lin

8

Espero que ya tengan una solución para leer todos esos. Pero encontré mi solución de la siguiente manera. Espero que ya tengas una celda UITextField. Entonces, al prepararse, simplemente mantenga el índice de la fila en la etiqueta del campo de texto.

cell.textField.tag = IndexPath.row;

Cree una activeTextFieldinstancia de UITextFieldcon alcance global como se muestra a continuación:

@interface EditViewController (){

    UITextField *activeTextField;

}

Entonces, ahora solo copia y pega mi código al final. Y tampoco olvides agregarUITextFieldDelegate

#pragma mark - TextField Delegation

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

    activeTextField = textField;

    return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField{

    activeTextField = nil;

}

Registra el teclado notifications

#pragma mark - Keyboard Activity

- (void)registerForKeyboardNotifications

{

    [[NSNotificationCenter defaultCenter] addObserver:self

                                         selector:@selector(keyboardWasShown:)

                                             name:UIKeyboardDidShowNotification object:nil];



    [[NSNotificationCenter defaultCenter] addObserver:self

                                         selector:@selector(keyboardWillBeHidden:)

                                             name:UIKeyboardWillHideNotification object:nil];



}

Maneja el teclado Notifications:

Llamado cuando UIKeyboardDidShowNotificationse envía el.

- (void)keyboardWasShown:(NSNotification*)aNotification

{

    NSDictionary* info = [aNotification userInfo];

    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);

    [self.tableView setContentInset:contentInsets];

    [self.tableView setScrollIndicatorInsets:contentInsets];

    NSIndexPath *currentRowIndex = [NSIndexPath indexPathForRow:activeTextField.tag inSection:0];

    [self.tableView scrollToRowAtIndexPath:currentRowIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];

}

Se llama cuando UIKeyboardWillHideNotificationse envía

- (void)keyboardWillBeHidden:(NSNotification*)aNotification

{

    UIEdgeInsets contentInsets = UIEdgeInsetsZero;

    [self.tableView setContentInset:contentInsets];

    [self.tableView setScrollIndicatorInsets:contentInsets];

}

Ahora queda una cosa, llame al registerForKeyboardNotificationsmétodo al ViewDidLoadmétodo de la siguiente manera:

- (void)viewDidLoad {

    [super viewDidLoad];

    // Registering keyboard notification

    [self registerForKeyboardNotifications];

    // Your codes here...

}

Ya terminaste, espero que tu textFieldsteclado ya no esté oculto.


6

Combinando y completando los espacios en blanco de varias respuestas (en particular, Ortwin Gentz, usuario 98013) y otra publicación, esto funcionará de fábrica para SDK 4.3 en un iPad en modo vertical u horizontal:

@implementation UIView (FindFirstResponder)
- (UIResponder *)findFirstResponder
{
  if (self.isFirstResponder) {        
    return self;     
  }

  for (UIView *subView in self.subviews) {
    UIResponder *firstResponder = [subView findFirstResponder];
    if (firstResponder != nil) {
      return firstResponder;
    }
  }

  return nil;
}
@end

@implementation MyViewController

- (UIResponder *)currentFirstResponder {
  return [self.view findFirstResponder];
}

- (IBAction)editingEnded:sender {
  [sender resignFirstResponder];
}

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

- (void)textFieldDidBeginEditing:(UITextField *)textField {
  UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview];
  [_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillShow:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {
    NSDictionary* userInfo = [notification userInfo];

    // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
    NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
    if (!keyboardFrameValue) {
      keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
    }

    // Reduce the tableView height by the part of the keyboard that actually covers the tableView
    CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
    CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
    CGRect frame = _tableView.frame;
    if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
      windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width);
      viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width);
    }
    frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = frame;
    [UIView commitAnimations];

    UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
    NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];

    // iOS 3 sends hide and show notifications right after each other
    // when switching between textFields, so cancel -scrollToOldPosition requests
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    _topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
    [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
}

- (void) scrollToOldPosition {
  [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillHide:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {

    NSDictionary* userInfo = [notification userInfo];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = self.view.bounds;
    [UIView commitAnimations];

    [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
  }
}   

@end

Utilicé este código en iOS 4.x muy bien, pero en iOS5 se bloquea en scrollToOldPosition porque _topmostRowBeforeKeyboardWasShown ya está liberado en ese momento. Aún no estoy seguro de cuál es la solución. Probablemente recuerde el índice en lugar del objeto.
Thomas Tempelmann

5

Si usa una vista uitableview para colocar sus campos de texto ( de Jeff Lamarche ), puede simplemente desplazar la vista de tabla usando el método delegado de esta manera.

(Nota: mis campos de texto se almacenan en una matriz con el mismo índice que la fila en la vista de tabla)

- (void) textFieldDidBeginEditing:(UITextField *)textField
    {

        int index;
        for(UITextField *aField in textFields){

            if (textField == aField){
                index = [textFields indexOfObject:aField]-1;
            }
        }

         if(index >= 0) 
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

        [super textFieldDidBeginEditing:textField];
    }

No actualiza el marco tableView. Entonces, las barras de desplazamiento y el comportamiento de desplazamiento son incorrectos cuando se muestra el teclado. Mira mi solución.
Ortwin Gentz

5

Las notificaciones del teclado funcionan, pero el código de muestra de Apple supone que la vista de desplazamiento es la vista raíz de la ventana. Usualmente este no es el caso. Debe compensar las barras de pestañas, etc., para obtener el desplazamiento correcto.

Es más fácil de lo que parece. Aquí está el código que uso en un UITableViewController. Tiene dos variables de instancia, hiddenRect y keyboardShown.

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification {
    if (keyboardShown)
        return;

    NSDictionary* info = [aNotification userInfo];

    // Get the frame of the keyboard.
    NSValue *centerValue = [info objectForKey:UIKeyboardCenterEndUserInfoKey];
    NSValue *boundsValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
    CGPoint keyboardCenter = [centerValue CGPointValue];
    CGRect keyboardBounds = [boundsValue CGRectValue];
    CGPoint keyboardOrigin = CGPointMake(keyboardCenter.x - keyboardBounds.size.width / 2.0,
                                         keyboardCenter.y - keyboardBounds.size.height / 2.0);
    CGRect keyboardScreenFrame = { keyboardOrigin, keyboardBounds.size };


    // Resize the scroll view.
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = scrollView.frame;
    CGRect keyboardFrame = [scrollView.superview convertRect:keyboardScreenFrame fromView:nil];
    hiddenRect = CGRectIntersection(viewFrame, keyboardFrame);

    CGRect remainder, slice;
    CGRectDivide(viewFrame, &slice, &remainder, CGRectGetHeight(hiddenRect), CGRectMaxYEdge);
    scrollView.frame = remainder;

    // Scroll the active text field into view.
    CGRect textFieldRect = [/* selected cell */ frame];
    [scrollView scrollRectToVisible:textFieldRect animated:YES];

    keyboardShown = YES;
}


// Called when the UIKeyboardDidHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
    // Reset the height of the scroll view to its original value
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = [scrollView frame];
    scrollView.frame = CGRectUnion(viewFrame, hiddenRect);

    keyboardShown = NO;
}

UIKeyboardCenterEndUserInfoKeyy UIKeyboardBoundsUserInfoKeyestán en desuso a partir de iOS 3.2. Vea mi solución a continuación que funciona en todas las versiones actuales de iOS ≥ 3.0.
Ortwin Gentz

5

Si usa Three20, entonces use la autoresizesForKeyboardpropiedad. Simplemente configure el -initWithNibName:bundlemétodo de su controlador de vista

self.autoresizesForKeyboard = YES

Esto se encarga de:

  1. Escuchar las notificaciones del teclado y ajustar el marco de la vista de tabla
  2. Desplazarse al primer respondedor

Hecho y hecho.


¿Qué es Three20 aquí? ¿Puedes especificar eso?
Centro comercial Mubin el

5

Mi acercamiento:

Primero subclases UITextField y agrego una propiedad indexPath. En el método CellFor ... entrego la propiedad indexPath.

Luego agrego el siguiente código:

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:textField.indexPath];

CGPoint cellPoint = [cell convertPoint:textField.center toView:self.tableView];
[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, cellPoint.y-50);}];

al textFieldShould / WillBegin ... etc.

Cuando el teclado desaparece, debes invertirlo con:

[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, 0);}];

4

Una solución más fluida. Se desliza en los métodos de delegado de UITextField, por lo que no requiere de mensajes con notificaciones de UIKeyboard.

Notas de implementación:

kSettingsRowHeight: la altura de un UITableViewCell.

offsetTarget y offsetThreshold se basan en kSettingsRowHeight. Si usa una altura de fila diferente, establezca esos valores en la propiedad y del punto. [alt: calcula el desplazamiento de fila de una manera diferente.]

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
CGFloat offsetTarget    = 113.0f; // 3rd row
CGFloat offsetThreshold = 248.0f; // 6th row (i.e. 2nd-to-last row)

CGPoint point = [self.tableView convertPoint:CGPointZero fromView:textField];

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
if (point.y > offsetThreshold) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y + kSettingsRowHeight,
                      frame.size.width,
                      frame.size.height);
} else if (point.y > offsetTarget) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y,
                      frame.size.width,
                      frame.size.height);
} else {
    self.tableView.frame = CGRectMake(0.0f,
                      0.0f,
                      frame.size.width,
                      frame.size.height);
}

[UIView commitAnimations];

return YES;

}

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

[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
self.tableView.frame = CGRectMake(0.0f,
                  0.0f,
                  frame.size.width,
                  frame.size.height);

[UIView commitAnimations];

return YES;

}


4

UITextField's delegateMétodo de uso :

Rápido

func textFieldShouldBeginEditing(textField: UITextField) -> bool {
  let txtFieldPosition = textField.convertPoint(textField.bounds.origin, toView: yourTableViewHere)
  let indexPath = yourTablViewHere.indexPathForRowAtPoint(txtFieldPosition)
  if indexPath != nil {
     yourTablViewHere.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true)
  }
  return true
}

C objetivo

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
  CGPoint txtFieldPosition = [textField convertPoint:CGPointZero toView: yourTablViewHere];
  NSLog(@"Begin txtFieldPosition : %@",NSStringFromCGPoint(txtFieldPosition));
  NSIndexPath *indexPath = [yourTablViewHere indexPathForRowAtPoint:txtFieldPosition];

  if (indexPath != nil) {
     [yourTablViewHere scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
  return YES;
}

Hola, estoy teniendo problemas para que esto funcione en Swift. Mis UITextFields conectados a UITableViewCell. Si implemento este código dentro de mi UIViewController, no tengo acceso a UITextFields. ¿Algunas ideas?
Vetuka

4

Solución completa Swift 4.2

He creado GIST con un conjunto de protocolos que simplifica el trabajo con la adición de espacio adicional cuando se muestre el teclado, oculta o se cambia.

Caracteristicas :

  • Funciona correctamente con los cambios en el marco del teclado (por ejemplo, la altura del teclado cambia como emojii → teclado normal).
  • Compatibilidad con TabBar y ToolBar para el ejemplo UITableView (en otros ejemplos recibe inserciones incorrectas).
  • Duración de la animación dinámica (no codificada).
  • Enfoque orientado al protocolo que podría modificarse fácilmente para sus propósitos.

Uso

Ejemplo de uso básico en el controlador de vista que contiene alguna vista de desplazamiento (la vista de tabla también es compatible, por supuesto).

class SomeViewController: UIViewController {
  @IBOutlet weak var scrollView: UIScrollView!

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    addKeyboardFrameChangesObserver()
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    removeKeyboardFrameChangesObserver()
  }
}

extension SomeViewController: ModifableInsetsOnKeyboardFrameChanges {
  var scrollViewToModify: UIScrollView { return scrollView }
}

Núcleo: el marco cambia al observador

El protocolo KeyboardChangeFrameObserveractivará el evento cada vez que se cambie el marco del teclado (incluyendo mostrar, ocultar, cambiar el marco).

  1. Llame addKeyboardFrameChangesObserver()al viewWillAppear()o método similar.
  2. Llame removeKeyboardFrameChangesObserver()al viewWillDisappear()o método similar.

Implementación: vista de desplazamiento

ModifableInsetsOnKeyboardFrameChangesEl protocolo agrega UIScrollViewsoporte al protocolo central. Cambia las inserciones de la vista de desplazamiento cuando se cambia el marco del teclado.

Su clase necesita configurar la vista de desplazamiento, las inserciones de uno aumentarán / disminuirán con los cambios en el marco del teclado.

var scrollViewToModify: UIScrollView { get }

3

Dado que tiene campos de texto en una tabla, la mejor manera es cambiar el tamaño de la tabla: debe configurar tableView.frame para que sea más pequeño en altura por el tamaño del teclado (creo que alrededor de 165 píxeles) y luego expandirlo nuevamente cuando Se descarta el teclado.

Opcionalmente, también puede deshabilitar la interacción del usuario para tableView en ese momento, si no desea que el usuario se desplace.


Secundo esto y me registro en UIKeyboardWillShowNotification para encontrar el tamaño del teclado dinámicamente.
benzado 03 de

Sin embargo, el número devuelto por el objeto de notificación no funciona. O al menos no lo hizo en 2.2, el número devuelto era incorrecto y tuve que codificar el valor 165 para ajustar la altura correctamente (estaba apagado de cinco a diez píxeles)
Kendall Helmstetter Gelner

2

Esto funciona perfectamente, y también en iPad.

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

    if(textField == textfield1){
            [accountName1TextField becomeFirstResponder];
        }else if(textField == textfield2){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield3 becomeFirstResponder];

        }else if(textField == textfield3){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield4 becomeFirstResponder];

        }else if(textField == textfield4){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield5 becomeFirstResponder];

        }else if(textField == textfield5){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield6 becomeFirstResponder];

        }else if(textField == textfield6){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:4 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield7 becomeFirstResponder];

        }else if(textField == textfield7){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:5 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield8 becomeFirstResponder];

        }else if(textField == textfield8){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:6 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield9 becomeFirstResponder];

        }else if(textField == textfield9){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textField resignFirstResponder];
        }

¿Por qué estás dudando y usando casos especiales para cada campo de texto? Identifique cada campo de texto del NSIndexPath de la celda y cambie esa desagradable declaración if en 2 líneas de código. Realmente desea una llamada cellForRowAtIndexPath y luego obtener el textField de la celda.
Alex Zavatone

En realidad, considerando cuán increíblemente inestable es esta situación en iOS, creo que está bien escribir un código "completamente desenrollado, ridículamente literal" para esta situación.
Fattie

Teniendo en cuenta esta respuesta, se dio hace más de 6 años.
WrightsCS

2

Intenté casi el mismo enfoque y se me ocurrió un código más simple y más pequeño para el mismo. Creé un IBOutlet iTextView y lo asocié con el UITextView en el IB.

 -(void)keyboardWillShow:(NSNotification *)notification
    {
        NSLog(@"Keyboard");
        CGRect keyFrame = [[[notification userInfo]objectForKey:UIKeyboardFrameEndUserInfoKey]CGRectValue];

        [UIView beginAnimations:@"resize view" context:nil];
        [UIView setAnimationCurve:1];
        [UIView setAnimationDuration:1.0];
        CGRect frame = iTableView.frame;
        frame.size.height = frame.size.height -  keyFrame.size.height;
        iTableView.frame = frame;
        [iTableView scrollRectToVisible:frame animated:YES];
        [UIView commitAnimations];

    }

2

Entonces, después de horas de trabajo agotador tratando de usar estas soluciones actuales (y fallando por completo), finalmente conseguí que las cosas funcionaran bien y las actualicé para usar los nuevos bloques de animación. Mi respuesta se basa completamente en la respuesta de Ortwin anterior .

Entonces, por alguna razón, el código anterior simplemente no funcionaba para mí. Mi configuración parecía bastante similar a los demás, pero tal vez porque estaba en un iPad o 4.3 ... no tengo idea. Estaba haciendo algunos cálculos extravagantes y disparaba mi vista de la mesa fuera de la pantalla.

Vea el resultado final de mi solución: http://screencast.com/t/hjBCuRrPC (ignore la foto. :-P)

Así que seguí con lo esencial de lo que estaba haciendo Ortwin, pero cambié la forma en que hacía algunas matemáticas para sumar el origen, tamaño y altura de la vista de mi tabla con la altura del teclado. Cuando resta la altura de la ventana de ese resultado, me dice cuánta intersección tengo. Si es mayor que 0 (también conocido como cierta superposición), realizo la animación de la altura del cuadro.

Además, hubo algunos problemas de redibujo que se resolvieron 1) Esperando para desplazarse a la celda hasta que se realizó la animación y 2) utilizando la opción UIViewAnimationOptionBeginFromCurrentState al ocultar el teclado.

Un par de cosas a tener en cuenta.

  • _topmostRowBeforeKeyboardWasShown y _originalFrame son variables de instancia declaradas en el encabezado.
  • self.guestEntryTableView es mi tableView (estoy en un archivo externo)
  • IASKCGRectSwap es el método de Ortwin para voltear las coordenadas de un marco
  • Solo actualizo la altura de la tabla Vista si se mostrarán al menos 50 píxeles.
  • Como no estoy en un UIViewController, no tengo self.view, así que solo devuelvo el tableView a su marco original

Nuevamente, no habría llegado a esta respuesta si Ortwin no hubiera proporcionado el quid de la misma. Aquí está el código:

- (IBAction)textFieldDidBeginEditing:(UITextField *)textField
{
    self.activeTextField = textField;

    if ([self.guestEntryTableView indexPathsForVisibleRows].count) {
        _topmostRowBeforeKeyboardWasShown = (NSIndexPath*)[[self.guestEntryTableView indexPathsForVisibleRows] objectAtIndex:0];
    } else {
        // this should never happen
        _topmostRowBeforeKeyboardWasShown = [NSIndexPath indexPathForRow:0 inSection:0];
        [textField resignFirstResponder];
    }
}

- (IBAction)textFieldDidEndEditing:(UITextField *)textField
{
    self.activeTextField = nil;
}

- (void)keyboardWillShow:(NSNotification*)notification {
    NSDictionary* userInfo = [notification userInfo];

    NSValue* keyboardFrameValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];

    // Reduce the tableView height by the part of the keyboard that actually covers the tableView
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
    CGRect viewRectAbsolute = [self.guestEntryTableView convertRect:self.guestEntryTableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
    CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
    if (UIInterfaceOrientationLandscapeLeft == orientation ||UIInterfaceOrientationLandscapeRight == orientation ) {
        windowRect = IASKCGRectSwap(windowRect);
        viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute);
        keyboardFrame = IASKCGRectSwap(keyboardFrame);
    }

    // fix the coordinates of our rect to have a top left origin 0,0
    viewRectAbsolute = FixOriginRotation(viewRectAbsolute, orientation, windowRect.size.width, windowRect.size.height);

    CGRect frame = self.guestEntryTableView.frame;
    _originalFrame = self.guestEntryTableView.frame;

    int remainder = (viewRectAbsolute.origin.y + viewRectAbsolute.size.height + keyboardFrame.size.height) - windowRect.size.height;

    if (remainder > 0 && !(remainder > frame.size.height + 50)) {
        frame.size.height = frame.size.height - remainder;
        float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
        [UIView animateWithDuration: duration
                        animations:^{
                            self.guestEntryTableView.frame = frame;
                        }
                        completion:^(BOOL finished){
                            UITableViewCell *textFieldCell = (UITableViewCell*) [[self.activeTextField superview] superview];
                            NSIndexPath *textFieldIndexPath = [self.guestEntryTableView indexPathForCell:textFieldCell];
                            [self.guestEntryTableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
                        }];
    }

}

- (void)keyboardWillHide:(NSNotification*)notification {
    NSDictionary* userInfo = [notification userInfo];
    float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    [UIView animateWithDuration: duration
                          delay: 0.0
                        options: (UIViewAnimationOptionBeginFromCurrentState)
                     animations:^{
                         self.guestEntryTableView.frame = _originalFrame;
                     }
                     completion:^(BOOL finished){
                         [self.guestEntryTableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
                     }];

}   

#pragma mark CGRect Utility function
CGRect IASKCGRectSwap(CGRect rect) {
    CGRect newRect;
    newRect.origin.x = rect.origin.y;
    newRect.origin.y = rect.origin.x;
    newRect.size.width = rect.size.height;
    newRect.size.height = rect.size.width;
    return newRect;
}

CGRect FixOriginRotation(CGRect rect, UIInterfaceOrientation orientation, int parentWidth, int parentHeight) {
    CGRect newRect;
    switch(orientation)
    {
        case UIInterfaceOrientationLandscapeLeft:
            newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), rect.origin.y, rect.size.width, rect.size.height);
            break;
        case UIInterfaceOrientationLandscapeRight:
            newRect = CGRectMake(rect.origin.x, parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height);
            break;
        case UIInterfaceOrientationPortrait:
            newRect = rect;
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height);
            break;
    }
    return newRect;
}

Agregué mi función FixOriginRotation que corrige el sistema de coordenadas de la vista antes de actualizar su marco, etc. Creo que esto es parte de por qué tenía problemas al principio. ¡No sabía que el sistema de coordenadas de ventana de iOS giraba con el dispositivo!
Bob Spryn

2

Esta solución funciona para mí, TENGA EN CUENTA la línea

[tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES];

Puede cambiar el valor de 160 para que coincida con usted.

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect bkgndRect = activeField.superview.frame;
                        bkgndRect.size.height += kbSize.height;
     [activeField.superview setFrame:bkgndRect];
     [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES];
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
   activeField = textField;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
 {
     activeField = nil;
 }
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    tableView.contentInset = contentInsets;
    tableView.scrollIndicatorInsets = contentInsets;
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect bkgndRect = activeField.superview.frame;
    //bkgndRect.size.height += kbSize.height;
    [activeField.superview setFrame:bkgndRect];
    [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height) animated:YES];
}

2

Muy interesante hilo de discusión, también enfrenté el mismo problema, puede ser peor porque

  1. Estaba usando una celda personalizada y el campo de texto estaba dentro de eso.
  2. Tuve que usar UIViewController para cumplir con mis requisitos, así que no puedo aprovechar UITableViewController.
  3. Tenía criterios de filtro / clasificación en la celda de mi tabla, es decir, sus celdas siguen cambiando y haciendo un seguimiento de la ruta de acceso índice y todo no ayudará.

Entonces leí los hilos aquí e implementé mi versión, lo que me ayudó a subir mis contenidos en iPad en modo horizontal . Aquí está el código (esto no es infalible y todo, pero solucionó mi problema) Primero debe tener un delegado en su clase de celda personalizada, que al comenzar la edición, envía el campo de texto a su controlador de vista y establece el campo activo = theTextField allí

// IMPLEMENTADO PARA MANEJAR EL MODO DE PAISAJE SOLAMENTE

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect aRect = myTable.frame;

    CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width);

    aRect.size.height -= kbSize.height+50;
// This will the exact rect in which your textfield is present
        CGRect rect =  [myTable convertRect:activeField.bounds fromView:activeField];
// Scroll up only if required
    if (!CGRectContainsPoint(aRect, rect.origin) ) {


            [myTable setContentOffset:CGPointMake(0.0, rect.origin.y) animated:YES];

    }


}

// Se llama cuando se envía UIKeyboardWillHideNotification

- (void)keyboardWillHide:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    myTable.contentInset = contentInsets;
    myTable.scrollIndicatorInsets = contentInsets;
    NSDictionary* info = [aNotification userInfo];
    CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width);
    CGRect bkgndRect = activeField.superview.frame;
    bkgndRect.size.height += kbSize.height;
    [activeField.superview setFrame:bkgndRect];
    [myTable setContentOffset:CGPointMake(0.0, 10.0) animated:YES];
}

-anoop4real


2

Acabo de resolver ese problema por mí mismo después de referir una gran cantidad de soluciones encontradas a través de Google y Stack Overflow.

Primero, asegúrese de haber configurado un IBOutlet de su UIScrollView, luego observe detenidamente Apple Doc: Administración de teclado . Finalmente, si puede desplazarse por el fondo, pero el teclado aún cubre los campos de texto, eche un vistazo a este código:

// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;

if (aRect.size.height < activeField.frame.origin.y+activeField.frame.size.height) {

    CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y+activeField.frame.size.height-aRect.size.height);

    [scrollView setContentOffset:scrollPoint animated:YES];

La principal diferencia entre esta pieza y la de Apple radica en la condición if. Creo que el cálculo de Apple de la distancia de desplazamiento y la condición de si el campo de texto cubierto por el teclado no es preciso, por lo que realicé mi modificación como se indicó anteriormente.

Déjame saber si funciona


2

Un ejemplo en Swift, usando el punto exacto del campo de texto de Get indexPath de UITextField en UITableViewCell con Swift :

func textFieldDidBeginEditing(textField: UITextField) {
    let pointInTable = textField.convertPoint(textField.bounds.origin, toView: self.accountsTableView)
    let textFieldIndexPath = self.accountsTableView.indexPathForRowAtPoint(pointInTable)
    accountsTableView.scrollToRowAtIndexPath(textFieldIndexPath!, atScrollPosition: .Top, animated: true)
}

1

Otro método fácil (solo funciona con una sección)

//cellForRowAtIndexPath
UItextField *tf;
[cell addSubview:tf];
tf.tag = indexPath.row;
tf.delegate = self;

//textFieldDidBeginEditing:(UITextField *)text
[[self.tableView scrollToRowsAtIndexPath:[NSIndexPath indexPathForRow:text.tag in section:SECTIONINTEGER] animated:YES];

1

Si su UITableView es administrado por una subclase de UITableViewController y no UITableView, y el delegado del campo de texto es el UITableViewController, debe administrar todo el desplazamiento automáticamente; todos estos otros comentarios son muy difíciles de implementar en la práctica.

Para un buen ejemplo, vea el proyecto de código de ejemplo de Apple: TaggedLocations.

Puede ver que se desplaza automáticamente, pero no parece haber ningún código que haga esto. Este proyecto también tiene celdas de vista de tabla personalizadas, por lo que si crea su aplicación con ella como guía, debería obtener el resultado deseado.


1

Así es como hice que esto funcionara, que es una mezcla de las respuestas de Sam Ho y Marcel W, y algunas de mis propias correcciones de errores en mi código. Estaba usando un UITableViewController. La tabla ahora cambia de tamaño correctamente cuando se muestra el teclado.

1) En viewDidLoadagregué:

self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

2) Había olvidado llamar a los superequivalentes en viewWillAppeary awakeFromNib. Agregué estos nuevamente.


1

UITableViewControllerhace el desplazamiento automáticamente, de hecho. La diferencia en comparación con el uso de a UIViewControlleres que debe crear Navbar-Buttonitems mediante programación utilizando NavigationController, cuando se utiliza a TableViewController.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.