【YYTextView 自增高編輯 && 類IQKeyboardManager功能】

【最終效果預(yù)覽】


實現(xiàn)效果預(yù)覽

【先決條件】
本文所有代碼針對的具體場景為信息發(fā)布頁,效果圖中
紅色 -- 標(biāo)題,青色 -- 摘要, 藍(lán)色 -- 圖片描述, 其中圖片可添加多張,在此不贅述。(表情切換、輸入功能正??捎?,由于涉及到具體項目信息,未展示。)
所有輸入框,均為YYTextView。

【具體需求】
類Facebook 、微博頭條文章的發(fā)布頁,這個需求對于安卓端來說好像相對簡單,但對iOS來說稍微有點困難。

【解決思路】
思路一:全局YYtextView 來實現(xiàn),意思是整個發(fā)布頁底層就是一個YYTextView,這樣的好處是文本編輯等等體驗都是無縫的。但YY有一個潛在問題,當(dāng)內(nèi)容渲染達(dá)到一定高度就會出現(xiàn)白板問題。 由于項目發(fā)布頁實際上圖片可能達(dá)到數(shù)百張,故放棄。

思路二:分析了微博的實現(xiàn),最終和他們類似底層采用UITableView,Cell上放置YYTextView 來做。

【疑難點】

  1. UITableView 輸入過程中如果采用reload方法來變高,會崩潰。
  2. Cell上文本增高時,UITableView 上移邏輯。
  3. 光標(biāo)起始定位問題

【最終實現(xiàn)】
可參考相關(guān)源碼 ,修改了YYTextView源碼實現(xiàn)了類似IQKeyboardManager 的自動調(diào)整功能(當(dāng)然是一定條件下)。

【控制器需要處理的代碼】

/// 注意相關(guān)的YYTextView實例 scrollEnabled 要設(shè)置為 NO !!!
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    //開啟調(diào)整功能
    [YYTextView setAutoCursorEnable:YES];
}

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

【UITableView 需要處理的代碼】

//自增高 更新YYTextView 高度
//觸發(fā)源方法
- (void)textViewDidChange:(YYTextView *)textView {
  ....
  CGFloat fltTextHeight = textView.textLayout.textBoundingSize.height;
  textView.scrollEnabled = NO; //必須設(shè)置為NO

  //這里動畫的作用是抵消,YYTextView 內(nèi)部動畫 防止視覺上的跳動。
  [UIView animateWithDuration:0.25 animations:^{
            textView.height = fltTextHeight;
   } completion:^(BOOL finished) {

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

  //獲取底層TableView 
  UITableView tableView = ....; // 假設(shè),具體怎樣設(shè)計代碼自行處理。
  //重要的部分 
  [tableView beginUpdates];
  //假設(shè)這里 textView 是放置在UITableView 的HeaderView上
  tableView.tableHeaderSize = layoutSize;
  [tableView endUpdates];
  ....

}

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

- (void)_scrollRangeToVisible:(YYTextRange *)range {
    if (!range) return;
    //獲取頂層ScrollView
    UIScrollView *scTop = [self _findTopScrollView];
    //從內(nèi)部布局容器中獲取 光標(biāo)位置
    CGRect rect = [_innerLayout rectForRange:range];
    if (CGRectIsNull(rect)) return;
    //轉(zhuǎn)換區(qū)域
    rect = [self _convertRectFromLayout:rect];
    //轉(zhuǎn)換區(qū)域到頂層SC
    CGRect rectTop = [_containerView convertRect:rect toView:scTop];
    
    //轉(zhuǎn)換區(qū)域到內(nèi)部文本容器
    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;
    //是否修改內(nèi)間距
    BOOL insetModified = NO;

    //鍵盤管理器
    YYTextKeyboardManager *mgr = [YYTextKeyboardManager defaultManager];
    
    //需要移動頂層容器的情況
    if (!self.scrollEnabled && [YYTextView autoCursorEnable]) {
        ///**添加自動調(diào)整外部頂層 UITableView 偏移,用來實現(xiàn)和IQKeyboard類似的功能
        //滾動鎖定狀態(tài)下
        //鍵盤彈起情況下
        CGRect topBounds = scTop.bounds;
        topBounds.origin = CGPointZero;
        //保存原始間距數(shù)據(jù)
        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;
        //區(qū)域交集
        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;
        //區(qū)域交集
        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
                //獲取當(dāng)前內(nèi)間距數(shù)據(jù)
                UIEdgeInsets originalContentInset = self.contentInset;
                UIEdgeInsets originalScrollIndicatorInsets = self.scrollIndicatorInsets;
                
                //默認(rèn)值為NO
                if (_insetModifiedByKeyboard) {
                    //從上一次偏移中獲取內(nèi)間距數(shù)據(jù)
                    originalContentInset = _originalContentInset;
                    originalScrollIndicatorInsets = _originalScrollIndicatorInsets;
                }
                
                if (originalContentInset.bottom < inter.size.height + extend) {
                    //當(dāng)前光標(biāo)被鍵盤遮擋
                    insetModified = YES;
                    if (!_insetModifiedByKeyboard) {
                        //第一次 保存原始內(nèi)間距等設(shè)置
                        _insetModifiedByKeyboard = YES;
                        _originalContentInset = self.contentInset;
                        _originalScrollIndicatorInsets = self.scrollIndicatorInsets;
                    }
                    
                    CGFloat fltDiffBottom = CGRectGetMaxY(bounds) - CGRectGetMaxY(inter);
                    
                    //內(nèi)間距更新
                    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;
    }
    ///由于不同版本的層級關(guān)系,這里可能會獲取到UITableViewWrapperView導(dǎo)致后續(xù)代碼出錯。
    ///correct the obj
    if ([viewTemp isKindOfClass: NSClassFromString(@"UITableViewWrapperView")]) {
        viewTemp = viewTemp.superview;
    }
    topScrollView = (UIScrollView *)viewTemp;
    return topScrollView;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,171評論 3 119
  • 唐詩與發(fā)展到現(xiàn)代設(shè)計的理念極為相似:簡潔、意境悠遠(yuǎn),有韻律美。 盡管詩只是唐朝時的通俗讀物,但在我心中它流傳至今已...
    山石陸紀(jì)閱讀 342評論 0 1
  • 狼人殺地廳級黨政領(lǐng)導(dǎo)第一次培訓(xùn)會議總結(jié) 狼人殺地廳級各單位: 第一次培訓(xùn)會議現(xiàn)已告一段落,經(jīng)過與會人員的共同努...
    Volca閱讀 514評論 0 1
  • CATransition 父類是CAAnimation 轉(zhuǎn)場動畫——CATransition CATransiti...
    翻這個墻閱讀 806評論 0 0
  • 本周閱讀季羨林的《一生的遠(yuǎn)行》,其中一章節(jié)季老先生與1980年返回哥廷根的時候?qū)懙摹!拔艺媸侨f萬沒有想到,經(jīng)過了三...
    康小妮子閱讀 343評論 0 1

友情鏈接更多精彩內(nèi)容