I have a UIPageViewController that only ever has two UIViewControllers in. There is a UISegmentControl above the UIPageViewController that changes between the segments "new" and "hot" depending on which page the UIPageViewController is on. This works about 90% of the time, however, if I swipe as indicated in this question, the UIPageViewController gets confused on which page it is on. This results in the UISegmentControl saying "new" when actually the "hot" page is being displayed.
Each view controller is assigned a page index (0 or 1) when it's first instantiated. Then the following code changes the segmented control index:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let index = (pageViewController.viewControllers?.last as? MyCollectionViewController)?.pageIndex,
let pageType = PageType(rawValue: index) {
currentPage = pageType
segmentedControl.selectedSegmentIndex = index
}
}
What's weird is that when the bug is occurring, I print out the pageIndex of the pageViewControllers last viewController and it agrees with the segmentedControl index. Yet on screen, it is 100% not the viewController it's claiming is being shown...
Related
I have the following function which I would like to call whenever the user swipes from one page to another (vertical paging):
func sendNotification (){
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "stopVideo"), object: nil)
print("called")
}
Currently, the function is called whenever the pageAfter or -before is created so to say. The function used to "create" the next/previous page looks like this (in this case it's viewControllerAfter):
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndexString = (viewController as! MyViewController).index
let currentIndex = indec.index(of: currentIndexString!)
//set if so that next page
if currentIndex! < indec.count - 1 {
let myViewController = MyViewController()
myViewController.index = indec[currentIndex! + 1]
sendNotification() //function is called
return myViewController
}
return nil
}
Since the UIPageViewController somewhat prepares the following pages and going back to the previous page also doesn't call the function (as the view controller does not need to be "created") I do not get the result I want. I was wondering if there is a function in which I can call sendNotification() on completion of the animation. I found a lot of questions regarding jumping to another page but none concerning my problem. I'd really appreciate your help!
You can use the UIPageViewControllerDelegate Functions
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController])
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
As the name of these functions say, the first one will be called, when the user swipes to another page of your PageViewController.
The Second one will be called, when the Transition has completed, which means the user sees the next page after the animation.
In both you can check for changes between the pages.
Also do not forget to check the completed variable of the second function. If the user starts swiping and then does not go to the next page (cause he releases the finger before it or similar) it is false and you do not have your new ViewController.
The array previousViewControllers holds the recently shown controllers where previousViewControllers[0] is probably the one shown before.
For more Information look up the documentation
Maybe it will be good for you to run your notification method in UIPageViewControllerDelegate method documentation documentation :
optional func pageViewController(_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool)
I want to create a general UIPageViewController to use in my project. Here is what it like.
When scroll or tap on the number in the titleView, the page view should scroll to the according index, and the according number in title should be highlight. But there is a bug when quickly scroll to next page and scroll back, the title view's status is incorrect.
For example, if I'm in page 2, then scroll to page 3 and quickly scroll back, now I'm in page2 but the button of 3 is highlight.
Here is the key codes:
//Delegaete
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
willShowPageIndex = getIndex(ofController: pendingViewControllers[0])
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard completed else { return }
// track the index
selectedPage = willShowPageIndex
}
fileprivate var selectedPage = 0 {
didSet {
//Change the button's font color, and move the underline
changeShowStatus(oldPage: oldValue, newPage: selectedPage)
}
}
It seems like when quickly scroll page view, the pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) didn't invoke. Maybe posting notification in datasource controller's viewWillAppear could avoid this problem, but I don't want this cause this PageViewController should be general.
Any one have better solution?
I have a UIPageViewController (custom one) inside a Container located in a regular UIViewController. i need to be able to call an event with each Page Change but ONLY if it really did change and not only half way or anything of that sort.
using:
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
is unreliable and not called each time for some reason.
if your answer contains anything about willTransitionToViewControllers or didFinishAnimating please elaborate and not just mention them, since i already know they exist but dont understand the proper way to use them.
Thank you
Use didFinishAnimating it has a completed and finished property so you know the page has actually changed. From the pageViewController you can get the currently displayed page, then get the position of this VC in your model.
First make sure your ViewController adopts UIPageViewControllerDelegate
Set the delegate (e.g. in viewDidLoad)
pageViewController.delegate = self
Then implement the following function:
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if (completed && finished) {
if let currentVC = pageViewController.viewControllers?.last {
let index = myViewControllers.indexOf(currentVC)
//do something with index
}
}
}
I'm scrolling between different views, which i on button click from the rootViewController would like to render to a image. I'm wondering what is the best approach for this? is it possible to retrieve the visible view from my contentViewController or would i need to recreate the view somehow by getting the visible view index and then access the object equal to this index?
To answer 'is it possible to retrieve the visible view from my contentViewController', not really. You need to set a pageIndex property in your CustomContentViewController. Keep trace of it in the didFinishAnimating method (You can find more robust way in other posts). Or you can directly trace the currentContentViewController
This is just a very simple example
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
self.currentContentViewController = pageViewController.viewControllers!.last as! CustomContentViewController
self.currentPageIndex = self.currentContentViewController.pageIndex
}
If you want to set a image, then do
#IBAction func buttonTapped(sender: UIButton) {
self.currentContentViewController.imageView.image = UIImage("MyImage.png")
}
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.
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:.
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).
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
}
}
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
}
}