iosswiftuipangesturerecognizeruiswipegesturerecognizer

Pan (not swipe) between view controllers


I am creating an app that has a user interface similar to Tinder.

By this, I mean that there are three primary view controllers that can be "panned" between. The user can simply drag to move to any of the three view controllers.

I have implemented something similar in my app with one important caveat: it utilizes swipe gesture recognizers and not pan gesture recognizers.

The end-result looks and feels very unnatural.

This is how I'd like my user interface to behave (ignore the Gecko):

enter image description here

This is how it currently behaves with a swipe gesture recognizer: enter image description here

Notice how with Tinder, you can pan to a new view controller without having completely committed the segue. And, if the pan hasn't displaced the current view controller beyond a minimum threshold, it will simply snap back into place. This is the behavior I am looking for.

Here is some code that I am currently using to mimic with a swipe gesture recognizer:

//The following logic is applied in a similar way to view controllers 1 and 3 as well.

class ViewController2: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        addSwipeGestureRecognizers()
    }

    func addSwipeGestureRecognizers() {
        let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeLeft))
        swipeLeft.direction = UISwipeGestureRecognizer.Direction.left
        self.view.addGestureRecognizer(swipeLeft)

        let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeRight))
        swipeRight.direction = UISwipeGestureRecognizer.Direction.right
        self.view.addGestureRecognizer(swipeRight)
    }

    @objc func handleSwipeLeft() {
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let viewController1 = storyboard.instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
        let transition:CATransition = CATransition()
        transition.duration = 0.25
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = CATransitionType.push
        transition.subtype = CATransitionSubtype.fromLeft
        self.navigationController!.view.layer.add(transition, forKey: kCATransition)
        navigationController!.pushViewController(viewController1, animated: false)
    }

    @objc func handleSwipeRight() {
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let viewController3 = storyboard.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
        let transition:CATransition = CATransition()
        transition.duration = 0.25
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = CATransitionType.push
        transition.subtype = CATransitionSubtype.fromRight
        self.navigationController!.view.layer.add(transition, forKey: kCATransition)
        navigationController!.pushViewController(viewController3, animated: false)
    }
}

I am also utilizing a custom UIViewControllerAnimatedTransitioning to make the pushed view controllers push the presenting view controller out from the left or right. This is done as opposed to the default stacking on top of the presenting view controller. I also have my UINavigationController's navigation bar hidden.


Solution

  • I'm not sure UINavigationController is the correct container controller to provide what you need. You might find that UIPageViewController is a better choice, I think it will provide the natural swipe gesture you are looking for out of the box, although it's quite an opaque class with some quirks of its own.

    To go the custom route (either with UINavigationController or the more flexible/customisable UIViewController custom presentation) you are on the right track with UIViewControllerAnimatedTransitioning and the other (numerous) associated protocols. In case you are in a hurry, that route will take you quite a long time to implement, so maybe a simple UIPageViewController implementation will have to do for the moment.