You need access to the internal UndoManager that TextEditor uses. Then you can use it to configure your undo button and to call undo()/redo() on, BUT I don't know of a way to get to that UndoManger.
You can however, create your own UIViewRepresentable that wraps a UITextView and then get access to the internal undo manager that UITextView uses.
Here’s how I did that:
Declare a binding in your UITextView:UIViewRepresentable to an UndoManager. Your UIViewRepresentable will set that binding to the UndoManager provided by the UITextView. Then your parent View has access to the internal UndoManager.
struct MyTextView: UIViewRepresentable {
/// The underlying UITextView. This is a binding so that a parent view can access it. You do not assign this value. It is created automatically.
@Binding var undoManager: UndoManager?
func makeUIView(context: Context) -> UITextView {
let uiTextView = UITextView()
// Expose the UndoManager to the caller. This is performed asynchronously to avoid modifying the view at an inappropriate time.
DispatchQueue.main.async {
undoManager = uiTextView.undoManager
}
return uiTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
}
}
struct ContentView: View {
/// The underlying UndoManager. Even though it looks like we are creating one here, ultimately, MyTextView will set it to its internal UndoManager.
@State private var undoManager: UndoManager? = UndoManager()
var body: some View {
NavigationView {
MyTextView(undoManager: $undoManager)
.toolbar {
ToolbarItem {
Button {
undoManager?.undo()
} label: {
Image(systemName: "arrow.uturn.left.circle")
}
}
}
}
}
}