减less多行UILabel中最后一行的宽度

我正在实现一个“多读”function,就像苹果AppStore中的一样。 但是,我正在使用多行UILabel 。 看看苹果的AppStore,他们如何减less最后一个可见线的宽度,以适应“更多”的文字,仍然截断尾巴(见图片)?

来自AppStore的iBooks示例图像

这似乎工作,至less在我做了有限的testing。 有两种公共方法。 如果您有多个标签,并且行数相同,则可以使用较短的标签 – 只需更改顶部的kNumberOfLines即可。 如果您需要传递不同标签的行数,请使用较长的方法。 请务必将您在IB中制作的标签类别更改为RDLabel。 使用这些方法,而不是setText :. 如果需要,这些方法将标签的高度扩展到kNumberOfLines,并且如果仍然被截断,则会将其展开以适合整个触摸的string。 目前,您可以触摸标签中的任意位置。 这不应该太难改变,所以只能触及… Mer会引起扩张。

 #import "RDLabel.h" #define kNumberOfLines 2 #define ellipsis @"...Mer ▾ " @implementation RDLabel { NSString *string; } #pragma Public Methods - (void)setTruncatingText:(NSString *) txt { [self setTruncatingText:txt forNumberOfLines:kNumberOfLines]; } - (void)setTruncatingText:(NSString *) txt forNumberOfLines:(int) lines{ string = txt; self.numberOfLines = 0; NSMutableString *truncatedString = [txt mutableCopy]; if ([self numberOfLinesNeeded:truncatedString] > lines) { [truncatedString appendString:ellipsis]; NSRange range = NSMakeRange(truncatedString.length - (ellipsis.length + 1), 1); while ([self numberOfLinesNeeded:truncatedString] > lines) { [truncatedString deleteCharactersInRange:range]; range.location--; } [truncatedString deleteCharactersInRange:range]; //need to delete one more to make it fit CGRect labelFrame = self.frame; labelFrame.size.height = [@"A" sizeWithFont:self.font].height * lines; self.frame = labelFrame; self.text = truncatedString; self.userInteractionEnabled = YES; UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(expand:)]; [self addGestureRecognizer:tapper]; }else{ CGRect labelFrame = self.frame; labelFrame.size.height = [@"A" sizeWithFont:self.font].height * lines; self.frame = labelFrame; self.text = txt; } } #pragma Private Methods -(int)numberOfLinesNeeded:(NSString *) s { float oneLineHeight = [@"A" sizeWithFont:self.font].height; float totalHeight = [s sizeWithFont:self.font constrainedToSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height; return nearbyint(totalHeight/oneLineHeight); } -(void)expand:(UITapGestureRecognizer *) tapper { int linesNeeded = [self numberOfLinesNeeded:string]; CGRect labelFrame = self.frame; labelFrame.size.height = [@"A" sizeWithFont:self.font].height * linesNeeded; self.frame = labelFrame; self.text = string; } 

有多种方法可以做到这一点,最优雅的是,由于您完全控制了如何显示文本,因此可以专门使用CoreText。

这里是一个混合选项,我们使用CoreText重新创build标签,确定它结束的位置,然后我们在正确的位置剪切标签文本string。

 NSMutableAttributedString *atrStr = [[NSAttributedString alloc] initWithString:label.text]; NSNumber *kern = [NSNumber numberWithFloat:0]; NSRange full = NSMakeRange(0, [atrStr string].length); [atrStr addAttribute:(id)kCTKernAttributeName value:kern range:full]; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)atrStr); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, label.frame); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFArrayRef lines = CTFrameGetLines(frame); CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, label.numberOfLines-1); CFRange r = CTLineGetStringRange(line); 

这给你的标签文本的最后一行的范围。 从那里开始,把它切开并把省略号放在你想要的地方是微不足道的。

第一部分创build一个属性string,它需要复制UILabel的行为(可能不是100%,但应该足够接近)的属性。 然后,我们创build一个framesetter和框架,并获取框架的所有行,从中提取标签最后一个预期行的范围。

这显然是某种黑客攻击,正如我所说的,如果你想完全控制你的文本,你最好用一个纯粹的CoreText标签实现。

由于这篇文章是从2013年开始的,我想从@rdelmar的Swift实现非常好的解决scheme。

考虑到我们正在使用UILabel的子类:

 private let kNumberOfLines = 2 private let ellipsis = " MORE" private var originalString: String! // Store the original text in the init private func getTruncatingText() -> String { var truncatedString = originalString.mutableCopy() as! String if numberOfLinesNeeded(truncatedString) > kNumberOfLines { truncatedString += ellipsis var range = Range<String.Index>( start: truncatedString.endIndex.advancedBy(-(ellipsis.characters.count + 1)), end: truncatedString.endIndex.advancedBy(-ellipsis.characters.count) ) while numberOfLinesNeeded(truncatedString) > kNumberOfLines { truncatedString.removeRange(range) range.startIndex = range.startIndex.advancedBy(-1) range.endIndex = range.endIndex.advancedBy(-1) } } return truncatedString } private func getHeightForString(str: String) -> CGFloat { return str.boundingRectWithSize( CGSizeMake(self.bounds.size.width, CGFloat.max), options: [.UsesLineFragmentOrigin, .UsesFontLeading], attributes: [NSFontAttributeName: font], context: nil).height } private func numberOfLinesNeeded(s: String) -> Int { let oneLineHeight = "A".sizeWithAttributes([NSFontAttributeName: font]).height let totalHeight = getHeightForString(s) return Int(totalHeight / oneLineHeight) } func expend() { var labelFrame = self.frame labelFrame.size.height = getHeightForString(originalString) self.frame = labelFrame self.text = originalString } func collapse() { let truncatedText = getTruncatingText() var labelFrame = self.frame labelFrame.size.height = getHeightForString(truncatedText) self.frame = labelFrame self.text = truncatedText } 

与旧的解决scheme不同,这将适用于任何types的文本属性(如NSParagraphStyleAttributeName)。

请随时批评和评论。 再次感谢@ rdelmar。

ResponsiveLabel是UILabel的一个子类,它允许添加响应触摸的自定义截断标记。