Rotate a UIView with Gestures

Add UIRotationGestureRecognizer to UIView

UIKit has many useful gesture recognizers. I found UIRotationGestureRecognizer a little tricker than the other gesture recognizer classes. I’m going to walk through rotating a UIView (or any subclass of UIView) corresponding to user’s gestures using UIRotationGestureRecognizer.

Here’s your final result will look like.

Initial Setup

You first create just a UIView with whatever color you want. I pick red. Put it onto your storyboard canvas.

Create an outlet from that view to ViewController class in your viewController.swift file.

Add UIRotationGestureRecognizer to UIView

Create a method that is called when the UIRotationGestureRecognizer detects any rotation motion.

func rotatedView(_ sender: UIRotationGestureRecognizer) {
print("rotation gesture is detected")
}

Set it to the gesture recognizer.

override func viewDidLoad() {
super.viewDidLoad()
let rotate = UIRotationGestureRecognizer(target: self, action: #selector(rotatedView(_:)))
redView.addGestureRecognizer(rotate)
}

Now build, run your project, and try any rotation motions to make sure the method is being called.

Detect Each State

Inside the method, we can detect when gestures begin, is changed, and is ended with the gesture recognizer’s property called state and if statement.

func rotatedView(_ sender: UIRotationGestureRecognizer) {
    if sender.state == .began {
print(“begin”)
} else if sender.state == .changed {
print(“changing”)
} else if sender.state == .ended {
print(“end”)
}
}

Let’s run it and see this really detect motion states.

You should see “begin” when you begin rotation gesture, see “changing” when rotating, and see “end” when ending rotating. Nothing changes because we haven’t done anything to update the view.

Now we are going to add code inside the three if statements to update the view. But before that, let’s understand how UIRotationGestureRecognizer works.

Understand the Properties of UIRotationGestureRecognizer

UIRotationGestureRecognizer has a property called rotation.

var rotation: CGFloat
The rotation of the gesture in radians.

rotation is a delta value that tracks the radian of how much a view has rotated with gestures. Note that it’s not an absolute value that allows us to know how much the current view is rotated on the horizontal axis.

When you are done with rotating and starting over rotating, this rotation property will first become 0 and increase.

rotate is a delta value of how much you rotate

When you start rotating, if you don’t want a view to go back to the original rotation (in this case, horizontally parallel) and want it to keep the rotation as the last time you rotated and to continue from that rotation, you have to keep track of the rotation every time when you take your fingers off the screen.

Keep Track of the Last Rotation

What we need to do for that is to store the rotation when you are done with your gesture and about to take your fingers off the screen, and use the rotation radian value for the next time you begin rotating.

Create a property called lastRotation.

var lastRotation: CGFloat = 0

The default value is 0 because you haven’t rotated yet.

In your rotatedView method, when you are done with rotating, assign sender.rotation to lastRotation to store the rotation.

} else if sender.state == .ended {
// Save the last rotation
lastRotation = sender.rotation
}

Restore the Last Rotation

Add originalRotation variable to store the rotation when the view begins rotating.

var originalRotation = CGFloat()
if sender.state == .began {

sender.rotation = lastRotation
originalRotation = sender.rotation
}

Assigning lastRotation to sender.rotation keeps the view rotated as it was the last time you rotated it. Otherwise, the view would go back to the default rotation which is horizontally. This is because sender.rotation is a delta value and set to 0 every time the view begin rotating by default. 0

originalRotation = sender.rotation stores the rotation at the time when the view is about to begin rotating.

Add the Delta Radian to the Original Rotation

We update the view when it is changed.

} else if sender.state == .changed {

let newRotation = sender.rotation + originalRotation
sender.view?.transform = CGAffineTransform(rotationAngle: newRotation)

}

Get the new rotation by adding the delta rotation value to the original rotation, and update the view. You should be able to rotate correctly.

Conclusion

It is important to understand what property is delta, absolute, or relative when you use UIGestureRecognizer. Once you understand what each property does, you will feel pretty confident of using it. You can take a look at the whole code in the demo project here.