Interactive push view controller

197 Views Asked by At

I am looking to have an interactive push view controller. So if the user pans from the right edge of the screen, it will pop to the next view controller. I have found this CocoaPods: https://github.com/rickytan/RTInteractivePush, but it is written in Objective-C, so I am unsure how to use it. On my own I have been able to come up with a pan gesture that pushes a view, however it is not interactive:

swipeGesture = UIPanGestureRecognizer(target: self, action:#selector(swiped(_:)))
swipeGesture.delegate = self
view.addGestureRecognizer(swipeGesture)

@objc func swiped(_ gestureRecognizer: UIPanGestureRecognizer) {
    let newView = View()
    self.navigationController?.pushViewController(newView, animated: true)
}

Any help would be greatly appreciated!

2

There are 2 best solutions below

0
Qazi Ammar On

The current code is perfect it you have only one viewcontroller up next.

But if you have to 2 or more viewController up next then interactive push is unuseful technique. Vies versa for the interactive pop controller we just have to pop top view form the navigation stack which make sense. Please have a look the the image below which describe the scenario for both push and pop.

enter image description here

0
Fabio On

You can do it programmatically with UIPageViewController:

Set your UIPageViewController class:

import UIKit

class MyControllerContainer: UIPageViewController {

// set UIPageViewController transition style
override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
    super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    print("init(coder:) has not been implemented")
}

var pages = [UIViewController]()
var pageControl = UIPageControl()
let initialPage = 0

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    setup()
    style()
    layout()
 }
}

Now set style, setup, and layout func:

extension MyControllerContainer {

func setup() {
    dataSource = self
    delegate = self
    
    pageControl.addTarget(self, action: #selector(pageControlDragged(_:)), for: .valueChanged)
    
    // create an array of viewController
    let page1 = ViewController1()
    let page2 = ViewController2()
    let page3 = ViewController3()
    
    pages.append(page1)
    pages.append(page2)
    pages.append(page3)
    
    // set initial viewController to be displayed
    setViewControllers([pages[initialPage]], direction: .forward, animated: true, completion: nil)
}

func style() {
    pageControl.translatesAutoresizingMaskIntoConstraints = false
    pageControl.currentPageIndicatorTintColor = .white
    pageControl.pageIndicatorTintColor = UIColor(white: 1, alpha: 0.3)
    pageControl.numberOfPages = pages.count
    pageControl.currentPage = initialPage
}

func layout() {
    view.addSubview(pageControl)
    
    NSLayoutConstraint.activate([
        pageControl.widthAnchor.constraint(equalTo: view.widthAnchor),
        pageControl.heightAnchor.constraint(equalToConstant: 20),
        view.bottomAnchor.constraint(equalToSystemSpacingBelow: pageControl.bottomAnchor, multiplier: 1),
    ])
 }
}

set how we change controller when pageControl Dragged:

extension MyControllerContainer {

// How we change controller when pageControl Dragged.
@objc func pageControlDragged(_ sender: UIPageControl) {
    setViewControllers([pages[sender.currentPage]], direction: .forward, animated: true, completion: nil)
 }
}

after that set UIPageViewController delegate and datasource:

// MARK: - DataSources

extension MyControllerContainer: UIPageViewControllerDataSource {

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    
    guard let currentIndex = pages.firstIndex(of: viewController) else { return nil }
    
    if currentIndex == 0 {
        return nil // stop presenting controllers when swipe from left to right in ViewController1
    } else {
        return pages[currentIndex - 1] // go previous
     }
    }

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    
    guard let currentIndex = pages.firstIndex(of: viewController) else { return nil }
    
    if currentIndex == 2 {
        print("Last index...")
    }
    
    if currentIndex < pages.count - 1 {
        return pages[currentIndex + 1] // go next
    } else {
        return nil // stop presenting controllers when swipe from right to left in ViewController3
    }
 }
}

// MARK: - Delegates

extension MyControllerContainer: UIPageViewControllerDelegate {

// How we keep our pageControl in sync with viewControllers
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    
    guard let viewControllers = pageViewController.viewControllers else { return }
    guard let currentIndex = pages.firstIndex(of: viewControllers[0]) else { return }
    
    pageControl.currentPage = currentIndex
 }
}

Now add your viewControllers, in my case 3:

// MARK: - ViewControllers

class ViewController1: UIViewController {

let mylabel1: UILabel = {
    let label = UILabel()
    label.text = "Controller 1"
    label.textAlignment = .center
    label.textColor = .white
    label.font = .systemFont(ofSize: 20, weight: .semibold)
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemRed
    
    view.addSubview(mylabel1)
    mylabel1.heightAnchor.constraint(equalToConstant: 50).isActive = true
    mylabel1.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
    mylabel1.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    mylabel1.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
 }
}

class ViewController2: UIViewController {

let mylabel2: UILabel = {
    let label = UILabel()
    label.text = "Controller 2"
    label.textAlignment = .center
    label.textColor = .white
    label.font = .systemFont(ofSize: 20, weight: .semibold)
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemGreen
    
    view.addSubview(mylabel2)
    mylabel2.heightAnchor.constraint(equalToConstant: 50).isActive = true
    mylabel2.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
    mylabel2.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    mylabel2.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
 }
}

class ViewController3: UIViewController {

let mylabel3: UILabel = {
    let label = UILabel()
    label.text = "Controller 3"
    label.textAlignment = .center
    label.textColor = .white
    label.font = .systemFont(ofSize: 20, weight: .semibold)
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemBlue
    
    view.addSubview(mylabel3)
    mylabel3.heightAnchor.constraint(equalToConstant: 50).isActive = true
    mylabel3.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
    mylabel3.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    mylabel3.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
 }
}

This is the result:

enter image description here