I’m making an attempt 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 going through to this point is that the UIView is at all times greater than the precise bounds that the textual content wants.
I do know that you should utilize 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 appears to be like like this:
import UIKit
ultimate 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 appears to be like like this (for now it simply renders the inexperienced rectangle behind the textual content, however it is going to have way more customized drawing afterward):
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 learn how to obtain the specified habits? Particularly, how can I make sure 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 screen and that I explicitly set the width and top of that view to some particular values to showcase the issue. The uiView (and subsequently the brown rectangle) has the width and top offered by the SwiftUI’s body modifier.