【最終效果預(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 來做。
【疑難點】
- UITableView 輸入過程中如果采用reload方法來變高,會崩潰。
- Cell上文本增高時,UITableView 上移邏輯。
- 光標(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;
}