How to get text / String from nth line of UILabel?

前端 未结 10 1632
孤街浪徒
孤街浪徒 2020-11-27 05:27

Is there an easy way to get (or simply display) the text from a given line in a UILabel?

My UILabel is correctly displaying my text and laying it out beautifully bu

10条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-27 05:50

    Very important change regarding iOS 11+

    Starting with iOS 11, Apple intentionally changed the behaviour of their word-wrapping feature for UILabel which effects detecting the String contents of individual lines in a multiline UILabel. By design, the word-wrapping of the UILabel now avoids orphaned text (single words in a new line), as discussed here: word wrapping in iOS 11

    Because of that, the way CTFrameGetLines(frame) returns the CTLine array of all lines in the label no longer works correctly if the new word-wrapping that avoids orphaned text takes effect in a particular line. To the contrary, it results in parts of the String that by the new word wrapping design would belong to the next line instead end up in the line in focus.

    A tested fix for this problem can be found in my altered version of @TheTiger's answer, which makes use of calculating the actual content size of the UILabel using sizeThatFits(size:), before using that size to create the rect / path written in Swift 4:

    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
        }
    }
    

    This UILabel extension returns the contents of the label as a String array with one entry per line exactly as presented to the eye of the user.

提交回复
热议问题