UITextView光标在帧更改帧之后

我有一个包含UITextViewUIViewCOntroller 。 当键盘出现时,我调整它像这样:

 #pragma mark - Responding to keyboard events - (void)keyboardDidShow:(NSNotification *)notification { NSDictionary* info = [notification userInfo]; CGRect keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect newTextViewFrame = self.textView.frame; newTextViewFrame.size.height -= keyboardSize.size.height + 70; self.textView.frame = newTextViewFrame; self.textView.backgroundColor = [UIColor yellowColor]; } - (void)keyboardWillHide:(NSNotification *)notification { NSDictionary* info = [notification userInfo]; CGRect keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect newTextViewFrame = self.textView.frame; newTextViewFrame.size.height += keyboardSize.size.height - 70; self.textView.frame = newTextViewFrame; } 

textView似乎rezise到正确的大小,但是当用户input光标结束了“外”textView框架。 见下图:

在这里输入图像说明

黄色区域是UITextView框架(我不知道R键旁边的蓝线是什么)。 我觉得这很有线。 如果这有什么不同,我使用iOS7。

任何想法或提示?

更新

我有一个UITextView的子类,用下面的方法绘制水平线(如果这有所作为):

 - (void)drawRect:(CGRect)rect { //Get the current drawing context CGContextRef context = UIGraphicsGetCurrentContext(); //Set the line color and width CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:229.0/255.0 green:244.0/255.0 blue:255.0/255.0 alpha:1].CGColor); CGContextSetLineWidth(context, 1.0f); //Start a new Path CGContextBeginPath(context); //Find the number of lines in our textView + add a bit more height to draw lines in the empty part of the view NSUInteger numberOfLines = (self.contentSize.height + rect.size.height) / self.font.lineHeight; CGFloat baselineOffset = 6.0f; //iterate over numberOfLines and draw each line for (int x = 0; x < numberOfLines; x++) { //0.5f offset lines up line with pixel boundary CGContextMoveToPoint(context, rect.origin.x, self.font.lineHeight*x + 0.5f + baselineOffset); CGContextAddLineToPoint(context, rect.size.width, self.font.lineHeight*x + 0.5f + baselineOffset); } // Close our Path and Stroke (draw) it CGContextClosePath(context); CGContextStrokePath(context); } 

为什么不给文本视图一个contentInset (和一个匹配的scrollIndicatorInsets )? 请记住,文本视图实际上是滚动视图。 这是处理键盘(或其他)干扰的正确方法。

有关contentInset更多信息,请参阅此问题。


这似乎还不够。 仍然使用插入,因为这是更正确的(特别是在iOS7上,键盘是透明的),但是你也需要额外的插入处理:

 - (void)viewDidLoad { [super viewDidLoad]; [self.textView setDelegate:self]; self.textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil]; } - (void)_keyboardWillShowNotification:(NSNotification*)notification { UIEdgeInsets insets = self.textView.contentInset; insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; self.textView.contentInset = insets; insets = self.textView.scrollIndicatorInsets; insets.bottom += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; self.textView.scrollIndicatorInsets = insets; } - (void)_keyboardWillHideNotification:(NSNotification*)notification { UIEdgeInsets insets = self.textView.contentInset; insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height; self.textView.contentInset = insets; insets = self.textView.scrollIndicatorInsets; insets.bottom -= [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height; self.textView.scrollIndicatorInsets = insets; } - (void)textViewDidBeginEditing:(UITextView *)textView { _oldRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end]; _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES]; } - (void)textViewDidEndEditing:(UITextView *)textView { [_caretVisibilityTimer invalidate]; _caretVisibilityTimer = nil; } - (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:YES]; } } -(void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } 

大量的工作,苹果应该提供更好的处理方式,但这个工作。

所有其他的答案我尝试对我来说有点奇怪。 使用NSTimer执行滚动操作也意味着用户不能向上滚动,因为脱字符随后将在屏幕外停止,并且会立即再次向下滚动。 最后,我坚持改变键盘通知事件的UITextView框架的原始方法,然后添加以下方法:

 - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { // Whenever the user enters text, see if we need to scroll to keep the caret on screen [self scrollCaretToVisible]; return YES; } - (void)scrollCaretToVisible { //This is where the cursor is at. CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end]; // Convert into the correct coordinate system caretRect = [self.view convertRect:caretRect fromView:self.textView]; if(CGRectEqualToRect(caretRect, _oldRect)) { // No change return; } _oldRect = caretRect; //This is the visible rect of the textview. CGRect visibleRect = self.textView.frame; //We will scroll only if the caret falls outside of the visible rect. if (!CGRectContainsRect(visibleRect, caretRect)) { // Work out how much the scroll position would have to change by to make the cursor visible CGFloat diff = (caretRect.origin.y + caretRect.size.height) - (visibleRect.origin.y + visibleRect.size.height); // If diff < 0 then this isn't to do with the iOS7 bug, so ignore if (diff > 0) { // Scroll just enough to bring the cursor back into view CGPoint newOffset = self.textView.contentOffset; newOffset.y += diff; [self.textView setContentOffset:newOffset animated:YES]; } } } 

对我来说就像一个魅力

已经有很多答案,我发现在我的情况下,它实际上更简单。 在keyboardWillShow我调整文本视图的contentInset并保持全屏幕。 虽然scrollRangeToVisible:对于我来说不像其他许多人那样工作,滚动视图方法(从中inheritanceUITextView)工作得很好。 这适用于我:

 - (void)textViewDidChange:(UITextView *)textView { CGRect caret = [_textView caretRectForPosition:_textView.selectedTextRange.end]; [_textView scrollRectToVisible:caret animated:YES]; } 

安德斯和利奥纳丹有很好的解决scheme。 但是,我需要稍微修改他们的答案,以使滚动与contentInset正常工作。 我遇到的问题是,textViewDidBeginEditing keyboardWasShown:keyboardWasShown:之前被调用keyboardWasShown:所以contentInset的变化没有得到反映第一次通过。 这是我做的:

在.h

 @interface NoteDayViewController : UIViewController <UITextViewDelegate> { UIEdgeInsets noteTextViewInsets; UIEdgeInsets noteTextViewScrollIndicatorInsets; CGRect oldRect; NSTimer *caretVisibilityTimer; float noteViewBottomInset; } @property (weak, nonatomic) IBOutlet UITextView *noteTextView; 

在.m

 - (void)registerForKeyboardNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil]; } - (void)keyboardWasShown:(NSNotification*)aNotification { CGFloat kbHeight = // get the keyboard height following your usual method UIEdgeInsets contentInsets = noteTextViewInsets; contentInsets.bottom = kbHeight; noteTextView.contentInset = contentInsets; UIEdgeInsets scrollInsets = noteTextViewScrollIndicatorInsets; scrollInsets.bottom = kbHeight; noteTextView.scrollIndicatorInsets = scrollInsets; [noteTextView setNeedsDisplay]; } - (void)keyboardWillBeHidden:(NSNotification*)aNotification { noteTextView.contentInset = noteTextViewInsets; noteTextView.scrollIndicatorInsets = noteTextViewScrollIndicatorInsets; [noteTextView setNeedsDisplay]; } - (void)textViewDidBeginEditing:(UITextView *)textView { oldRect = [noteTextView caretRectForPosition:noteTextView.selectedTextRange.end]; noteViewBottomInset = noteTextView.contentInset.bottom; caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(scrollCaretToVisible) userInfo:nil repeats:YES]; } - (void)textViewDidEndEditing:(UITextView *)textView { [caretVisibilityTimer invalidate]; caretVisibilityTimer = nil; } - (void)scrollCaretToVisible { // This is where the cursor is at. CGRect caretRect = [noteTextView caretRectForPosition:noteTextView.selectedTextRange.end]; // test if the caret has moved OR the bottom inset has changed if(CGRectEqualToRect(caretRect, oldRect) && noteViewBottomInset == noteTextView.contentInset.bottom) return; // reset these for next time this method is called oldRect = caretRect; noteViewBottomInset = noteTextView.contentInset.bottom; // this is the visible rect of the textview. CGRect visibleRect = noteTextView.bounds; visibleRect.size.height -= (noteTextView.contentInset.top + noteTextView.contentInset.bottom); visibleRect.origin.y = noteTextView.contentOffset.y; // We will scroll only if the caret falls outside of the visible rect. if (!CGRectContainsRect(visibleRect, caretRect)) { CGPoint newOffset = noteTextView.contentOffset; newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height, 0); [noteTextView setContentOffset:newOffset animated:NO]; // must be non-animated to work, not sure why } } 

这是我最终做的,似乎工作的东西:

 - (void)textViewKeyboardWillShow:(NSNotification *)notification { NSDictionary* info = [notification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; // self.textViewBottomSpace.constant = NSLayoutConstraint in IB (bottom position) self.textViewBottomSpace.constant = kbSize.height + 70; [self.textView setNeedsDisplay]; } - (void)textViewKeyboardWillHide:(NSNotification *)notification { self.textViewBottomSpace.constant = 0; [self.textView setNeedsDisplay]; } - (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 + 10, 0); [self.textView setContentOffset:newOffset animated:YES]; } } - (void)textViewDidEndEditing:(UITextView *)textView { [_caretVisibilityTimer invalidate]; _caretVisibilityTimer = nil; } - (void)textViewDidBeginEditing:(UITextView *)textView { self.oldRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end]; self.caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(scrollCaretToVisible) userInfo:nil repeats:YES]; } 

这个问题的一个更简单的解决scheme是更新文本视图框架响应textViewDidBegingEditing委托方法。 有关更多详情,请参阅以下内容:

如何在iOS 7中显示键盘时重新调整UITextView的大小

对于那些在UIScrollView中有一个UITextView的人来说,iOS <7负责将插入符滚动到视图中:下面是iOS 7(以及5和6)的工作原理。

 // This is the scroll view reference @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; // Track the current UITextView @property (weak, nonatomic) UITextView *activeField; - (void)textViewDidBeginEditing:(UITextView *)textView { self.activeField = textView; } - (void)textViewdDidEndEditing:(UITextView *)textView { self.activeField = nil; } // Setup the keyboard observers that take care of the insets & initial scrolling [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil]; - (void)keyboardWasShown:(NSNotification*)aNotification { // Set the insets above the keyboard NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIEdgeInsets insets = self.vForm.contentInset; insets.bottom += kbSize.height; self.vForm.contentInset = insets; insets = self.vForm.scrollIndicatorInsets; insets.bottom += kbSize.height; self.vForm.scrollIndicatorInsets = insets; // Scroll the active text field into view CGRect aRect = self.vForm.frame; aRect.size.height -= kbSize.height; CGPoint scrollPoint = CGPointMake(0.0, self.activeField.frame.origin.y); [self.scrollView setContentOffset:scrollPoint animated:YES]; } - (void)keyboardWillBeHidden:(NSNotification*)aNotification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; self.vForm.contentInset = contentInsets; self.vForm.scrollIndicatorInsets = contentInsets; } // This is where the magic happens. Set the class with this method as the UITextView's delegate. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { // Scroll the textview to the caret position [textView scrollRangeToVisible:textView.selectedRange]; // Scroll the scrollview to the caret position within the textview CGRect targetRect = [textView caretRectForPosition:textView.selectedTextRange.end]; targetRect.origin.y += self.activeField.frame.origin.y; [self.scrollView scrollRectToVisible:targetRect animated:YES]; return YES; } 

我试图包括大部分所需的胶水代码。 唯一缺less的事情是设置U​​ITextView的委托和解散键盘。

花了2-3天来弄清楚以前的工作。 谢谢,苹果。

上面的Angel Naydenov的评论是正确的,特别是在从英文切换到显示暗示的日文键盘的情况下。

切换键盘时,调用UIKeyboardWillHideNotification ,但不调用UIKeyboardWillHideNotification

所以你必须调整插入使用绝对值,而不是使用+=

无关, [self.textView setContentOffset:newOffset animated:YES]; 在第二次显示键盘后,实际上不会改变iOS 7.1中的graphics,这可能是一个错误。 我使用的解决方法是取代

 [self.textView setContentOffset:newOffset animated:YES]; 

 [UIView animateWithDuration:.25 animations:^{ self.textView.contentOffset = newOffset; }]; 

利奥·纳坦,你起步很好,但你的执行效率相对较低。 这是用较less的代码来做的更好的方法:

 // Add Keyboard Notification Listeners in ViewDidLoad [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil]; // And Add The Following Methods - (void)_keyboardWillShowNotification:(NSNotification*)notification { CGRect textViewFrame = self.textView.frame; textViewFrame.size.height -= ([notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height + 4.0); self.textView.frame = textViewFrame; } - (void)_keyboardWillHideNotification:(NSNotification*)notification { CGRect textViewFrame = self.textView.frame; textViewFrame.size.height += ([notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height + 4.0); self.textView.frame = textViewFrame; } - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { NSRange typingRange = NSMakeRange(textView.text.length - 1, 1); [textView scrollRangeToVisible:typingRange]; return YES; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }