我有一个带有一些属性文本的UITextView
,其中textContainer.maximumNumberOfLine
被设置(在此例中为3)。
我想找到带属性字符串的字符范围内省略号字符的索引。
例如:
原始字符串:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit"
截断后显示的字符串:
Lorem ipsum
dolor sit amet,
consectetur...
如何确定...
的索引?
我有一个带有一些属性文本的UITextView
,其中textContainer.maximumNumberOfLine
被设置(在此例中为3)。
我想找到带属性字符串的字符范围内省略号字符的索引。
例如:
原始字符串:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit"
截断后显示的字符串:
Lorem ipsum
dolor sit amet,
consectetur...
如何确定...
的索引?
这是一个NSAttributedString的扩展函数,可以处理单行和多行文本。
我花了大约8个小时才想到这个方法,所以我觉得我可以将它作为问答帖子发布出来。
(Swift 2.2)
/**
Returns the index of the ellipsis, if this attributed string is truncated, or NSNotFound otherwise.
*/
func truncationIndex(maximumNumberOfLines: Int, width: CGFloat) -> Int {
//Create a dummy text container, used for measuring & laying out the text..
let textContainer = NSTextContainer(size: CGSize(width: width, height: CGFloat.max))
textContainer.maximumNumberOfLines = maximumNumberOfLines
textContainer.lineBreakMode = NSLineBreakMode.ByTruncatingTail
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = NSTextStorage(attributedString: self)
textStorage.addLayoutManager(layoutManager)
//Determine the range of all Glpyhs within the string
var glyphRange = NSRange()
layoutManager.glyphRangeForCharacterRange(NSMakeRange(0, self.length), actualCharacterRange: &glyphRange)
var truncationIndex = NSNotFound
//Iterate over each 'line fragment' (each line as it's presented, according to your `textContainer.lineBreakMode`)
var i = 0
layoutManager.enumerateLineFragmentsForGlyphRange(glyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in
if (i == maximumNumberOfLines - 1) {
//We're now looking at the last visible line (the one at which text will be truncated)
let lineFragmentTruncatedGlyphIndex = glyphRange.location
if lineFragmentTruncatedGlyphIndex != NSNotFound {
truncationIndex = layoutManager.truncatedGlyphRangeInLineFragmentForGlyphAtIndex(lineFragmentTruncatedGlyphIndex).location
}
stop.memory = true
}
i += 1
}
return truncationIndex
}
请注意,这个还没有经过除了一些简单的情况以外的测试。可能会有需要进行一些调整的边缘情况。
我使用Swift 5更新了@Tim Malseed的解决方案
extension UILabel {
var truncationIndex: Int? {
guard let text = text else {
return nil
}
let attributes: [NSAttributedString.Key: UIFont] = [.font: font]
let attributedString = NSAttributedString(string: text, attributes: attributes)
let textContainer = NSTextContainer(
size: CGSize(width: frame.size.width,
height: CGFloat.greatestFiniteMagnitude)
)
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = NSLineBreakMode.byTruncatingTail
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = NSTextStorage(attributedString: attributedString)
textStorage.addLayoutManager(layoutManager)
//Determine the range of all Glpyhs within the string
var glyphRange = NSRange()
layoutManager.glyphRange(
forCharacterRange: NSMakeRange(0, attributedString.length),
actualCharacterRange: &glyphRange
)
var truncationIndex = NSNotFound
//Iterate over each 'line fragment' (each line as it's presented, according to your `textContainer.lineBreakMode`)
var i = 0
layoutManager.enumerateLineFragments(
forGlyphRange: glyphRange
) { rect, usedRect, textContainer, glyphRange, stop in
if (i == self.numberOfLines - 1) {
//We're now looking at the last visible line (the one at which text will be truncated)
let lineFragmentTruncatedGlyphIndex = glyphRange.location
if lineFragmentTruncatedGlyphIndex != NSNotFound {
truncationIndex = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: lineFragmentTruncatedGlyphIndex).location
}
stop.pointee = true
}
i += 1
}
return truncationIndex
}
}
firstUnlaidCharacter()
或者firstUnlaidGlyph()
比枚举行片段更好? - DrOverbuildfirstUnlaidCharacter()
和firstUnlaidGlyph()
这两个方法与迭代每个行片段一样有效,而且后者根本就没有运行。此外,我还必须将换行模式更改为“.byWordWrap
”。 除此之外,这真的很有帮助。 - DrOverbuild