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 ?
}
}
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.