objective c - for - ¿Cómo se mueve programáticamente un UIScrollView para enfocar en un control encima del teclado?
xcode ios 12 (13)
Creo que es mejor usar notificaciones de teclado porque no se sabe si el primer respondedor (el control con foco en) es un campo de texto o una vista de texto (o lo que sea). Así que juste crea una categoría para encontrar al primer respondedor:
#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
@implementation UIResponder (FirstResponder)
+(id)currentFirstResponder {
currentFirstResponder = nil;
[[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil];
return currentFirstResponder;
}
-(void)findFirstResponder:(id)sender {
currentFirstResponder = self;
}
@end
entonces
-(void)keyboardWillShowNotification:(NSNotification*)aNotification{
contentScrollView.delegate=nil;
contentScrollView.scrollEnabled=NO;
contentScrollViewOriginalOffset = contentScrollView.contentOffset;
UIResponder *lc_firstResponder = [UIResponder currentFirstResponder];
if([lc_firstResponder isKindOfClass:[UIView class]]){
UIView *lc_view = (UIView *)lc_firstResponder;
CGRect lc_frame = [lc_view convertRect:lc_view.bounds toView:contentScrollView];
CGPoint lc_point = CGPointMake(0, lc_frame.origin.y-lc_frame.size.height);
[contentScrollView setContentOffset:lc_point animated:YES];
}
}
Eventualmente deshabilite el desplazamiento y establezca el delegado en cero y luego restaurelo para evitar algunas acciones durante la edición del primer respondedor. Como dijo james_womack, mantenga el desplazamiento original para restaurarlo en el método keyboardWillHideNotification.
-(void)keyboardWillHideNotification:(NSNotification*)aNotification{
contentScrollView.delegate=self;
contentScrollView.scrollEnabled=YES;
[contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES];
}
Tengo 6 UITextFields
en mi UIScrollView
. Ahora, puedo desplazarme por solicitud del usuario. Pero cuando aparece el teclado, algunos campos de texto están ocultos.
Eso no es fácil de usar.
¿Cómo se desplaza programáticamente la vista para asegurarme de que el teclado no oculta el campo de texto?
En Swift 1.2+ haga algo como esto:
class YourViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
_yourTextField.delegate = self //make sure you have the delegate set to this view controller for each of your textFields so textFieldDidBeginEditing can be called for each one
...
}
func textFieldDidBeginEditing(textField: UITextField) {
var point = textField.convertPoint(textField.frame.origin, toView: _yourScrollView)
point.x = 0.0 //if your textField does not have an origin at 0 for x and you don''t want your scrollView to shift left and right but rather just up and down
_yourScrollView.setContentOffset(point, animated: true)
}
func textFieldDidEndEditing(textField: UITextField) {
//Reset scrollview once done editing
scrollView.setContentOffset(CGPoint.zero, animated: true)
}
}
Este es mi código, espero que te ayude. Funciona bien en caso de que tenga muchos campos de texto
CGPoint contentOffset;
bool isScroll;
- (void)textFieldDidBeginEditing:(UITextField *)textField {
contentOffset = self.myScroll.contentOffset;
CGPoint newOffset;
newOffset.x = contentOffset.x;
newOffset.y = contentOffset.y;
//check push return in keyboar
if(!isScroll){
//180 is height of keyboar
newOffset.y += 180;
isScroll=YES;
}
[self.myScroll setContentOffset:newOffset animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
//reset offset of content
isScroll = NO;
[self.myScroll setContentOffset:contentOffset animated:YES];
[textField endEditing:true];
return true;
}
tenemos un punto contentOffset para guardar contentoffset de scrollview antes del show keyboar. Luego desplazaremos el contenido para aproximadamente 180 (altura del teclado). cuando tocas return en keyboar, desplazaremos el contenido al punto anterior (es contentOffset). Si tiene muchos campos de texto, no toca regresar en el teclado pero toca otro campo de texto, será +180. Así que tenemos toque de verificación de retorno
Esto es lo que funcionó para mí. Tener una variable de instancia que contenga el valor del desplazamiento de UIScrollView antes de que la vista se ajuste para el teclado, de modo que pueda restaurar el estado anterior después de que UITextField regrese:
//header
@interface TheViewController : UIViewController <UITextFieldDelegate> {
CGPoint svos;
}
//implementation
- (void)textFieldDidBeginEditing:(UITextField *)textField {
svos = scrollView.contentOffset;
CGPoint pt;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:scrollView];
pt = rc.origin;
pt.x = 0;
pt.y -= 60;
[scrollView setContentOffset:pt animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[scrollView setContentOffset:svos animated:YES];
[textField resignFirstResponder];
return YES;
}
Finalmente, una solución simple:
UIScrollView* v = (UIScrollView*) self.view ;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:v];
rc.origin.x = 0 ;
rc.origin.y -= 60 ;
rc.size.height = 400;
[self.scroll scrollRectToVisible:rc animated:YES];
¡Ahora creo que solo se combina esto con el enlace de arriba y está configurado!
He creado una subclase universal, UIScrollView y UITableView, que se encarga de mover todos los campos de texto 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á su marco y el desplazamiento del contenido para asegurarse de que la vista esté visible, con una animación que coincida con el teclado emergente. Cuando el teclado desaparece, restaura su tamaño anterior.
Debería funcionar básicamente con cualquier configuración, ya sea una interfaz basada en UITableView o una que consiste en vistas colocadas manualmente.
Here está.
(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor''s note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
Las respuestas publicadas hasta ahora no me funcionaron porque tengo una estructura anidada bastante profunda de UIViews. Además, tuve el problema de que algunas de esas respuestas solo funcionaban en determinadas orientaciones de dispositivos.
Esta es mi solución, que con suerte te hará perder menos tiempo en esto.
Mi UIViewTextView se deriva de UIView, es un delegado UITextView y agrega una UITextView después de haber leído algunos parámetros de un archivo XML para esa UITextView (esa parte XML se deja aquí para mayor claridad).
Aquí está la definición de la interfaz privada:
#import "UIViewTextView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreGraphics/CGColor.h>
@interface UIViewTextView (/**/) {
@private
UITextView *tf;
/*
* Current content scroll view
* position and frame
*/
CGFloat currentScrollViewPosition;
CGFloat currentScrollViewHeight;
CGFloat kbHeight;
CGFloat kbTop;
/*
* contentScrollView is the UIScrollView
* that contains ourselves.
*/
UIScrollView contentScrollView;
}
@end
En el método init tengo que registrar los controladores de eventos:
@implementation UIViewTextView
- (id) initWithScrollView:(UIScrollView*)scrollView {
self = [super init];
if (self) {
contentScrollView = scrollView;
// ...
tf = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 241, 31)];
// ... configure tf and fetch data for it ...
tf.delegate = self;
// ...
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(keyboardWasShown:) name: UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:@selector(keyboardWasHidden:) name: UIKeyboardWillHideNotification object:nil];
[self addSubview:tf];
}
return(self);
}
Una vez hecho esto, necesitamos manejar el evento show del teclado. Se llama a esto antes de que se invoque textViewBeginEditing, por lo que podemos usarlo para descubrir algunas propiedades del teclado. En esencia, queremos saber la altura del teclado. Esto, lamentablemente, debe tomarse desde su propiedad de ancho en modo apaisado:
-(void)keyboardWasShown:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGSize kbSize = kbRect.size;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat sWidth = screenRect.size.width;
CGFloat sHeight = screenRect.size.height;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if ((orientation == UIDeviceOrientationPortrait)
||(orientation == UIDeviceOrientationPortraitUpsideDown)) {
kbHeight = kbSize.height;
kbTop = sHeight - kbHeight;
} else {
//Note that the keyboard size is not oriented
//so use width property instead
kbHeight = kbSize.width;
kbTop = sWidth - kbHeight;
}
Luego, necesitamos desplazarnos cuando comenzamos a editar. Hacemos esto aquí:
- (void) textViewDidBeginEditing:(UITextView *)textView {
/*
* Memorize the current scroll position
*/
currentScrollViewPosition = contentScrollView.contentOffset.y;
/*
* Memorize the current scroll view height
*/
currentScrollViewHeight = contentScrollView.frame.size.height;
// My top position
CGFloat myTop = [self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow.rootViewController.view].y;
// My height
CGFloat myHeight = self.frame.size.height;
// My bottom
CGFloat myBottom = myTop + myHeight;
// Eventual overlap
CGFloat overlap = myBottom - kbTop;
/*
* If there''s no overlap, there''s nothing to do.
*/
if (overlap < 0) {
return;
}
/*
* Calculate the new height
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight + overlap);
/*
* Set the new height
*/
[contentScrollView setFrame:nrect];
/*
* Set the new scroll position
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = contentScrollView.contentOffset.y + overlap;
[contentScrollView setContentOffset:npos animated:NO];
}
Cuando terminamos la edición, hacemos esto para restablecer la posición de desplazamiento:
- (void) textViewDidEndEditing:(UITextView *)textView {
/*
* Reset the scroll view position
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight);
[contentScrollView setFrame:nrect];
/*
* Reset the scroll view height
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = currentScrollViewPosition;
[contentScrollView setContentOffset:npos animated:YES];
[tf resignFirstResponder];
// ... do something with your data ...
}
No hay nada más que hacer en el teclado: controlador de eventos ocultos; lo dejamos de todos modos:
-(void)keyboardWasHidden:(NSNotification*)aNotification {
}
Y eso es.
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
@end
Modifiqué algunas de las soluciones anteriores para que sea más fácil de entender y usar. Utilicé un IBOutlet para que múltiples campos de texto puedan enlazar a la función con "Edición Comenzó" desde "Eventos enviados" de los campos de texto. ** No olvides tener una salida para tu vista de desplazamiento
- (IBAction)moveViewUpwards:(id)sender
{
CGRect rc = [sender convertRect:[sender bounds] toView:scrollView];
rc.origin.x = 0 ;
rc.origin.y -= 60 ;
rc.size.height = 400;
[scrollView scrollRectToVisible:rc animated:YES];
}
Puede verificarlo: https://github.com/michaeltyson/TPKeyboardAvoiding (utilicé esa muestra para mis aplicaciones). Está funcionando muy bien. Espero que eso te ayude.
En realidad, aquí hay un tutorial completo sobre el uso de TPKeyboardAvoiding, que puede ayudar a alguien
(1) descargue el archivo comprimido del enlace github. agregue estos cuatro archivos a su proyecto de Xcode:
(2) construye tu hermosa forma en IB. agregue un UIScrollView. coloque los elementos del formulario DENTRO de la vista de desplazamiento . (Nota: consejo extremadamente útil con respecto al constructor de interfaz: https://.com/a/16952902/294884 )
(3) haga clic en la vista de desplazamiento . luego en el botón superior derecho, tercer botón, verá la palabra "UIScrollView". usando copiar y pegar, cámbielo a "TPKeyboardAvoidingScrollView"
(4) eso es todo. ponga la aplicación en la tienda de aplicaciones y facture a su cliente.
(Además, simplemente haga clic en la pestaña Inspector de la vista de desplazamiento. Puede preferir activar o desactivar el rebote y las barras de desplazamiento, su preferencia).
Comentario personal: recomiendo usar la vista de desplazamiento (o la vista de colección) para los formularios de entrada, en casi todos los casos. no use una vista de tabla. es problemático por muchas razones. y simplemente, es increíblemente más fácil usar una vista de desplazamiento. simplemente colóquelo de la manera que desee. es 100% wysiwyg en el constructor de interfaces. Espero eso ayude
Sé que esto es viejo, pero aún ninguna de las soluciones anteriores tenía todas las características de posicionamiento sofisticado necesarias para esa animación "perfecta" libre de errores, compatible con versiones anteriores y sin parpadeos.
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];
}
Si configura el delegate
de los campos de texto en un objeto controlador en su programa, puede hacer que ese objeto implemente textFieldDidBeginEditing:
y textFieldShouldReturn:
methods. El primer método se puede usar para desplazarse a su campo de texto y el segundo método se puede usar para desplazarse hacia atrás.
Puede encontrar el código que he usado para esto en mi blog: deslizar UITextViews para evitar el teclado . No probé este código para las vistas de texto en UIScrollView
pero debería funcionar.
Usa cualquiera de estos,
CGPoint bottomOffset = CGPointMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height);
[self.MainScrollView setContentOffset:bottomOffset animated:YES];
o
[self.MainScrollView scrollRectToVisible:CGRectMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height-30, MainScrollView.frame.size.width, MainScrollView.frame.size.height) animated:YES];
simple y mejor
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
// self.scrlViewUI.contentOffset = CGPointMake(0, textField.frame.origin.y);
[_scrlViewUI setContentOffset:CGPointMake(0,textField.center.y-90) animated:YES];
tes=YES;
[self viewDidLayoutSubviews];
}