【最终效果预览】
【先决条件】
本文所有代码针对的具体场景为信息发布页,效果图中
红色 -- 标题,青色 -- 摘要, 蓝色 -- 图片描述, 其中图片可添加多张,在此不赘述。(表情切换、输入功能正常可用,由于涉及到具体项目信息,未展示。)
所有输入框,均为YYTextView。
【具体需求】
类Facebook 、微博头条文章的发布页,这个需求对于安卓端来说好像相对简单,但对iOS来说稍微有点困难。
【解决思路】
思路一:全局YYtextView 来实现,意思是整个发布页底层就是一个YYTextView,这样的好处是文本编辑等等体验都是无缝的。但YY有一个潜在问题,当内容渲染达到一定高度就会出现白板问题。 由于项目发布页实际上图片可能达到数百张,故放弃。
思路二:分析了微博的实现,最终和他们类似底层采用UITableView,Cell上放置YYTextView 来做。
【疑难点】
- UITableView 输入过程中如果采用reload方法来变高,会崩溃。
- Cell上文本增高时,UITableView 上移逻辑。
- 光标起始定位问题
【最终实现】
可参考相关源码 ,修改了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;
}