Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get each line of text in a UILabel

Tags:

I'm trying to add each line in a UILabel to an array, but the code I'm using doesn't appear to be terribly accurate.

func getLinesArrayOfStringInLabel(label:UILabel) -> [String] {

    guard let text: NSString = label.text as? NSString else { return [] }
    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(NSAttributedStringKey.font, 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
}


let label = UILabel()
label.numberOfLines = 0
label.frame = CGRect(x: 40, y: 237, width: 265, height: 53)
label.font = UIFont.systemFont(ofSize: 22, weight: UIFont.Weight.regular)
label.text = "Hey there how's it going today?"
label.backgroundColor = .red
bg.addSubview(label)
print(getLinesArrayOfStringInLabel(label: label))

This prints

["Hey there how\'s it going ", "today?"]

But the label looks like this:

enter image description here

I expected to get ["Hey there how\'s it ", "going today?"]. What's going on?

like image 634
slider Avatar asked Oct 25 '17 02:10

slider


1 Answers

So it appears to be something with UILabel and not something wrong with the function you are using. It was my suspicion that a CATextLayer would render the lines how they are returned from that method and I found out sadly :( that I am right.

Here is a picture of my results: enter image description here

The red is the exact code you used to create your UILabel.

The green is a CATextLayer with all of the same characteristics of the UILabel from above including font, fontsize, and frame size.

The yellow is a subclassed UIView that is replacing its own layer and returning a CATextLayer. I am attaching it below. You can continue to build it out to meet your needs but I think this is the real solution and the only one that will have the get lines matching the visible lines the user sees. If you come up with a better solution please let me know.

import UIKit

class AGLabel: UIView {

    var alignment : String = kCAAlignmentLeft{
        didSet{
            configureText()
        }
    }
    var font : UIFont = UIFont.systemFont(ofSize: 16){
        didSet{
            configureText()
        }
    }
    var fontSize : CGFloat = 16.0{
        didSet{
            configureText()
        }
    }
    var textColor : UIColor = UIColor.black{
        didSet{
            configureText()
        }
    }

    var text : String = ""{
        didSet{
            configureText()
        }
    }


    override class var layerClass: AnyClass {
        get {
            return CATextLayer.self
        }
    }

    func configureText(){
        if let textLayer = self.layer as? CATextLayer{
            textLayer.foregroundColor = textColor.cgColor
            textLayer.font = font
            textLayer.fontSize = fontSize
            textLayer.string = text
            textLayer.contentsScale = UIScreen.main.scale
            textLayer.contentsGravity = kCAGravityCenter
            textLayer.isWrapped = true
        }
    }
}

You should also check out Core-Text-Label on GitHub. It renders exactly as the CATextLayers do and would match the return of the get lines. It won't work for my particular needs as I need mine to be resizable and it crashes but if resizing is not need then I would check it out.

Finally I am back again and it appears that it could be a problem of word wrap that was started in iOS 11 where they do not leave an orphan word on a line.

like image 67
agibson007 Avatar answered Oct 20 '22 23:10

agibson007