I’m attempting to develop my very own UIView
that leverages TextKit2 to take management over the textual content rendering to use customized drawing.
The issue I’m dealing with thus far is that the UIView is at all times greater than the precise bounds that the textual content wants.
I do know that you need to use layoutFragmentFrame
or renderingSurfaceBounds
inside a subclass of NSTextLayoutFragment
to get the precise bounds for the textual content – however I used to be not ready resize the view accordingly.
This screenshot illustrates the issue:
My UIView
implementation seems to be like this:
import UIKit
remaining class TextKit2UIView: UIView, NSTextLayoutManagerDelegate {
let contentStorage = NSTextContentStorage()
let layoutManager = NSTextLayoutManager()
let textContainer = NSTextContainer(measurement: .zero)
override init(body: CGRect) {
tremendous.init(body: body)
contentStorage.addTextLayoutManager(layoutManager)
layoutManager.textContainer = textContainer
layoutManager.delegate = self
backgroundColor = .brown.withAlphaComponent(0.2)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = .byTruncatingTail
contentStorage.attributedString = NSAttributedString(
string: "Integer bibendum luctus metus, vel mattis diam consequat non. Phasellus magna augue, faucibus et ullamcorper sit amet, vehicula quis nunc. Phasellus nisl arcu, tempus id metus ac, tempus mollis nisi. Duis in ultrices lectus.",
attributes: [.font: UIFont.systemFont(ofSize: 18)]
)
}
required init?(coder: NSCoder) { nil }
// utilizing my very own customized NSTextLayoutFragment
func textLayoutManager(_ textLayoutManager: NSTextLayoutManager,
textLayoutFragmentFor location: NSTextLocation,
in textElement: NSTextElement) -> NSTextLayoutFragment {
CustomLayoutFragment(textElement: textElement, vary: textElement.elementRange)
}
override func layoutSubviews() {
tremendous.layoutSubviews()
textContainer.measurement = bounds.measurement
layoutManager.ensureLayout(for: contentStorage.documentRange)
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
layoutManager.enumerateTextLayoutFragments(from: nil, choices: []) { fragment in
fragment.draw(at: fragment.layoutFragmentFrame.origin, in: ctx)
return true
}
}
}
And my NSTextLayoutFragment
subclass seems to be like this (for now it simply renders the inexperienced rectangle behind the textual content, however it would have far more customized drawing in a while):
class CustomLayoutFragment: NSTextLayoutFragment {
override func draw(at renderingOrigin: CGPoint, in ctx: CGContext) {
let completeRect = self.layoutFragmentFrame
let shapeRect = Rectangle().path(in: completeRect).cgPath
ctx.saveGState()
ctx.addPath(shapeRect)
ctx.setFillColor(UIColor.inexperienced.withAlphaComponent(0.2).cgColor)
ctx.fillPath()
ctx.restoreGState()
tremendous.draw(at: renderingOrigin, in: ctx)
}
}
May somebody please clarify the way to obtain the specified habits? Particularly, how can I be certain that the uiView’s body matches the textual content’s body, eliminating the seen brown background that the screenshot reveals?
Please observe that I used SwiftUI to indicate the UIView on display and that I explicitly set the width and top of that view to some particular values to showcase the issue. The uiView (and due to this fact the brown rectangle) has the width and top supplied by the SwiftUI’s body modifier.