1

I have a PageViewController, P, that contains two child ViewControllers, A and B. Both A and B allow a user to enter some data into a form. If the user begins editing the form, I keep track in a boolean variable:

var formEdited = false;

In the event that the user would like to move away from the form, and formEdited is true, I'd like to warn them and say "Are you sure you want to abandon the changes you have in the form?". In the event that they are sure, I'd like to store their data. Otherwise, I'd let them discard the data and move on with their swiping.

As a result, I tried doing something like this in both A and B:

 override func viewWillDisappear(_ animated: Bool) {
    if (formEdited) {
        let dialogMessage = UIAlertController(title: "Confirm", message: "Are you sure you want to abandon the changes you have in the form?", preferredStyle: .alert);
        let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
            super.viewWillDisappear(animated);
        })
        let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
            // TODO:: what to do here
            self.myCustomFuctionToStoreData(); 
            super.viewWillAppear(true);

        }
        dialogMessage.addAction(ok);
        dialogMessage.addAction(cancel);
        self.present(dialogMessage, animated: true, completion: nil);
    }
}

As a result, I can see my popup when I try to swipe away from the View. If I click "Cancel", the view remains. ( Which is what I want ) However, if I retry to swipe again, I no longer see the alert box, and the UI changes. ( Which is not what I want. I want it to re-prompt )

I believe that my code needs to react more appropriately when a viewWillDisappear. I think I need to somehow prevent the view from disappearing after this line above:

// TODO:: what to do here

Note: I've tried answers from a few other posts, like this: How do I Disable the swipe gesture of UIPageViewController? , Disable swipe gesture in UIPageViewController , or even these two : Disable UIPageViewController Swipe - Swift and Checking if a UIViewController is about to get Popped from a navigation stack? .

The last two might be most appropriate, but I don't want to disable any gestures nor do i see how i can inject a prevention. I simply want to make the swiping away from a child view a conditional function. How would I do this from my child view ( child of PageView ) in Swift 4 ?

angryip
  • 2,140
  • 5
  • 33
  • 67

1 Answers1

0

It turns out that implementing conditional scroll operations in a UIPageView is trivial. These are the steps I've taken to solve the problem. ( Updates to this code to make it more efficient are encouraged )

For starters, your UIPageViewController must not be the dataSource. This means that during your scroll operations in child view controllers, nothing will register. ( Which is ok for now ) Instead, you'd want to implement logic for which view is shown when as functions that can be called by the children. These two methods can be added to your UIPageView :

func goToNextView(currentViewController : UIViewController) {
    var movingIdx = 0;
    let viewControllerIndex = orderedViewControllers.index(of: currentViewController) ?? 0;
    if (viewControllerIndex + 1 <= (orderedViewControllers.count - 1)) {
        movingIdx = viewControllerIndex + 1;
    }
    self.setViewControllers([orderedViewControllers[movingIdx]], direction: .forward, animated: true, completion: nil);
}

func goToPreviousView(currentViewController : UIViewController) {
    var movingIdx = 0;
    let viewControllerIndex = orderedViewControllers.index(of: currentViewController) ?? -1;
    if (viewControllerIndex == -1) {
        movingIdx = 0;
    } else if (viewControllerIndex - 1 >= 0) {
        movingIdx = viewControllerIndex - 1;
    } else {
        movingIdx = orderedViewControllers.count - 1;
    }
    self.setViewControllers([orderedViewControllers[movingIdx]], direction: .reverse, animated: true, completion: nil);
}

Notes:

It would make sense to update the lines containing ?? 0; to a way to trow an error, or show some default screen.

orderedViewControllers is a list of all child views that this UIPageView controller contains

These methods will be called from child views, so keeping them at this layer makes them very reusable

Lastly, in your child views, you'll need a way to recognize gestures and react on gestures:

override func viewDidLoad() {
    super.viewDidLoad();

    let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleGesture))
    swipeLeft.direction = .left
    self.view.addGestureRecognizer(swipeLeft)

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

}

And handle the gesture:

@objc func handleGesture(gesture: UISwipeGestureRecognizer) -> Void {
    if gesture.direction == UISwipeGestureRecognizerDirection.left {
        parentPageViewController.goToNextView(currentViewController: self);
    }
    else if gesture.direction == UISwipeGestureRecognizerDirection.right {
        parentPageViewController.goToPreviousView(currentViewController: self);
    }
}

Notes:

In handleGesture function, you'd add your conditional checks to determine if goToNextView or goToPreviousView is ok.

angryip
  • 2,140
  • 5
  • 33
  • 67