iosswiftuikituitextview

How to trigger the new Format Panel (iOS 18) via a button press in UITextView?


I'm trying to programmatically trigger the appearance of the new Format Panel introduced in the WWDC24 session "What's new in UIKit" when a button is pressed.

So far, I've enabled text formatting by setting allowsEditingTextAttributes = true. This works for showing the Format Panel through the edit menu (when long-pressing or selecting text). However, I can't find a way to make the panel appear directly via a button press in the keyboard toolbar.

Does anyone know if this is possible? If so, how?

Here’s the (simplified) code I’m working with:

struct TextEditorView: UIViewRepresentable {
    @Binding var text: NSAttributedString

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> UITextView {
        let textEditorView = UITextView()
    
        textEditorView.addToolbar()
        textEditorView.allowsEditingTextAttributes = true
        textEditorView.delegate = context.coordinator
        
        return textEditorView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.attributedText = text
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
    
        var parent: TextEditorView
    
        init(_ uiTextView: TextEditorView) {
            self.parent = uiTextView
        }
    
        func textViewDidChange(_ textView: UITextView) {
            self.parent.text = textView.attributedText
        }
    }
}
extension UITextView {
    func addToolbar() {
        let toolbar = UIToolbar()

        let formatButton = UIBarButtonItem(
            image: UIImage(systemName: "textformat.alt"),
            style: .plain,
            target: self,
            action: #selector(showTextFormattingPanel)
        )
        
        toolbar.items = [formatButton]
        self.inputAccessoryView = toolbar
    }
    
    @objc private func showTextFormattingPanel() {
        // ? Show Format Panel ?
    }
}

Solution

  • There is no public API for triggering the display of the new formatting screen of a UITextView. When you select the new Format -> More... context menu in a UITextView, it results in a call to the private API _showTextFormattingOptions:. So one solution is to directly call that private API:

    @objc private func showTextFormattingPanel() {
        // Show Format Panel
        self.perform(NSSelectorFromString("_showTextFormattingOptions:"), with: self)
    }
    

    This does work in a test development app. But this is far from ideal. It's quite possible this could cause an app rejection for using a private API. That's easy to avoid though will a little obfuscation of the selector string. The bigger problem is that the private API could change in a future iOS update which would result in the app crashing due to calling an unrecognized selector.

    A much more difficult solution would be to create and present your own instance of UITextFormattingViewController. You would need to provide a delegate to handle all of the value changes and you would then need to manually apply them to the text view. This solution would be a lot more work. It also replicates all of the built-in functionality provided by the private API of UITextView. And the UITextFormattingViewController documentation contains no comments so it's really hard to know how many of the APIs work.