This the second in a series of tutorials in which we build Freehand Drawing for iOS. In this part we’ll add undo functionality and the ability to draw dots.
In the previous post we built a simple UIView subclass that handles touches and draws the stroke. We found some performance and memory issues and fixed them. Now we’ve reached the point where we want to add more functionality to this simple implementation.
In the initial implementation we didn’t add a way for the user to draw dots. We can interpret that she wants to draw a dot when she taps on the screen.
Let’s try to add this to the current code, with the simplest implementation we can think of. You can go directly to the changes here.
So the first thing we need to do is add a gesture recognizer for taps, and draw a dot when the gesture is ended:
Now we need to draw a dot. Drawing a dot itself is achieved by drawing a filled circle. But then we duplicate a lot of the functionality and optimizations we previously did when drawing lines. We can refactor to share that code.
This is how
drawLine and the new
drawPoint methods look, with the common code refactored into a utility method:
And this is how it looks (warning, programmer art ahead):
This works fine, but as we’ll see in the next section, our approach of adding more code to existing classes is not very flexible.
The main feature we want to talk about in this post is adding undo. You probably know how it works but let’s review it anyway. We’ll add a button which lets the user to go back in time and delete the last line or point she drew.
This will allow her to correct mistakes or simply change her mind after drawing something. The button can be tapped repeatedly with the effect of deleting more and more lines and dots until the drawing is as empty as it was in the beginning.
Bear in mind that one ‘undo’ will remove the last full line or dot the user drew between touching the screen and lifting the finger from the screen, not the last part of the last line.
The current code is simple and understandable. To add undo, possibly the first idea that comes to mind is to add some kind of memory of the path we have been drawing, and then remove the last part when user taps the button. That could be a simple ordered array of points the user went through.
But what about the fact that we want to undo a whole stroke? This means we need to keep track of when the stroke started and when it ended.
What about dots? We need to differentiate between drawing dots and drawing lines. And then, how do we actually undo a line? We have a cached buffer with the accumulated contents the user has been drawing, and we can only render on top of it.
Another issue is that all the logic and display is in the same object. The only way to access touch data and the history of user finger movements is to add that functionality directly to the view. It seems that our
DrawView object code will grow very fast, so it will be harder to understand and maintain in the future.
The command pattern encapsulates the execution of some actions (the command), and abstracts the user of the actions, so it does not know how they are performed. The commands can then be used in a homogeneous way, and higher level operations become trivial, such as reorder, persistence, or in our case, undo.
Let’s try to model our commands. In our domain the commands are strokes the user draws with her finger. Let’s introduce a protocol for them:
Commands should only do one thing, and that is execute their encapsulated actions. The setup, autorelease pool and offscreen buffer are not the responsibility of the command itself, rather it’s the responsibility of the environment on which our command works. Let’s express this with our protocols:
We can now create our first commands directly from our
drawPoint methods. To draw a line we need two points, the width, color, and a context from the canvas:
Similarly we can construct our command to draw a circle:
Separating View and Logic
The most important step is to separate the logic of the commands from the view. Our view in this case will act as the environment for the commands to execute on. It will also do all the required performance optimisations, but there will be no logic about gestures or commands.
We can create a different object, a controller if you will, to do all the gesture and command logic. This object doesn’t need to be a UIViewController subclass. In fact it only has to be an NSObject subclass as it needs to offer selectors for callbacks of gesture recognizers. This object will hold an ordered list of commands.
You can find the change here. Let’s highlight the key changes. First we have introduced another protocol, called
DrawCommandReceiver, just to decouple the view and controller objects, as executing a command needs the context to be set up in the view:
The view contains much less code, conforms to two protocols and has no gesture logic:
All the logic is moved to a new class, called
FreehandDrawingController. It contains the same gesture handling as before, but it delegates the execution of the commands to the canvas:
Now we’ve refactored our code and made it easier to reason about by having a list of commands that are executed in the canvas. The remaining question is how to actually implement an undo.
As you may recall, the way that in Core Graphics all changes are additive. This means that executing any API will draw on top of what was there previously. We also store the drawing in an offscreen image that we are continuously modifying and replacing onscreen.
A way to see undo graphically is as a deletion, but how do we express this in the terms of our API? There are several ways, so let’s consider just two of them:
- A command knows how to undo itself, by drawing what was there before it was executed.
- A command only knows how to do itself and undo is implemented at the queue level.
Redoing whole queue
We can think of undo as executing all commands but the last one in order, on an empty canvas. Implementing undo this way makes code and commands simpler. The commands only need to know how to ‘do’ the action, but not how to ‘undo’ it.
The drawback of this solution is that undoing will take longer if the queue has many commands. The slowness is likely not to be perceived by the user as they tap a button. Let’s implement it and see if it will affect the user experience.
We’ll add a button to our toolbar, which will result in a method call of our new
FreehandDrawController. The controller will then remove the last command from the queue, clear the canvas and execute all the other commands in order:
We’ll need a new
reset method on our canvas protocol, implemented in the view:
If you run this, you’ll see that it does not work as expected. There are two problems:
- Undo is very slow every tap, even when user draws only a few lines.
- We’re deleting the last part of the last line, not the whole line itself. This is not what we expect when we tap the button.
Fixing the performance problem means changing the
DrawCommandReceiver protocol to accept an ordered list of commands to execute, as opposed to only one command. This will allow the underlying view to set up the context and change the buffer only once for the whole set of commands.
You can see the previous changes here.
To make our undo operation behave as intended, we can take advantage our types, which requires very little effort.
A whole line is constructed by all the intermediate points the user is going through with her finger. We can model this in our controller by using a composed command.
A composed command will be a command that contains an ordered list of commands. These commands will be single line commands. The queue itself will contain either composed commands (a whole line) or a circle command (a dot). That way we’ll still undo the last command in the queue, but actually undo the whole stroke.
Check the changes here.
The composed command:
This does not expose the internal array of commands but allows the addition of a new command. Removing a command is not needed for now.
We can use this new command in our controller. We’ll accumulate line commands in a temporary composed command, until the user finishes the stroke gesture. When the gesture is finished we’ll save the composed command in the queue:
With these changes we’ve finished our undo implementation.
We’ve added the ability to draw dots and undo. When implementing undo we refactored the code to make it easier to understand and maintain in future.
Taking leverage of the command design pattern and some domain modelling we could easily implement undo as an operation that removes that last command and executes all accumulated commands in order on a blank canvas.
In the next post we’ll improve how the stroke looks.