Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS UIPageViewController Gets Confused and ViewControllers Are Not Displayed

We have left and right buttons set up for the user to page through different cars quickly. Our Page View Controller loses the view controller if the user taps quickly to the next page 10 or more times.

Here is the vehicle page with the car showing correctly (blurred to hide non-relevant information). See image here:

Shows Vehicle Correctly

If scrolling animation is on (true), it loses the vehicle page after tapping the right arrow 6 or more times quickly. See image here:

enter image description here

Code:

private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
    let viewControllers = [viewController]
    let isAnimated = true // false always works. However, animation is required.
    setViewControllers(viewControllers, direction: direction, animated: isAnimated, completion: nil)
}

While debugging and when the page view controller has stopped showing the cars, I ensured that the view controller being set is not nil and the listing (car) is also non-nil.

I tried a variant of the solution from UIPageViewController, how do I correctly jump to a specific page without messing up the order specified by the data source? where the completion block is used. However, it did not work.

weak var pvcw: UIPageViewController? = self
setViewControllers(viewControllers, direction: direction, animated: true, completion: {(_ finished: Bool) -> Void in
    let pvcs: UIPageViewController? = pvcw
    if pvcs == nil {
        return
    }
    DispatchQueue.main.async(execute: {() -> Void in
        pvcs?.setViewControllers(viewControllers, direction: direction, animated: false) {(_ finished: Bool) -> Void in }
    })
})

Any ideas? Thank you.

Update

I noticed that sometimes the contained View Controller can be off centered as opposed to entirely missing.

View Controller Off Centered

I looked deeper into the scenario of the view controller missing entirely. Clicking on "Debug View Hierarchy" and turning on "Show Clipped Content" revealed the following when the View Controller is missing entirely:

Turning on Show Clipped Content

Clipped Content

So, it seems the missing content is clipped / out of bounds.

Showing only the wireframes reveals the following:

wire frames only

The Page View Controller has a

  • _UIPageViewControllerContentView that contains a
  • _UIQueuingScrollView that contains a
  • UIView that contains a
  • VehicleDetailTableViewController (the UITableViewController with a car image and details).

I also see the _UIQueuingScrollView's bounds is quite different when things are weird. The bounds have an x of 1125 as opposed to an X of 375 when everything is normal.

This only happens when using a Transition Style of scroll as opposed to Page Curl. When using Page Curl, things work fine.

How can we prevent / fix this?

Second Update

This code makes the problem go away. However, it leaves a more jarring experience. Perhaps due to the delay of 0.4 seconds, the blue background shows sometimes in normal use.

private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {

    let viewControllers = [viewController]
    setViewControllers(viewControllers, direction: direction, animated: true, completion: { (_) in

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: {
            self.setViewControllers(viewControllers, direction: direction, animated: false, completion: nil)
        })
    })
}

This is not a good user experience. Is there a better approach?

I want the scroll transitions to be smooth, not briefly show the blue background, and to not lose its content aka View Controller content.

like image 767
finneycanhelp Avatar asked Feb 19 '18 03:02

finneycanhelp


3 Answers

A simple solution is to decouple the button taps from the view controller changes by adding a small "tap ahead" buffer. Create a button queue (use a simple NSMutableArray that acts as FIFO queue) where you add each navigation button tap, then call a dequeue function if the queue was empty before the add.

In the dequeue function you remove the first entry and change view accordingly, then call itself again in the setViewControllers completion handler if the queue is not empty.

Make sure to do the processing on the main thread only to avoid threading problems. If you want, you can also add restrictions on how many "tap ahead" you allow, and perhaps flush the queue on directional changes.

like image 60
LGP Avatar answered Oct 30 '22 13:10

LGP


Although the real answer is to have View Controllers that are as simple as possible (but no simpler), here is the code that fixed the problem with the side effect of showing the background on occasion when the user navigates to the next View Controller.

private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {

    let viewControllers = [viewController]
    setViewControllers(viewControllers, direction: direction, animated: true, completion: { (_) in

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: {
            self.setViewControllers(viewControllers, direction: direction, animated: false, completion: nil)
        })
    })
}
like image 2
finneycanhelp Avatar answered Oct 30 '22 13:10

finneycanhelp


I'm not sure if this solution would be suitable for your users but if the problem occurs due to the user navigating quickly you could implement a lock that would disallow this quick navigation. Essentially:

private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
    guard !isChangingPages else { return }
    isChangingPages = true
    let viewControllers = [viewController]
    let isAnimated = true // false always works. However, animation is required.
    setViewControllers(viewControllers, direction: direction, animated: isAnimated, completion: { [weak self] _ in 
        self?.isChangingPages = false
    })
}

This way you'd have to finish transitioning to the new page before allowing the transition to the next.

This would likely result in confusion for the user if you kept the navigation buttons enabled while this bool was set to true (tapping without seeing a result). But the logic could be changed to disable the buttons and reenable them in the completion block (that way they'd fade in/out during the page change).

like image 1
Steve Avatar answered Oct 30 '22 12:10

Steve