I'm building a SwiftUI iPhone app. I've set up some undo behaviour by listening for shakes on my main View and invoking the UndoManager methods, which sometimes have my own actions registered.
Here's some of the code in the main View:
@Environment(\.undoManager) private var undoManager: UndoManager!
.onReceive(
NotificationCenter.default.publisher(for: UIDevice.deviceDidShake),
perform: handleShake
)
private func handleShake(_: Any) {
if let undoManager = undoManager {
if undoManager.canUndo {
undo()
} else if undoManager.canRedo {
redo()
}
}
}
This is all working, but I have a strange situation:
- Perform some action that registers a custom action (e.g. mark a TODO as 'Done')
- Focus in a
TextFieldand edit it - Shake the phone
What happens then is that BOTH the edit in the TextField is undone AND the custom action is undone!
Debug print statements confirm that only one shake is being detected.
I've tried commenting out my custom shake handling, in case iOS was already handling shake to undo for text fields itself, but it's not. Without my code connecting the shake to the UndoManager, TextField edits are not undone on shake.
Another interesting data point is that, if I haven't registered any custom undo actions with the UndoManager, then edit a TextField, then UndoManager.canUndo is false and so the shake doesn't undo anything. I even put in a call to undoManager.undo() when canUndo is false, and this does nothing to the TextField!
So, any help pointing towards what I might have done wrong, or what I'm not understanding about how undo is supposed to work in SwiftUI, would be very appreciated!
Edit:
This part of the code may be relevant, too. Is that override correct? Should it also call the super implementation somehow?
extension UIDevice {
static let deviceDidShake = Notification.Name("deviceDidShake")
}
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with: UIEvent?) {
guard motion == .motionShake else { return }
NotificationCenter.default.post(name: UIDevice.deviceDidShake, object: nil)
}
}