【最终效果预览】 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。