Subclass UITextView using TextKit2

Instead of implementing a textview from scratch (UITextInput it a lot of work/boilerplate)

It makes sense for me to subclass UITextView.

However, when subclassing it seems this is limited to TextKit 1 only, I get an assertion failure:

*** Assertion failure in -[_UITextKit1LayoutController initWithTextView:textContainer:], _UITextKit1LayoutController.m:72

I thought I would just need to call the super init:

super.init(usingTextLayoutManager: true)

But this isn't a designated initialiser:

Must call a designated initializer of the superclass 'UITextView'

Is there a way to do this and override the layout manager so that it uses TextKit 2 in the subclass?

(My aim is to then draw the fragments manually using TextKit2 to get a custom layout while ultimately using all of the UITextView implementation as 99% of it is what I want - other than custom drawing of text fragments).

My code is below:

class DocumentTextView: UITextView {
    private let _textLayoutManager = NSTextLayoutManager()

    private var textContentStorage: NSTextContentStorage {
        textLayoutManager!.textContentManager as! NSTextContentStorage
    }

    override var textLayoutManager: NSTextLayoutManager? {
        _textLayoutManager
    }

    init() {
        let textContainer = NSTextContainer(size: .zero)

        super.init(frame: .zero, textContainer: textContainer)

        _textLayoutManager.textContainer = textContainer

        textContentStorage.attributedString = NSAttributedString(string: text, attributes: [
            .foregroundColor: UIColor.label,
        ])
        textContentStorage.addTextLayoutManager(_textLayoutManager)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
Answered by DTS Engineer in 800456022

Is there a way to do this and override the layout manager so that it uses TextKit 2 in the subclass?

To create a UITextView subclass that uses TextKit2, you can use the designated initialer init(frame:textContainer:) directly, as shown below:

class MyTextView: UITextView { ... }

let textLayoutManager = NSTextLayoutManager()
let textContentStorage = NSTextContentStorage()
textContentStorage.addTextLayoutManager(textLayoutManager)

let size = CGSize(width: 200, height: 0) // The initial size.
let textContainer = NSTextContainer(size: )
textLayoutManager.textContainer = textContainer

let myTextView = MyTextView(frame: .zero, textContainer: textLayoutManager.textContainer)

My aim is to then draw the fragments manually using TextKit2 to get a custom layout while ultimately using all of the UITextView implementation as 99% of it is what I want - other than custom drawing of text fragments

I am unclear what you are trying to achieve, but when implementing custom drawing with UITextView, people typically use their own text layout fragment (NSTextLayoutFragment) subclass + textLayoutManager(_:textLayoutFragmentFor:in:), rather than creating a UITextView subclass.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Is there a way to do this and override the layout manager so that it uses TextKit 2 in the subclass?

To create a UITextView subclass that uses TextKit2, you can use the designated initialer init(frame:textContainer:) directly, as shown below:

class MyTextView: UITextView { ... }

let textLayoutManager = NSTextLayoutManager()
let textContentStorage = NSTextContentStorage()
textContentStorage.addTextLayoutManager(textLayoutManager)

let size = CGSize(width: 200, height: 0) // The initial size.
let textContainer = NSTextContainer(size: )
textLayoutManager.textContainer = textContainer

let myTextView = MyTextView(frame: .zero, textContainer: textLayoutManager.textContainer)

My aim is to then draw the fragments manually using TextKit2 to get a custom layout while ultimately using all of the UITextView implementation as 99% of it is what I want - other than custom drawing of text fragments

I am unclear what you are trying to achieve, but when implementing custom drawing with UITextView, people typically use their own text layout fragment (NSTextLayoutFragment) subclass + textLayoutManager(_:textLayoutFragmentFor:in:), rather than creating a UITextView subclass.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks @DTS Engineer for the suggestion and code!

In answer to the second part, I’m wanting to recreate a Pages/Word style document writing app that has custom rendering of the same body of text over multiple “pages“.

I’ve got a rough working prototype using custom TextKit2 drawing to views but implementing UITextInput manually is A LOT of work.

I was hoping instead to make use of UITextView given I only want to control the layout (drawing) of the fragments and essentially get the text editing experience for free.

But this has proved very difficult (there’s also almost no documentation on this topic other than 2 WWDC videos so it’s a much more difficult task).

I‘ve been able to achieve the above by using a UITextView (without subclassing) and using custom rendering but this draws the text on top of the UITextView in its multiple pages, duplicating the drawn text (not what I want as it effectively draws the UITextView’s text on a layer below as normal, then my text is rendered above it in separate page views, added as subviews to the UITextView)

So I thought perhaps the next step would be to subclass UITextView for more control.

Hopefully you can see what I’m trying to do, does that make sense?

Subclass UITextView using TextKit2
 
 
Q