FREE TRIAL: Accelerate your app development career with Hacking with Swift+! >>

SOLVED: SwiftUI app life cycle, Undo/Redo, and menu bar items

Forums > macOS

TL;DR: how do you wire up the Edit->Undo action to a specific window's UndoManager, given you're doing it all with SwiftUI?

--

The long (and possibly interesting) version:

  1. Start with a macOS document-based application. Because your model is going to be complex (more complex and fungible than would allow for struct model objects, even with @State members, change the template's document to be a ReferenceFileDocument. This isn't too bad (okay, it makes for a lot of extra model code in my case, but I don't mind because I used to do Java in the enterprise so this part is a cakewalk) you just have to make two versions of everything that's mutable -- a class and a struct.
  2. Next, add an UndoManager to the document type. There is a (page)[https://www.swiftdevjournal.com/undomanager-introduction/] out there that asserts, "Document-based Mac and iOS apps have an undo manager for each document. Use the undoManager property to access the document’s undo manager." That may be true, but I can't prove it. What I see is that the document provided by the app template just implements a protocol rather than inheriting from a class. So, maybe that's my problem right there.
  3. Now, add mutators to the class model object(s). The reason we do this is because, in addition to changing the state, we're also registering undo/redo actions. This means that the UndoManager needs to be passed in to these methods, but that's okay. So our Views are going to hold @ObservableObject references to the appropriate document, which contains the model object hierarchy which is being displayed. This is actually great, since when the View has a control that has updated state, it can tell the document objectWillChange.send() and then do the model update, which needs to have the document's UpdateManager anyway. All win!
  4. Except! When we run the app and then perform an action which registers an undo action, the Undo menu item in the application's Edit menu is a) disabled and b) doesn't do anything.

So, there's a disconnect, right? There are UndoManager instances (one per document) that have undo-able actions registered; how do we tell the menu item which UndoManager it should pay attention to right now?

   

It should use the UndoManager located in the Environment under the current View. You would access it like:

@Environment(\.undoManager) var undoManager

This article seems a bit complex but it might work for you or at least offer some inspiration. I confess I haven't really tried much with document-based SwiftUI apps on macOS yet.

1      

So, it turns out that the One True UndoManager is handed off in the environment. The answer is, don't create an UndoManager on your document, instead, declare it in your View:

struct MyView: View {
    @Environment(\.undoManager) var undoManager // <-- How in the world would I know this?

    ...

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Get started

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.