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

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

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

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

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


当前回答

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

其他回答

如果你使用UITableViewController而不是UIViewController,它会自动这样做。

因此,经过数小时的艰苦工作,尝试使用这些当前的解决方案(完全失败),我终于让事情正常工作,并更新它们以使用新的动画块。我的回答完全基于Ortwin上面的回答。

所以不管出于什么原因,上面的代码就是不适合我。我的设置似乎和其他人的很相似,但可能是因为我用的是iPad或4.3……不知道。它在做一些古怪的数学运算然后把我的tableview从屏幕上拍下来。

见我的解决方案的最终结果:http://screencast.com/t/hjBCuRrPC(请忽略照片。: - p)

所以我沿用了Ortwin的基本原理,但改变了计算原点的方法。Y和尺寸。我的表格视图的高度和键盘的高度。当我从结果中减去窗口的高度时,它告诉我有多少交集。如果它大于0(也就是有一些重叠),我执行帧高度的动画。

此外,还有一些重绘问题得到了解决,1)等待滚动到单元格直到动画完成,2)使用UIViewAnimationOptionBeginFromCurrentState选项隐藏键盘。

有几件事需要注意。

_topmostrowbeforekeyboardwasshow & _originalFrame是在头文件中声明的实例变量。 自我。guestEntryTableView是我的tableView(我在外部文件中) iaskcgrecswap是Ortwin用于翻转帧坐标的方法 我只更新tableView的高度如果它至少有50px会显示 因为我不在UIViewController中,所以我没有self。视图,我只是把tableView返回到它的原始框架

同样,如果我Ortwin没有提供关键,我也不会得到这个答案。代码如下:

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

以下是我的解决方案,灵感来自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;
}

基于bartjomiej semazynk解决方案的Swift 3最简单的解决方案:

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

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
        tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
    }
}

@objc 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)
    })
}

键盘通知可以工作,但苹果的示例代码假设滚动视图是窗口的根视图。通常情况并非如此。您必须补偿标签栏等,以获得正确的偏移量。

这比听起来容易。下面是我在UITableViewController中使用的代码。它有两个实例变量,hiddenRect和keyboardshow。

// 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;
}