有没有一种简单的方法来获取(或简单地显示)UILabel中给定行的文本?
我的UILabel正确地显示我的文本并将其完美地展示出来,但偶尔我需要能够显示某些行,但显然我需要知道UILabel如何定位所有这些。
我知道这可以通过子字符串轻松完成,但我需要知道该行的起点和终点。
或者,如果UILabel的框架有某种偏移并且隐藏了我不想看到的其他内容,我可以滚动UILabel。
我无法发现任何显示如何轻松完成此操作的内容。有人有什么好主意吗?
谢谢
iphaaw
我不认为有这样的本地方式(如“takethenline”方法)。 我可以找出一个棘手的解决方案,但我不确定是最好的解决方案。 您可以将标签拆分为一系列单词。 然后你可以循环数组并检查文本高度,直到这个单词如下:
NSString *texttocheck;
float old_height = 0;
int linenumber = 0;
for (x=0; x<[wordarray lenght]; x++) {
texttocheck = [NSString stringWithFormat:@"%@ %@", texttocheck, [wordarray objectAtIndex:x]];
float height = [text sizeWithFont:textLabel.font
constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999)
lineBreakMode:UILineBreakModeWordWrap].height;
if (old_height < height) {
linenumber++;
}
}
如果高度改变,则意味着在单词之前有一个换行符。 我现在无法检查语法是否正确写入,因此您必须自己检查。
对不起,我的声誉太低,无法发表评论。这是来自Philipp Jahoda的https://stackoverflow.com/a/53783203/2439941的评论。
您的代码片段完美无瑕,直到我们在UILabel上启用动态类型。当我们将文本大小设置为iOS设置应用程序中的最大值时,它开始错过返回数组的最后一行中的字符。甚至完全错过最后一行有大量文字。
我们设法通过使用不同的方式来获取frame
来解决这个问题:
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }
现在它适用于任何动态类型大小。
完整的功能是:
extension UILabel {
/// creates an array containing one entry for each line of text the label has
var lines: [String]? {
guard let text = text, let font = font else { return nil }
let attStr = NSMutableAttributedString(string: text)
attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }
var linesArray: [String] = []
for line in lines {
let lineRef = line as! CTLine
let lineRange = CTLineGetStringRange(lineRef)
let range = NSRange(location: lineRange.location, length: lineRange.length)
let lineString = (text as NSString).substring(with: range)
linesArray.append(lineString)
}
return linesArray
}
}
我有更好的方法来找到它。
你可以在CoreText.framework的帮助下得到这个。
1.添加CoreText.framework。
2.进口#import <CoreText/CoreText.h>
。
然后使用以下方法:
- (NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label {
NSString *text = [label text];
UIFont *font = [label font];
CGRect rect = [label frame];
CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
NSMutableArray *linesArray = [[NSMutableArray alloc]init];
for (id line in lines)
{
CTLineRef lineRef = (__bridge CTLineRef )line;
CFRange lineRange = CTLineGetStringRange(lineRef);
NSRange range = NSMakeRange(lineRange.location, lineRange.length);
NSString *lineString = [text substringWithRange:range];
[linesArray addObject:lineString];
}
return (NSArray *)linesArray;
}
调用此方法: -
NSArray *linesArray = [self getLinesArrayOfStringInLabel:yourLabel];
现在你可以使用linesArray
。
SWIFT 4版本
func getLinesArrayOfString(in label: UILabel) -> [String] {
/// An empty string's array
var linesArray = [String]()
guard let text = label.text, let font = label.font else {return linesArray}
let rect = label.frame
let myFont: CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attStr = NSMutableAttributedString(string: text)
attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: myFont, range: NSRange(location: 0, length: attStr.length))
let frameSetter: CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path: CGMutablePath = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: rect.size.width, height: 100000), transform: .identity)
let frame: CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else {return linesArray}
for line in lines {
let lineRef = line as! CTLine
let lineRange: CFRange = CTLineGetStringRange(lineRef)
let range = NSRange(location: lineRange.location, length: lineRange.length)
let lineString: String = (text as NSString).substring(with: range)
linesArray.append(lineString)
}
return linesArray
}
使用:
let lines: [String] = getLinesArrayOfString(in: label)
斯威夫特3
func getLinesArrayFromLabel(label:UILabel) -> [String] {
let text:NSString = label.text! as NSString // TODO: Make safe?
let font:UIFont = label.font
let rect:CGRect = label.frame
let myFont:CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
let frameSetter:CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path:CGMutablePath = CGMutablePath()
path.addRect(CGRect(x:0, y:0, width:rect.size.width, height:100000))
let frame:CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
let lines = CTFrameGetLines(frame) as NSArray
var linesArray = [String]()
for line in lines {
let lineRange = CTLineGetStringRange(line as! CTLine)
let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
let lineString = text.substring(with: range)
linesArray.append(lineString as String)
}
return linesArray
}
Swift 2(Xcode 7)版本(测试,并从Swift 1回答重新编辑)
func getLinesArrayOfStringInLabel(label:UILabel) -> [String] {
let text:NSString = label.text! // TODO: Make safe?
let font:UIFont = label.font
let rect:CGRect = label.frame
let myFont:CTFontRef = CTFontCreateWithName(font.fontName, font.pointSize, nil)
let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
let frameSetter:CTFramesetterRef = CTFramesetterCreateWithAttributedString(attStr as CFAttributedStringRef)
let path:CGMutablePathRef = CGPathCreateMutable()
CGPathAddRect(path, nil, CGRectMake(0, 0, rect.size.width, 100000))
let frame:CTFrameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
let lines = CTFrameGetLines(frame) as NSArray
var linesArray = [String]()
for line in lines {
let lineRange = CTLineGetStringRange(line as! CTLine)
let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
let lineString = text.substringWithRange(range)
linesArray.append(lineString as String)
}
return linesArray
}
回答正确发布!!!!
-(NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label
{
NSString *text = [label text];
UIFont *font = [label font];
CGRect rect = [label frame];
CTFontRef myFont = CTFontCreateWithName(( CFStringRef)([font fontName]), [font pointSize], NULL);
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
[attStr addAttribute:(NSString *)kCTFontAttributeName value:( id)myFont range:NSMakeRange(0, attStr.length)];
CFRelease(myFont);
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(( CFAttributedStringRef)attStr);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
NSArray *lines = ( NSArray *)CTFrameGetLines(frame);
NSMutableArray *linesArray = [[NSMutableArray alloc]init];
for (id line in lines)
{
CTLineRef lineRef = ( CTLineRef )line;
CFRange lineRange = CTLineGetStringRange(lineRef);
NSRange range = NSMakeRange(lineRange.location, lineRange.length);
NSString *lineString = [text substringWithRange:range];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithFloat:0.0]));
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithInt:0.0]));
//NSLog(@"''''''''''''''''''%@",lineString);
[linesArray addObject:lineString];
}
[attStr release];
CGPathRelease(path);
CFRelease( frame );
CFRelease(frameSetter);
return (NSArray *)linesArray;
}
这是Swift 3版本,用于获取标签中的所有行。 (@fredpi有一个类似的答案,但它只适用于第一行)
extension UILabel {
func getArrayOfLinesInLabel() -> [String] {
let text = NSString(string: self.text ?? "-- -- -- --")
let font = self.font ?? // Your default font here
let rect = self.frame
let myFont = CTFontCreateWithName(font.fontName as CFString?, font.pointSize, nil)
let attStr = NSMutableAttributedString(string: text as String)
attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSRange(location: 0, length: attStr.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = CGPath(rect: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height), transform: nil)
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
guard let lines = CTFrameGetLines(frame) as? [CTLine] else {
return []
}
var linesArray = [String]()
for line in lines {
let lineRange = CTLineGetStringRange(line)
let range = NSRange(location: lineRange.location, length: lineRange.length)
let lineString = text.substring(with: range)
linesArray.append(lineString as String)
}
return linesArray
}
}
关于iOS 11+的非常重要的变化
从iOS 11开始,Apple有意改变了UILabel
的自动换行功能的行为,该功能可以检测多行String
中各行的UILabel
内容。根据设计,UILabel
的自动换行现在可以避免孤立的文本(新行中的单个单词),如下所述:word wrapping in iOS 11
因此,如果避免孤立文本的新自动换行在特定行中生效,那么CTFrameGetLines(frame)
返回标签中所有行的CTLine
数组的方式将无法正常工作。相反,它导致String
的部分由新的自动换行设计属于下一行而不是最终在焦点的行。
可以在我更改的@TheTiger's answer版本中找到针对此问题的测试修复程序,该版本使用UILabel
计算sizeThatFits(size:)
的实际内容大小,然后使用该大小创建用Swift 4编写的rect / path:
extension UILabel {
/// creates an array containing one entry for each line of text the label has
var lines: [String]? {
guard let text = text, let font = font else { return nil }
let attStr = NSMutableAttributedString(string: text)
attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = CGMutablePath()
// size needs to be adjusted, because frame might change because of intelligent word wrapping of iOS
let size = sizeThatFits(CGSize(width: self.frame.width, height: .greatestFiniteMagnitude))
path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity)
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }
var linesArray: [String] = []
for line in lines {
let lineRef = line as! CTLine
let lineRange = CTLineGetStringRange(lineRef)
let range = NSRange(location: lineRange.location, length: lineRange.length)
let lineString = (text as NSString).substring(with: range)
linesArray.append(lineString)
}
return linesArray
}
}
这个UILabel
扩展将标签的内容作为String
数组返回,每行一个条目完全呈现给用户的眼睛。
Swift 3 - Xcode 8.1
我把以前答案中的代码放在一起,创建一个Swift 3,Xcode 8.1兼容的extension
到UILabel
返回标签的第一行。
import CoreText
extension UILabel {
/// Returns the String displayed in the first line of the UILabel or "" if text or font is missing
var firstLineString: String {
guard let text = self.text else { return "" }
guard let font = self.font else { return "" }
let rect = self.frame
let attStr = NSMutableAttributedString(string: text)
attStr.addAttribute(String(kCTFontAttributeName), value: CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil), range: NSMakeRange(0, attStr.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: rect.size.width + 7, height: 100))
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
guard let line = (CTFrameGetLines(frame) as! [CTLine]).first else { return "" }
let lineString = text[text.startIndex...text.index(text.startIndex, offsetBy: CTLineGetStringRange(line).length-2)]
return lineString
}
}
要使用它,只需在firstLineString
实例上调用UILabel
,如下所示:
let firstLine = myLabel.firstLineString
如果所有字符都以相同的大小显示,即它们被包含在一个通用大小的框中,您可以利用它。 (例如,日语字符就是这种情况。)
否则,您可以查询显示字体中每个字符的大小,并计算该行必须是什么。
唯一的担心是你的计算可能不同意Apple在幕后所做的事情 - 在这种情况下,我建议你去覆盖文本框架绘图的麻烦。在文档中查找核心文本。
(我可能一直这样做错了,但我没有发现Apple的方法在文档中给出的非常准确,所以我自己做了别的事情。)
接受的答案非常好。
我重构了两个地方:
CGFloat.greatestFiniteMagnitude
extension
的UILabel
youLabel.layoutIfNeeded()
获得正确的帧大小。
这是代码:
extension UILabel {
var stringLines: [String] {
guard let text = text, let font = font else { return [] }
let ctFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attStr = NSMutableAttributedString(string: text)
attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: ctFont, range: NSRange(location: 0, length: attStr.length))
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude), transform: .identity)
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return [] }
return lines.map { line in
let lineRef = line as! CTLine
let lineRange: CFRange = CTLineGetStringRange(lineRef)
let range = NSRange(location: lineRange.location, length: lineRange.length)
return (text as NSString).substring(with: range)
}
}
}