当select文本字段时,使UITableView滚动
经过大量的反复试验,我放弃了提问。 我见过很多有类似问题的人,但不能得到所有正确的答案。
我有一个由自定义单元格组成的UITableView
。 单元格由5个相邻的文本字段组成(有点像网格)。
当我尝试滚动和编辑UITableView
底部的单元格时,我无法设法让我的单元格正确定位在键盘上方。
我已经看到了很多关于改变视图大小的答案,但是目前为止它们都没有很好地工作。
有没有人可以用一个具体的代码示例来澄清“正确的”方法?
如果您使用UITableViewController而不是UIViewController,它将自动执行此操作。
滚动function可以简单得多:
- (void) textFieldDidBeginEditing:(UITextField *)textField { UITableViewCell *cell; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { // Load resources for iOS 6.1 or earlier cell = (UITableViewCell *) textField.superview.superview; } else { // Load resources for iOS 7 or later cell = (UITableViewCell *) textField.superview.superview.superview; // TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Cell! } [tView scrollToRowAtIndexPath:[tView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES]; }
而已。 根本没有计算。
我正在做一些非常相似的东西,不需要计算特定的代码。 只需检查代码上的注释:
在MyUIViewController.h中
@interface MyUIViewController: UIViewController <UITableViewDelegate, UITableViewDataSource> { UITableView *myTableView; UITextField *actifText; } @property (nonatomic, retain) IBOutlet UITableView *myTableView; @property (nonatomic, retain) IBOutlet UITextField *actifText; - (IBAction)textFieldDidBeginEditing:(UITextField *)textField; - (IBAction)textFieldDidEndEditing:(UITextField *)textField; -(void) keyboardWillHide:(NSNotification *)note; -(void) keyboardWillShow:(NSNotification *)note; @end
在MyUIViewController.m中
@implementation MyUIViewController @synthesize myTableView; @synthesize actifText; - (void)viewDidLoad { // Register notification when the keyboard will be show [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; // Register notification when the keyboard will be hide [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } // To be link with your TextField event "Editing Did Begin" // memoryze the current TextField - (IBAction)textFieldDidBeginEditing:(UITextField *)textField { self.actifText = textField; } // To be link with your TextField event "Editing Did End" // release current TextField - (IBAction)textFieldDidEndEditing:(UITextField *)textField { self.actifText = nil; } -(void) keyboardWillShow:(NSNotification *)note { // Get the keyboard size CGRect keyboardBounds; [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds]; // Detect orientation UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; CGRect frame = self.myTableView.frame; // Start animation [UIView beginAnimations:nil context:NULL]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:0.3f]; // Reduce size of the Table view if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) frame.size.height -= keyboardBounds.size.height; else frame.size.height -= keyboardBounds.size.width; // Apply new size of table view self.myTableView.frame = frame; // Scroll the table view to see the TextField just above the keyboard if (self.actifText) { CGRect textFieldRect = [self.myTableView convertRect:self.actifText.bounds fromView:self.actifText]; [self.myTableView scrollRectToVisible:textFieldRect animated:NO]; } [UIView commitAnimations]; } -(void) keyboardWillHide:(NSNotification *)note { // Get the keyboard size CGRect keyboardBounds; [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds]; // Detect orientation UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; CGRect frame = self.myTableView.frame; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:0.3f]; // Increase size of the Table view if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) frame.size.height += keyboardBounds.size.height; else frame.size.height += keyboardBounds.size.width; // Apply new size of table view self.myTableView.frame = frame; [UIView commitAnimations]; } @end
Swift 1.2+版本:
class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var activeText: UITextField! @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) } func textFieldDidBeginEditing(textField: UITextField) { activeText = textField } func textFieldDidEndEditing(textField: UITextField) { activeText = nil } func keyboardWillShow(note: NSNotification) { if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() { var frame = tableView.frame UIView.beginAnimations(nil, context: nil) UIView.setAnimationBeginsFromCurrentState(true) UIView.setAnimationDuration(0.3) frame.size.height -= keyboardSize.height tableView.frame = frame if activeText != nil { let rect = tableView.convertRect(activeText.bounds, fromView: activeText) tableView.scrollRectToVisible(rect, animated: false) } UIView.commitAnimations() } } func keyboardWillHide(note: NSNotification) { if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() { var frame = tableView.frame UIView.beginAnimations(nil, context: nil) UIView.setAnimationBeginsFromCurrentState(true) UIView.setAnimationDuration(0.3) frame.size.height += keyboardSize.height tableView.frame = frame UIView.commitAnimations() } } }
我有同样的问题,但注意到它只出现在一个视图。 于是我开始寻找控制器的差异。
我发现滚动行为是在- (void)viewWillAppear:(BOOL)animated
超级实例的- (void)viewWillAppear:(BOOL)animated
中设置的。
所以一定要这样实现:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // your code }
如果使用UIViewController
或UITableViewController
,则无关紧要; 通过在UIViewController
中将UITableView
作为self.view的子视图进行检查。 这是一样的行为。 视图不允许滚动,如果调用[super viewWillAppear:animated];
失踪。
我想我已经想出了解决scheme来匹配苹果的应用程序的行为。
首先,在你的viewWillAppear:订阅键盘通知,所以你知道什么时候键盘会显示和隐藏,并且系统会告诉你键盘的大小,但是不要忘记取消注册在你的viewWillDisappear中:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
实现类似于下面的方法,以便在键盘显示时调整tableView的大小以匹配可见区域。 在这里,我正在分别跟踪键盘的状态,所以我可以select何时将tableView自动设置回全高,因为您在每次现场更改时都会收到这些通知。 不要忘记实现keyboardWillHide:并select适当的地方来修复你的tableView大小。
-(void) keyboardWillShow:(NSNotification *)note { CGRect keyboardBounds; [[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keyboardBounds]; keyboardHeight = keyboardBounds.size.height; if (keyboardIsShowing == NO) { keyboardIsShowing = YES; CGRect frame = self.view.frame; frame.size.height -= keyboardHeight; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:0.3f]; self.view.frame = frame; [UIView commitAnimations]; } }
现在这里是滚动位,我们首先计算几个大小,然后我们看到我们在可见区域的位置,然后设置我们要滚动的矩形,使其成为基于文本字段中心的上半部分或下半部分的半视图在视图中的位置。 在这种情况下,我们有一个UITextFields数组和一个跟踪它们的枚举,所以将rowHeight乘以行号给出了该外部视图中帧的实际偏移量。
- (void) textFieldDidBeginEditing:(UITextField *)textField { CGRect frame = textField.frame; CGFloat rowHeight = self.tableView.rowHeight; if (textField == textFields[CELL_FIELD_ONE]) { frame.origin.y += rowHeight * CELL_FIELD_ONE; } else if (textField == textFields[CELL_FIELD_TWO]) { frame.origin.y += rowHeight * CELL_FIELD_TWO; } else if (textField == textFields[CELL_FIELD_THREE]) { frame.origin.y += rowHeight * CELL_FIELD_THREE; } else if (textField == textFields[CELL_FIELD_FOUR]) { frame.origin.y += rowHeight * CELL_FIELD_FOUR; } CGFloat viewHeight = self.tableView.frame.size.height; CGFloat halfHeight = viewHeight / 2; CGFloat midpoint = frame.origin.y + (textField.frame.size.height / 2); if (midpoint < halfHeight) { frame.origin.y = 0; frame.size.height = midpoint; } else { frame.origin.y = midpoint; frame.size.height = midpoint; } [self.tableView scrollRectToVisible:frame animated:YES]; }
这似乎很好地工作。
我可能错过了这个,因为我没有在这里读完整篇文章,但是我提出的看起来似乎很简单。 我没有把这个通过榨汁机,在所有情况下testing,但它似乎应该工作得很好。
只需通过键盘的高度调整tableview的contentInset,然后将单元格滚动到底部即可:
- (void)keyboardWasShown:(NSNotification *)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0); self.myTableView.contentInset = contentInsets; self.myTableView.scrollIndicatorInsets = contentInsets; [self.myTableView scrollToRowAtIndexPath:self.currentField.indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }
而且当然
- (void)keyboardWasHidden:(NSNotification *)aNotification { [UIView animateWithDuration:.3 animations:^(void) { self.myTableView.contentInset = UIEdgeInsetsZero; self.myTableView.scrollIndicatorInsets = UIEdgeInsetsZero; }]; }
这太简单了吗? 我错过了什么? 到目前为止,它在为我工作好,但正如我所说,我没有把它通过榨汁机…
如果你可以使用UITableViewController
,你可以免费获得这个function。 但有时候,这不是一个选项,特别是如果你需要多个视图,而不仅仅是UITableView
。
这里介绍的一些解决scheme不适用于iOS≥4,有些不适用于iPad或横向模式,有些不适用于蓝牙键盘(我们不需要滚动),有些则不适用在多个文本字段之间切换时工作。 所以如果你select任何解决scheme,一定要testing这些情况。 这是我们在InAppSettingsKit中 使用的解决scheme:
- (void)_keyboardWillShow:(NSNotification*)notification { if (self.navigationController.topViewController == self) { NSDictionary* userInfo = [notification userInfo]; // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0 NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"]; if (!keyboardFrameValue) { keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"]; } // Reduce the tableView height by the part of the keyboard that actually covers the tableView CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds; if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) { windowRect = IASKCGRectSwap(windowRect); } CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]]; if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) { viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute); } CGRect frame = _tableView.frame; frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute); [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; _tableView.frame = frame; [UIView commitAnimations]; UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview; NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell]; // iOS 3 sends hide and show notifications right after each other // when switching between textFields, so cancel -scrollToOldPosition requests [NSObject cancelPreviousPerformRequestsWithTarget:self]; [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } } - (void) scrollToOldPosition { [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES]; } - (void)_keyboardWillHide:(NSNotification*)notification { if (self.navigationController.topViewController == self) { NSDictionary* userInfo = [notification userInfo]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; _tableView.frame = self.view.bounds; [UIView commitAnimations]; [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1]; } }
这是InAppSettingsKit中类的完整代码 。 要testing它,请使用“完整列表”子窗格,您可以在其中testing上述场景。
Swift 3最简单的解决scheme,基于BartłomiejSemańczyk解决scheme :
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil) } deinit { NotificationCenter.default.removeObserver(self) } // MARK: Keyboard Notifications func keyboardWillShow(notification: NSNotification) { if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height { tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0) } } func keyboardWillHide(notification: NSNotification) { UIView.animate(withDuration: 0.2, animations: { // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) }) }
Swift最简单的解决scheme:
override func viewDidLoad() { super.viewDidLoad() searchBar?.becomeFirstResponder() NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillShow(_:)), name: UIKeyboardDidShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillHide(_:)), name: UIKeyboardDidHideNotification, object: nil) } deinit { NSNotificationCenter.defaultCenter().removeObserver(self) } func keyboardWillShow(notification: NSNotification) { if let userInfo = notification.userInfo { if let keyboardHeight = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue.size.height { tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0) } } } func keyboardWillHide(notification: NSNotification) { UIView.animateWithDuration(0.2, animations: { self.table_create_issue.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) }) // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here }
从几个答案(尤其是Ortwin Gentz,用户98013)和另一篇文章中结合并填充空白,这将在纵向或横向模式下在iPad上以SDK 4.3开箱即用:
@implementation UIView (FindFirstResponder) - (UIResponder *)findFirstResponder { if (self.isFirstResponder) { return self; } for (UIView *subView in self.subviews) { UIResponder *firstResponder = [subView findFirstResponder]; if (firstResponder != nil) { return firstResponder; } } return nil; } @end @implementation MyViewController - (UIResponder *)currentFirstResponder { return [self.view findFirstResponder]; } - (IBAction)editingEnded:sender { [sender resignFirstResponder]; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return NO; } - (void)textFieldDidBeginEditing:(UITextField *)textField { UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview]; [_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES]; } - (void)keyboardWillShow:(NSNotification*)notification { if ([self currentFirstResponder] != nil) { NSDictionary* userInfo = [notification userInfo]; // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0 NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"]; if (!keyboardFrameValue) { keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"]; } // Reduce the tableView height by the part of the keyboard that actually covers the tableView CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds; CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]]; CGRect frame = _tableView.frame; if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) { windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width); viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width); } frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute); [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; _tableView.frame = frame; [UIView commitAnimations]; UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview; NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell]; // iOS 3 sends hide and show notifications right after each other // when switching between textFields, so cancel -scrollToOldPosition requests [NSObject cancelPreviousPerformRequestsWithTarget:self]; _topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0]; [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } } - (void) scrollToOldPosition { [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES]; } - (void)keyboardWillHide:(NSNotification*)notification { if ([self currentFirstResponder] != nil) { NSDictionary* userInfo = [notification userInfo]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; _tableView.frame = self.view.bounds; [UIView commitAnimations]; [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1]; } } @end
我希望你们已经有了解读所有这些的解决scheme。 但是我发现我的解决scheme如下。 我期待你已经有一个单元格与UITextField。 所以在准备时只要将行索引保留在文本字段的标签中。
cell.textField.tag = IndexPath.row;
使用全局作用域创build一个activeTextField,UITextField的实例如下:
@interface EditViewController (){ UITextField *activeTextField; }
所以,现在你只需要复制粘贴我的代码。 也不要忘记添加UITextFieldDelegate
#pragma mark - TextField Delegation - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{ activeTextField = textField; return YES; } - (void)textFieldDidEndEditing:(UITextField *)textField{ activeTextField = nil; }
注册键盘通知
#pragma mark - Keyboard Activity - (void)registerForKeyboardNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil]; }
Handles Keyboard Notifications:
Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0); [self.tableView setContentInset:contentInsets]; [self.tableView setScrollIndicatorInsets:contentInsets]; NSIndexPath *currentRowIndex = [NSIndexPath indexPathForRow:activeTextField.tag inSection:0]; [self.tableView scrollToRowAtIndexPath:currentRowIndex atScrollPosition:UITableViewScrollPositionTop animated:YES]; }
Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; [self.tableView setContentInset:contentInsets]; [self.tableView setScrollIndicatorInsets:contentInsets]; }
Now one thing is left, Call the registerForKeyboardNotifications method in to ViewDidLoad method as follows:
- (void)viewDidLoad { [super viewDidLoad]; // Registering keyboard notification [self registerForKeyboardNotifications]; // Your codes here... }
You are done, hope your textFields will no longer hidden by the keyboard.
THE RIGHT ANSWER is Sam Ho's answer:
"If you use UITableViewController instead of UIViewController, it will automatically do so.".
Just make sure to connect your UITableView to the TableView property of the UITableViewController (so eg do not add it as a subview of the View property of the UITableViewController).
Also make sure to set the AutoresizingMask property of your UITableView to FlexibleHeight
My approach:
I first subclass UITextField and add an indexPath property. In the cellFor… Method i hand over the indexPath property.
Then I add following code:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:textField.indexPath]; CGPoint cellPoint = [cell convertPoint:textField.center toView:self.tableView]; [UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, cellPoint.y-50);}];
to the textFieldShould/WillBegin…etc.
When the Keyboard disappears you have to reverse it with:
[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, 0);}];
Keyboard notifications work, but Apple's sample code for that assumes that the scroll view is the root view of the window. This is usually not the case. You have to compensate for tab bars, etc., to get the right offset.
It is easier than it sounds. Here is the code I use in a UITableViewController. It has two instance variables, hiddenRect and keyboardShown.
// Called when the UIKeyboardDidShowNotification is sent. - (void)keyboardWasShown:(NSNotification*)aNotification { if (keyboardShown) return; NSDictionary* info = [aNotification userInfo]; // Get the frame of the keyboard. NSValue *centerValue = [info objectForKey:UIKeyboardCenterEndUserInfoKey]; NSValue *boundsValue = [info objectForKey:UIKeyboardBoundsUserInfoKey]; CGPoint keyboardCenter = [centerValue CGPointValue]; CGRect keyboardBounds = [boundsValue CGRectValue]; CGPoint keyboardOrigin = CGPointMake(keyboardCenter.x - keyboardBounds.size.width / 2.0, keyboardCenter.y - keyboardBounds.size.height / 2.0); CGRect keyboardScreenFrame = { keyboardOrigin, keyboardBounds.size }; // Resize the scroll view. UIScrollView *scrollView = (UIScrollView *) self.tableView; CGRect viewFrame = scrollView.frame; CGRect keyboardFrame = [scrollView.superview convertRect:keyboardScreenFrame fromView:nil]; hiddenRect = CGRectIntersection(viewFrame, keyboardFrame); CGRect remainder, slice; CGRectDivide(viewFrame, &slice, &remainder, CGRectGetHeight(hiddenRect), CGRectMaxYEdge); scrollView.frame = remainder; // Scroll the active text field into view. CGRect textFieldRect = [/* selected cell */ frame]; [scrollView scrollRectToVisible:textFieldRect animated:YES]; keyboardShown = YES; } // Called when the UIKeyboardDidHideNotification is sent - (void)keyboardWasHidden:(NSNotification*)aNotification { // Reset the height of the scroll view to its original value UIScrollView *scrollView = (UIScrollView *) self.tableView; CGRect viewFrame = [scrollView frame]; scrollView.frame = CGRectUnion(viewFrame, hiddenRect); keyboardShown = NO; }
If you use Three20
, then use the autoresizesForKeyboard
property. Just set in the your view controller's -initWithNibName:bundle
method
self.autoresizesForKeyboard = YES
This takes care of:
- Listening for keyboard notifications and adjusting the table view's frame
- Scrolling to the first responder
Done and done.
Use UITextField's
delegate
method :
迅速
func textFieldShouldBeginEditing(textField: UITextField) -> bool { let txtFieldPosition = textField.convertPoint(textField.bounds.origin, toView: yourTableViewHere) let indexPath = yourTablViewHere.indexPathForRowAtPoint(txtFieldPosition) if indexPath != nil { yourTablViewHere.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true) } return true }
Objective-C的
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { CGPoint txtFieldPosition = [textField convertPoint:CGPointZero toView: yourTablViewHere]; NSLog(@"Begin txtFieldPosition : %@",NSStringFromCGPoint(txtFieldPosition)); NSIndexPath *indexPath = [yourTablViewHere indexPathForRowAtPoint:txtFieldPosition]; if (indexPath != nil) { [yourTablViewHere scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } return YES; }
If you use a uitableview to place your textfields ( from Jeff Lamarche ), you can just scroll the tableview using the delegate method like so.
(Note: my text fields are stored in an array with the same index as there row in the tableview)
- (void) textFieldDidBeginEditing:(UITextField *)textField { int index; for(UITextField *aField in textFields){ if (textField == aField){ index = [textFields indexOfObject:aField]-1; } } if(index >= 0) [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [super textFieldDidBeginEditing:textField]; }
A more stream-lined solution. It slips into the UITextField delegate methods, so it doesn't require messing w/ UIKeyboard notifications.
Implementation notes:
kSettingsRowHeight — the height of a UITableViewCell.
offsetTarget and offsetThreshold are baed off of kSettingsRowHeight. If you use a different row height, set those values to point's y property. [alt: calculate the row offset in a different manner.]
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { CGFloat offsetTarget = 113.0f; // 3rd row CGFloat offsetThreshold = 248.0f; // 6th row (ie 2nd-to-last row) CGPoint point = [self.tableView convertPoint:CGPointZero fromView:textField]; [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.2]; [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; CGRect frame = self.tableView.frame; if (point.y > offsetThreshold) { self.tableView.frame = CGRectMake(0.0f, offsetTarget - point.y + kSettingsRowHeight, frame.size.width, frame.size.height); } else if (point.y > offsetTarget) { self.tableView.frame = CGRectMake(0.0f, offsetTarget - point.y, frame.size.width, frame.size.height); } else { self.tableView.frame = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height); } [UIView commitAnimations]; return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; [UIView beginAnimations:nil context:nil]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:0.2]; [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; CGRect frame = self.tableView.frame; self.tableView.frame = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height); [UIView commitAnimations]; return YES;
}
I ran into something like your problem (I wanted a screen similar to the iPhone's settings.app with a bunch of editable cells stacked on on top of another) and found that this approach worked well:
sliding uitextfields around to avoid
Since you have textfields in a table, the best way really is to resize the table – you need to set the tableView.frame to be smaller in height by the size of the keyboard (I think around 165 pixels) and then expand it again when the keyboard is dismissed.
You can optionally also disable user interaction for the tableView at that time as well, if you do not want the user scrolling.
This soluton works for me, PLEASE note the line
[tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES];
You can change the 160 value to match it work with you
- (void)keyboardWasShown:(NSNotification*)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; CGRect bkgndRect = activeField.superview.frame; bkgndRect.size.height += kbSize.height; [activeField.superview setFrame:bkgndRect]; [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES]; } - (void)textFieldDidBeginEditing:(UITextField *)textField { activeField = textField; } -(void)textFieldDidEndEditing:(UITextField *)textField { activeField = nil; } // Called when the UIKeyboardWillHideNotification is sent - (void)keyboardWillBeHidden:(NSNotification*)aNotification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; tableView.contentInset = contentInsets; tableView.scrollIndicatorInsets = contentInsets; NSDictionary* info = [aNotification userInfo]; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; CGRect bkgndRect = activeField.superview.frame; //bkgndRect.size.height += kbSize.height; [activeField.superview setFrame:bkgndRect]; [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height) animated:YES]; }
Very interesting discussion thread, i also faced the same problem may be worse one because
- I was using a custom cell and the textfield was inside that.
- I had to use UIViewController to meet my requirements so cant take advantage of UITableViewController.
- I had filter/ sort criterias in my table cell, ie ur cells keeps on changing and keeping track of the indexpath and all will not help.
So read the threads here and implemented my version, which helped me in pushing up my contents in iPad in landscape mode. Here is code ( this is not fool proof and all, but it fixed my issue) First u need to have a delegate in your custom cell class, which on editing begins, sends the textfield to ur viewcontroller and set the activefield = theTextField there
// IMPLEMENTED TO HANDLE LANDSCAPE MODE ONLY
- (void)keyboardWasShown:(NSNotification*)aNotification { NSDictionary* info = [aNotification userInfo]; CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; CGRect aRect = myTable.frame; CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width); aRect.size.height -= kbSize.height+50; // This will the exact rect in which your textfield is present CGRect rect = [myTable convertRect:activeField.bounds fromView:activeField]; // Scroll up only if required if (!CGRectContainsPoint(aRect, rect.origin) ) { [myTable setContentOffset:CGPointMake(0.0, rect.origin.y) animated:YES]; } }
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)aNotification { UIEdgeInsets contentInsets = UIEdgeInsetsZero; myTable.contentInset = contentInsets; myTable.scrollIndicatorInsets = contentInsets; NSDictionary* info = [aNotification userInfo]; CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width); CGRect bkgndRect = activeField.superview.frame; bkgndRect.size.height += kbSize.height; [activeField.superview setFrame:bkgndRect]; [myTable setContentOffset:CGPointMake(0.0, 10.0) animated:YES]; }
-anoop4real
An example in Swift, using the exact point of the text field from Get indexPath of UITextField in UITableViewCell with Swift :
func textFieldDidBeginEditing(textField: UITextField) { let pointInTable = textField.convertPoint(textField.bounds.origin, toView: self.accountsTableView) let textFieldIndexPath = self.accountsTableView.indexPathForRowAtPoint(pointInTable) accountsTableView.scrollToRowAtIndexPath(textFieldIndexPath!, atScrollPosition: .Top, animated: true) }
This works perfectly, and on iPad too.
- (BOOL)textFieldShouldReturn:(UITextField *)textField { if(textField == textfield1){ [accountName1TextField becomeFirstResponder]; }else if(textField == textfield2){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield3 becomeFirstResponder]; }else if(textField == textfield3){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield4 becomeFirstResponder]; }else if(textField == textfield4){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield5 becomeFirstResponder]; }else if(textField == textfield5){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield6 becomeFirstResponder]; }else if(textField == textfield6){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:4 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield7 becomeFirstResponder]; }else if(textField == textfield7){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:5 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield8 becomeFirstResponder]; }else if(textField == textfield8){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:6 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textfield9 becomeFirstResponder]; }else if(textField == textfield9){ [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES]; [textField resignFirstResponder]; }
So after hours of grueling work trying to use these current solutions (and utterly failing) I finally got things working well, and updated them to use the new animation blocks. My answer is entirely based on Ortwin's answer above .
So for whatever reason the code above was just not working for me. My setup seemed fairly similar to others, but maybe because I was on an iPad or 4.3… no idea. It was doing some wacky math and shooting my tableview off the screen.
See end result of my solution: http://screencast.com/t/hjBCuRrPC (Please ignore the photo. :-P)
So I went with the gist of what Ortwin was doing, but changed how it was doing some math to add up the origin.y & size.height of my table view with the height of the keyboard. When I subtract the height of the window from that result , it tells me how much intersection I have going on. If its greater than 0 (aka there is some overlap) I perform the animation of the frame height.
In addition there were some redraw issues that were solved by 1) Waiting to scroll to the cell until the animation was done and 2) using the UIViewAnimationOptionBeginFromCurrentState option when hiding the keyboard.
A couple things to note.
- _topmostRowBeforeKeyboardWasShown & _originalFrame are instance variables declared in the header.
- self.guestEntryTableView is my tableView (I'm in an external file)
- IASKCGRectSwap is Ortwin's method for flipping the coordinates of a frame
- I only update the height of the tableView if at least 50px of it is going to be showing
- Since I'm not in a UIViewController I don't have self.view, so I just return the tableView to its original frame
Again, I wouldn't have gotten near this answer if I Ortwin didn't provide the crux of it. 代码如下:
- (IBAction)textFieldDidBeginEditing:(UITextField *)textField { self.activeTextField = textField; if ([self.guestEntryTableView indexPathsForVisibleRows].count) { _topmostRowBeforeKeyboardWasShown = (NSIndexPath*)[[self.guestEntryTableView indexPathsForVisibleRows] objectAtIndex:0]; } else { // this should never happen _topmostRowBeforeKeyboardWasShown = [NSIndexPath indexPathForRow:0 inSection:0]; [textField resignFirstResponder]; } } - (IBAction)textFieldDidEndEditing:(UITextField *)textField { self.activeTextField = nil; } - (void)keyboardWillShow:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; NSValue* keyboardFrameValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]; // Reduce the tableView height by the part of the keyboard that actually covers the tableView UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds; CGRect viewRectAbsolute = [self.guestEntryTableView convertRect:self.guestEntryTableView.bounds toView:[[UIApplication sharedApplication] keyWindow]]; CGRect keyboardFrame = [keyboardFrameValue CGRectValue]; if (UIInterfaceOrientationLandscapeLeft == orientation ||UIInterfaceOrientationLandscapeRight == orientation ) { windowRect = IASKCGRectSwap(windowRect); viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute); keyboardFrame = IASKCGRectSwap(keyboardFrame); } // fix the coordinates of our rect to have a top left origin 0,0 viewRectAbsolute = FixOriginRotation(viewRectAbsolute, orientation, windowRect.size.width, windowRect.size.height); CGRect frame = self.guestEntryTableView.frame; _originalFrame = self.guestEntryTableView.frame; int remainder = (viewRectAbsolute.origin.y + viewRectAbsolute.size.height + keyboardFrame.size.height) - windowRect.size.height; if (remainder > 0 && !(remainder > frame.size.height + 50)) { frame.size.height = frame.size.height - remainder; float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; [UIView animateWithDuration: duration animations:^{ self.guestEntryTableView.frame = frame; } completion:^(BOOL finished){ UITableViewCell *textFieldCell = (UITableViewCell*) [[self.activeTextField superview] superview]; NSIndexPath *textFieldIndexPath = [self.guestEntryTableView indexPathForCell:textFieldCell]; [self.guestEntryTableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; }]; } } - (void)keyboardWillHide:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; [UIView animateWithDuration: duration delay: 0.0 options: (UIViewAnimationOptionBeginFromCurrentState) animations:^{ self.guestEntryTableView.frame = _originalFrame; } completion:^(BOOL finished){ [self.guestEntryTableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES]; }]; } #pragma mark CGRect Utility function CGRect IASKCGRectSwap(CGRect rect) { CGRect newRect; newRect.origin.x = rect.origin.y; newRect.origin.y = rect.origin.x; newRect.size.width = rect.size.height; newRect.size.height = rect.size.width; return newRect; } CGRect FixOriginRotation(CGRect rect, UIInterfaceOrientation orientation, int parentWidth, int parentHeight) { CGRect newRect; switch(orientation) { case UIInterfaceOrientationLandscapeLeft: newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), rect.origin.y, rect.size.width, rect.size.height); break; case UIInterfaceOrientationLandscapeRight: newRect = CGRectMake(rect.origin.x, parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height); break; case UIInterfaceOrientationPortrait: newRect = rect; break; case UIInterfaceOrientationPortraitUpsideDown: newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height); break; } return newRect; }
Another easy method (only works with one section)
//cellForRowAtIndexPath UItextField *tf; [cell addSubview:tf]; tf.tag = indexPath.row; tf.delegate = self; //textFieldDidBeginEditing:(UITextField *)text [[self.tableView scrollToRowsAtIndexPath:[NSIndexPath indexPathForRow:text.tag in section:SECTIONINTEGER] animated:YES];
If your UITableView is managed by a subclass of UITableViewController and not UITableView, and the text field delegate is the UITableViewController, it should manage all the scrolling automatically — all these other comments are very difficult to implement in practice.
For a good example see the apple example code project: TaggedLocations.
You can see that it scrolls automatically, but there doesn't seem to be any code that does this. This project also has custom table view cells, so if you build your application with it as a guide, you should get the desired result.
Here is how I made this work, which is a mixture of Sam Ho and Marcel W's answers, and some of my own bug fixes made to my crappy code. I was using a UITableViewController. The table now resizes correctly when the keyboard is shown.
1) In viewDidLoad
I added:
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
2) I had forgotten to call the super
equivalents in viewWillAppear
and awakeFromNib
. I added these back in.
I have just solved such a problem by myself after I referred a mass of solutions found via Google and Stack Overflow.
First, please assure that you have set up an IBOutlet of your UIScrollView, Then please take a close look at Apple Doc: Keyboard Management . Finally, if you can scroll the background, but the keyboard still covers the Text Fields, please have a look at this piece of code:
// If active text field is hidden by keyboard, scroll it so it's visible // Your application might not need or want this behavior. CGRect aRect = self.view.frame; aRect.size.height -= kbSize.height; if (aRect.size.height < activeField.frame.origin.y+activeField.frame.size.height) { CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y+activeField.frame.size.height-aRect.size.height); [scrollView setContentOffset:scrollPoint animated:YES];
The main difference between this piece and Apple's lies in the if condition. I believe apple's calculation of scroll distance and condition of whether text field covered by keyboard are not accurate, so I made my modification as above.
Let me know if it works
Easy and fast solution.
I just scroll to the right cell whenever scrolling happens
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Assuming I know table now is in this mode "_keepMyCellOnTop" & I know selected cell "_selectedCellIndex" or scroll to selected cell
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (_keepMyCellOnTop) { [self.tableView scrollToRowAtIndexPath:_selectedCellIndex atScrollPosition:UITableViewScrollPositionTop animated:NO]; } }
This will prevent scrolling.
Placing the code in -(void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
will result a scroll up and down