2222@interface QMUITextView ()
2323
2424@property (nonatomic , assign ) BOOL debug;
25- @property (nonatomic , assign ) BOOL textChangedBecauseOfPaste; // 标志本次触发对handleTextChange:的调用,是否因为粘贴
25+ @property (nonatomic , assign ) BOOL shouldRejectSystemScroll; // 如果在 handleTextChanged: 里主动调整 contentOffset,则为了避免被系统的自动调整覆盖,会利用这个标记去屏蔽系统对 setContentOffset: 的调用
2626@property (nonatomic , assign ) BOOL callingSizeThatFitsByAutoResizable; // 标志本次调用 sizeThatFits: 是因为 handleTextChange: 里计算高度导致的
2727
2828@property (nonatomic , strong ) UILabel *placeholderLabel;
@@ -217,7 +217,7 @@ - (void)handleTextChanged:(id)sender {
217217 // 计算高度
218218 if (self.autoResizable ) {
219219
220- // 注意,这里 iOS 8 及以下有兼容问题 ,请查看文件里的 sizeThatFits:
220+ // 注意,这里 iOS 10 以下有兼容问题 ,请查看文件里的 sizeThatFits:
221221 self.callingSizeThatFitsByAutoResizable = YES ;
222222 CGFloat resultHeight = [textView sizeThatFits: CGSizeMake (CGRectGetWidth (self .bounds), CGFLOAT_MAX )].height ;
223223 self.callingSizeThatFitsByAutoResizable = NO ;
@@ -231,27 +231,28 @@ - (void)handleTextChanged:(id)sender {
231231 }
232232 }
233233
234- // iOS7的textView在内容可滚动的情况下,最后一行输入时文字会跑到可视区域外,因此要修复一下
235- // 由于我们在文字换行的瞬间更改了输入框高度,所以即便内容不可滚动,换行瞬间contentOffset也是错的,所以这里完全接管了对contentOffset的自动调整
234+ // 系统的 UITextView 在文字可滚动的情况下在最后一行输入,文字是贴边的(并不会考虑 textContainerInset.bottom),所以这里接管了 UITextView 的文字滚动
236235 CGRect caretRect = [textView caretRectForPosition: textView.selectedTextRange.end];
237- if (self.debug ) NSLog (@" 调整前,caretRect.maxY = %f , contentOffset.y = %f , bounds.height = %f " , CGRectGetMaxY (caretRect), textView.contentOffset .y , CGRectGetHeight (textView.bounds ));
236+ if (self.debug ) NSLog (@" 调整前,caretRect.maxY = %.2f , contentOffset.y = %.2f , contentSize.height = %.2f bounds.height = %.2f " , CGRectGetMaxY (caretRect), textView.contentOffset .y , textView. contentSize . height , CGRectGetHeight (textView.bounds ));
238237
239238 CGFloat caretMarginBottom = self.textContainerInset .bottom ;
240- if (ceil ( CGRectGetMaxY (caretRect) + caretMarginBottom) >= textView.contentOffset .y + CGRectGetHeight (textView.bounds )) {
241- CGFloat contentOffsetY = MAX (0 , CGRectGetMaxY (caretRect) + caretMarginBottom - CGRectGetHeight (textView.bounds ));
242- if (self.debug ) NSLog (@" 调整后,contentOffset.y = %f " , contentOffsetY);
239+ if (CGRectGetMaxY (caretRect) + caretMarginBottom >= textView.contentOffset .y + CGRectGetHeight (textView.bounds )) {
240+ CGFloat contentOffsetY = fmax (0 , ceil ( CGRectGetMaxY (caretRect) + caretMarginBottom - CGRectGetHeight (textView.bounds ) ));
241+ if (self.debug ) NSLog (@" 调整后,contentOffset.y = %.2f , contentSize.height = %.2f " , contentOffsetY, textView. contentSize . height );
243242
244- // 如果是粘贴导致光标掉出可视区域,则用动画去调整它(如果不用动画会不准,因为此时contentSize还是错的)
245- // 如果是普通的键入换行导致光标掉出可视区域,则不用动画,否则会跳来跳去,但这会带来的问题就是换行没动画,不优雅😂
246- [textView setContentOffset: CGPointMake (textView.contentOffset.x, contentOffsetY) animated: self .textChangedBecauseOfPaste ? YES : NO ];
243+ self.shouldRejectSystemScroll = YES ;
244+ // 用 dispatch 延迟一下,因为在文字发生换行时,系统自己会做一些滚动,我们要延迟一点才能避免被系统的滚动覆盖
245+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW , (int64_t )(0 * NSEC_PER_SEC )), dispatch_get_main_queue (), ^{
246+ self.shouldRejectSystemScroll = NO ;
247+ [textView setContentOffset: CGPointMake (textView.contentOffset.x, contentOffsetY) animated: NO ];
248+ });
247249 }
248- self.textChangedBecauseOfPaste = NO ;
249250 }
250251}
251252
252253- (CGSize)sizeThatFits : (CGSize)size {
253- // iOS 8 调用 sizeThatFits: 会导致文字跳动,因此自己计算 https://github.com/QMUI/QMUI_iOS/issues/92
254- if (IOS_VERSION < 9.0 && IOS_VERSION >= 8 .0 && self.callingSizeThatFitsByAutoResizable ) {
254+ // iOS 10 以下调用 sizeThatFits: 会导致文字跳动,因此自己计算 https://github.com/QMUI/QMUI_iOS/issues/92
255+ if (IOS_VERSION < 10 .0 && self.callingSizeThatFitsByAutoResizable ) {
255256 CGFloat contentWidth = size.width - UIEdgeInsetsGetHorizontalValue (self.textContainerInset ) - UIEdgeInsetsGetHorizontalValue (self.contentInset );
256257 CGRect textRect = [self .attributedText boundingRectWithSize: CGSizeMake (contentWidth, CGFLOAT_MAX ) options: NSStringDrawingUsesLineFragmentOrigin context: nil ];
257258 CGSize resultSize = CGSizeMake (size.width , CGRectGetHeight (textRect) + UIEdgeInsetsGetVerticalValue (self.textContainerInset ) + UIEdgeInsetsGetVerticalValue (self.contentInset ));
@@ -287,11 +288,6 @@ - (void)updatePlaceholderLabelHidden {
287288 }
288289}
289290
290- - (void )paste : (id )sender {
291- self.textChangedBecauseOfPaste = YES ;
292- [super paste: sender];
293- }
294-
295291- (NSUInteger )lengthWithString : (NSString *)string {
296292 return self.shouldCountingNonASCIICharacterAsTwo ? string.qmui_lengthWhenCountingNonASCIICharacterAsTwo : string.length ;
297293}
@@ -422,6 +418,24 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
422418 }
423419}
424420
421+ - (void )setContentOffset : (CGPoint)contentOffset animated : (BOOL )animated {
422+ if (!self.shouldRejectSystemScroll ) {
423+ [super setContentOffset: contentOffset animated: animated];
424+ if (self.debug ) NSLog (@" %@ , contentOffset.y = %.2f " , NSStringFromSelector (_cmd), contentOffset.y );
425+ } else {
426+ if (self.debug ) NSLog (@" 被屏蔽的 %@ , contentOffset.y = %.2f " , NSStringFromSelector (_cmd), contentOffset.y );
427+ }
428+ }
429+
430+ - (void )setContentOffset : (CGPoint)contentOffset {
431+ if (!self.shouldRejectSystemScroll ) {
432+ [super setContentOffset: contentOffset];
433+ if (self.debug ) NSLog (@" %@ , contentOffset.y = %.2f " , NSStringFromSelector (_cmd), contentOffset.y );
434+ } else {
435+ if (self.debug ) NSLog (@" 被屏蔽的 %@ , contentOffset.y = %.2f " , NSStringFromSelector (_cmd), contentOffset.y );
436+ }
437+ }
438+
425439- (void )scrollViewDidZoom : (UIScrollView *)scrollView {
426440 if ([self .originalDelegate respondsToSelector: _cmd ]) {
427441 [self .originalDelegate scrollViewDidZoom: scrollView];
0 commit comments