源码在这里 ,使用Cocoapod:
pod 'DYFoldLabel'
简介
设置一段显示不完整文字省略号后的折叠按钮。
先看效果:
功能亮点:
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位置的坐标,在判断点击位置时不准确。
如图:
实现如下:
-(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();
}
}
}
}