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:
If scrolling animation is on (true), it loses the vehicle page after tapping the right arrow 6 or more times quickly. See image 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.
I noticed that sometimes the contained View Controller can be off centered as opposed to entirely missing.
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:
So, it seems the missing content is clipped / out of bounds.
Showing only the wireframes reveals the following:
The Page View Controller has a
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?
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.
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.
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)
})
})
}
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With