DEV Community

Cover image for How to Create a Draggable Floating View on iOS
Arya
Arya

Posted on

How to Create a Draggable Floating View on iOS

Long story short, on our latest project, our team has to implement a floating view that can be dragged freely on the screen.
For those who have an Android development background, think of it as a floating action button lookalike, but instead of staying still on certain parts of the screen, it can be dragged freely!
For those who don’t, have a look at these images :

Floating View Example on Tokopedia

The floating view is circled in red, as shown on the image above.

After conducting some research, we found out that it was not that hard to implement it from scratch on an iOS app.
This tutorial will cover every single thing you need to create one.

Note: Since I have not found the official name for this component on Apple’s HIG, I will just continue to call it a draggable view in this article.

Preparation

Create an iOS project on Xcode, choose storyboard as the project’s interface and UIKit App Delegate as its life cycle. Then we end up with the usual Xcode starter code that you love so much:

draggable view example part 1

Then, go to Main.storyboard, add a UIView, and place it somewhere on the screen. You are free to decide where to put the view. For example, mine would be:

draggable view example part 2

I made a red UIView with a 70pt width and height, aligning its trailing to a safe area by 16pt constraint and 300pt to the safe area’s bottom.

For simplicity’s sake, our draggable view will just be a circular view with some background colour on it. You could always go further by customizing it (e.g. by adding an image or something else).
What we have to do next is to connect our newly made view to the ViewController:

part3

If we run our application right now, we will see a little red box on the screen.

Let’s Get Started

In order to make our view draggable, we have to add a gesture recognizer called UIPanGestureRecognizer.
Apple’s explanation of UIPanGestureRecognizer is quite straightforward:

“A discrete gesture recognizer that interprets panning gestures.

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:)).”
In simpler terms, this gesture can detect any translation that happens on the view, even enabling you to acquire its attributes such as velocity, final location, etc.

Let’s try to implement it on our ViewController:

//
//  ViewController.swift
//  draggabledemo
//
//  Created by Philip Indra Prayitno ( https://github.com/s1rent ) on 03/12/20
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var draggableView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.draggableView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handler)))
    }

    @objc func handler(gesture: UIPanGestureRecognizer){
        print(gesture.location(in: self.view))
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s the play-by-play :
Firstly we set a UIPanGestureRecognizer to our draggable view, where we also made a callback function for our recognizer (which is called every time the panning event is emitted).
On the handler function, we simply print the gesture’s current location.

Try to run the application, then drag the view freely. Notice that although our view is not moving anywhere, we can see some kind of tuple on the debugging console, which is the current gesture location given in a coordinate. We’re on track to achieve our goal!

Now What?

Now that we have set a gesture recognizer, all we have to do is to update our view’s location. And since we already have our final location from the handler function, let’s just reuse that.
Add this code to our ViewController:

//
//  ViewController.swift
//  draggabledemo
//
//  Created by Philip Indra Prayitno ( https://github.com/s1rent ) on 03/12/20.
//
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var draggableView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.draggableView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handler)))
    }

    @objc func handler(gesture: UIPanGestureRecognizer){
        let location = gesture.location(in: self.view)
        let draggedView = gesture.view
        draggedView?.center = location
    }
}
Enter fullscreen mode Exit fullscreen mode

In the code above:

  • we get the gesture’s current location.
  • we assign the gesture view that sent the panning event (our draggable view) to a variable.
  • we then update the view’s center with the current location retrieved before.

If you try to run the app again, our view is eventually draggable. Yay!

part4

But something is not right, although we successfully made our view draggable, we can drag it beyond the screen, which is not a normal behaviour.

Besides that, the normal draggable view has the ability to drag itself to the nearest side of the screen when the user stops dragging, while ours does not.

So what can we do about it?

Actually, to achieve this behaviour, we only need to do a little workaround. We can apply some logic when the user stops dragging. Add this code to our ViewController:

//
//  ViewController.swift
//  draggabledemo
//
//  Created by Philip Indra Prayitno ( https://github.com/s1rent ) on 03/12/20.
//
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var draggableView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.draggableView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handler)))
    }

    @objc func handler(gesture: UIPanGestureRecognizer){
        let location = gesture.location(in: self.view)
        let draggedView = gesture.view
        draggedView?.center = location

        if gesture.state == .ended {
            if self.draggableView.frame.midX >= self.view.layer.frame.width / 2 {
                UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
                    self.draggableView.center.x = self.view.layer.frame.width - 40
                }, completion: nil)
            }else{
                UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
                    self.draggableView.center.x = 40
                }, completion: nil)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s the logic :

  • we add some logic to our handler to make the view have the behaviour we expect.
  • we check whether the user has stopped dragging, then we do a comparison between the current view’s midX position and its superview’s width divided by half. We do the comparison to determine whether the current view is closer to its superview’s left or right side. If the view’s midX is greater than or equal to half the width of its superview (closer to the right side of the screen), we then update its X position as its superview width minus 40. This means that our view’s final location will be located at 40pt from its superview’s trailing. If it is closer to the left screen, we then update its X position to 40. This means that our view’s final location will be located 40pt away from its superview trailing.

Try to run the app. Voila! Our view now behaves as expected:

part5

Conclusion

As you can see, creating a draggable view on iOS is not as hard as it seems. We only need to implement some logic regarding the final position of the view.

Happy coding!

Discussion (0)