web-dev-qa-db-fra.com

Désactiver le défilement UIScrollView lorsque UITextField devient le premier répondeur

Quand un UITextField, incorporé dans un UIScrollView devient premier répondant, disons que l'utilisateur qui tape un caractère, le UIScrollView défile automatiquement vers ce champ, existe-t-il un moyen de le désactiver?

Duplicate rdar: // 16538222 over

58
Rabih

S'appuyant sur la réponse de Moshe ...

Sous-classe UIScrollView et substituer la méthode suivante:

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated

Laissez le vide. Travail accompli!

59
Luke

Je suis aux prises avec le même problème et j'ai enfin trouvé une solution.

J'ai étudié comment le défilement automatique est effectué en suivant la trace des appels et découvert qu'un [UIFieldEditor scrollSelectionToVisible] interne est appelé lorsqu'une lettre est saisie dans la variable UITextField. Cette méthode semble agir sur la UIScrollView de l'ancêtre le plus proche de la UITextField.

Ainsi, sur textFieldDidBeginEditing, en encapsulant la UITextField avec une nouvelle UIScrollView de même taille (c'est-à-dire, l'insertion de la vue entre la UITextField et sa superview), cela bloquera le défilement automatique. Enfin, supprimez cette enveloppe sur textFieldDidEndEditing.

Le code va comme:

- (void)textFieldDidBeginEditing:(UITextField*)textField {  
    UIScrollView *wrap = [[[UIScrollView alloc] initWithFrame:textField.frame] autorelease];  
    [textField.superview addSubview:wrap];  
    [textField setFrame:CGRectMake(0, 0, textField.frame.size.width, textField.frame.size.height)]; 
    [wrap addSubview: textField];  
}  

- (void)textFieldDidEndEditing:(UITextField*)textField {  
  UIScrollView *wrap = (UIScrollView *)textField.superview;  
  [textField setFrame:CGRectMake(wrap.frame.Origin.x, wrap.frame.Origin.y, wrap.frame.size.width, textField.frame.size.height)];
  [wrap.superview addSubview:textField];  
  [wrap removeFromSuperview];  
}  

j'espère que cela t'aides!

53
Taketo Sano

J'ai eu le même problème avec la désactivation du défilement automatique d'une UITextView étant une cellule de UITableView. J'ai pu résoudre le problème en utilisant l'approche suivante:

@interface MyTableViewController : UITableViewController<UITextViewDelegate>

@implementation MyTableViewController {
    BOOL preventScrolling;
    // ...
}

// ... set self as the delegate of the text view

- (void)textViewDidBeginEditing:(UITextView *)textView {
    preventScrolling = YES;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (preventScrolling) {
        [self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:NO];
    }
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    preventScrolling = NO;
}

Définir scrollViewWillBeginDragging est utilisé pour restaurer le comportement de défilement par défaut, lorsque l'utilisateur lance lui-même le défilement.

7
tonso

Comme Taketo l'a mentionné, lorsqu'un UITextField est créé en tant que premier répondant, sa première vue parent de type UIScrollView (s'il en existe un) fait défiler l'écran pour rendre visible cette UITextField. Le plus simple consiste à envelopper chaque UITextField dans une UIScrollView (ou, idéalement, dans un seul dummy UIScrollView). Ceci est très similaire à la solution de Taketo, mais cela devrait vous donner des performances légèrement meilleures, et cela gardera votre code (ou votre interface dans Interface Builder) beaucoup plus propre à mon avis.

5
danbretl

Il ressemble à UIScrollview qui contient UITextfield, ajuste automatiquement son contenu de décalage; quand textfield va devenir le premier intervenant . Cela peut être résolu en ajoutant textfield dans scrollview de même taille, puis en ajoutant à la vue de défilement principale. au lieu d'ajouter directement à scrollview principal

    // Swift

    let rect = CGRect(x: 0, y: 0, width: 200, height: 50)

    let txtfld = UITextField()
    txtfld.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)

    let txtFieldContainerScrollView = UIScrollView()
    txtFieldContainerScrollView.frame = rect
    txtFieldContainerScrollView.addSubview(txtfld)
    // Now add this txtFieldContainerScrollView in desired UITableViewCell, UISCrollView.. etc
    self.mainScrollView.addSubview(txtFieldContainerScrollView)

    // Am33T
2
Amit Tandel

En vous appuyant sur la réponse de Luke, pour résoudre le problème suivant: sa solution désactive complètement le défilement automatique, vous pouvez le désactiver sélectivement comme suit:

//  TextFieldScrollView
#import <UIKit/UIKit.h>

@interface TextFieldScrollView : UIScrollView

@property (assign, nonatomic) IBInspectable BOOL preventAutoScroll;

@end

@implementation TextFieldScrollView

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
    if (self.preventAutoScroll == NO) {
        [super scrollRectToVisible:rect animated:animated];
    }
}

@end

De cette façon, vous pouvez configurer complètement Interface Builder pour désactiver le défilement automatique, tout en ayant le contrôle total à tout moment pour le réactiver (bien que la raison de votre choix me dépasse).

2
mbm29414

C'est comme ça que je le fais:

C’est très simple, vous devez renvoyer votre propre contentOffset pour n’importe quel scrollRectToVisible. 

De cette façon, vous ne nuisez pas au comportement normal ni à la fluidité des choses - vous fournissez simplement les mêmes fonctionnalités dans le même canal, avec vos propres améliorations.

#import <UIKit/UIKit.h>

@protocol ExtendedScrollViewDelegate <NSObject>

- (CGPoint)scrollView:(UIScrollView*)scrollView offsetForScrollingToVisibleRect:(CGRect)rect;

@end

@interface ExtendedScrollView : UIScrollView

@property (nonatomic, unsafe_unretained) id<ExtendedScrollViewDelegate> scrollToVisibleDelegate;

@end

#import "ExtendedScrollView.h"

@implementation ExtendedScrollView

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
    if (_scrollToVisibleDelegate && [_scrollToVisibleDelegate respondsToSelector:@selector(scrollView:offsetForScrollingToVisibleRect:)])
    {
        [self setContentOffset:[_scrollToVisibleDelegate scrollView:self offsetForScrollingToVisibleRect:rect] animated:animated];
    }
    else
    {
        [super scrollRectToVisible:rect animated:animated];
    }
}

@end
1
daniel.gindi

J'ai une vue de collection avec un champ de texte tout en haut, imitant le UITableView.tableHeaderView. Ce champ de texte est situé dans l'espace de décalage de contenu négatif afin qu'il n'interfère pas avec le reste de la vue de collection. En gros, je détecte si l'utilisateur effectue le défilement dans la vue de défilement et si le champ de texte est le premier répondeur et si la vue de défilement défile au-delà du haut de son contenu. Ce code exact n’aidera pas nécessairement tout le monde, mais ils pourront le manipuler pour s’adapter à leur cas.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    // This is solving the issue where making the text field first responder
    // automatically scrolls the scrollview down by the height of the search bar.
    if (!scrollView.isDragging && !scrollView.isDecelerating &&
        self.searchField.isFirstResponder &&
        (scrollView.contentOffset.y < -scrollView.contentInset.top)) {

        [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
    }
}
0
TPoschel

J'ai essayé la réponse de @ TaketoSano, mais cela ne semble pas fonctionner. Mon cas est que je n'ai pas de vue de défilement, mais seulement une vue avec plusieurs champs de texte.

Et finalement, j'ai une solution de contournement. Il y a deux noms de notification par défaut pour le clavier dont j'ai besoin: 

  • UIKeyboardDidShowNotification quand le clavier a montré; 
  • UIKeyboardWillHideNotification quand le clavier va se cacher.

Voici l'exemple de code que j'ai utilisé:

- (void)viewDidLoad {
  [super viewDidLoad];

  ...

  NSNotificationCenter * notificationCetner = [NSNotificationCenter defaultCenter];
  [notificationCetner addObserver:self
                         selector:@selector(_keyboardWasShown:)
                             name:UIKeyboardDidShowNotification
                           object:nil];
  [notificationCetner addObserver:self
                         selector:@selector(_keyboardWillHide:)
                             name:UIKeyboardWillHideNotification
                           object:nil];
}

- (void)_keyboardWasShown:(NSNotification *)note {
  [self.view setFrame:(CGRect){{272.f, 55.f}, {480.f, 315.f}}];
}

- (void)_keyboardWillHide:(NSNotification *)note {
  [self.view setFrame:(CGRect){{272.f, 226.5f}, {480.f, 315.f}}];
}

Ici, le (CGRect){{272.f, 226.5f}, {480.f, 315.f}} est le cadre par défaut de la vue lorsque le clavier est masqué. Et (CGRect){{272.f, 55.f}, {480.f, 315.f}} est le cadre de la vue lorsque le clavier est affiché.

Et b.t.w., le changement d'image de la vue sera automatiquement appliqué à l'animation, c'est vraiment parfait!

0
Kjuly

Je ne connais aucune propriété de UIScrollView qui le permettrait. Ce serait une mauvaise expérience utilisateur de pouvoir désactiver cela, à mon humble avis.

Cela dit, il peut être possible de sous-classer UIScrollView et de remplacer certaines de ses méthodes pour vérifier que la UITextfield n'est pas un premier intervenant avant le défilement.

0
Moshe