经过大量的试验和错误,我放弃了,并提出了这个问题。我见过很多人有类似的问题,但不能得到所有的答案工作正确。
我有一个由自定义单元格组成的UITableView。单元格由相邻的5个文本字段组成(有点像网格)。
当我试图滚动和编辑UITableView底部的单元格时,我不能设法让我的单元格正确地定位在键盘上方。
我看到过很多关于改变视图大小的回答……但到目前为止,没有一种效果很好。
谁能用一个具体的代码示例来阐明这样做的“正确”方式?
经过大量的试验和错误,我放弃了,并提出了这个问题。我见过很多人有类似的问题,但不能得到所有的答案工作正确。
我有一个由自定义单元格组成的UITableView。单元格由相邻的5个文本字段组成(有点像网格)。
当我试图滚动和编辑UITableView底部的单元格时,我不能设法让我的单元格正确地定位在键盘上方。
我看到过很多关于改变视图大小的回答……但到目前为止,没有一种效果很好。
谁能用一个具体的代码示例来阐明这样做的“正确”方式?
当前回答
我正在用这些,它们就像魔法一样管用:
bskey控制板- bskey控制板github
tpkeyboardavoidance - tpkeyboardavoidance github
其他回答
我正在用这些,它们就像魔法一样管用:
bskey控制板- bskey控制板github
tpkeyboardavoidance - tpkeyboardavoidance github
以下是我的解决方案,灵感来自iOS7日历应用程序的“事件编辑”屏幕。
该解决方案的关键之一是当用户滚动表格时键盘被取消。
实现:
1)添加属性,将存储选定的文本字段:
@property (strong) UITextField *currentTextField;
和BOOL变量,我们将使用它来检查当用户滚动表时是否需要隐藏键盘。
BOOL hideKeyboardOnScroll;
2)处理UITextField委托回调:
#pragma mark - UITextFieldDelegate
- (void) textFieldDidBeginEditing: (UITextField *) textField {
self.currentTextField = textField;
}
- (void) textFieldDidEndEditing: (UITextField *) textField {
self.currentTextField = nil;
}
- (BOOL) textFieldShouldReturn: (UITextField *) textField {
[textField resignFirstResponder];
CGPoint newContentOffset = CGPointZero;
if (tableView.contentSize.height > tableView.frame.size.height) {
newContentOffset.y = MIN(tableView.contentOffset.y, tableView.contentSize.height - tableView.frame.size.height);
}
[tableView setContentOffset: newContentOffset animated: YES];
return YES;
}
3)处理UIScrollViewDelegate方法来检查用户滚动视图。
#pragma mark - UIScrollViewDelegate
- (void) scrollViewDidScroll: (UIScrollView *) scrollView {
if (hideKeyboardOnScroll == YES) {
[self.currentTextField resignFirstResponder];
}
}
4)在视图控制器的[viewWillAppear]方法中订阅键盘通知,并在[viewWillDisappear]方法中取消订阅。
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear: animated];
[ [NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillShow:)
name: UIKeyboardWillShowNotification object: nil];
[ [NSNotificationCenter defaultCenter] addObserver: self selector: @selector(keyboardWillHide:)
name: UIKeyboardWillHideNotification object: nil];
}
- (void) viewWillDisappear: (BOOL) animated {
[super viewWillDisappear: animated];
[ [NSNotificationCenter defaultCenter] removeObserver: self name: UIKeyboardDidShowNotification object: nil];
[ [NSNotificationCenter defaultCenter] removeObserver: self name: UIKeyboardWillHideNotification object: nil];
}
5)处理键盘通知:
- (void) keyboardWillShow: (NSNotification *) notification {
CGRect keyboardFrame = [ [ [notification userInfo] objectForKey: UIKeyboardFrameBeginUserInfoKey] CGRectValue];
// Find cell with textfield.
CGRect textFieldFrame = [tableView convertRect: self.currentTextField.frame fromView: self.currentTextField];
NSIndexPath *indexPath = [tableView indexPathForRowAtPoint: textFieldFrame.origin];
UITableViewCell *cell = [tableView cellForRowAtIndexPath: indexPath];
//
// Shrink tableView size.
CGRect tableViewFrame = tableView.frame;
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width,
self.view.frame.size.height - tableView.frame.origin.y - keyboardFrame.size.height);
//
// Check if cell is visible in shrinked table size.
BOOL cellIsFullyVisible = YES;
if ( cell.frame.origin.y < tableView.contentOffset.y ||
(cell.frame.origin.y + cell.frame.size.height) > (tableView.contentOffset.y + tableView.frame.size.height) ) {
cellIsFullyVisible = NO;
}
//
// If cell is not fully visible when scroll table to show cell;
if (cellIsFullyVisible == NO) {
CGPoint contentOffset = CGPointMake(tableView.contentOffset.x, CGRectGetMaxY(cell.frame) - tableView.frame.size.height);
if (cell.frame.origin.y < tableView.contentOffset.y) {
contentOffset.y = cell.frame.origin.y;
}
contentOffset.y = MAX(0, contentOffset.y);
// For some reason [setContentOffset] is called without delay then
// this code may not work for some cells. That why we call it with brief delay.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration: 0.5 animations:^{
[tableView setContentOffset: contentOffset animated: NO];
} completion: ^(BOOL finished) {
hideKeyboardOnScroll = YES;
}];
});
} else {
hideKeyboardOnScroll = YES;
}
//
// Finally restore original table frame.
tableView.frame = tableViewFrame;
//
}
- (void) keyboardWillHide: (NSNotification *) notification {
[super keyboardWillHide: notification];
hideKeyboardOnScroll = NO;
}
UITableViewController会自动进行滚动。 与使用UIViewController相比的区别是,当使用TableViewController时,你必须通过使用NavigationController以编程方式创建navbar - buttonitem。
Swift最简单的解决方案:
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
}
Swift 4.2或更高版本
override func viewDidLoad() {
super.viewDidLoad()
searchBar?.becomeFirstResponder()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardDidHideNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height
accountSettingsTableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}
}
@objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: { self.accountSettingsTableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) })}
}
如果你能使用UITableViewController,你就能免费得到这个功能。然而,有时这不是一个选项,特别是当你需要多个视图而不仅仅是UITableView时。
这里介绍的一些解决方案在iOS≥4上不起作用,一些在iPad或横屏模式下不起作用,一些不适用于蓝牙键盘(我们不希望有任何滚动),一些在多个文本字段之间切换时不起作用。因此,如果您选择任何解决方案,请确保测试这些情况。这是我们在InAppSettingsKit中使用的解决方案:
- (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中该类的完整代码。要测试它,请使用“Complete List”子窗格,您可以在其中测试上面提到的场景。