web-dev-qa-db-fra.com

Comment déplacer par programme un UIScrollView pour qu'il se concentre dans un contrôle au-dessus du clavier?

J'ai 6 UITextFields sur mon UIScrollView. Maintenant, je peux faire défiler à la demande de l'utilisateur. Mais lorsque le clavier apparaît, certains champs de texte sont masqués.

Ce n'est pas convivial.

Comment faire défiler la vue par programmation pour que je sois sûr que le clavier ne cache pas le champ de texte?

50
mamcx

Enfin, une solution 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];

Maintenant, je pense que c'est seulement combiner cela avec le lien ci-dessus et c'est réglé!

38
mamcx

Voici ce qui a fonctionné pour moi. Avoir une variable d'instance qui contient la valeur du décalage de UIScrollView avant que la vue ne soit ajustée pour le clavier afin que vous puissiez restaurer l'état précédent après le retour de UITextField:

//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;
}
77
james_womack

J'ai mis en place une sous-classe universelle UIScrollView et UITableView qui prend soin de déplacer tous les champs de texte à l'intérieur du chemin du clavier.

Lorsque le clavier est sur le point d'apparaître, la sous-classe trouvera la sous-vue sur le point d'être modifiée et ajustera son décalage de cadre et de contenu pour s'assurer que la vue est visible, avec une animation correspondant à la fenêtre contextuelle du clavier. Lorsque le clavier disparaît, il restaure sa taille précédente.

Il devrait fonctionner avec pratiquement n'importe quelle configuration, soit une interface basée sur UITableView, soit une interface composée de vues placées manuellement.

Ici c'est.


(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor's note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
22
Michael Tyson

simple et meilleur

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

Si vous définissez le delegate de vos champs de texte sur un objet contrôleur dans votre programme, vous pouvez demander à cet objet d'implémenter le textFieldDidBeginEditing: et textFieldShouldReturn: méthodes. La première méthode peut ensuite être utilisée pour faire défiler jusqu'à votre champ de texte et la seconde méthode peut être utilisée pour revenir en arrière.

Vous pouvez trouver le code que j'ai utilisé pour cela dans mon blog: Glisser UITextViews autour pour éviter le clavier . Je n'ai pas testé ce code pour les vues de texte dans un UIScrollView mais cela devrait fonctionner.

2
Matt Gallagher

Les réponses publiées jusqu'à présent n'ont pas fonctionné pour moi car j'ai une structure imbriquée assez profonde d'UIViews. De plus, j'ai eu le problème que certaines de ces réponses ne fonctionnaient que sur certaines orientations de périphérique.

Voici ma solution, qui, espérons-le, vous fera perdre moins de temps à ce sujet.

Mon UIViewTextView dérive de UIView, est un délégué UITextView et ajoute un UITextView après avoir lu certains paramètres d'un fichier XML pour cet UITextView (cette partie XML est laissée ici pour plus de clarté).

Voici la définition de l'interface privée:

#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

Dans la méthode init, je dois enregistrer les gestionnaires d'événements:

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

Une fois cela fait, nous devons gérer l'événement de présentation du clavier. Ceci est appelé avant l'appel de textViewBeginEditing, nous pouvons donc l'utiliser pour découvrir certaines propriétés du clavier. Essentiellement, nous voulons connaître la hauteur du clavier. Cela, malheureusement, doit être pris de sa propriété width en mode paysage:

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

Ensuite, nous devons réellement faire défiler lorsque nous commençons à éditer. Nous le faisons ici:

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

Lorsque nous terminons l'édition, nous faisons cela pour réinitialiser la position de défilement:

- (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 ...

}

Il n'y a plus rien à faire dans le clavier était un gestionnaire d'événements caché; on le laisse quand même:

-(void)keyboardWasHidden:(NSNotification*)aNotification {
}

Et c'est tout.

/*
   // Only override drawRect: if you perform custom drawing.
   // An empty implementation adversely affects performance during animation.
   - (void)drawRect:(CGRect)rect
   {
   // Drawing code
   }
 */

@end
1
Matthias Nott

Vous pouvez le vérifier: https://github.com/michaeltyson/TPKeyboardAvoiding (J'ai utilisé cet exemple pour mes applications). Cela fonctionne si bien. J'espère que cela vous aide.


En fait, voici un tutoriel complet sur l'utilisation de TPKeyboardAvoiding, qui peut aider quelqu'un

(1) téléchargez le fichier Zip à partir du lien github. ajoutez ces quatre fichiers à votre projet Xcode:

enter image description here

(2) construisez votre belle forme dans IB. ajoutez un UIScrollView. placez les éléments du formulaire dans la vue de défilement . (Remarque - astuce extrêmement utile concernant le constructeur d'interface: https://stackoverflow.com/a/16952902/294884 )

enter image description here

(3) cliquez sur dans la vue de défilement . puis en haut à droite, troisième bouton, vous verrez le mot "UIScrollView". en utilisant le copier-coller, le changer en "TPKeyboardAvoidingScrollView"

enter image description here

(4) c'est tout. mettez l'application dans l'App Store et facturez votre client.

(En outre, cliquez simplement sur l'onglet Inspecteur de la vue de défilement. Vous pouvez préférer activer ou désactiver le rebond et les barres de défilement - votre préférence.)

Commentaire personnel - Je recommande fortement d'utiliser la vue de défilement (ou la vue de collection) pour les formulaires de saisie, dans presque tous les cas. n'utilisez pas une vue de table. c'est problématique pour plusieurs raisons. et tout simplement, il est incroyablement plus facile d'utiliser une vue de défilement. il suffit de le disposer comme vous le souhaitez. c'est 100% wysiwyg dans le constructeur d'interface. j'espère que ça aide

1
hightech

Ceci est mon code, j'espère qu'il vous aidera. Cela fonctionne bien si vous avez beaucoup de champs de texte

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

nous avons un point contentOffset pour enregistrer le contentoffset de scrollview avant le keyboar show. Ensuite, nous ferons défiler le contenu pour y environ 180 (hauteur du clavier). lorsque vous touchez retour dans le clavier, nous faisons défiler le contenu vers l'ancien point (c'est contentOffset). Si vous avez plusieurs champs de texte, vous ne touchez pas retour dans le clavier mais vous touchez un autre champ de texte, il sera +180. Nous avons donc un chèque tactile de retour

1
Heo Đất Hades

Je sais que c'est vieux, mais aucune des solutions ci-dessus n'avait tout le nécessaire de positionnement sophistiqué requis pour cette animation "parfaite" sans bug, rétrocompatible et sans scintillement.
Permettez-moi de partager ma solution (en supposant que vous avez configuré 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];
}
1
Martin Ullrich

J'ai modifié certaines des solutions ci-dessus pour en faciliter la compréhension et l'utilisation. J'ai utilisé un IBOutlet pour que plusieurs champs de texte puissent se lier à la fonction avec "L'édition a commencé" de "Evénements envoyés" des champs de texte. ** N'oubliez pas d'avoir une sortie pour votre écran de défilement

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

}
0
pdpAxis

Utilisez l'un de ces éléments,

CGPoint bottomOffset = CGPointMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height);

[self.MainScrollView setContentOffset:bottomOffset animated:YES];

ou

[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];
0
Ramdhas

Dans Swift 1.2+ faites quelque chose comme ceci:

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

}
0
ColossalChris

Je pense qu'il vaut mieux utiliser les notifications du clavier parce que vous ne savez pas si le premier intervenant (le contrôle avec le focus) est un textField ou un textView (ou autre). Il suffit donc de créer une catégorie pour trouver le premier répondant:

#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

puis

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

Désactivez éventuellement le défilement et définissez le délégué sur zéro, puis restaurez-le pour éviter certaines actions lors de l'édition du premier répondeur. Comme l'a dit james_womack, conservez le décalage d'origine pour le restaurer dans une méthode keyboardWillHideNotification.

-(void)keyboardWillHideNotification:(NSNotification*)aNotification{

    contentScrollView.delegate=self;
    contentScrollView.scrollEnabled=YES;
    [contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES];
}
0
Bejil