【最終效果預覽】 git
【先決條件】 本文全部代碼針對的具體場景爲信息發佈頁,效果圖中 紅色 -- 標題,青色 -- 摘要, 藍色 -- 圖片描述, 其中圖片可添加多張,在此不贅述。(表情切換、輸入功能正常可用,因爲涉及到具體項目信息,未展現。) 全部輸入框,均爲YYTextView。github
【具體需求】 類Facebook 、微博頭條文章的發佈頁,這個需求對於安卓端來講好像相對簡單,但對iOS來講稍微有點困難。bash
【解決思路】 思路一:全局YYtextView 來實現,意思是整個發佈頁底層就是一個YYTextView,這樣的好處是文本編輯等等體驗都是無縫的。但YY有一個潛在問題,當內容渲染達到必定高度就會出現白板問題。 因爲項目發佈頁實際上圖片可能達到數百張,故放棄。app
思路二:分析了微博的實現,最終和他們相似底層採用UITableView,Cell上放置YYTextView 來作。佈局
【疑難點】動畫
【最終實現】 可參考相關源碼 ,修改了YYTextView源碼實現了相似IQKeyboardManager 的自動調整功能(固然是必定條件下)。ui
【控制器須要處理的代碼】spa
/// 注意相關的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的改動】 主要修改部分rest
- (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;
}
///correct the result
if ([viewTemp isKindOfClass: NSClassFromString(@"UITableViewWrapperView")]) {
viewTemp = viewTemp.superview;
}
topScrollView = (UIScrollView *)viewTemp;
return topScrollView;
}
複製代碼
最後但願能幫到有須要的人,由於是直接在工做項目中修改因此暫無Demo。