Using NSUndoManager for fun and profit

Damiaan Twelker
LIVEOP X Team
Published in
4 min readOct 9, 2018

In the real world, where the real world is restricted to the world of computer software, undo operations are a common phenomenon. The use cases that usually come to mind when thinking of undo operations are productivity applications, such as document editors, or creativity applications such as photo editors. Indeed, in the LIVEOP X iOS app, a drawing component formed our first encounter with NSUndoManager as well.

Productivity plays a key role in LIVEOP X. In a critical situation, there is no room for error, misinterpretation or “trying to switch it on and off”. If the application does not respond the user expects it to, the user will simply abandon the app, stow the tablet, and provide first aid without all the essential information the app has to offer. To prevent this and to reach the goal of creating value for the user, we must set the highest standards in terms of productivity and app performance. This starts with the development, where sometimes we find ourselves getting some help from unexpected sources.

Within our GEO tool, first responders are handed a paint bucket to draw instructions on the map and share these with others. The hand-drawn instructions are rendered in real-time across all zoom-levels on the map, as if they were there from the beginning. The tool especially comes in handy to sketch out approach paths for units that are still en-route, or to assess the impact area of potentially toxic gas clouds. Part of the drawing tool is an undo button that undos the previously drawn line segment. An illustration is given below.

Drawing an approach route over Dam Square in Amsterdam. The view encompasses a variety of information, such as building type colouring (BAG), height information through the use of 3D buildings, and satellite imagery.

How does that look in code? Very simple. At the same time when the line segment is added to the map, we register an undo operation with a single NSUndoManager for the exact same line segment.

- (void)addHandDrawnAnnotations:(NSArray <id> *)annotations{
for (id annotation in annotations) {
[self.mapController addAnnotation:annotation];} [self.undoManager registerUndoWithTarget:self selector:@selector(removeHandDrawnAnnotations:) object:annotations];}

NSUndoManager's APIs make it incredibly easy to use. In fact, its applicability and suitability in a codebase extends far beyond its typical use cases as described earlier.

Undo outside a clear user context

In our GEO configuration portal, customers can define custom actions and attach them to map layers. Then whenever an annotation belonging to a map layer is selected in the iOS app, the actions configured for that layer will be executed. An action could be to select additional map layers, or to reveal additional UI elements. An action is just a description telling the app what to do when the user selects an annotation. When an action changes in the server configuration, we simply recreate the view model representing the action.

The actions run just once. But whenever a map layer is deselected, either programmatically or by the user, or when a new annotation is selected from the same map layer, we want the impact of previously executed actions to be voided: all actions assume a clean slate. At first thought this would require a lot of hairy code checking what actions have been applied, finding and matching it with the consequences in the app, and discarding those. It is however infeasible to couple the consequences (e.g. extra UI added as a result of an action) with the action view models, since the view models are recreated on the fly. NSUndoManager forms a great solution here. Plus, overall we like to avoid hairy code if possible.

On the map layer view model, we define an NSUndoManager with one group. The group is opened on creation. Then whenever we want to undo a layer’s actions, we close the group, call -undo, and open a new group. We use grouping in order to get rid of all actions at once. Undo actions are registered at the same time actions are executed. In case of an action that adds a button to the UI, this looks as follows:

Here model is the map layer view model. Note that NSUndoManager provides two methods for registering undo actions. In case the undo selector has just one argument, registerUndoWithTarget:selector:object can be used. For more complicated undo handling, we could resort to registerUndoWithTarget:handler:.

When the layer is deselected, or when a new annotation is selected from the same layer, we close the one undo group and call -undo, causing the consequences of all actions to be discarded:

In conclusion, NSUndoManager is a very powerful tool that comes in handy in a lot of situations in which one might not immediately think of it. In the use case highlighted above, we save ourselves a lot of unnecessary and hard to maintain spaghetti code.

--

--