iOS UILabel textAlignment .justified results in wrong rect by layoutManager.boundingRect

I have a UILabel subclass showing NSAttributedString in which I need to draw a rounded rectangle background color around links:

import UIKit

class MyLabel: UILabel {
    
    private var linkRects = [[CGRect]]()
    private let layoutManager = NSLayoutManager()
    private let textContainer = NSTextContainer(size: .zero)
    private let textStorage = NSTextStorage()
    
    override func draw(_ rect: CGRect) {
        
        let path = UIBezierPath()
        linkRects.forEach { rects in
            rects.forEach { linkPieceRect in
                path.append(UIBezierPath(roundedRect: linkPieceRect, cornerRadius: 2))
            }
        }
        UIColor.systemGreen.withAlphaComponent(0.4).setFill()
        path.fill()
        
        super.draw(rect)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    private func setup() {
        
        numberOfLines = 0
        adjustsFontForContentSizeCategory = true
        isUserInteractionEnabled = true
        lineBreakMode = .byWordWrapping
        contentMode = .redraw
        clearsContextBeforeDrawing = true
        isMultipleTouchEnabled = false
        
        backgroundColor = .red.withAlphaComponent(0.1)
                
        textContainer.lineFragmentPadding = 0
        textContainer.maximumNumberOfLines = numberOfLines
        textContainer.lineBreakMode = lineBreakMode
        textContainer.layoutManager = layoutManager
        
        layoutManager.textStorage = textStorage
        layoutManager.addTextContainer(textContainer)
        
        textStorage.addLayoutManager(layoutManager)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        calculateRects()
    }
    
    private func calculateRects(){
        
        linkRects.removeAll()
        
        guard let attributedString = attributedText else {
            return
        }
        
        textStorage.setAttributedString(attributedString)
        
        let labelSize = frame.size
        textContainer.size = labelSize
        
        layoutManager.ensureLayout(for: textContainer)
        
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        
        print("labelSize: \(labelSize)")
        print("textBoundingBox: \(textBoundingBox)")
        
        var wholeLineRanges = [NSRange]()
        layoutManager.enumerateLineFragments(forGlyphRange: NSRange(0 ..< layoutManager.numberOfGlyphs)) { _, rect, _, range, _ in
            wholeLineRanges.append(range)
            print("Whole line: \(rect), \(range)")
        }
                
        attributedString.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedString.length)) { value, clickableRange, _ in
            if value != nil {
                
                var rectsForCurrentLink = [CGRect]()

                wholeLineRanges.forEach { wholeLineRange in
                    if let linkPartIntersection = wholeLineRange.intersection(clickableRange) {
                        
                        var rectForLinkPart = layoutManager.boundingRect(forGlyphRange: linkPartIntersection, in: textContainer)
                        
                        rectForLinkPart.origin.y = rectForLinkPart.origin.y + (textContainer.size.height - textBoundingBox.height) / 2 // Adjust for vertical alignment

                        rectsForCurrentLink.append(rectForLinkPart)
                        
                        print("Link rect: \(rectForLinkPart), \(linkPartIntersection)")
                    }
                }
                
                if !rectsForCurrentLink.isEmpty {
                    linkRects.append(rectsForCurrentLink)
                }
            }
        }

        print("linkRects: \(linkRects)")
        
        setNeedsDisplay()
    }

}

And I use this as such:

let label = MyLabel()

label.setContentHuggingPriority(.required, for: .vertical)
label.setContentHuggingPriority(.required, for: .horizontal)
view.addSubview(label)
label.snp.makeConstraints { make in
    make.width.lessThanOrEqualTo(view.safeAreaLayoutGuide.snp.width).priority(.required)
    make.horizontalEdges.greaterThanOrEqualTo(view.safeAreaLayoutGuide).priority(.required)
    make.center.equalTo(view.safeAreaLayoutGuide).priority(.required)
}

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .justified

let s = NSMutableAttributedString(string: "Lorem Ipsum: ", attributes: [.font: UIFont.systemFont(ofSize: 17, weight: .regular), .paragraphStyle: paragraphStyle])
s.append(NSAttributedString(string: "This property controls the maximum number of lines to use in order to fit the label's text into its bounding rectangle.", attributes: [.link: URL(string: "https://news.ycombinator.com/") as Any, .foregroundColor: UIColor.link, .font: UIFont.systemFont(ofSize: 14, weight: .regular), .paragraphStyle: paragraphStyle]))

label.attributedText = s

Notice the paragraphStyle.alignment = .justified

This results in:

https://i.sstatic.net/7Aqh1GGe.png

As you can see, the green rect background is starting a bit further to the right and also ending much further to the right.

If I set the alignment to be .left or .center, then it gives me the correct rects:

https://i.sstatic.net/Lh0Uw3Fd.png

https://i.sstatic.net/oHSNrBA4.png

Also note that if I keep .justified but change the font size for the "Lorem Ipsom:" part to be a bit different, lets say 16 instead of 17, then it gives me the correct rect too:

https://i.sstatic.net/9Qat0QCK.png

Also note that if we remove some word from the string, then also it starts giving correct rect. It seems like if the first line is too squished, then it reports wrong rects.

Why is .justified text alignment giving me wrong rects? How can I fix it?

iOS UILabel textAlignment .justified results in wrong rect by layoutManager.boundingRect
 
 
Q