43

I have a UIPageViewController which I am providing page data for using an implementation of UIPageControllerDelegate and UIPageControllerDataSource.

It's all working fine, but I want to be able to add items to the page data and reorder the page data.

If a user has already got to the last of the pages, and then I add an item, they can't get to the next page because viewControllerAfterViewController: has already been called. If they scroll back one and then forward two they can get to the new page fine, so the data is setup correctly. How can I tell the UIPageViewController to refresh its store of what comes next?

Similarly I would like to reorder the collection that is backing the page view. But if I do this I'll get the same problem - the page view will think the next page is still what it was last time the current page was loaded.

I guess I'm looking for something similar to reloadData: on UITableView.

Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213
ssloan
  • 2,226
  • 4
  • 26
  • 40

4 Answers4

96

I found a workaround to force UIPageViewController to forget about cached view controllers of neighboring pages that are currently not displayed:

pageViewController.dataSource = nil;
pageViewController.dataSource = self;

I do this everytime I change the set of pages. Of course this doesn't affect the currently displayed page.

With this workaround I avoid the caching bug and can still use animated:YES in setViewControllers:direction:animated:completion:.

Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213
  • 4
    Interesting. Where do you unset and reset the dataSource? Before `setViewControllers:direction:animated:completion:`, after it, or in the completion block? Thanks. – ernesto Mar 21 '14 at 17:43
  • Actually, I do this 0.01 seconds after didFinish in a dispatch_async block. – Ortwin Gentz Mar 21 '14 at 20:40
  • 4
    Thank you! This method works when using a data source. The accepted answer didn't work for me, at least on iOS 8. Using "setViewControllers:direction:animated:completion:" with the current view controller did not cause the data source to be called again. – Michael McGuire Oct 02 '14 at 21:33
  • Thx for this solution. :-) – Alex Sfinx87 Jun 16 '15 at 12:19
  • tip: After you reset `dataSource` (a.k.a. `pageViewController.dataSource = nil;`) your method `-viewControllerBefore` and `-viewControllerAfter` will be called again. For restore the dataSource. – Mr.Fingers Mar 17 '16 at 12:43
  • You need to reassign dataSource like above if `presentationCountForPageViewController` returns a dynamic value which might change after the refresh. – Ozgur Vatansever May 12 '16 at 20:56
  • Double plus good. Why this is not documented -- inexcusable. Confirmed for iOS 10, Swift 2.3, Xcode 8 – BaseZen Nov 14 '16 at 23:21
  • 1
    This doesn't work, at least not in iOS 10.3. The crash happens in setViewControllers(), so doing something 0.01 seconds afterward is too late. I tried this trick before calling setViewControllers(), and it doesn't work. – Kartick Vaddadi Mar 29 '17 at 04:08
  • doesn't work iOS 10 when you call `setViewControllers:direction:animated:completion:` fast enough, still got wrong content offset. – Gon Jun 12 '17 at 12:49
  • And what if I also want to update the currently displayed page ? – DJTano Nov 02 '20 at 20:38
  • @DJTano that's the purpose of `setViewControllers:direction:animated:completion:`. Even though it asks for an array of view controllers, you usually send a single array (exception is a 2-element array if `spineLocation = .mid`) with the current VC. – Ortwin Gentz Nov 03 '20 at 00:36
  • @OrtwinGentz got it. Thanks!! :) – DJTano Nov 07 '20 at 21:21
31

You need to call setViewControllers:direction:animated:completion:.

Also, in iOS 6, watch out if you're using UIPageViewControllerTransitionStyleScroll style, as there is a major caching bug if animated: is YES (see my discussion here: UIPageViewController navigates to wrong page with Scroll transition style).

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 2
    Thanks - the setViewControllers:direction:animated:completion: did it. Cheers for mentioning the bug too. – ssloan Mar 10 '13 at 19:35
4

Here's my complete implementation of @ortwin-gentz's answer in the didFinish delegate method in Swift 2, which works perfectly for me:

func pageViewController(
    pageViewController: UIPageViewController,
    didFinishAnimating finished: Bool,
    previousViewControllers: [UIViewController],
    transitionCompleted completed: Bool
) {
    if !completed { return }
    dispatch_async(dispatch_get_main_queue()) {
        pageViewController.dataSource = nil
        pageViewController.dataSource = self
    }
}
theory
  • 9,178
  • 10
  • 59
  • 129
4

Swift 4 Version for reload of data of pageviewcontroller, to @theory's version.

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    if !completed { return }
    DispatchQueue.main.async() {
        self.dataSource = nil
        self.dataSource = self
    }
}
Parion
  • 428
  • 9
  • 18