我的UIViewCOntroller
包含UITextView
。当键盘出现时我按照这样调整大小:
#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似乎重新调整到合适的大小,但是当用户键入光标时,最终会在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);
}
答案 0 :(得分:20)
为什么不给文字视图contentInset
(以及匹配的scrollIndicatorInsets
),而不是调整框架大小?请记住,文本视图实际上是滚动视图。这是处理键盘(或其他)干扰的正确方法。
有关contentInset
的详细信息,请参阅this问题。
这似乎还不够。仍然使用插图,因为这更正确(特别是在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];
}
很多工作,Apple应该提供更好的处理插入符号的方法,但这很有效。
答案 1 :(得分:6)
我试过的所有其他答案对我来说都有点奇怪。使用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];
}
}
}
对我来说就像一个魅力
答案 2 :(得分:4)
已经有很多答案,我发现在我的情况下它实际上要简单得多。在 keyboardWillShow 上,我调整文本视图的contentInset
并保持帧全屏。虽然scrollRangeToVisible:
对我来说不像其他许多人那样,但滚动视图方法(UITextView继承的方法)工作得很好。这对我有用:
- (void)textViewDidChange:(UITextView *)textView
{
CGRect caret = [_textView caretRectForPosition:_textView.selectedTextRange.end];
[_textView scrollRectToVisible:caret animated:YES];
}
答案 3 :(得分:3)
Anders和Leo Natan有很好的解决方案。但是,我需要稍微修改一下它们的答案,以便使用contentInset使滚动正常工作。我遇到的问题是在textViewDidBeginEditing:
之前调用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
}
}
答案 4 :(得分:1)
这就是我最终做的事情,以及似乎有效的事情:
- (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];
}
答案 5 :(得分:1)
此问题的一个更简单的解决方案是更新文本视图框以响应textViewDidBegingEditing委托方法。有关详细信息,请参阅以下内容:
答案 6 :(得分:1)
对于UITextView
内UIScrollView
的人,其中iOS&lt; 7负责将插入符号滚动到视图中:以下是iOS 7(以及5&amp; 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;
}
我试图包含大部分所需的胶水代码。唯一缺少的是设置UITextView的委托并解除键盘。
花了2-3天才弄明白以前的工作。谢谢,Apple。
答案 7 :(得分:1)
Angel Naydenov上面的评论是正确的,尤其是在从英语切换到日语键盘的情况下。
切换键盘时,会调用UIKeyboardWillShowNotification
,但不会调用UIKeyboardWillHideNotification
。
因此,您必须调整插入值以使用绝对值,而不是使用+=
。
无关紧要,第二次显示键盘后,[self.textView setContentOffset:newOffset animated:YES];
实际上不会改变iOS 7.1中的图形,这可能是一个错误。我使用的解决方法是替换
[self.textView setContentOffset:newOffset animated:YES];
与
[UIView animateWithDuration:.25 animations:^{
self.textView.contentOffset = newOffset;
}];
答案 8 :(得分:0)
Leo Natan,你开始时表现不错,但执行效率相对较低。这是使用更少代码执行此操作的更好方法:
// 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];
}