如何以编程方式移动UIScrollView以在键盘上方的控件中进行对焦?
我在我的UIScrollView
上有6个UITextFields
。 现在,我可以通过用户请求滚动。 但是,当键盘出现时,一些文本字段被隐藏。
这不是用户友好的。
如何以编程方式滚动视图,所以我确定键盘不隐藏文本框?
这是为我工作的。 在调整键盘的视图之前,有一个实例variables保存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; }
最后,一个简单的修复:
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];
现在我想只是把这个与上面的链接结合起来设置!
我已经把一个通用的,放入UIScrollView和UITableView的子类放在一起,负责移动键盘中的所有文本字段。
当键盘即将出现时,子类将查找即将编辑的子视图,并调整其框架和内容偏移量,以确保视图可见,animation与键盘popup相匹配。 当键盘消失时,它会恢复原来的大小。
它应该基本上与任何设置,无论是基于UITableView的界面,或手动放置视图组成。
在这里 。
(For google: TPKeyboardAvoiding, TPKeyboardAvoidingScrollView, TPKeyboardAvoidingCollectionView.) Editor's note: TPKeyboardAvoiding seems to be continually updated and fresh, as of 2014.
如果您将文本字段的delegate
设置为程序中的控制器对象,则可以使该对象实现textFieldDidBeginEditing:
和textFieldShouldReturn:
方法。 第一种方法可以用来滚动到你的文本字段,第二种方法可以用来回滚。
您可以在我的博客中find我用于此的代码: 滑动UITextViews以避免使用键盘 。 我没有在UIScrollView
testing这个代码的文本视图,但它应该工作。
简单而最好
- (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]; }
到目前为止发布的答案不适用于我,因为我有一个相当深的UIViews嵌套结构。 另外,我遇到了一些问题,这些答案只能在某些设备方向上工作。
这是我的解决scheme,希望能让你浪费更less的时间。
我的UIViewTextView派生自UIView,是一个UITextView委托,并且在从UITextView的XML文件中读取了一些参数之后,添加了一个UITextView(XML部分在这里为了清楚起见而省略)。
这是私人界面的定义:
#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
在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); }
一旦完成,我们需要处理键盘显示事件。 这在调用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; }
接下来,我们需要在开始编辑时实际滚动。 我们这样做:
- (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]; }
当我们结束编辑时,我们这样做来重置滚动位置:
- (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 ... }
隐藏的事件处理程序在键盘上没有什么可做的; 我们离开它:
-(void)keyboardWasHidden:(NSNotification*)aNotification { }
就是这样。
/* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end
我知道这是旧的,但上述解决scheme仍然没有一个“完美”的无错,向后兼容和无闪烁animation所需的所有花式定位的东西。
让我分享我的解决scheme(假设你已经设置了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, eg 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]; }
你可以检查出来: https : //github.com/michaeltyson/TPKeyboardAvoiding (我用我的应用程序的示例)。 它工作得很好。 我希望能帮到你。
其实,这里有一个关于使用TPKeyboardAvoiding的完整教程,可以帮助某人
(1)从github链接下载zip文件 。 将这四个文件添加到您的Xcode项目中:
(2)在IB中build立你的美丽forms。 添加一个UIScrollView。 坐在滚动视图中的表单项 。 (注 – 关于界面生成器的非常有用的提示 : https : //stackoverflow.com/a/16952902/294884 )
(3)点击滚动视图 。 然后在右上angular,第三个button,你会看到单词“UIScrollView”。 使用复制和粘贴, 将其更改为 “TPKeyboardAvoidingScrollView”
(4)就是这样。 把应用程序放在应用程序商店,并为您的客户收费。
(另外,只需点击滚动视图的检查器选项卡,您可能更喜欢打开或closures弹跳和滚动条 – 您的首选项。)
个人评论 – 我强烈build议在几乎所有情况下使用滚动视图(或集合视图)input表单。 不要使用表格视图。 这是有问题的,原因很多。 而且很简单,使用滚动视图非常容易。 只要把它放在任何你想要的方式。 它是在界面生成器100%wysiwyg。 希望能帮助到你
这是我的代码,希望它能帮助你。 如果你有很多的文本框,它工作正常
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; }
我们有一个contentOffset在keyboar显示之前保存scrollview的contentoffset。 然后,我们将滚动y约180(keyboar的高度)的内容。 当你触摸键盘上的返回时,我们将滚动内容到旧点(这是contentOffset)。 如果你有很多文本框,你不用触摸键盘上的返回键,但是你触摸另一个文本框,它将会+180。 所以我们检查触摸返回
我修改了上面的一些解决scheme,使其更易于理解和使用。 我使用了一个IBOutlet,以便多个文本字段可以通过文本字段的“发送事件”从“编辑开始”链接到该function。 **不要忘记有你的滚动查看sockets
- (IBAction)moveViewUpwards:(id)sender { CGRect rc = [sender convertRect:[sender bounds] toView:scrollView]; rc.origin.x = 0 ; rc.origin.y -= 60 ; rc.size.height = 400; [scrollView scrollRectToVisible:rc animated:YES]; }
使用任何这些,
CGPoint bottomOffset = CGPointMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height); [self.MainScrollView setContentOffset:bottomOffset animated:YES];
要么
[self.MainScrollView scrollRectToVisible:CGRectMake(0, self.MainScrollView.contentSize.height - self.MainScrollView.bounds.size.height-30, MainScrollView.frame.size.width, MainScrollView.frame.size.height) animated:YES];
我认为最好使用键盘通知,因为你不知道第一个响应者(焦点控件)是一个textField还是一个textView(或其他)。 所以正确的创build一个类别来寻找第一响应者:
#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
然后
-(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]; } }
最终禁用滚动并将委托设置为零,然后恢复它以避免在第一响应者的编辑期间的一些动作。 像james_womack说,保持原来的偏移量,以在keyboardWillHideNotification方法中恢复它。
-(void)keyboardWillHideNotification:(NSNotification*)aNotification{ contentScrollView.delegate=self; contentScrollView.scrollEnabled=YES; [contentScrollView setContentOffset:contentScrollViewOriginalOffset animated:YES]; }
在Swift 1.2+中做这样的事情:
class YourViewController: UIViewController, UITextFieldDelegate { override func viewDidLoad() { super.viewDidLoad() _yourTextField.delegate = self //make sure you have the delegate set to this view controller for each of your textFields so textFieldDidBeginEditing can be called for each one ... } func textFieldDidBeginEditing(textField: UITextField) { var point = textField.convertPoint(textField.frame.origin, toView: _yourScrollView) point.x = 0.0 //if your textField does not have an origin at 0 for x and you don't want your scrollView to shift left and right but rather just up and down _yourScrollView.setContentOffset(point, animated: true) } func textFieldDidEndEditing(textField: UITextField) { //Reset scrollview once done editing scrollView.setContentOffset(CGPoint.zero, animated: true) } }