1

So I'm working on an App right now and one of the specs of this app is to have a swipe between views functionality on specific pages kind of like the camera on Instagram.

enter image description here

My current solution is to set the initial view controller as a UIPageViewController which has two view controllers one is the main tab bar controller (first image) for the whole app and the second view controller is the one shown in pink in the image above, and then to enable and disable sliding functionality for the PageViewController depending on whether the current view is meant to have access to the second 'pink' view controller. (PS open to a totally different architecture than this if anyone knows of one, this is just the best I could do given my limited knowledge on iOS)

Normally every thing works fine. However, when I move my finger's in a very particular pattern, the entire app stops functioning. The pattern is shown below:

enter image description here

The pattern is basically:

  1. Swipe slowly to the left a little bit until part of the second 'pink' view controller is showing (image 2 above)

  2. Swipe quickly to the right causing the empty space to the left of the main view controller to show (image 3)

  3. Quickly Let go and let the main view controller fall back into place (image 4)

(*edit - Probably worth noting that if I do this same pattern slowly instead of quickly sliding right and letting go everything works just fine)

(PS if there's a way to upload a screen recording let me know)

Anyways, If I do this just one time, my entire app stops working. Basically every page stops loading data, and any time I click on a button on the main page (eg. the likes button shown in the images) I get an "Unbalanced calls to begin/end appearance transitions for AppName.ViewController" and the new view controller shows up with no data.

Furthermore, as soon as I swipe over to the second page 'pink' view controller and then back to the main app, everything works again.

I don't really know what code is relevant to this problem (spent about 5 hours trying to figure that out with no luck), so I'm just gonna post my UIPageViewController class for now, if you think the problem is coming from somewhere else let me know and I'll post that code.

// MARK: -> Properties
class PageViewController: UIPageViewController {
    var pages = [UIViewController]()
}

// MARK: -> Lifecycle
extension PageViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = self

        setViewController(withIdentifier: "MainTabController")
        setViewController(withIdentifier: "SecondaryPage") // The 'Pink' screen

        setViewControllers([pages.first!], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
    }
}

// MARK: -> Helpers
fileprivate extension PageViewController {
    func setViewController(withIdentifier storyboardIdentifier: String){
        let page: UIViewController! = storyboard?.instantiateViewController(withIdentifier: storyboardIdentifier)
        storyboard?.configure(viewController: page)
        self.pages.append(page)
    }
}

// MARK: -> UIPageController Data Source
extension PageViewController: UIPageViewControllerDataSource {
    func presentationIndex(for pageViewController: UIPageViewController)-> Int {
        return pages.count
    }
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let cur = pages.index(of: viewController)!
        if cur == 0 { return nil }
        let prev = abs((cur - 1) % pages.count)
        return pages[prev]
    }
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let cur = pages.index(of: viewController)!
        if cur == (pages.count - 1) { return nil }
        let nxt = abs((cur + 1) % pages.count)
        return pages[nxt]
    }
}

Update

Not sure what exactly this means but I'm sure it's relevant. I added this extension and method to my PageViewController

extension PageViewController: UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        print(completed)
    }
}

(and then obviously set the delegate to self in viewDidLoad)

This printed true and false as you would expect except when I swiped in the pattern I described above, in which case it never even fired the method and printed nothing at all.

Josh Hadik
  • 403
  • 5
  • 16
  • Keeping a list of view controllers (your `pages` array) in conjunctions with a UIPageViewController seems like a really bad idea and could have something to do with the problem. The UIPageViewController already knows what its children are; let it manage them for you. There is a lot of complicated prefetching and caching going on in a scrolling page view controller, and you are quite likely to mess things up in relation to the timing of that. – matt Nov 17 '17 at 03:49
  • Indeed, I'm willing to bet that that's the problem. You cannot "change your mind" about what the next page view controller is, because it's already too late; the precaching has seen to that. Thus this is unlikely to be a successful use a page view controller. – matt Nov 17 '17 at 03:50
  • @matt I read a few tutorials today on setting up a page controller [here](http://samwize.com/2015/10/13/how-to-create-uipageviewcontroller-in-storyboard-in-container-view/) and [here](https://stackoverflow.com/a/26024779/3985721) and both used the same pages array so I kind of assumed that was how a UIPageViewController new what it's children were. Without that array how does the controller even know what it's children are? – Josh Hadik Nov 17 '17 at 04:03
  • @matt but yeah it didn't seem like UIPageViewController was meant for this but I couldn't think of a better way to do it so I just went with it – Josh Hadik Nov 17 '17 at 04:04
  • If the tutorials did that, they were wrong. The page view controller keeps track of its own children. You just answer the questions `viewControllerBefore` and `viewControllerAfter` when asked. However it sounds to me like you don't actually _know_ those answers (esp because you do not know when you will be asked — it is _not_ when the user is "turning the page", it is much sooner because of precaching). – matt Nov 17 '17 at 04:46
  • So I don't quite see why you don't use a simple paging scroll view and manage it yourself. It's only two pages for heaven's sake. – matt Nov 17 '17 at 04:47
  • @matt If I used a paging scroll view is it possible to have the tab bar and navigation bar move off the screen when you swipe from the first view to the second one like in the picture I posted? (Not looking for a solution, just a simple yes or no. I'll figure out how to do it if it's possible) – Josh Hadik Nov 17 '17 at 05:33
  • I don't see why not. A paging UIPageViewController _is_ a paging scroll view. What it can do, you can do. – matt Nov 17 '17 at 06:11

0 Answers0