Gestures and MVVM
By: Riccardo Cipolleschi
Tags: Mobile App Development, Apple, iOS, UI, Swift

Programming

Gestures and MVVM

How to properly propagate gesture updates in an MVVM architecture

I have never worked with gestures in iOS. Seems weird, but it’s the truth. This week, I had to integrate a gesture in an app to test a couple of things that we are developing, but I struggled a bit to make it work properly with our MVVM architecture.

At Bending Spoons we are extremely eager to help out our colleagues and to share what we know. Lucky for me, I have a colleague who worked on that same issue before and shared a nice trick to solve the problem elegantly. I thought that if I benefitted from this hint, someone else could as well.

The trick consists of using the gesture recognizer to keep track of the starting situation instead of using a variable in our VM. Let’s see how it can be done!

The Architecture

In a Model-View-ViewModel (MVVM) architecture, we would like for our View to be an exact representation of the data contained in the View Model (VM) .

When changes in the VM are discrete, everything works beautifully: if we have a VM with a var name: String and a UILabel in the View to render that name, the MVVM architecture provides an automatic way to update the view when the name changes.

However, when things get updated in a continuum, as it happens for the gestures, things can get a little bit messy. In this situation, there are continuous updates to the VM and you may have to work with differential values considering the VM in the previous iteration and the new one. But we don’t have the concept of a previous iteration! In my case, I wanted to move an element on the screen, based on a pan gesture performed by the user.

Here are my View Model and the function of the view that layout the elements.

The VM contains just a CGPoint that represents an offset, the displacement of the ball from the center of the screen.

The layout is performed by setting the center of the ball in the center of the screen, summing it up with the offset property in my VM . To simplify the sum operations, I wrote a straightforward extension for CGPoint :

Of course, when the model changes, we want to redraw the screen and move the ball into the eventually new position. This can be done easily with a property observer and an update(...) method in our view:

Thanks to this code, whenever the VM is set, the update function is invoked and we instruct UIKit that the View needs to be layout again: in the new layout cycle, the layoutSubviews method will be invoked to redraw the screen.

Connecting the View to the VC

At this point, we need to implement the gesture and to connect it with our view.

To implement a gesture, we need to add a GestureRecognizer. In this specific case, we will use a UIPanGestureRecognizer . The UIPanGestureRecognizer is an object that is able to detect when the user is dragging a finger on the screen, how much are they drag it, and how fast. The pattern to connect a gesture recognizer to an element is the standard Target-Action iOS pattern. Here the code to create a gesture recognizer, assign it to the view, and connect it to the view model:

In this snippet, you can see:

the declaration of the ball we need to move (line 4) the declaration of the interaction to connect the gesture with the VC (line 7) how to add the ball view to the View (line 10) how to declare a gesture recognizer and how to add it to the view (line 13 and 14) how to handle when the gesture is actually recognized (the method panGestureRecognized at line 17 )

In the panGestureRecognized method, we handle the gesture. Every gesture recognizer has a state variable that tells us in which state of the recognition we are.

If we have just detected the gesture, we are in the began state.

state. If the user is still dragging, we are in the changed state.

state. If the gesture is finished, we could be in the ended state in the canceled state, depending on what the user did.

In this method, we can notify the VC that a gesture has been correctly recognized so that it can update the VM accordingly. For sake of completeness, here the VC code that updates the VM :

Implementing The Gesture

Finally, let’s see how to properly implement gesture recognition. I’ll show the code first, and then we will analyze it together:

I added a comment on all the lines to let everyone follow the code with ease.

The most interesting chunk is the began block. Here we detect that the interaction has started. What we want is to move the ball from its current position. This is given by the center of the screen plus the current offset, as it is set in the VM .

Then, we need to know how much the user dragged the ball with respect to the parent's view. Line 15 does exactly that: it asks the gesture recognizer to retrieve the translation in the parent’s view coordinate system. With those two values, we can compute the new offset from the center, by just summing them up.

Then, here the trick: we set this new value as the current translation of the gesture recognizer (line 21) !

If we analyze the GestureRecognizer documentation, in fact, we know that whenever a new gesture is recognized, the gesture recognizer will start the computation from 0 . So, if you are recognizing a translation and you move the ball from the center of the view on the right, the translation will be of

(+x, +0) , independently from the previous position of the element. If you are rotating an object, it will always start from 0 , despite you already rotate it before, and so on for each other gesture recognizer.

This simple trick, instead, let you set the initial translation, rotation, or zoom so that every other value obtained by the gesture recognizer will be computed with respect to that new initial value instead of 0 .

The rest of the snippet is pretty straightforward: we just notify the VC about the translation. Also, after we set the new starting point, implementing the changed and ended state is as trivial as getting the new value and notifying the VC . And this is the result:

Note: if you try this code in a playground, the performances would be sluggish. I think that the Playgrounds perform some other operations and they do not render the screen with the same frequency of the simulator. However, a real device or even an iOS simulator will work like a charm.

The whole code for this screen ( VC , VM , View ) can be found in this gist.

Conclusion

Today we have seen how to plug gesture recognition in a MVVM architecture. To achieve a readable and elegant code, we leveraged the setTranslation API to set into the gesture recognizer the initial point for the gesture. All the gesture recognizers have their own version of this API: for example, the UIRotateGestureRecognizer has a setRotation method.

Thanks to this trick we are now able to write all our interactions in a very clean way, leveraging what the platform offers, avoiding glitches, and keeping our architecture neat.

I am working on iOS for 5 years now, and every week there is some new trick I learn. And this continues to amaze me, every day. And you? What are some tricks that you use and that may be unknown to many others? I would be extremely happy to learn them!