Dragging views around in Swift

image about Dragging views around in Swift

Hello dear colleagues, Leo here. This is a tutorial on dragging views around in Swift.

We’ll explore some dragging views and a particular problem that I had when trying to do that. The code will be the simple as it can be to focus on what is interesting about dragging views in iOS. We’ll be using UIPanGestureRecognizer to literally get the translation of the gesture.

Special shoutout for this week’s supporter: Milko – You are awesome!

The result we are trying to achieve is in the gif below:

via GIPHY

Let’s code! But first…

 

Painting of The Day

The La Carrera de Cuádrigas( or Chariot Race) is a 1909 painting from Ulpiano Checa . He was a Spanish painter, sculptor, poster designer, and illustrator. He used both impressionistic and academic techniques, and mainly painted historical subjects and this painting is the roman chariot races.
I choose this painting because the horses are *dragging* their chariots, so a post about dragging views around is more than suitable for this.

 

The Problem – Dragging views around in Swift

You need to move some view dragging it around with the user finger input.

The first thing we start is a new project.

Then in the ViewController viewDidLoad, you add these lines:

override func viewDidLoad() {
    super.viewDidLoad()
    
    for cardNumber in 0...2 {
        let cardView = UIView()
        
        switch cardNumber {
        case 0:
            cardView.backgroundColor = .systemTeal
        case 1:
            cardView.backgroundColor = .systemRed
        case 2:
            cardView.backgroundColor = .systemBlue
        default:
            break
        }
        
        cardView.frame = CGRect(x: 0, y: 0, width: 200, height: 300)
        view.addSubview(cardView)
        cardView.center = view.center
        
        cardView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handleCardTapped))) // we'll add this func later, just setup this here for now
    }
}

The code above is pretty straightforward and the only thing it isn’t so standard is the UIPanGestureRecognizer. The docs explain exactly what pan gesture recognizer will recognize:

Clients of this class can, in their action methods, query the UIPanGestureRecognizer object for the current translation of the gesture (translation(in:)) and the velocity of the translation (velocity(in:)). They can specify a view’s coordinate system to use for the translation and velocity values. Clients can also reset the translation to a desired value.

One very important part is the last phrase: Clients can also reset the translation to the desired value. But now you are thinking: why I should reset the translation value? Well… Let’s look at the code below and we’ll discuss more this.

 

Coding The Gesture to Drag Views in Swift

Add this func below the viewDidLoad:

@objc private func handleCardTapped(_ gesture: UIPanGestureRecognizer) { // mark 1
    
    if let cardView = gesture.view { // mark 2
        
        let point = gesture.translation(in: view) // mark 3
        
        cardView.center = CGPoint(x: cardView.center.x + point.x, y: cardView.center.y + point.y) // mark 4
        gesture.setTranslation(CGPoint.zero, in: view) // mark 5
    }
}

Let’s break it down because this is very important and I struggled a lot with it:

  1. Mark 1 – Here is the function that will be called every time the user drags your view. It will receive a UIPanGestureRecognizer a very useful object that we will explore some attributes of them.
  2. Mark 2 – One of the attributes of UIPanGestureRecognizer is the view the gesture recognizer is attached to. This is important because we want to have access to the view’s attributes so we can modify it the way we want.
  3. Mark 3 – This is important. The return of this method is a CGPoint. The CGPoint returned is relative to a view. In this example, it’s irrelevant if you choose the UIViewController’s view or the cardView. This attribute is additive, meaning that as long you drag your finger it will just add to the returning CGPoint value. Every time the system calls this function if the user doesn’t end the dragging it will add just add the new dragged value to the old. This is very useful when you just want the initial and final places of the dragging, but it’s not very good for real-time dragging.
  4. Mark 4 – We just add the X and Y of the view, with the X and Y of the gesture point. This is very simple but can be problematic because of the additive feature of gesture translation, the view will move way faster than it should if you don’t manage the gesture value. Imagine this, if you drag your finger 2 points on the X axis, this method will be called several times because it will call it with CGPoint(x: 0.1 , y:0) , CGPoint(x: 0.2 , y:0), CGPoint(x: 0.3 , y:0), CGPoint(x: 0.4 , y:0), CGPoint(x: 0.5 , y:0) and so on until get to CGPoint(x: 2 , y:0). This means will be adding all this values to the cardView X axis and this is far from what we wanted.
  5. Mark 5 – This is where we correct the gesture value. As the docs say we can RESET the value of the translation. So every time the view call this gesture recognizer it will be a blank state this way doing what we wanted.

 

Now let’s explore other features of UIPanGestureRecognizer.

 

Exotic features of UIPanGestureRecognizer

You can get the velocity of the dragging if this is relevant for your code too:

let velocity = gesture.velocity(in: view)

This will result in a CGPoint. But differently, the translation property will interpret the velocity of the pan gesture in the coordinate system of the specified view. The velocity of the pan gesture is expressed in points per second. The velocity is broken into horizontal and vertical components. So this is very good if you are doing a tinder-like app that a fast swipe left already indicates a big NO for someone.

You also can set minimumNumberOfTouches and maximumNumberOfTouches that are related to how many fingers need to touch to begin the gesture and the maximum number that can touch for gesture recognition.

 

States of the Pan gesture

The declaration of UIPanGestureRecognizer inside UIKit is:

open class UIPanGestureRecognizer : UIGestureRecognizer {...}

This gives access to all the UIGestureRecognizer properties, one, in particular, is called state. The state is very important because it allows you to control all the life cycles of the dragging. Let’s see what states are and an example of use:

“`

extension UIGestureRecognizer {
    
    public enum State : Int {
        
        case possible = 0
        case began = 1
        case changed = 2
        case ended = 3
        case cancelled = 4
        case failed = 5
        
        public static var recognized: UIGestureRecognizer.State { get }
    }
}

An example of use is just adding these lines in the handleCardTapped callback method:

if gesture.state == .ended {
    print("gesture ended")
}

This way you can respond to the end of the gesture. Imagine that you have a view representing a trash can and you need to know if the position of the view dragged is within the trash can area to delete some file. This could be done with the gesture state recognizer, pretty neat! Isn’t it?

 

 iOS 13.4 New Feature – Scroll Types Mask

The final feature that I want to show here is the property allowedScrollTypesMask of UIPanGestureRecognizer. Setting this mask enables the pan gesture to recognize scroll events, like a mouse scroll movement or a two-finger scroll on a trackpad. See UIScrollType to easy dragging views around in Swift.

The allowedScrollTypesMask are a OptionSet struct. This will be a topic for future posts but what you need to understand is that every static var stands for a unique value that can be used.

@available(iOS 13.4, *)
public struct UIScrollTypeMask : OptionSet {
    
    public init(rawValue: Int)
    
    public static var discrete: UIScrollTypeMask { get }
    
    public static var continuous: UIScrollTypeMask { get }
    
    public static var all: UIScrollTypeMask { get }
}

There are three values in the UIScrollTypeMask. The continuous type that you should allow if you want that the view could be scrolled by a trackpad movement, like the two-finger scroll. The discrete type that allows your view to translating the scroll movement of a mouse. And the all type is the union of both properties above mentioned.

The only negative thing is that this feature is only for iOS 13.4 or above so some of us will have to wait to use it.

And this is the end!

 

Summary – Dragging views around in Swift

Today we read about the pan gesture in iOS. Some features of UIPanGestureRecognizer and the states of a GestureRecognizer. This article was a tutorial about how to drag views in Swift and iOS.

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits: image

Share this post:

Related posts

Sponsor