# How to interact with a (NS)UndoManager?

**URL:** <https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894>\
**Category:** Swift Composable Architecture\
**Created:** [October 4, 2020, 4:19am UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894 "2020-10-04T04:19:25Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![tgrapperon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tgrapperon/32/13066_2.png) [@tgrapperon](https://forums.swift.org/u/tgrapperon)\
**Post date:** [October 4, 2020, 4:19am UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894/1 "2020-10-04T04:19:26Z")

</div>

Hello!  
In my mixed UIKit/SwiftUI app, I would like to interact with some regular UIKit/AppKit UndoManager, but I'm not sure about the way to setup things.

I guess interacting with the UndoManager is a side effect, so let put it in the environment

```swift
struct State: Equatable {
  var value: String = ""
}
enum Action {
  case changeString(String)
}
struct Environment {
  var undoManager: UndoManager
}

```

The classical way to support undo/redo with the UndoManager with blocks is

```swift
func changeString(_ string: String) {
  let currentValue = state.value
  undoManager.registerUndo(withTarget: self) { target in
    target.changeString(currentValue)
  }
  undoManager.setActionName("change string")
  state.value = string
}

```

In this way, each time it's undoing, it's registering itself for redoing, and reciprocally.  
I'm trying to reproduce the same with SCA. I would like to continue using a regular UndoManager because it is automatically linked in several places by UIKit/AppKit.

The issue is that UndoManager needs to act on the behalf of the user and send a `.changeString(String)` to some `ViewStore` or `Store` when it's undoing. `UndoManager` doesn't seem to retain the target, so I need to retain it explicitly in case it went away when we are undoing.

So I'm extending `UndoManager`:

```swift
extension UndoManager {
  func registerUndo<State, Action>(action: Action,
                                     store: Store<State, Action>,
                                     name: String? = nil) 
                                       where State: Equatable {
    registerUndo(withTarget: store) { [store] _ in
            ViewStore(store).send(action)
    }
    name.map(setActionName)
  }
}

```

I'm capturing explicitly the `store` (it should be automatic, but I'm marking that's intentional). I'm also creating a single use ViewStore to send the action.

I also need to extend `Action`

```swift
enum Action {
  case changeString(String, Store<State, Action>)
}

```

and the reducer for this action becomes

```swift
let reducer = Reducer<State, Action, Environment> {
    state, action, environment in
  
  switch action {
  case let .changeString(string, store):
    let current = state.string
    state.string = string
    return .fireAndForget { 
      environment.registerUndo(action: .string1(current, store), 
                                store: store, 
                                 name: "change string") 
    }
  }
}

```

And when I'm making a change, I'm sending `.changeString("A", store)` to the `ViewStore`.

It seems to work OK. I don't see retain cycles, and captured stores are released after the undo block evaluates. I don't have very much experience with SCA, and maybe I'm doing something completely illegal, or I'm missing something.

Does anybody have any comment or alternative way to achieve this?  
Thanks!

---

<div class="post-metadata">

**Author:** ![mathieutozer](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mathieutozer/32/13429_2.png) [@mathieutozer](https://forums.swift.org/u/mathieutozer)\
**Post date:** [October 10, 2020, 7:46pm UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894/2 "2020-10-10T19:46:58Z")

</div>

I am wrestling with this too - my thinking is that if we want to hook into the platform UndoManager there isn't a way around having to pass it a store since it needs to send an action back to an object, although it doesn't seem that nice to pass stores as parameters to actions...

I was hoping there was some higher order way of solving this - I am thinking there might be a way to write a pullback function like the forEach functions which will pass the store as a parameter for you like this.

```
  counterReducer.passingStore(
    state: \CounterListState.counters,
    action: /CounterListAction.counter(store:action:),
    environment: { _ in CounterEnvironment() }
  )

```

Conceptually I think an action is just a redo, and undo is the opposite of that. So the undo stack is just a FIFO array of Effects. I wonder if we could write a class to wrap the undo manager which maintains its own list of effects and registered uniform target / selector?

It would be great if the Composable Architecture authors could weigh in here 🙂

---

<div class="post-metadata">

**Author:** ![innoreq](https://avatars.discourse-cdn.com/v4/letter/i/dec6dc/32.png) [@innoreq](https://forums.swift.org/u/innoreq)\
**Post date:** [April 14, 2021, 2:57pm UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894/3 "2021-04-14T14:57:37Z")

</div>

Well, especially the recent .form enhancements make it even unclearer to me how an undo/redo mechanism could be properly realized. Any feedback of the TCA authors would be highly appreciated here as well...

---

<div class="post-metadata">

**Author:** ![alexandramedway](https://avatars.discourse-cdn.com/v4/letter/a/e19b73/32.png) [@alexandramedway](https://forums.swift.org/u/alexandramedway)\
**Post date:** [May 24, 2021, 2:25pm UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894/4 "2021-05-24T14:25:25Z")

</div>

Hey all! We had a similar need and came up with the following approach. You can set up the state in a similar way to the following:

```
struct State: Equatable {
  var value: String = ""
}
enum Action {
  case onDidFinishLaunching // Called when you open your application window for the first time
  case closeApp // Called when you close your application
  case changeString(String)
}
struct Environment {
  var undoClient: TCAUndoClient
}

```

Where the TCAUndoClient is a struct that provides the following public interface:

```
struct TCAUndoClient {
    var undoEffects: Effect<Action, Never>
    func register(_ undoAction: Action, _ actionName: String) -> Void
}

```

Now, you can implement this struct with something like the following (passing in your UndoManager)

```
struct TCAUndoClient {
    private var undos: PassthroughSubject<Action, Never> = PassthroughSubject()
    var undoEffects: Effect<Action, Never> {
        return undos.eraseToEffect()
    }
    init(undoManager: UndoManager) {
        self.undoManager = undoManager
    }
    weak var undoManager: UndoManager?
    func register(undoAction: Action, actionName: String) {
        undoManager?.registerUndo(withTarget: undos) { undos in
            undos.send(undoAction)
        }
        undoManager?.setActionName(actionName)
    }
}

```

Now, in the environment, we have access to a stream of Actions from the TCAUndoManager, which we can subscribe to when the app launches and unsubscribe from when the app closes. Your reducer would look like this:

```
let reducer = Reducer<State, Action, Environment> { state, action, environment in
  struct UndoEffectsCancellable: Hashable { }
  switch action {
  case .onDidFinishLaunching:
    // Subscribe to the stream of actions
    return undoManager.undoEffects
                    .map { $0 }
                    .cancellable(id: UndoEffectsCancellable(), cancelInFlight: true)
  case .closeApp:
    // Unsubscribe 
    return Effect.cancel(id: UndoEffectsCancellable())
  case let .changeString(string, store):
    let originalValue = state.string
    state.string = string
    return .fireAndForget { 
      environment.register(.changeString(originalValue), "Change String") 
    }
  }
}

```

And if you wanted to extract the undo logic entirely from your reducer, you could do something like this instead:

```
let reducer = Reducer<State, Action, Environment> { state, action, environment in
  struct UndoEffectsCancellable: Hashable { }
  switch action {
  case .onDidFinishLaunching:
    // Subscribe to the stream of actions
    return undoManager.undoEffects
                    .map { $0 }
                    .cancellable(id: UndoEffectsCancellable(), cancelInFlight: true)
  case .closeApp:
    // Unsubscribe 
    return Effect.cancel(id: UndoEffectsCancellable())
  case let .changeString(string, store):
    state.string = string
    return .none
  }
}.undoReducer()
extension Reducer {
    func undoReducer() -> Self {
        .init { (state, action, env) -> Effect<Action, Never> in
            switch action {
            case .changeString:
                let originalValue = state.string
                let effects = self.run(&state, action, env)
                environment.register(.changeString(originalValue), "Change String")                 
                return effects
            default:
                return self.run(&state, action, env)
            }
        }
    }
}

```

---

<div class="post-metadata">

**Author:** ![tgrapperon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tgrapperon/32/13066_2.png) [@tgrapperon](https://forums.swift.org/u/tgrapperon)\
**Post date:** [May 24, 2021, 3:59pm UTC](https://forums.swift.org/t/how-to-interact-with-a-ns-undomanager/40894/5 "2021-05-24T15:59:01Z")

</div>

Thanks! This is indeed a good idea, as the `PassthroughSubject` is not "leaking" the store or a `ViewStore`.  
The only downside it that a `Store` must be observing the subject at all time to maintain coherence, but I guess it's not an issue at all if this store is the root store or very close.  
I'm working on a higher order reducer to manage undo/redo for any state that I'm planning to open-source. Currently, its `UndoManager` is read-only, but I'll experiment with this idea to see if I can reinstall read/write functions in the `UndoManager`.
