经过大量的试验和错误,我放弃了,并提出了这个问题。我见过很多人有类似的问题,但不能得到所有的答案工作正确。

我有一个由自定义单元格组成的UITableView。单元格由相邻的5个文本字段组成(有点像网格)。

当我试图滚动和编辑UITableView底部的单元格时,我不能设法让我的单元格正确地定位在键盘上方。

我看到过很多关于改变视图大小的回答……但到目前为止,没有一种效果很好。

谁能用一个具体的代码示例来阐明这样做的“正确”方式?


当前回答

我刚刚发现另一个bug时使用UITableViewController。当键盘出现时,它不会自动滚动。我注意到这是因为contentInsetAdjustmentBehavior = .never在UITableView上。

其他回答

如果你使用uitableview来放置你的文本字段(来自Jeff Lamarche),你可以像这样使用委托方法滚动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];
    }

如果你的UITableView是由UITableViewController的子类管理的,而不是UITableView,并且文本字段委托是UITableViewController,它应该自动管理所有的滚动——所有这些其他注释在实践中是很难实现的。

有关一个好例子,请参阅apple示例代码项目:TaggedLocations。

你可以看到它自动滚动,但似乎没有任何代码做到这一点。该项目还具有自定义表视图单元格,因此,如果使用它作为指南构建应用程序,应该会得到预期的结果。

如果你能使用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”子窗格,您可以在其中测试上面提到的场景。

Swift 4.2的小变化…

在我的UITableView上,我有很多部分,但我必须避免浮动标题效果,所以我使用了“dummyViewHeight”方法,就像在Stack Overflow上看到的其他地方一样… 这就是我对这个问题的解决方案(它也适用于键盘+工具栏+建议):

将其声明为类常量:

let dummyViewHeight: CGFloat = 40.0

Then

override func viewDidLoad() {
    super.viewDidLoad()
    //... some stuff here, not needed for this example

    // Create non floating header
    tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: dummyViewHeight))
    tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)

    addObservers()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    removeObservers()
}

这里所有的魔法……

@objc func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height
        tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
        tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: keyboardHeight, right: 0)
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    UIView.animate(withDuration: 0.25) {
        self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: self.dummyViewHeight))
        self.tableView.contentInset = UIEdgeInsets(top: -self.dummyViewHeight, left: 0, bottom: 0, right: 0)
    }
}

执行滚动的函数可以简单得多:

- (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];
}

就是这样。根本不需要计算。