【YYTextView 自增高编辑 && 类IQKeyboardManager功能】

【最终效果预览】


实现效果预览

【先决条件】
本文所有代码针对的具体场景为信息发布页,效果图中
红色 -- 标题,青色 -- 摘要, 蓝色 -- 图片描述, 其中图片可添加多张,在此不赘述。(表情切换、输入功能正常可用,由于涉及到具体项目信息,未展示。)
所有输入框,均为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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 唐诗与发展到现代设计的理念极为相似:简洁、意境悠远,有韵律美。 尽管诗只是唐朝时的通俗读物,但在我心中它流传至今已...
    山石陆纪阅读 293评论 0 1
  • 狼人杀地厅级党政领导第一次培训会议总结 狼人杀地厅级各单位: 第一次培训会议现已告一段落,经过与会人员的共同努...
    Volca阅读 453评论 0 1
  • CATransition 父类是CAAnimation 转场动画——CATransition CATransiti...
    翻这个墙阅读 752评论 0 0
  • 本周阅读季羡林的《一生的远行》,其中一章节季老先生与1980年返回哥廷根的时候写的。“我真是万万没有想到,经过了三...
    康小妮子阅读 286评论 0 1