[[IQKeyboardManager sharedManager] registerTextFieldViewClass:[YYTextView class]
didBeginEditingNotificationName:YYTextViewTextDidBeginEditingNotification
didEndEditingNotificationName:YYTextViewTextDidEndEditingNotification];
//默認關閉IQKeyboard 在須要的界面啓用
[IQKeyboardManager sharedManager].enable = NO;
[[IQKeyboardManager sharedManager] setEnableAutoToolbar:NO];
複製代碼
佈局問題ide
若是配合AutoLayout 計算Size後務必更新約束工具
目前可以保證沒有問題的方法 SizeThatFits 【注意】若是文本以大段空白(\n)做爲結尾,可能致使計算出來的高度有誤,因此實際中最好進行空白段落處理。oop
//preferLayoutWidth佈局指定寬度
CGSize fitSize =[self.txtContent sizeThatFits:CGSizeMake(preferLayoutWidth,CGFLOAT_MAX)];
//寬度修正_當文本中包含大量空格時,Size會出問題
float fixWidth = fitSize.width;
if(fitSize.width > preferLayoutWidth)
{
fixWidth = preferLayoutWidth;
}
複製代碼
輸入標點符號後,鍵盤不自動切換的問題。(對比UITextView)參見點#392佈局
增長屬性NumberOfLine、TruncationToken用於知足行數限制的個別需求(主要應用場景,帶有選擇菜單、文本選擇、放大鏡等效果的YYLabel)參見點#392測試
YYTextView的SuperView爲UIScrollView時,文本選擇及複製菜單的問題。
修改YYKit源碼,增長代理方法
YYTextView.h
@protocol YYTextViewSelectMenuDelegate
/** *@brief在即將進入選擇文本選擇狀態時調用 */
-(void)textViewWillShowSelectMenu:(YYTextView *)textView;
/** *@brief在即將推出選擇文本選擇狀態時調用 */
-(void)textViewWillHideSelectMenu:(YYTextView *)textView;
@end
YYTextView.m
-(void)_showMenu {
…//代碼塊最後
if([self.delegateSelectMenu respondsToSelector:@selector(textViewWillShowSelectMenu:)]){
[self.delegateSelectMenu textViewWillShowSelectMenu:self];
}
}
-(void)_hideMenu {
if(_state.showingMenu){
_state.showingMenu = NO;
UIMenuController *menu =[UIMenuController sharedMenuController];
[menu setMenuVisible:NO animated:YES];
if([self.delegateSelectMenu respondsToSelector:@selector(textViewWillHideSelectMenu:)]){
[self.delegateSelectMenu textViewWillHideSelectMenu:self];
}
}
if(_containerView.isFirstResponder){
_state.ignoreFirstResponder = YES;
[_containerView resignFirstResponder];// it will call[self becomeFirstResponder],ignore it temporary.
_state.ignoreFirstResponder = NO;
}
}
複製代碼
需求YYTextView添加控件後,須要自動更新ContentSize
YYTextView子類重寫
-(void)setContentSize:(CGSize)contentSize{
//不影響其它的位置
//viewExPanle子類屬性用於添加控件
if(!self.viewExPanle){
[super setContentSize:contentSize];
return;
}
//實際文本內容Size
CGSize txtSize = self.textLayout.textBoundingSize;
if(txtSize.width > self.preferredMaxLayoutWidth){
txtSize.width = self.preferredMaxLayoutWidth;
}
CGFloat fltExControlHeight = 0;
//這裏必需要獲取真實的數據
if(!self.viewExPanle.hidden){
fltExControlHeight += self.viewExPanle.height
self.fltExTopMargin;//fltExTopMargin子類屬性,控件與文本的間距
}
txtSize.height += fltExControlHeight;
[super setContentSize:txtSize];
}
//子類方法提供外部調用以更新ContentSize
-(void)updateContentSizeByExPanleChange{
if(!self.viewExPanle){
return;
}
//只爲觸發方法設置
[self setContentSize:CGSizeZero];
}
複製代碼
爲了調整文本行間距在子類中設置了linePositionModifier,能夠給子類暴露一些方法,可自定義行間距參見YYKit Demo WBTextLinePositionModifier
爲了適應需求(表情特麼不規範!!!),表情在文本中寬度自由
YYTextUtilities.h
新增
/** Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size. It may used to create custom emoji. @param fontSizeThe specified font size. @param imageScale圖片寬高比 @return The font glyph bounding rect. 高度統一,寬度自由 @saylor--爲了適應寬度自由的圖片做爲表情 */
static inline CGRect YYEmojiGetGlyphBoundingLongRectWithFontSize(CGFloat fontSize,CGFloat imageScale){
CGRect rect;
rect.origin.x = 0;
rect.size.height = YYEmojiGetAscentWithFontSize(fontSize);
rect.size.width = rect.size.height * imageScale*0.75;
if(fontSize < 16){
rect.origin.y = -0.2525 * fontSize;
} else if(16 <= fontSize && fontSize <= 24){
rect.origin.y = 0.1225 * fontSize -6;
} else {
rect.origin.y = -0.1275 * fontSize;
}
return rect;
}
NSAttributedString+YYText.m
+(NSMutableAttributedString *)attachmentStringWithEmojiImage:(UIImage *)image
fontSize:(CGFloat)fontSize{
…
//原方法
//CGRect bounding1 = YYEmojiGetGlyphBoundingRectWithFontSize(fontSize);
//計算圖片寬高比
CGFloat imageScale = image.size.width / image.size.height;
CGRect bounding = YYEmojiGetGlyphBoundingLongRectWithFontSize(fontSize,imageScale);
….
}
複製代碼
//添加IQKeyboard支持 YYTextView
[[IQKeyboardManager sharedManager] registerTextFieldViewClass:[YYTextView class]
didBeginEditingNotificationName:YYTextViewTextDidBeginEditingNotification
didEndEditingNotificationName:YYTextViewTextDidEndEditingNotification];
//默認關閉IQKeyboard 在須要的界面啓用
[IQKeyboardManager sharedManager].enable = NO;
[[IQKeyboardManager sharedManager] setEnableAutoToolbar:NO];
複製代碼
#YYLabel
異步繪製,在列表刷新時YYLabel會有閃爍的問題。
開啓異步繪製的相關屬性
self.displaysAsynchronously = YES;
//2018.01.04 注:忽略該句代碼,否者變動字體、字號等屬性不會觸發UI更新。
//self.ignoreCommonProperties = YES;
self.fadeOnAsynchronouslyDisplay = NO;
self.fadeOnHighlight = NO;
//重寫方法
-(void)setText:(NSString *)text {
//用於處理異步繪製刷新閃爍的問題
//strCache自行聲明
if([self.strCache isEqualToString:text]){
return;
//防止將內容置爲nil
if(text.length == 0){
text = @"";
}
self.strCache = text;
//[super setText:text]; //這裏能夠註釋掉
NSAttributedString *atrContent =[[NSAttributedString alloc]initWithString:text];
[self fixLayoutSizeWithContent:atrContent];
}
//對於使用如下方式工做的朋友須要注意!!! 不然會引發刷新閃爍的問題
- (void)setAttributedText:(NSAttributedString *)attributedText {
//用於處理 異步繪製 刷新閃爍的問題
/* 注意:該段代碼存在問題,相同文本內容,但不一樣的屬性設置會致使誤判。 if ([self.atrCache isEqualToAttributedString:attributedText] || [self.atrCache.string isEqualToString:attributedText.string]) { return; } */
//變動
if ([self.atrCache isEqualToAttributedString:attributedText]) {
return;
}
//防止將內容置爲nil
if (attributedText.length == 0) {
attributedText = [[NSAttributedString alloc]initWithString:@""];
}
self.atrCache = attributedText;
//使用富文本進行賦值時,因此須要禁用自定義的解析,不然解析代碼會覆蓋掉富文本的相關屬性。如:字體、顏色等
[self fixLayoutSizeWithContent:attributedText];
}
複製代碼
-(void)fixLayoutSizeWithContent:(NSAttributedString *)strContent {
CGFloat perfixLayoutWidth = self.preferredMaxLayoutWidth;//佈局指定寬度
//次要-Frame佈局
if(self.size.width && !perfixLayoutWidth){
perfixLayoutWidth = self.size.width;
}
NSMutableAttributedString *text =[[NSMutableAttributedString alloc]initWithAttributedString:strContent];
//這裏注意要將原有的富文本佈局屬性持有
text.alignment = self.textAlignment;
text.font = self.font;
text.textColor = self.textColor;
NSRange selectedRange = text.rangeOfAll;
//我這裏有一個自定義的解析器
//此處增長一個判斷,主要是爲了防止富文本賦值的狀況下,解析會覆蓋掉相關的屬性。並且須要解析的場景,簡單文本賦值應該都能處理了。!!! 控制屬性,自行添加
if(isEnableParser){
[self.customTextParser parseText:text selectedRange:&selectedRange];
}
YYTextContainer *container =[YYTextContainer new];
container.size = CGSizeMake(perfixLayoutWidth,HUGE);
container.linePositionModifier = self.customTextModifier;
container.maximumNumberOfRows = self.numberOfLines;
container.truncationType = YYTextTruncationTypeEnd;
YYTextLayout *textLayout =[YYTextLayout layoutWithContainer:container text:text];
CGSize layoutSize = textLayout.textBoundingSize;
//寬度修正
if(layoutSize.width > perfixLayoutWidth){
layoutSize.width = perfixLayoutWidth;
}
//這個方法參照YYKit Demo中結果高度始終有問題,因此未採用
//CGFloat height =[self.customTextModifier heightForLineCount:textLayout.rowCount];
//iOS 10高度修正,發現iOS10和大屏6P會出現高度問題,若是不修正一個奇怪的現象就是,YYLabel的高亮文本在點擊時會有跳動。但YYTextView並無發現這樣的狀況
if((DP_IS_IOS10||DP_IS_IPHONE6PLUS)&& textLayout.rowCount > 1){
//height += 4;
layoutSize.height += 4;//這個值也是調來調去
}
//最奇葩的是這裏賦值必定是這個順序,若是順序不對也是Bug多多
self.size = layoutSize;
self.textLayout = textLayout;
}
複製代碼
配合AutoLayout:記得在賦值text後,更新Size約束
#更新
搜狗輸入法,全鍵盤模式下發現輸入英文時,會在鍵盤的聯想狀態欄處出現連字狀況。
系統輸入法,全鍵盤模式下發現點擊聯想欄出的單詞輸入時,單詞會被空格打斷。
系統輸入法的問題,暫時還原。由於會引發其餘問題...問題修復,問題本質緣由是替換文本完成後_selectedTextRange有些狀況下沒有獲得更新。
問題跟蹤
搜狗輸入法點擊聯想欄處輸入單詞的流程和系統是不一樣的,搜狗是先調用-(void)deleteBackward()
方法,該方法會被調用屢次刪除以前輸入的內容(上次輸入的內容),而後再調用- (void)insertText:(NSString *)text
方法插入聯想詞。
系統輸入法,不會走上述流程,只有一步調用- (void)replaceRange:(YYTextRange *)range withText:(NSString *)text
來完成文本替換。
///其實以上問題的根本緣由,基本上就是下面這幾個代理方法的調用問題
@protocol UITextInputDelegate <NSObject>
- (void)selectionWillChange:(nullable id <UITextInput>)textInput;
- (void)selectionDidChange:(nullable id <UITextInput>)textInput;
- (void)textWillChange:(nullable id <UITextInput>)textInput;
- (void)textDidChange:(nullable id <UITextInput>)textInput;
@end
複製代碼
- (void)_replaceRange:(YYTextRange *)range withText:(NSString *)text notifyToDelegate:(BOOL)notify
- (BOOL)_parseText
///處理後的代碼
- (void)_replaceRange:(YYTextRange *)range withText:(NSString *)text notifyToDelegate:(BOOL)notify{
if (_isExcludeNeed) {
notify = NO;
}
if (NSEqualRanges(range.asRange, _selectedTextRange.asRange)) {
//這裏的代理方法須要註釋掉 -- 注意:該處註釋會引發,iOS13下的雙光標問題。不要再參考了!!!
// if (notify) [_inputDelegate selectionWillChange:self];
NSRange newRange = NSMakeRange(0, 0);
newRange.location = _selectedTextRange.start.offset + text.length;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
// if (notify) [_inputDelegate selectionDidChange:self];
} else {
if (range.asRange.length != text.length) {
if (notify) [_inputDelegate selectionWillChange:self];
NSRange unionRange = NSIntersectionRange(_selectedTextRange.asRange, range.asRange);
if (unionRange.length == 0) {
// no intersection
if (range.end.offset <= _selectedTextRange.start.offset) {
NSInteger ofs = (NSInteger)text.length - (NSInteger)range.asRange.length;
NSRange newRange = _selectedTextRange.asRange;
newRange.location += ofs;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
}
} else if (unionRange.length == _selectedTextRange.asRange.length) {
// target range contains selected range
_selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(range.start.offset + text.length, 0)];
} else if (range.start.offset >= _selectedTextRange.start.offset &&
range.end.offset <= _selectedTextRange.end.offset) {
// target range inside selected range
NSInteger ofs = (NSInteger)text.length - (NSInteger)range.asRange.length;
NSRange newRange = _selectedTextRange.asRange;
newRange.length += ofs;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
} else {
// interleaving
if (range.start.offset < _selectedTextRange.start.offset) {
NSRange newRange = _selectedTextRange.asRange;
newRange.location = range.start.offset + text.length;
newRange.length -= unionRange.length;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
} else {
NSRange newRange = _selectedTextRange.asRange;
newRange.length -= unionRange.length;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
}
}
_selectedTextRange = [self _correctedTextRange:_selectedTextRange];
if (notify) [_inputDelegate selectionDidChange:self];
}
}
//這裏的代理方法 須要判斷,若是設置瞭解析器則不執行,分析解析器方法中重複執行會有問題。
if (!self.textParser) [_inputDelegate textWillChange:self];
NSRange newRange = NSMakeRange(range.asRange.location, text.length);
[_innerText replaceCharactersInRange:range.asRange withString:text];
[_innerText removeDiscontinuousAttributesInRange:newRange];
if (!self.textParser) [_inputDelegate textDidChange:self];
/*
修正光標位置的方法放在這裏,由於此處已經替換文本完畢
問題的本質緣由,替換完文本後 range 沒有獲得更新
*/
// NSLog(@"Correct cursor position");
if (range.asRange.location + range.asRange.length == _selectedTextRange.asRange.location && _selectedTextRange.asRange.length == 0) {
//修正_selectedTextRange
[_inputDelegate selectionWillChange:self];
_selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(_selectedTextRange.asRange.location + text.length - range.asRange.length, 0)];
[_inputDelegate selectionDidChange:self];
}
}
複製代碼
- (BOOL)_parseText {
if (self.textParser) {
YYTextRange *oldTextRange = _selectedTextRange;
NSRange newRange = _selectedTextRange.asRange;
//此處方法須要註釋掉
// if(!_isExcludeNeed)[_inputDelegate textWillChange:self];
BOOL textChanged = [self.textParser parseText:_innerText selectedRange:&newRange];
// if(!_isExcludeNeed)[_inputDelegate textDidChange:self];
YYTextRange *newTextRange = [YYTextRange rangeWithRange:newRange];
newTextRange = [self _correctedTextRange:newTextRange];
if (![oldTextRange isEqual:newTextRange]) {
[_inputDelegate selectionWillChange:self];
_selectedTextRange = newTextRange;
[_inputDelegate selectionDidChange:self];
}
return textChanged;
}
return NO;
}
複製代碼
1. 系統全鍵盤輸入模式下,若是碰巧觸發了自動糾正模式,此時若是點擊鍵盤刪除鍵,會執行如下流程:
-[UIKeyboardlmpl deleteBackwardAndNotify:]
-[YYTextView setSelectedTextRange:]
...省略部分系統內部調用
-[UIKeyboardlmpl applyAutocorrection:]
-[YYTextView replaceRange:withText:]
...
-[YYTextView deleteBackward]
問題就出在`-[YYTextView replaceRange:withText:]`得到的text參數,傳過來的居然是聯想框內高亮的候選詞。真心不知道是怎麼回事,若是有了解的兄弟但願能講解下。
對比UITextView的表現來看,這一步徹底是多餘的。
2. 搜狗輸入法全鍵盤模式下,輸入英文字符後,點擊聯想框內的候選詞發現覆蓋現象,正確的表現應該是替換文本、清空聯想框內的候選詞,而後會追加一個空格。但事實上並非這樣的,也不知道是否是蘋果有意的讓第三方這樣去運行。從相關的代理方法中,並無找到引發該問題的實質緣由。`Tip:系統輸入法是會有一個追加空格的動做的,具體能夠調試看`
代碼執行流程:
- [UIKeyboadlmp deleteBackwardAndNotify:]
- [YYTextView deleteBackward]
- [YYTextView replaceRange:withText:]
- [YYTextView setSelectedTextRange:]
//這個會執行屢次,每次只能刪除一個字符
//刪除後 再進行插入操做
...
- [UIKeyboardlmpl insertText:]
- [YYTextView insertText:]
- [YYTextView replaceRange:withText:]
複製代碼
關於上述問題的定位
-[YYTextView replaceRange:withText:] YYTextView.m:3541
-[UIKeyboardImpl applyAutocorrection:] 0x00000001077f78ca
-[UIKeyboardImpl acceptAutocorrection:executionContextPassingTIKeyboardCandidate:] 0x00000001077ed735
-[UIKeyboardImpl acceptAutocorrectionForWordTerminator:executionContextPassingTIKeyboardCandidate:] 0x00000001077ece54
__56-[UIKeyboardImpl acceptAutocorrectionForWordTerminator:]_block_invoke 0x00000001077ecc77
-[UIKeyboardTaskQueue continueExecutionOnMainThread] 0x000000010805c782
-[UIKeyboardTaskQueue performTaskOnMainThread:waitUntilDone:] 0x000000010805cbab
-[UIKeyboardImpl acceptAutocorrectionForWordTerminator:] 0x00000001077ecb87
-[UIKeyboardImpl acceptAutocorrection] 0x00000001077ef6d9
-[UIKeyboardImpl prepareForSelectionChange] 0x00000001077e5770
-[YYTextView setSelectedTextRange:] YYTextView.m:3377
複製代碼
哈 具體是哪裏的問題,週一再分析~~~ 更新解決方法以下: 從上面貼出來的調用棧來看,問題基本出在如下代碼中。 -[YYTextView setSelectedTextRange:] YYTextView.m:3377
註釋了之後,果真清爽了。
#pragma mark - @protocol UITextInput
- (void)setSelectedTextRange:(YYTextRange *)selectedTextRange {
if (!selectedTextRange) return;
selectedTextRange = [self _correctedTextRange:selectedTextRange];
if ([selectedTextRange isEqual:_selectedTextRange]) return;
[self _updateIfNeeded];
[self _endTouchTracking];
[self _hideMenu];
_state.deleteConfirm = NO;
_state.typingAttributesOnce = NO;
//這裏有問題 selectionWillChange 不明緣由打斷了deleteBackwardAndNotify 執行
// [_inputDelegate selectionWillChange:self];
_selectedTextRange = selectedTextRange;
_lastTypeRange = _selectedTextRange.asRange;
// [_inputDelegate selectionDidChange:self];
[self _updateOuterProperties];
[self _updateSelectionView];
if (self.isFirstResponder) {
[self _scrollRangeToVisible:_selectedTextRange];
}
}
複製代碼
2017年09月04日 update
+ (YYTextLayout *)_shrinkLayoutWithLayout:(YYTextLayout *)layout {
if (layout.text.length && layout.lines.count == 0) {
YYTextContainer *container = layout.container.copy;
// container.maximumNumberOfRows = 1;
CGSize containerSize = container.size;
if (!container.verticalForm) {
containerSize.height = YYTextContainerMaxSize.height;
} else {
containerSize.width = YYTextContainerMaxSize.width;
}
container.size = containerSize;
return [YYTextLayout layoutWithContainer:container text:layout.text];
} else {
return nil;
}
}
複製代碼
YYTextLayout.m 源文件作以下修改 參考連接
//535行起
if (constraintSizeIsExtended) {
if (isVerticalForm) {
// if (rect.origin.x + rect.size.width >
// constraintRectBeforeExtended.origin.x +
// constraintRectBeforeExtended.size.width) break;
if (!CGRectIntersectsRect(rect, constraintRectBeforeExtended)) break;
} else {
if (rect.origin.y + rect.size.height >
constraintRectBeforeExtended.origin.y +
constraintRectBeforeExtended.size.height) break;
}
}
複製代碼
2018年01月09日 update
發如今使用
textLayout
對YYLabel
進行徹底控制佈局時,若是__賦空值__則預先設置的textAlignment
將自動轉換爲NSTextAlignmentNatural
。
這裏就直接上代碼了:
//出現問題的代碼 有兩部分
//1. NSAttributedString+YYText.m
//text爲空時,該宏的展開式並不會獲得執行。在此狀況下設置alignment是無心義的~~~
//結果就是_innerText.alignment 是默認值 NSTextAlignmentNatural
#define ParagraphStyleSet(_attr_)
//2. YYLabel.m
- (void)_updateOuterTextProperties {
_text = [_innerText plainTextForRange:NSMakeRange(0, _innerText.length)];
_font = _innerText.font;
if (!_font) _font = [self _defaultFont];
_textColor = _innerText.color;
if (!_textColor) _textColor = [UIColor blackColor];
//*******更改部分********
//由於上面的問題,這裏_innerText.alignment 是不可以採信的。
BOOL isEmptyStr = _innerText.length == 0;
if(!isEmptyStr)_textAlignment = _innerText.alignment;
if(!isEmptyStr)_lineBreakMode = _innerText.lineBreakMode;
//*******更改部分********
NSShadow *shadow = _innerText.shadow;
_shadowColor = shadow.shadowColor;
#if !TARGET_INTERFACE_BUILDER
_shadowOffset = shadow.shadowOffset;
#else
_shadowOffset = CGPointMake(shadow.shadowOffset.width, shadow.shadowOffset.height);
#endif
_shadowBlurRadius = shadow.shadowBlurRadius;
_attributedText = _innerText;
[self _updateOuterLineBreakMode];
}
複製代碼
2018年02月05日 update
【殘疾光標問題】 非YYKit 自身問題,權當自省了。
問題現象:YYTextView 使用時,蘋方14號字體條件下,文本初始編輯狀態,輸入提示光標爲正常的半高(殘疾)。 提示: 不要隨意更改原有邏輯!!! 遵循原有設計的默認值,如CoreText 默認字號爲12,而我這裏設置的解析器和行間距調整器的默認字號爲14。 出現問題,先找自身緣由。
- (void)setFont:(UIFont *)font {
/*** 有問題的邏輯
if ([self.customTextParser.font isEqual:font]) {
return;
}
[super setFont:font];
*/
/****改正後
[super setFont:font];
if ([self.customTextParser.font isEqual:font]) {
return;
}
*/
self.customTextParser.font = font;
self.customTextModifier.font = font;
self.linePositionModifier = self.customTextModifier;
}
複製代碼
【YYTextView 內存泄露問題】
檢測工具 : MLeaksFinder 特定情形,YYTextView處於編輯狀態下,且文本框中有內容。在退出當前控制器,MLeaksFinder 提示內存泄露,並在再次進入控制器,且調用reloadInputViews 時,內存獲得釋放。
問題梳理過程:
1. 懷疑 _innerLayout、_innerContainer 未獲得正確釋放,然無結果。
2. 發現一個特性,若是文本框處於激活狀態且無內容時,退出控制器時並不會形成內存泄露。
a. 因爲在YYTextDemo中嘗試,開始懷疑賦值方式有問題。 作出如下嘗試:
- (void)willMoveToSuperview:(UIView *)newSuperview{
[super willMoveToSuperview:newSuperview];
//嘗試賦值時機
if (!_state.firstInitFlag) {
_state.firstInitFlag = YES;
_innerText = [[NSMutableAttributedString alloc]initWithString:@""];
return;
}
// for (int i = 0; i< self.subviews.count; i++) {
// id view = self.subviews[i];
// NSLog(@"___ view %@",view);
// }
NSLog(@"retainCount %@",[self valueForKey:@"retainCount"]);
NSLog(@"----------");
// self.text = nil;
NSLog(@"**********");
}
/*這裏的嘗試起到了一些效果,發現若是YYTextView在首次加載時
_innerText 手動置空處理(注意不能夠是 nil ),在不主動收起鍵盤的狀況下,且手動輸入內容。
退出控制器時,可獲得正確釋放。
*/
b. 嘗試調整賦值時機,天真的思考是否是能夠把富文本的賦值方式 延後到某個時機。
主要目的是,初始嘗試手動清空,而後insertText 方式加載文本內容。最大的問題是,
這種狀況只要收起鍵盤,再彈起鍵盤,最終仍然得不到釋放。
3.目標轉移至,追蹤_innerText 的使用上。 以及
becomeFirstResponder
resignFirstResponder
這兩個方法都作了什麼。
複製代碼
UIKeyboardlmpl
這個對象,看名字就和鍵盤有點關係。 因爲對這個也不是十分理解,因此就不囉嗦了。問題出如今下面的方法中。
#pragma mark - @protocol UIKeyInput
- (BOOL)hasText {
return _innerText.string.length > 0;
}
- (void)insertText:(NSString *)text {
略...
}
- (void)deleteBackward {
略...
}
//下面是原文件
@protocol UIKeyInput <UITextInputTraits>
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) BOOL hasText;
#else
- (BOOL)hasText;
#endif
- (void)insertText:(NSString *)text;
- (void)deleteBackward;
@end
複製代碼
【注意!!!】 終極解決方案在這裏~~~
//YYTextView.m
#pragma mark - @protocol UIKeyInput
- (BOOL)hasText {
//return _innerText.string.length > 0;
return NO;
}
複製代碼
因爲是剛出爐的,也沒敢在線上環境測試。 看到的朋友們,謹慎對待吧。打算通過一段時間測試後,再適用到線上環境。
【YYTextView 複用時,解析狀態丟失】
項目中有在cell上使用YYTextView展現文本的須要,發現解析的連接等文本高亮狀態在複用時丟失。
YYTextView.m 更改
- (void)setText:(NSString *)text {
if (_text == text || [_text isEqualToString:text]){
//複用的狀況下,解析狀態會丟失
if ([self _parseText])_state.needUpdate = YES;
return;
}
[self _setText:text];
_state.selectedWithoutEdit = NO;
_state.deleteConfirm = NO;
[self _endTouchTracking];
[self _hideMenu];
[self _resetUndoAndRedoStack];
[self replaceRange:[YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)] withText:text];
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
if (_attributedText == attributedText){
//複用的狀況下,解析狀態會丟失
if ([self _parseText])_state.needUpdate = YES;
return;
}
....
....
}
複製代碼
2018年11月19日 update
【YYTextView 選中模式消除】 相關源碼修改
修改的顯著效果:不再用擔憂選擇模式下的兩個Dot,在頁面切換或者滾動時的消除問題。
【錯誤修復】因爲YYActiveObj.h
使用類屬性(class property)記錄處於激活狀態的YYTextView
實例,其中static
關鍵字的應用致使實例的生命週期發生變化,導致實例不被釋放。
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface YYActiveObj : NSObject
@property(class,nonatomic,weak) UIView *viewActive;
@end
NS_ASSUME_NONNULL_END
#import "YYActiveObj.h"
static UIView *_viewActive;
@implementation YYActiveObj
+ (void)setViewActive:(UIView *)viewActive{
_viewActive = viewActive;
}
+ (UIView *)viewActive{
return _viewActive;
}
@end
複製代碼
如上,此時聲明的weak
修飾其實並不起做用,緣由就在於static
。
2019年4月2日
【iOS13 DarkMode適配】
若是你也在作該適配的話,那麼極可能遇到如下的問題。 嘗試了不少次,最大的問題竟出在UIColor。 不過出現的問題,疑似和YY內部在Runloop 即將休眠時進行繪製任務具備很大的相關性。具體緣由還不能肯定,等之後再深究一下。
這裏先看下系統UILabel的暗夜適配找尋一下靈感
drawTextInRext
,而翻看YY能看到其使用的
CTRunDraw()
。因爲一開始對UIDynamicProviderColor有誤解,也嘗試過解析其中打包的顏色,來經過查看CTRun的屬性集來判斷當前是否正確渲染。
....然而,在YYLabel應用上述方案時可能正常,但YYTeView卻出現了其它的問題。 ....排查中發現,某些時候UITraitCollection.currentTraitCollection
解析出的顏色,和對應的狀態不符。 ....最終發現,colorWithDynamicProvider
中回調的狀態可能出現和當前系統狀態不一致的狀況,也就是說這個回調有點不那麼可信了... 誤我青春
YYLabel.m 添加以下代碼
#pragma mark - DarkMode Adapater
#ifdef __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if([UITraitCollection.currentTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]){
[self.layer setNeedsDisplay];
}
} else {
// Fallback on earlier versions
}
}
#endif
複製代碼
YYTextView.m 添加以下代碼
#pragma mark - Dark mode Adapter
#ifdef __IPHONE_13_0
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection{
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if([UITraitCollection.currentTraitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]){
[self _commitUpdate];
}
} else {
// Fallback on earlier versions
}
}
#endif
複製代碼
額外要作的事情
- (void)setColor:(UIColor *)color {
[self setColor:color range:NSMakeRange(0, self.length)];
}
- (void)setStrokeColor:(UIColor *)strokeColor {
[self setStrokeColor:strokeColor range:NSMakeRange(0, self.length)];
}
- (void)setStrikethroughColor:(UIColor *)strikethroughColor {
[self setStrikethroughColor:strikethroughColor range:NSMakeRange(0, self.length)];
}
- (void)setUnderlineColor:(UIColor *)underlineColor {
[self setUnderlineColor:underlineColor range:NSMakeRange(0, self.length)];
}
複製代碼
/// 坑爹的方法 有時候是不靈的
UIColor *dynamicColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * provider) {
if ([provider userInterfaceStyle] == UIUserInterfaceStyleLight) {
return lightColor;
}
else {
return darkColor;
}
}];
///建議 從頂層獲取當前系統的,暗黑模式狀態。
UIColor *dynamicColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * provider) {
/// keyWindow 讀取當前狀態
if(keyWindow.isDark){
return darkColor;
}
return lightColor;
}];
複製代碼
2019年6月21日
【iOS13 下出現scrollsToTop失效】
在iOS 13.0 版本上,但凡建立YYTextView實例,即會使全局的 scrollsToTop 回頂功能失效。
排查最終結果爲 YYTextEffectWindow 需作以下變動:
+ (instancetype)sharedWindow {
static YYTextEffectWindow *one = nil;
if (one == nil) {
// iOS 9 compatible
NSString *mode = [NSRunLoop currentRunLoop].currentMode;
if (mode.length == 27 &&
[mode hasPrefix:@"UI"] &&
[mode hasSuffix:@"InitializationRunLoopMode"]) {
return nil;
}
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![UIApplication isAppExtension]) {
one = [self new];
one.frame = (CGRect){.size = kScreenSize};
one.userInteractionEnabled = NO;
one.windowLevel = UIWindowLevelStatusBar + 1;
//此到處理 iOS 13版本出現的問題
if (@available(iOS 13.0, *)) {
one.hidden = YES;
}else{
one.hidden = NO;
}
// for iOS 9:
one.opaque = NO;
one.backgroundColor = [UIColor clearColor];
one.layer.backgroundColor = [UIColor clearColor].CGColor;
}
});
return one;
}
複製代碼
2019年6月21日
【iOS13 下雙光標問題】
問題產生緣由:
- 機制更改
- 以前所作的解決搜狗輸入法、系統鍵盤單詞中斷問題所作的修改存在問題。
機制更改 在真機上嘗試了不少次,確認以下: iOS13下,只要遵循了UITextInput
相關協議,在進行文本選擇操做時系統會自動派生出UITextSelectionView
系列組件,顯然和YY
自有的YYTextSelectionView
衝突了。(此處隱藏一顆彩蛋)
以前對YYTextView
所作的代碼變動,武斷的註釋掉了以下方法:
if (notify) [_inputDelegate selectionWillChange:self];
if (notify) [_inputDelegate selectionDidChange:self];
複製代碼
會形成內部對文本選擇相關的數據錯亂,固然這種影響目前只在iOS13下可以看到表現。
【解決方案】
UITextSelectionView
/// YYTextView.m
/// 增長標記位
struct {
.....
unsigned int trackingDeleteBackward : 1; ///< track deleteBackward operation
unsigned int trackingTouchBegan : 1; /// < track touchesBegan event
} _state;
/// 方法重寫
- (void)addSubview:(UIView *)view{
[super addSubview:view];
Class Cls_selectionView = NSClassFromString(@"UITextSelectionView");
Class Cls_selectionGrabberDot = NSClassFromString(@"UISelectionGrabberDot");
if ([view isKindOfClass:[Cls_selectionGrabberDot class]]) {
view.layer.contents = [UIView new];
}
if ([view isKindOfClass:[Cls_selectionView class]]) {
view.hidden = YES;
}
}
/// 方法修改
/// Replace the range with the text, and change the `_selectTextRange`.
/// The caller should make sure the `range` and `text` are valid before call this method.
- (void)_replaceRange:(YYTextRange *)range withText:(NSString *)text notifyToDelegate:(BOOL)notify{
if (_isExcludeNeed) {
notify = NO;
}
if (NSEqualRanges(range.asRange, _selectedTextRange.asRange)) {
//這裏的代理方法須要註釋掉 【廢止】
//if (notify) [_inputDelegate selectionWillChange:self];
/// iOS13 下,雙光標問題 即是由此而生。
if (_state.trackingDeleteBackward)[_inputDelegate selectionWillChange:self];
NSRange newRange = NSMakeRange(0, 0);
newRange.location = _selectedTextRange.start.offset + text.length;
_selectedTextRange = [YYTextRange rangeWithRange:newRange];
//if (notify) [_inputDelegate selectionDidChange:self];
/// iOS13 下,雙光標問題 即是由此而生。
if (_state.trackingDeleteBackward) [_inputDelegate selectionDidChange:self];
///恢復標記
_state.trackingDeleteBackward = NO;
} else {
.....
.....
}
- (void)deleteBackward {
//標識出刪除動做:用於解決雙光標相關問題
_state.trackingDeleteBackward = YES;
[self _updateIfNeeded];
.....
.....
}
- (void)_updateSelectionView {
_selectionView.frame = _containerView.frame;
_selectionView.caretBlinks = NO;
_selectionView.caretVisible = NO;
_selectionView.selectionRects = nil;
.....
.....
if (@available(iOS 13.0, *)) {
if (_state.trackingTouchBegan) [_inputDelegate selectionWillChange:self];
[[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
if (_state.trackingTouchBegan) [_inputDelegate selectionDidChange:self];
}else{
[[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
}
if (containsDot) {
[self _startSelectionDotFixTimer];
} else {
[self _endSelectionDotFixTimer];
.....
.....
}
複製代碼
2019年8月26日
【YYTextView 預輸入文本狀態下選中功能異常】
【解決方案】
- (void)_showMenu {
//過濾預輸入狀態
if (_markedTextRange != nil) {
return;
}
CGRect rect;
if (_selectionView.caretVisible) {
rect = _selectionView.caretView.frame;
...
...
複製代碼
2019年9月5日
【iOS13新增編輯手勢】- 暫時禁用
編輯手勢 描述借鑑 複製:三指捏合 剪切:兩次三指捏合 粘貼:三指鬆開 撤銷:三指向左划動(或三指雙擊) 重作:三指向右划動 快捷菜單:三指單擊
#ifdef __IPHONE_13_0
- (UIEditingInteractionConfiguration)editingInteractionConfiguration{
return UIEditingInteractionConfigurationNone;
}
#endif
複製代碼
2019年9月24日
【iOS13下文本第一次選中時 藍點問題】
其實以前說的彩蛋,就是關於系統派生出來的
selection
組件。之因此對其隱藏的方式使用hidden 、CGSizeZero 並且區別使用的緣由就是,這些組件其實會躲避。
#pragma mark - override
- (void)addSubview:(UIView *)view{
//解決藍點問題
Class Cls_selectionGrabberDot = NSClassFromString(@"UISelectionGrabberDot");
if ([view isKindOfClass:[Cls_selectionGrabberDot class]]) {
view.backgroundColor = [UIColor clearColor];
view.tintColor = [UIColor clearColor];
view.size = CGSizeZero;
}
//獲取UITextSelectionView
//解決雙光標問題
Class Cls_selectionView = NSClassFromString(@"UITextSelectionView");
if ([view isKindOfClass:[Cls_selectionView class]]) {
view.backgroundColor = [UIColor clearColor];
view.tintColor = [UIColor clearColor];
view.hidden = YES;
}
[super addSubview:view];
}
複製代碼
2019年9月29日