web-dev-qa-db-fra.com

Comment redimensionner UITextView lorsque le clavier est affiché avec iOS 7

J'ai un contrôleur de vue qui contient une UITextView en plein écran. Lorsque le clavier est affiché, je souhaite redimensionner l’affichage du texte afin qu’il ne soit pas caché sous le clavier.

C'est une approche assez standard avec iOS, comme décrit dans cette question:

Comment redimensionner UITextView sur iOS lorsqu'un clavier apparaît?

Cependant, avec iOS 7, si l'utilisateur appuie sur la vue de texte dans la moitié inférieure de l'écran, lorsque le texte redimensionne, le curseur reste en dehors de l'écran. La vue du texte défile uniquement pour afficher le curseur lorsque l'utilisateur appuie sur Entrée.

13
ColinE

Bien que la réponse donnée par @Divya m’ait conduit à la solution correcte (j’ai donc attribué la prime), ce n’est pas une réponse très claire! Ici c'est en détail:

L'approche standard pour s'assurer qu'une vue de texte n'est pas masquée par le clavier à l'écran consiste à mettre à jour son cadre lorsque le clavier est affiché, comme détaillé dans cette question:

Comment redimensionner UITextView sur iOS lorsqu'un clavier apparaît?

Cependant, avec iOS 7, si vous modifiez le cadre d'affichage du texte dans votre gestionnaire pour la notification UIKeyboardWillShowNotification, le curseur restera hors de l'écran, comme décrit dans cette question.

La solution à ce problème consiste à modifier le cadre de la vue texte en réponse à la méthode textViewDidBeginEditing delegate:

@implementation ViewController {
    CGSize _keyboardSize;
    UITextView* textView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    textView = [[UITextView alloc] initWithFrame:CGRectInset(self.view.bounds, 20.0, 20.0)];    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];


    NSMutableString *textString = [NSMutableString new];
    for (int i=0; i<100; i++) {
        [textString appendString:@"cheese\rpizza\rchips\r"];
    }
    textView.text = textString;

}

- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textViewFrame.size.height -= 216;
    textView.frame = textViewFrame;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
    textView.frame = textViewFrame;
    [textView endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}

@end

REMARQUE: malheureusement textViewDidBeginEdting se déclenche avant la notification UIKeyboardWillShowNotification, d'où la nécessité de coder en dur la hauteur du clavier.

3
ColinE

J'ai lu les docs qui parlent de ce sujet même . Je l'ai traduit en Swift et cela a fonctionné à merveille pour moi. 

Ceci est utilisé pour une page entière UITextView comme iMessage.

J'utilise iOS 8.2 et Swift sur XCode 6.2 et voici mon code. Appelez simplement cette setupKeyboardNotifications à partir de votre viewDidLoad ou d’une autre méthode d’initialisation. 

func setupKeyboardNotifications() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
    let kbSize = infoNSValue.CGRectValue().size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsetsZero
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

De même, si vous rencontrez des problèmes avec le curseur se trouvant au bon endroit lors de la rotation, vérifiez le changement d'orientation et faites défiler jusqu'à la bonne position. 

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
    scrollToCaretInTextView(codeTextView, animated: true)
}

func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
    var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
    rect.size.height += textView.textContainerInset.bottom
    textView.scrollRectToVisible(rect, animated: animated)
}

Swift 3:

func configureKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.cgRectValue.size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}
23
Johnston

Avec la mise en page automatique, il est beaucoup plus facile (à condition de bien comprendre la mise en page automatique) de gérer:

Au lieu d'essayer d'identifier et de redimensionner les vues affectées, vous créez simplement un cadre parent pour tout le contenu de votre vue. Ensuite, si kbd apparaît, vous redimensionnez le cadre et si vous avez correctement configuré les contraintes, la vue réorganisera correctement toutes ses vues enfants. Pas besoin de tripoter beaucoup de code difficile à lire pour cela.

En fait, dans une question similaire j'ai trouvé un lien vers ceci excellent tutoriel à propos de cette technique.

En outre, les autres exemples ici qui utilisent textViewDidBeginEditing au lieu de UIKeyboardWillShowNotification ont un gros problème:

Si l'utilisateur est connecté à un clavier Bluetooth externe, le contrôle reste poussé vers le haut, même si aucun clavier à l'écran ne s'affiche. Ce n'est pas bien.

Donc, pour résumer:

  1. Utiliser la mise en page automatique
  2. Utilisez la notification UIKeyboardWillShowNotification, Et non les événements de TextEditField pour décider quand redimensionner vos vues .

Vous pouvez également consulter la réponse de LeoNatan. Cela pourrait même être une solution plus simple et plus propre (je n'ai pas encore essayé moi-même).

4
Thomas Tempelmann

Ne redimensionnez pas la vue texte. Réglez plutôt contentInset et scrollIndicatorInsets bottom à la hauteur du clavier.

Voir ma réponse ici: https://stackoverflow.com/a/18585788/983912


Modifier

J'ai apporté les modifications suivantes à votre exemple de projet:

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)_scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.Origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.Origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);

        [self.textView setContentOffset:newOffset animated:NO];
    }
}

Retrait de l’ancienne position du curseur, ainsi que de l’animation désactivée. Semble maintenant bien fonctionner.

3
Leo Natan

Suivre travaille pour moi: 

Fichier .h

@interface ViewController : UIViewController <UITextViewDelegate> {

    UITextView *textView ;

}

@property(nonatomic,strong)IBOutlet UITextView *textView;

@end

fichier .m  

@implementation ViewController
@synthesize textView;
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
    //UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame];
    textView.frame = textViewFrame;
    textView.delegate = self;
    textView.returnKeyType = UIReturnKeyDone;
    textView.backgroundColor = [UIColor greenColor];
    textView.textColor = [UIColor blackColor];
    [self.view addSubview:textView];

}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
    NSLog(@"textViewShouldBeginEditing:");
    return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
    NSLog(@"textViewDidBeginEditing:");
   CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 224.0f);

    textView1.frame = textViewFrame;

}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
    NSLog(@"textViewShouldEndEditing:");
       return YES;
}
- (void)textViewDidEndEditing:(UITextView *)textView{
    NSLog(@"textViewDidEndEditing:");
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
       return YES;
}

- (void)textViewDidChange:(UITextView *)textView{
    NSLog(@"textViewDidChange:");
}

- (void)textViewDidChangeSelection:(UITextView *)textView{
    NSLog(@"textViewDidChangeSelection:");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan:withEvent:");
    CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);

    textView.frame = textViewFrame;
    [self.view endEditing:YES];
    [super touchesBegan:touches withEvent:event];
}
@end
2
Divya Bhalodiya

@ Johnston a trouvé une bonne solution. Voici une variante utilisant UIKeyboardWillChangeFrameNotification qui rend correctement compte des changements de taille du clavier (c'est-à-dire afficher/masquer la barre QuickType). Il gère également correctement le cas où la vue texte est incorporée dans un contrôleur de navigation (c'est-à-dire où la variable contentInset n'est pas égale à zéro sinon). C'est aussi écrit dans Swift 2.

override func viewDidLoad() {
    :

    NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil) { (notification) -> Void in
        guard let userInfo = notification.userInfo,
            let keyboardFrameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
            else { return }

        let windowCoordinatesKeyboardFrameEnd = keyboardFrameEndValue.CGRectValue() // window coordinates
        let keyboardFrameEnd = self.view.convertRect(windowCoordinatesKeyboardFrameEnd, fromView: nil) // view coordinates

        var inset = self.textView.contentInset
        inset.bottom = CGRectGetMaxY(self.textView.frame) - CGRectGetMinY(keyboardFrameEnd) // bottom inset is the bottom of textView minus top of keyboard
        self.textView.contentInset = inset
        self.textView.scrollIndicatorInsets = inset
    }
}
0
jrc

Voici ma solution, juillet 2015, utilisant Swift 1.2 sur Xcode 6.4 et ciblant iOS 7.1 - une combinaison de plusieurs approches. Emprunté Johnston's clavier transmettant le code Swift. C'est un peu un bidouillage, mais c'est simple et ça marche.

J'ai un Vanext UITextView dans une seule vue.

Je ne voulais pas l'intégrer dans un UIScrollView selon la documentation d'Apple . Je voulais juste que UITextView soit redimensionné lorsque le clavier du logiciel est apparu et redimensionné à l'original lorsque le clavier a été désactivé.

Ce sont les étapes de base:

  1. Configurer les notifications au clavier
  2. Configurer la contrainte de présentation dans "Interface Builder" (TextView au bord inférieur dans mon cas)
  3. Créez un IBOutlet pour cette contrainte dans le fichier de code approprié afin de pouvoir l'ajuster par programme
  4. Utiliser les notifications du clavier pour intercepter les événements et obtenir la taille du clavier
  5. Ajustez par programme la contrainte IBOutlet en utilisant la taille du clavier pour redimensionner TextView.
  6. Remettez tout en place lorsque le clavier est désactivé.

Alors, sur le code.

J'ai configuré la sortie de contrainte en haut du fichier de code via le glisser-déposer habituel dans le générateur d'interface: @IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!

J'ai également mis en place une variable globale dans laquelle je peux sauvegarder l'état des choses avant que le clavier n'arrive: var myUITextViewBottomConstraintBackup: CGFloat = 0

Implémentez les notifications au clavier, appelez cette fonction dans viewDidLoad ou dans toute autre section de démarrage/configuration:

func setupKeyboardNotifications() {

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)

    }

Ensuite, ces deux fonctions seront appelées automatiquement lorsque le clavier est affiché/désactivé:

func keyboardWasShown(aNotification:NSNotification) {

    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.CGRectValue().size

    let newHeight = kbSize.height

    //backup old constraint size
    myUITextViewBottomConstraintOld = myUITextViewBottomConstraint.constant 

    // I subtract 50 because otherwise it leaves a gap between keyboard and text view. I'm sure this could be improved on.
    myUITextViewBottomConstraint.constant = newHeight - 50 

func keyboardWillBeHidden(aNotification:NSNotification) {
    //restore to whatever AutoLayout set it before you messed with it
    myUITextViewBottomConstraint.constant = myUITextViewBottomConstraintOld 

}

Le code fonctionne, avec un problème mineur:

  • Il ne réagit pas au ruban de texte prédictif situé au-dessus de l'ouverture/de la fermeture du clavier. C'est à dire. il en tiendra compte lorsque le clavier sera appelé, mais si vous le glissiez vers le haut ou le bas lorsque le clavier est affiché, la contrainte ne sera pas ajustée. C'est un événement séparé qui doit être géré. Ce n'est pas assez d'une fonctionnalité pour que je m'ennuie avec.
0
barko

je l'avais fait et son travail complètement.

  #define k_KEYBOARD_OFFSET 95.0

-(void)keyboardWillAppear {
    // Move current view up / down with Animation
    if (self.view.frame.Origin.y >= 0)
    {
        [self moveViewUp:NO];
    }
    else if (self.view.frame.Origin.y < 0)
    {
        [self moveViewUp:YES];
    }
}

-(void)keyboardWillDisappear {
    if (self.view.frame.Origin.y >= 0)
    {
        [self moveViewUp:YES];
    }
    else if (self.view.frame.Origin.y < 0)
    {
        [self moveViewUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    //if ([sender isEqual:_txtPassword])
   // {
        //move the main view up, so the keyboard will not hide it.
        if  (self.view.frame.Origin.y >= 0)
        {
            [self moveViewUp:YES];
        }
    //}
}

//Custom method to move the view up/down whenever the keyboard is appeared / disappeared
-(void)moveViewUp:(BOOL)bMovedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.4]; // to slide the view up

    CGRect rect = self.view.frame;
    if (bMovedUp) {
        // 1. move the Origin of view up so that the text field will come above the keyboard
        rect.Origin.y -= k_KEYBOARD_OFFSET;

        // 2. increase the height of the view to cover up the area behind the keyboard
        rect.size.height += k_KEYBOARD_OFFSET;
    } else {
        // revert to normal state of the view.
        rect.Origin.y += k_KEYBOARD_OFFSET;
        rect.size.height -= k_KEYBOARD_OFFSET;
    }

    self.view.frame = rect;

    [UIView commitAnimations];
}

- (void)viewWillAppear:(BOOL)animated
{
    // register keyboard notifications to appear / disappear the keyboard
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillAppear)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillDisappear)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    // unregister for keyboard notifications while moving to the other screen.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];
}
0
Patel Jigar