iOS开发iOS学习专题

【YYTextView 自增高编辑 && 类IQ

2018-08-07  本文已影响67人  Saylor_Lone

【最终效果预览】


实现效果预览

【先决条件】
本文所有代码针对的具体场景为信息发布页,效果图中
红色 -- 标题,青色 -- 摘要, 蓝色 -- 图片描述, 其中图片可添加多张,在此不赘述。(表情切换、输入功能正常可用,由于涉及到具体项目信息,未展示。)
所有输入框,均为YYTextView。

【具体需求】
类Facebook 、微博头条文章的发布页,这个需求对于安卓端来说好像相对简单,但对iOS来说稍微有点困难。

【解决思路】
思路一:全局YYtextView 来实现,意思是整个发布页底层就是一个YYTextView,这样的好处是文本编辑等等体验都是无缝的。但YY有一个潜在问题,当内容渲染达到一定高度就会出现白板问题。 由于项目发布页实际上图片可能达到数百张,故放弃。

思路二:分析了微博的实现,最终和他们类似底层采用UITableView,Cell上放置YYTextView 来做。

【疑难点】

  1. UITableView 输入过程中如果采用reload方法来变高,会崩溃。
  2. Cell上文本增高时,UITableView 上移逻辑。
  3. 光标起始定位问题

【最终实现】
可参考相关源码 ,修改了YYTextView源码实现了类似IQKeyboardManager 的自动调整功能(当然是一定条件下)。

【控制器需要处理的代码】

/// 注意相关的YYTextView实例 scrollEnabled 要设置为 NO !!!
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    //开启调整功能
    [YYTextView setAutoCursorEnable:YES];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [YYTextView setAutoCursorEnable:NO];
}

【UITableView 需要处理的代码】

//自增高 更新YYTextView 高度
//触发源方法
- (void)textViewDidChange:(YYTextView *)textView {
  ....
  CGFloat fltTextHeight = textView.textLayout.textBoundingSize.height;
  textView.scrollEnabled = NO; //必须设置为NO

  //这里动画的作用是抵消,YYTextView 内部动画 防止视觉上的跳动。
  [UIView animateWithDuration:0.25 animations:^{
            textView.height = fltTextHeight;
   } completion:^(BOOL finished) {

  }];
  ....
  ....  
  
  CGSize layoutSize = originalSize;
  layoutSize.height = topInset + bottomInset + fltTextHeight;

  //获取底层TableView 
  UITableView tableView = ....; // 假设,具体怎样设计代码自行处理。
  //重要的部分 
  [tableView beginUpdates];
  //假设这里 textView 是放置在UITableView 的HeaderView上
  tableView.tableHeaderSize = layoutSize;
  [tableView endUpdates];
  ....

}

【YYTextView的改动】
主要修改部分

- (void)_scrollRangeToVisible:(YYTextRange *)range {
    if (!range) return;
    //获取顶层ScrollView
    UIScrollView *scTop = [self _findTopScrollView];
    //从内部布局容器中获取 光标位置
    CGRect rect = [_innerLayout rectForRange:range];
    if (CGRectIsNull(rect)) return;
    //转换区域
    rect = [self _convertRectFromLayout:rect];
    //转换区域到顶层SC
    CGRect rectTop = [_containerView convertRect:rect toView:scTop];
    
    //转换区域到内部文本容器
    rect = [_containerView convertRect:rect toView:self];

    if (rect.size.width < 1) {
        rect.size.width = 1;
        rectTop.size.width = 1;
    }
    if (rect.size.height < 1) {
        rect.size.height = 1;
        rectTop.size.height = 1;
    }
    
    CGFloat extend = 3;
    //是否修改内间距
    BOOL insetModified = NO;

    //键盘管理器
    YYTextKeyboardManager *mgr = [YYTextKeyboardManager defaultManager];
    
    //需要移动顶层容器的情况
    if (!self.scrollEnabled && [YYTextView autoCursorEnable]) {
        ///**添加自动调整外部顶层 UITableView 偏移,用来实现和IQKeyboard类似的功能
        //滚动锁定状态下
        //键盘弹起情况下
        CGRect topBounds = scTop.bounds;
        topBounds.origin = CGPointZero;
        //保存原始间距数据
        if(!_isAutoCursorEnable){
            _isAutoCursorEnable = YES;
            _originalTopContentInset = scTop.contentInset;
            _originalTopScrollIndicatorInsets = scTop.scrollIndicatorInsets;
        }
        
        CGRect kbTopRect = [mgr convertRect:mgr.keyboardFrame toView:scTop];
        kbTopRect.origin.y -= _extraAccessoryViewHeight;
        kbTopRect.size.height += _extraAccessoryViewHeight;
        //修正键盘位置
        kbTopRect.origin.x -= scTop.contentOffset.x;
        kbTopRect.origin.y -= scTop.contentOffset.y;
        //区域交集
        CGRect inter = CGRectIntersection(topBounds, kbTopRect);
        UIEdgeInsets newTopInset = UIEdgeInsetsZero;
        newTopInset.bottom = inter.size.height + extend;
        
        UIViewAnimationOptions curve;
        if (kiOS7Later) {
            curve = 7 << 16;
        } else {
            curve = UIViewAnimationOptionCurveEaseInOut;
        }
        [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | curve animations:^{
    
            [scTop setContentInset:newTopInset];
            [scTop setScrollIndicatorInsets:newTopInset];
            [scTop scrollRectToVisible:CGRectInset(rectTop, -extend, -extend) animated:NO];
            
        } completion:NULL];
        
        return;
    }
    
    if (mgr.keyboardVisible && self.window && self.superview && self.isFirstResponder && !_verticalForm) {
        //键盘弹起情况下
        CGRect bounds = self.bounds;
        bounds.origin = CGPointZero;
        CGRect kbRect = [mgr convertRect:mgr.keyboardFrame toView:self];
        kbRect.origin.y -= _extraAccessoryViewHeight;
        kbRect.size.height += _extraAccessoryViewHeight;
        //修正键盘位置
        kbRect.origin.x -= self.contentOffset.x;
        kbRect.origin.y -= self.contentOffset.y;
        //区域交集
        CGRect inter = CGRectIntersection(bounds, kbRect);
        if (!CGRectIsNull(inter) && inter.size.height > 1 && inter.size.width > extend) { // self is covered by keyboard
            if (CGRectGetMinY(inter) > CGRectGetMinY(bounds)) { // keyboard below self.top
                //获取当前内间距数据
                UIEdgeInsets originalContentInset = self.contentInset;
                UIEdgeInsets originalScrollIndicatorInsets = self.scrollIndicatorInsets;
                
                //默认值为NO
                if (_insetModifiedByKeyboard) {
                    //从上一次偏移中获取内间距数据
                    originalContentInset = _originalContentInset;
                    originalScrollIndicatorInsets = _originalScrollIndicatorInsets;
                }
                
                if (originalContentInset.bottom < inter.size.height + extend) {
                    //当前光标被键盘遮挡
                    insetModified = YES;
                    if (!_insetModifiedByKeyboard) {
                        //第一次 保存原始内间距等设置
                        _insetModifiedByKeyboard = YES;
                        _originalContentInset = self.contentInset;
                        _originalScrollIndicatorInsets = self.scrollIndicatorInsets;
                    }
                    
                    CGFloat fltDiffBottom = CGRectGetMaxY(bounds) - CGRectGetMaxY(inter);
                    
                    //内间距更新
                    UIEdgeInsets newInset = originalContentInset;
                    UIEdgeInsets newIndicatorInsets = originalScrollIndicatorInsets;
                    
                    //固定为键盘高度
                    newInset.bottom = inter.size.height + extend + fltDiffBottom;
                    newIndicatorInsets.bottom = newInset.bottom;

                    UIViewAnimationOptions curve;
                    if (kiOS7Later) {
                        curve = 7 << 16;
                    } else {
                        curve = UIViewAnimationOptionCurveEaseInOut;
                    }
                    [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | curve animations:^{
                        
                        [super setContentInset:newInset];
                        [super setScrollIndicatorInsets:newIndicatorInsets];
                        [self scrollRectToVisible:CGRectInset(rect, -extend, -extend) animated:NO];

                    } completion:NULL];
                }
            }
        }
    }
    if (!insetModified) {
        [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut animations:^{
            
            [self _restoreInsetsAnimated:NO];
            [self scrollRectToVisible:CGRectInset(rect, -extend, -extend) animated:NO];
        } completion:NULL];
    }
}

/// Restore contents insets if modified by keyboard.
- (void)_restoreInsetsAnimated:(BOOL)animated {
    if (_insetModifiedByKeyboard) {
        _insetModifiedByKeyboard = NO;
        if (animated) {
            [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut  animations:^{
                [super setContentInset:_originalContentInset];
                [super setScrollIndicatorInsets:_originalScrollIndicatorInsets];
            } completion:NULL];
        } else {
            [super setContentInset:_originalContentInset];
            [super setScrollIndicatorInsets:_originalScrollIndicatorInsets];
        }
    }
    
    if ([YYTextView autoCursorEnable]) {
        
        UIScrollView *scTop = [self _findTopScrollView];
        
        if (animated) {
            [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut  animations:^{
                //还原顶层容器间距
                [scTop setContentInset:_originalTopContentInset];
                [scTop setScrollIndicatorInsets:_originalTopScrollIndicatorInsets];
                
            } completion:NULL];
        } else {
            //还原顶层容器间距
            [scTop setContentInset:_originalTopContentInset];
            [scTop setScrollIndicatorInsets:_originalTopScrollIndicatorInsets];
        }
    }
    
}

/// Find top scrollView, Implement function like IQKeyboard.
- (UIScrollView *)_findTopScrollView {
    UIScrollView *topScrollView = nil;
    UIView *viewTemp = self.superview;
    while (viewTemp && ![viewTemp isKindOfClass:[UIScrollView class]]) {
        viewTemp = viewTemp.superview;
    }
    ///
    这里需要校正     
    ///
    topScrollView = (UIScrollView *)viewTemp;
    
    return topScrollView;
}

最后希望能帮到有需要的人,因为是直接在工作项目中修改所以暂无Demo。
欢迎,探讨~~~

【更改】

/// Find top scrollView, Implement function like IQKeyboard.
- (UIScrollView *)_findTopScrollView {
    UIScrollView *topScrollView = nil;
    UIView *viewTemp = self.superview;
    while (viewTemp && ![viewTemp isKindOfClass:[UIScrollView class]]) {
        viewTemp = viewTemp.superview;
    }
    ///由于不同版本的层级关系,这里可能会获取到UITableViewWrapperView导致后续代码出错。
    ///correct the obj
    if ([viewTemp isKindOfClass: NSClassFromString(@"UITableViewWrapperView")]) {
        viewTemp = viewTemp.superview;
    }
    topScrollView = (UIScrollView *)viewTemp;
    return topScrollView;
}
上一篇下一篇

猜你喜欢

热点阅读