objective-c 如何以编程方式将 UIScrollView 移动到键盘上方的控件中?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/484855/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How programmatically move a UIScrollView to focus in a control above keyboard?
提问by mamcx
I have 6 UITextFieldson my UIScrollView. Now, I can scroll by user request. But when the keyboard appear, some textfields are hidden.
我有6个UITextFields在我的UIScrollView。现在,我可以按用户请求滚动。但是当键盘出现时,一些文本字段被隐藏了。
That is not user-friendly.
这不是用户友好的。
How scroll programmatically the view so I get sure the keyboard not hide the textfield?
如何以编程方式滚动视图,以便确保键盘不会隐藏文本字段?
采纳答案by mamcx
Finally, a simple fix:
最后,一个简单的修复:
UIScrollView* v = (UIScrollView*) self.view ;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:v];
rc.origin.x = 0 ;
rc.origin.y -= 60 ;
rc.size.height = 400;
[self.scroll scrollRectToVisible:rc animated:YES];
Now I think is only combine this with the link above and is set!
现在我认为只有将其与上面的链接结合起来才能设置!
回答by james_womack
Here's what worked for me. Having an instance variable that holds the value of the UIScrollView's offset before the view is adjusted for the keyboardso you can restore the previous state after the UITextField returns:
这对我有用。在针对键盘调整视图之前,拥有一个保存 UIScrollView 偏移值的实例变量,以便您可以在 UITextField 返回后恢复先前的状态:
//header
@interface TheViewController : UIViewController <UITextFieldDelegate> {
CGPoint svos;
}
//implementation
- (void)textFieldDidBeginEditing:(UITextField *)textField {
svos = scrollView.contentOffset;
CGPoint pt;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:scrollView];
pt = rc.origin;
pt.x = 0;
pt.y -= 60;
[scrollView setContentOffset:pt animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[scrollView setContentOffset:svos animated:YES];
[textField resignFirstResponder];
return YES;
}
回答by Michael Tyson
I've put together a universal, drop-in UIScrollView and UITableView subclass that takes care of moving all text fields within it out of the way of the keyboard.
我已经组合了一个通用的、嵌入式 UIScrollView 和 UITableView 子类,负责将其中的所有文本字段移出键盘。
When the keyboard is about to appear, the subclass will find the subview that's about to be edited, and adjust its frame and content offset to make sure that view is visible, with an animation to match the keyboard pop-up. When the keyboard disappears, it restores its prior size.
当键盘即将出现时,子类会找到将要编辑的子视图,并调整其框架和内容偏移以确保该视图可见,并带有动画以匹配键盘弹出。当键盘消失时,它会恢复原来的大小。
It should work with basically any setup, either a UITableView-based interface, or one consisting of views placed manually.
它基本上适用于任何设置,无论是基于 UITableView 的界面,还是由手动放置的视图组成的界面。
Hereit is.
在这里。
(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.)
Editor's note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
回答by Matt Gallagher
If you set the delegateof your text fields to a controller object in your program, you can have that object implement the textFieldDidBeginEditing:and textFieldShouldReturn:methods. The first method can then be used to scroll to your text field and the second method can be used to scroll back.
如果delegate将文本字段的设置为程序中的控制器对象,则可以让该对象实现textFieldDidBeginEditing:和textFieldShouldReturn:方法。第一种方法可用于滚动到您的文本字段,第二种方法可用于向后滚动。
You can find code I have used for this in my blog: Sliding UITextViews around to avoid the keyboard. I didn't test this code for text views in a UIScrollViewbut it should work.
您可以在我的博客中找到我为此使用的代码:Sliding UITextViews around to avoid the keyboard。我没有针对 a 中的文本视图测试此代码,UIScrollView但它应该可以工作。
回答by NRV
simple and best
简单和最好
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
// self.scrlViewUI.contentOffset = CGPointMake(0, textField.frame.origin.y);
[_scrlViewUI setContentOffset:CGPointMake(0,textField.center.y-90) animated:YES];
tes=YES;
[self viewDidLayoutSubviews];
}
回答by Matthias Nott
The answers posted so far didn't work for me as I've a quite deep nested structure of UIViews. Also, the I had the problem that some of those answers were working only on certain device orientations.
到目前为止发布的答案对我不起作用,因为我有一个非常深的 UIView 嵌套结构。此外,我遇到的问题是,其中一些答案仅适用于某些设备方向。
Here's my solution, which will hopefully make you waste some less time on this.
这是我的解决方案,希望可以让您在这方面浪费更少的时间。
My UIViewTextView derives from UIView, is a UITextView delegate and adds a UITextView after having read some parameters from an XML file for that UITextView (that XML part is left out here for clarity).
我的 UIViewTextView 派生自 UIView,是一个 UITextView 委托,并在从该 UITextView 的 XML 文件中读取了一些参数后添加了一个 UITextView(为了清楚起见,此处省略了该 XML 部分)。
Here's the private interface definition:
这是私有接口定义:
#import "UIViewTextView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <CoreGraphics/CGColor.h>
@interface UIViewTextView (/**/) {
@private
UITextView *tf;
/*
* Current content scroll view
* position and frame
*/
CGFloat currentScrollViewPosition;
CGFloat currentScrollViewHeight;
CGFloat kbHeight;
CGFloat kbTop;
/*
* contentScrollView is the UIScrollView
* that contains ourselves.
*/
UIScrollView contentScrollView;
}
@end
In the init method I have to register the event handlers:
在 init 方法中,我必须注册事件处理程序:
@implementation UIViewTextView
- (id) initWithScrollView:(UIScrollView*)scrollView {
self = [super init];
if (self) {
contentScrollView = scrollView;
// ...
tf = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 241, 31)];
// ... configure tf and fetch data for it ...
tf.delegate = self;
// ...
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(keyboardWasShown:) name: UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:@selector(keyboardWasHidden:) name: UIKeyboardWillHideNotification object:nil];
[self addSubview:tf];
}
return(self);
}
Once that's done, we need to handle the keyboard show event. This gets called before the textViewBeginEditing is called, so we can use it to find out some properties of the keyboard. In essence, we want to know the height of the keyboard. This, unfortunately, needs to be taken from its width property in landscape mode:
完成后,我们需要处理键盘显示事件。这在调用 textViewBeginEditing 之前被调用,因此我们可以使用它来找出键盘的一些属性。本质上,我们想知道键盘的高度。不幸的是,这需要从横向模式下的宽度属性中获取:
-(void)keyboardWasShown:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGSize kbSize = kbRect.size;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat sWidth = screenRect.size.width;
CGFloat sHeight = screenRect.size.height;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if ((orientation == UIDeviceOrientationPortrait)
||(orientation == UIDeviceOrientationPortraitUpsideDown)) {
kbHeight = kbSize.height;
kbTop = sHeight - kbHeight;
} else {
//Note that the keyboard size is not oriented
//so use width property instead
kbHeight = kbSize.width;
kbTop = sWidth - kbHeight;
}
Next, we need to actually scroll around when we start editing. We do this here:
接下来,我们需要在开始编辑时实际滚动。我们在这里这样做:
- (void) textViewDidBeginEditing:(UITextView *)textView {
/*
* Memorize the current scroll position
*/
currentScrollViewPosition = contentScrollView.contentOffset.y;
/*
* Memorize the current scroll view height
*/
currentScrollViewHeight = contentScrollView.frame.size.height;
// My top position
CGFloat myTop = [self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow.rootViewController.view].y;
// My height
CGFloat myHeight = self.frame.size.height;
// My bottom
CGFloat myBottom = myTop + myHeight;
// Eventual overlap
CGFloat overlap = myBottom - kbTop;
/*
* If there's no overlap, there's nothing to do.
*/
if (overlap < 0) {
return;
}
/*
* Calculate the new height
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight + overlap);
/*
* Set the new height
*/
[contentScrollView setFrame:nrect];
/*
* Set the new scroll position
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = contentScrollView.contentOffset.y + overlap;
[contentScrollView setContentOffset:npos animated:NO];
}
When we end editing, we do this to reset the scroll position:
当我们结束编辑时,我们这样做是为了重置滚动位置:
- (void) textViewDidEndEditing:(UITextView *)textView {
/*
* Reset the scroll view position
*/
CGRect crect = contentScrollView.frame;
CGRect nrect = CGRectMake(crect.origin.x, crect.origin.y, crect.size.width, currentScrollViewHeight);
[contentScrollView setFrame:nrect];
/*
* Reset the scroll view height
*/
CGPoint npos;
npos.x = contentScrollView.contentOffset.x;
npos.y = currentScrollViewPosition;
[contentScrollView setContentOffset:npos animated:YES];
[tf resignFirstResponder];
// ... do something with your data ...
}
There's nothing left to do in the keyboard was hidden event handler; we leave it in anyway:
键盘上没有什么可做的了隐藏事件处理程序;无论如何,我们将其保留:
-(void)keyboardWasHidden:(NSNotification*)aNotification {
}
And that's it.
就是这样。
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
@end
回答by Martin Ullrich
I know this is old, but still none of the solutions above had all the fancy positioning stuff required for that "perfect" bug-free, backwards compatible and flicker-free animation.
Let me share my solution (assuming you have set up UIKeyboardWill(Show|Hide)Notification):
我知道这是旧的,但仍然没有上述解决方案具有“完美”无错误、向后兼容和无闪烁动画所需的所有花哨定位内容。
让我分享我的解决方案(假设您已设置UIKeyboardWill(Show|Hide)Notification):
// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
// if we have no view or are not visible in any window, we don't care
if (!self.isViewLoaded || !self.view.window) {
return;
}
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardFrameInWindow;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];
// the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];
CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);
// this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_scrollView.contentInset = newContentInsets;
_scrollView.scrollIndicatorInsets = newContentInsets;
/*
* Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
* that should be visible, e.g. a purchase button below an amount text field
* it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
*/
if (_focusedControl) {
CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.
CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;
// this is the visible part of the scroll view that is not hidden by the keyboard
CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;
if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
// scroll up until the control is in place
CGPoint newContentOffset = _scrollView.contentOffset;
newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);
// make sure we don't set an impossible offset caused by the "nice visual offset"
// if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);
[_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
} else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
// if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
CGPoint newContentOffset = _scrollView.contentOffset;
newContentOffset.y = controlFrameInScrollView.origin.y;
[_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
}
}
[UIView commitAnimations];
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
// if we have no view or are not visible in any window, we don't care
if (!self.isViewLoaded || !self.view.window) {
return;
}
NSDictionary *userInfo = notification.userInfo;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
// undo all that keyboardWillShow-magic
// the scroll view will adjust its contentOffset apropriately
_scrollView.contentInset = UIEdgeInsetsZero;
_scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
[UIView commitAnimations];
}
回答by hightech
You may check it out: https://github.com/michaeltyson/TPKeyboardAvoiding(I used that sample for my apps). It is working so well. I hope that helps you.
您可以查看:https: //github.com/michaeltyson/TPKeyboardAvoiding(我在我的应用程序中使用了该示例)。它工作得很好。我希望这对你有帮助。
Actually, here's a full tutorial on using TPKeyboardAvoiding, which may help someone
实际上,这是关于使用 TPKeyboardAvoiding 的完整教程,这可能对某人有所帮助
(1) download the zip filefrom the github link. add these fourfiles to your Xcode project:
(1)从github链接下载zip文件。将这四个文件添加到您的 Xcode 项目中:


(2) build your beautiful form in IB. add a UIScrollView. sit the form items INSIDE the scroll view. (Note - extremely useful tipregarding interface builder: https://stackoverflow.com/a/16952902/294884)
(2) 在IB中建立你美丽的表格。添加一个 UIScrollView。 将表单项放在滚动视图中。(注意 -关于界面构建器的非常有用的提示:https: //stackoverflow.com/a/16952902/294884)


(3) click on the scroll view. then at the top right, third button, you'll see the word "UIScrollView". using copy and paste, change it to"TPKeyboardAvoidingScrollView"
(3) 点击滚动视图。然后在右上角,第三个按钮,你会看到“UIScrollView”这个词。使用复制和粘贴,将其更改为“TPKeyboardAvoidingScrollView”


(4) that's it. put the app in the app store, and bill your client.
(4) 就是这样。将应用程序放入应用程序商店,然后向您的客户收费。
(Also, just click on the Inspector tab of the scroll view. You may prefer to turn on or off bouncing and the scroll bars - your preference.)
(此外,只需单击滚动视图的“检查器”选项卡。您可能更喜欢打开或关闭弹跳和滚动条 - 您的偏好。)
Personal comment - I strongly recommend using scroll view (or collection view) for input forms, in almost all cases. do not use a table view.it's problematic for many reasons. and quite simply, it's incredibly easier to use a scroll view. just lay it out any way you want. it is 100% wysiwyg in interface builder. hope it helps
个人评论 - 我强烈建议在几乎所有情况下对输入表单使用滚动视图(或集合视图)。不要使用表格视图。出于多种原因,这是有问题的。很简单,使用滚动视图非常容易。随心所欲地布置它。它在界面构建器中是 100% 所见即所得。希望能帮助到你
回答by Heo ??t Hades
This is my code, hope it will help you. It work ok in case you have many textfield
这是我的代码,希望对您有所帮助。如果您有很多文本字段,它可以正常工作
CGPoint contentOffset;
bool isScroll;
- (void)textFieldDidBeginEditing:(UITextField *)textField {
contentOffset = self.myScroll.contentOffset;
CGPoint newOffset;
newOffset.x = contentOffset.x;
newOffset.y = contentOffset.y;
//check push return in keyboar
if(!isScroll){
//180 is height of keyboar
newOffset.y += 180;
isScroll=YES;
}
[self.myScroll setContentOffset:newOffset animated:YES];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
//reset offset of content
isScroll = NO;
[self.myScroll setContentOffset:contentOffset animated:YES];
[textField endEditing:true];
return true;
}
we have a point contentOffset to save contentoffset of scrollview before keyboar show. Then we will scroll content for y about 180 (height of keyboar). when you touch return in keyboar, we will scroll content to old point(it is contentOffset). If you have many textfield, you don't touch return in keyboar but you touch another textfield, it will +180 . So we have check touch return
我们有一个点 contentOffset 来保存键盘显示之前滚动视图的 contentoffset 。然后我们将 y 的内容滚动大约 180(键盘的高度)。当您在键盘中触摸返回时,我们会将内容滚动到旧点(即 contentOffset)。如果您有很多文本字段,则不要在键盘中触摸 return 而是触摸另一个文本字段,它将 +180 。所以我们有检查触摸返回
回答by Bejil
I think it's better use keyboard notifications because you don't know if the first responder (the control with focus on) is a textField or a textView (or whatever). So juste create a category to find the first responder :
我认为最好使用键盘通知,因为您不知道第一响应者(具有焦点的控件)是 textField 还是 textView (或其他)。所以juste创建一个类别来查找第一响应者:
#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
@implementation UIResponder (FirstResponder)
+(id)currentFirstResponder {
currentFirstResponder = nil;
[[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil];
return currentFirstResponder;
}
-(void)findFirstResponder:(id)sender {
currentFirstResponder = self;
}
@end
then
然后
-(void)keyboardWillShowNotification:(NSNotification*)aNotification{
contentScrollView.delegate=nil;
contentScrollView.scrollEnabled=NO;
contentScrollViewOriginalOffset = contentScrollView.contentOffset;
UIResponder *lc_firstResponder = [UIResponder currentFirstResponder];
if([lc_firstResponder isKindOfClass:[UIView class]]){
UIView *lc_view = (UIView *)lc_firstResponder;
CGRect lc_frame = [lc_view convertRect:lc_view.bounds toView:contentScrollView];
CGPoint lc_point = CGPointMake(0, lc_frame.origin.y-lc_frame.size.height);
[contentScrollView setContentOffset:lc_point animated:YES];
}
}
Eventually disable the scroll and set the delegate to nil then restore it to avoid some actions during the edition of the first responder. Like james_womack said, keep the original offset to restore it in a keyboardWillHideNotification method.
最终禁用滚动并将委托设置为 nil 然后将其恢复以避免在第一响应者的版本中进行某些操作。就像 james_womack 说的,保留原始偏移量以在 keyboardWillHideNotification 方法中恢复它。
-(void)keyboardWillHideNotification:(NSNotification*)aNotification{
contentScrollView.delegate=self;
contentScrollView.scrollEnabled=YES;
[contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES];
}

