[置顶] 自动滚动的 tableView 和高度自适应的 textView

本文接着上篇文章(传送门)继续来说如何让 tableView 自动滚动到一个合适的位置。

源代码已上传到 github(传送门),欢迎 star。


先来看看效果图:

[置顶] 自动滚动的 tableView 和高度自适应的 textView_第1张图片[置顶] 自动滚动的 tableView 和高度自适应的 textView_第2张图片


每一行 cell 类似于空间里的一条说说。

从第一幅图中可以看到,当点击某一条说说的评论按钮时,键盘弹出来后 tableView 会自动滚动,直到这条说说正好出现在输入框上方为止。

如果不这么做,这一条说说就会被键盘挡住,这样肯定不行。


能考虑到这一点只是初级的,中级的是第二幅图。(你问我高级的是啥?我也在思考中)

你给别人评论的时候,textView 肯定不是一开始就很多行吧?(还是那句话,打开朋友圈或者空间自己看看就知道了,事实胜于雄辩啊)

什么?你问我为啥不用 textField?textField 只能有一行,这正是它和 textView 的主要区别之一。如果你不知道这个,那说明你的基础知识还有很多欠缺哦~


言归正传,你给别人评论,如果写了好几行怎么办?

首先 textView 要高度自适应,能想到这一点只是初级的。中级的是,textView 变高了,那岂不是又会挡住这条说说了吗?所以此时还要让 tableView 自动滚动。(看第二幅图)


上面这些都是纸上谈兵。说了那么多,有的人会问:“tableView 到底怎么滚动?”

简单来说就一句话:

[self.myTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];


我们来看看它的函数原型:

- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition;

它有三个参数,indexPath 就是你想滚动的那个cell,animated 决定是否有动画效果,scrollPosition 是一个枚举:

typedef NS_ENUM(NSInteger, UITableViewScrollPosition) {
    UITableViewScrollPositionNone,
    UITableViewScrollPositionTop,    
    UITableViewScrollPositionMiddle,   
    UITableViewScrollPositionBottom
};

注意,它不是滚动的方向,而是滚动结束后这个 cell 相对于 tableVIew 的位置。比如说设置为 UITableViewScrollPoistionTop,那么滚动结束后这个 cell 就会处于屏幕最上面。如果设置为 UITableViewScrollViewPositionNone,那么 tableView 就不会滚动。显然我们需要设置为 UITableViewScrollPositionBottom。


看到这里,初级的人会继续往下看,中级的人则会停下来去思考一下现在自己能不能解决。


我们先说简单的,假设评论永远只有一行,此时我们只需要考虑键盘弹出来以后让 tableVIew 滚动就行了。

那要怎么做呢?监听键盘。

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didShowKey:) name:UIKeyboardDidChangeFrameNotification object:nil];
}

当键盘的 frame 发生变化时,就会调用我们自定义的 didShowKey:方法。

- (void)didShowKey:(NSNotification *)notific {
    NSDictionary *userInfo = notific.userInfo;
    // Get the origin of the keyboard.
    CGRect rect = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
    CGFloat h = 0.0f;
    if (fabs(rect.origin.y - ScreenHeight) < 1e-3) {
        self.tableView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
    } else {
        h = fmax(self.commentInputView.frame.size.height, 60.0f);
        self.tableView.frame = CGRectMake(0, 0, ScreenWidth, rect.origin.y - h + 44);
    }
    [self.tableView selectRowAtIndexPath:self.selectedIndexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
    
    // Get the duration of the animation.
    NSTimeInterval duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    [UIView animateWithDuration:duration animations:^{
        self.commentInputView.frame = CGRectMake(0, rect.origin.y - h, ScreenWidth, h);
    }];
}


参数 notific 是 NSNotification 类型的,在这里我们要用到它的两个属性,一个是键盘的 origin.y,也就是离屏幕上方的距离。另一个是键盘弹出时动画的时间 duration。我们要设置评论输入框的动画时间和这个一样,这样看起来才会显得流畅。


从代码中我们可以看到,其实当键盘弹出来时,我们是把 tableView 的高度变小了,然后让对应的 cell 滚动到 tableView 的下面。当收回键盘时,再把 tableView 的高度变回为屏幕的高度即可。


那么如果评论有多行怎么办?

我们需要实现 UITextViewDelegate 中的两个方法:

1. - (void)textViewDidChange:(UITextView *)textView; 

2. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;


在这两个方法中动态计算 textView 中的文字高度(具体计算方法见我的另一篇文章:传送门),然后通过代理或者block,把高度返回给 controller,在 controller 中再次修改 tableView 的高度即可。


举个栗子��吧:

- (void)textViewDidChange:(UITextView *)textView {
    CGFloat height = [self contentSizeOfText:textView.text];
    _offsetY = self.frame.origin.y;
    if (height != self.frame.size.height) {
        _offsetY = _offsetY - (height - self.frame.size.height);
        self.frame = CGRectMake(0, _offsetY, [UIScreen mainScreen].bounds.size.width, height);
        if (self.changeFrame) {
            self.changeFrame(_offsetY);
        } 
    }
    
    textView.frame = CGRectMake(0, 0, self.frame.size.width, height);
}

contentSizeOfText:是自定义的计算指定文字高度的方法。

if (height != self.frame.size.height) 就是说,如果 textView 的高度发生了改变,那么我就需要把新的高度传给 controller。

在这里我们就举个 block 的栗子吧,changeFrame 就是我们自定义的 block,它有一个参数,这个参数就是我们需要传给 controller 的 textView 的高度。然后在 controller 中实现这个 block 即可:

__weak typeof(self) weakSelf = self;
_commentInputView.inputViewFrameChanged = ^(CGRect aFrame) {
    CGRect frame = weakSelf.tableView.frame;
    frame.size.height = aFrame.origin.y + 44;
    weakSelf.tableView.frame = frame;
    [weakSelf.tableView scrollToRowAtIndexPath:weakSelf.selectedIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
};

主要的部分就是这些,当然还有很多细节部分,完整的代码见我的 github:传送门。别忘了点击右上角的 star 哦~




你可能感兴趣的:(github,ios,动画,Objective-C,textview)