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