DYFoldLabel:一行程式碼實現摺疊 label(OC)
原始碼在這裡 ,使用Cocoapod:
pod 'DYFoldLabel'
簡介
設定一段顯示不完整文字省略號後的摺疊按鈕。
先看效果:

效果圖.gif
功能亮點:
1.用category實現,無需繼承UILabel。
2.可以相容自動佈局。
3.解決大多數第三方文字點選在特殊情況有誤問題。
方法呼叫
/** 設定一段顯示不完整文字省略號後的摺疊按鈕。 @param foldText 摺疊按鈕文字 @param font 摺疊按鈕文字字型 @param color 摺疊按鈕文字顏色 @param block 摺疊按鈕回撥block,傳入nil禁止點選 */ - (void)setFoldText:(NSString *)foldText textFont:(UIFont *)font textColor:(UIColor *)color clickBlock:(DYFoldBtnClickBlock)block; /** 設定是否展開label。 */ - (void)foldLabel:(BOOL)folded;
核心思路:
1.計算第幾行結束。因為摺疊文字的font可以和文字不一致,最後一行可能比其他行會佔用更多行的空間。
- (NSInteger)lineReplaceWithLine:(CFIndex)lineCount lines:(CFArrayRef)lines fontDiff:(CGFloat)fontDiff { //計算單行高度 CGFloat ascent = 0,descent = 0,leading = 0; CTLineRef line = CFArrayGetValueAtIndex(lines, 0); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat lineHeight = ascent + descent + leading; //計算最後一行的index。 //eg:如果正常可以顯示4行,摺疊文字的font.poinSize = 20,正常文字的font.poinSize = 15, //那麼最後一行會佔用兩行的高度,index就會比實際行數少1。 NSInteger lineIndex = 0,index = 0; NSInteger number = self.numberOfLines - 1; CGFloat totalLineHeight = 0; while (totalLineHeight < (lineHeight + fontDiff) && lineCount >= index) { if (totalLineHeight < (lineHeight + leading)) { index++; } totalLineHeight = lineHeight * index; } lineIndex = lineCount - index;//這裡需要+1,因為最後一行沒計算在內,又因為陣列最後一個元素索引=count-1,所以+1抵消 lineIndex = (self.numberOfLines > 0) ? MIN(lineIndex, number) : lineCount; return lineIndex; }
2.計算被替換文字的長度。要加省略號以及摺疊文字,需要替換掉最後一行最後一部分文字。
- (NSInteger)subLenthWithString:(NSMutableAttributedString *)string lineRange:(NSRange)range text:(NSString *)text textFont:(UIFont *)font{ //摺疊文字寬度 CGFloat foldWidth = [self dy_sizeForText:text Font:font size:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) mode:0].width; CGFloat spaceTextWidth = 0.0; NSInteger index = 0; while (spaceTextWidth < foldWidth) { NSString *spaceText = [string attributedSubstringFromRange:NSMakeRange(range.location + range.length - index, index)].string; spaceTextWidth = [self dy_sizeForText:spaceText Font:self.font size:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) mode:0].width; index++; } return index; }
3.點選事件。市面上大多數第三方ULabel部分文字的點選事件在自動佈局情況下會出錯,因為在label大小(下圖綠色部分)大於文字所能佔的大小時,文字會居中,coreText獲取的位置是文字在label的top位置的座標,在判斷點選位置時不準確。
如圖:

label大於文字範圍.png
實現如下:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ DYFoldBtnClickBlock clickBlock = objc_getAssociatedObject(self, "clickBlock"); if (!clickBlock || self.isFolded) return; UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView:self]; CGPoint clickPoint = CGPointMake(location.x, self.bounds.size.height - location.y); CTFrameRef _frame = (__bridge CTFrameRef)(objc_getAssociatedObject(self, "frameRef")); //獲取最後行 CFIndex endLineIndex = [objc_getAssociatedObject(self, "endLineIndex") integerValue]; CFArrayRef lines = CTFrameGetLines(_frame); //獲取行上行、下行、間距 CGFloat ascent = 0; CGFloat descent = 0; CGFloat leading = 0; CTLineRef endLine = CFArrayGetValueAtIndex(lines, endLineIndex); CTLineGetTypographicBounds(endLine, &ascent, &descent, &leading); UIFont *font = objc_getAssociatedObject(self, "foldFont"); CGFloat endLineHeight = leading + MAX(font.pointSize, self.font.pointSize); //計算點選位置是否在摺疊行內 CGPoint origins[CFArrayGetCount(lines)]; CTFrameGetLineOrigins(_frame, CFRangeMake(0, 0), origins); CGPoint endLineOrigin = origins[endLineIndex]; //顯示的文字高度 CGFloat textHeight = self.bounds.size.height - endLineOrigin.y + endLineHeight; //最後一行的範圍判斷。 if (clickPoint.y <= (self.bounds.size.height * 0.5 - textHeight * 0.5 + endLineHeight) && clickPoint.y >= (self.bounds.size.height * 0.5 - textHeight * 0.5 )) { CFIndex index = CTLineGetStringIndexForPosition(endLine, clickPoint); NSString *foldText = objc_getAssociatedObject(self, "foldText"); NSRange range = NSMakeRange(self.text.length - foldText.length, foldText.length); //判斷點選的字元是否在需要處理點選事件的字串範圍內 if (range.location <= index) { if (clickBlock) { clickBlock(); } } } }
如果對你有幫助的話,能給 DYFoldLabel 打賞一個小星星嗎:two_hearts: