I need to show some media contents on UIPageViewController and allow user to swipe between medias (just like a gallery) and I do not know how many videos or images are ahead because I have to get them one by one from server. How can I use UIPageViewController with unknown page numbers and just knowing previous and next page view controller?
You can try using Collection view like a page view controller. You can try making the collection view cell to fit the screen like a page view controller and Update the data source and collection view as and when required.
You can see a sample here in HomeViewController
For UIPageViewController you do not need to provide total number of items to make it works. When setting up your UIPageViewController, you only need to set the View Controller you want to display when it first loaded by following code:
pageViewController.setViewControllers([viewControllerToBeDisplayed], direction: .forward, animated: false, completion: nil)
And then to enable user to go back and forth, you just need to implement following methods of UIPageViewControllerDataSource:
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
Hope it helps.
Related
There are many SO questions talk about preload but seems none of them discuss how to do this during initialize.
I want to preload the next page when UIPageViewController is initialized.
For example, I initial UIPageViewController with the following code.
// [firstVC, secondVC]
setViewControllers([firstVC], direction: .forward, animated: true)
Unfortunately, datasource delegate function won't be called at the moment.
They will be called when user interaction involve.
In another word, when user swipe page.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
I tried to "scroll" view programmatically.
It shows blank, there is nothing on the scrollView at the region.
// self is a UIPageViewController
if let scrollView = self.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
var offset = scrollView.contentOffset
offset.x += 200
scrollView.setContentOffset(offset, animated: true)
}
Also tried to set 2 viewControllers when initialize but app crash.
setViewControllers([firstVC, secondVC], direction: .forward, animated: true)
Can I preload without user interaction?
Update.
So when user open the pageVC, I want to do a small swipe to user to demo this new feature, like the left image, since user doesn't know they can swipe so no user interaction at the moment.
If I can't preload the next view, it will look like the right image (white means nothing in this sample), empty view will make user confused, they could think this is a bug.
Update:
Github
I have difficulties to scroll programatically my UIPageViewController embedded in a main UIViewController, here is the architecture of my screen :
At the bottom of my screen I’m using UIPageViewController to display 2 controllers.
At the top of my screen I have a UICollectionView which is not part of the pageViewController.
For some needs, I synchronized the scroll of the collectionView with the scroll of the pagerViewController.
So now I have a pagerViewController that is scrolling when user scrolls into my collectionView. I’m using : pagerScrollView.setContentOffset(CGPoint(...) to do that.
My problem is that UIPageViewController uses its gestureRecognizers property to call its datasource methods properly. But when playing with pagerScrollView.setContentOffset, its dataSource methods are not called, so the next controller is not provided.
These are not called :
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
I tried to call pagerController.setViewControllers in the scrollViewWillBeginDragging method, but the controller is immediately changed, the UIPageViewController don’t keep the controller that is being transitionned.
My question is the following :
Is there a way to tell the UIPageViewController which controller is next / before without losing the controller that is currently transitioned ? Or is there a way to fire datasource methods using the setContentOffset method on my pager ?
When scrolling into the pagerViewController, obviously everything is working well because its datasource methods are called.
Any idea?
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.
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:
The pattern is basically:
Swipe slowly to the left a little bit until part of the second 'pink' view controller is showing (image 2 above)
Swipe quickly to the right causing the empty space to the left of the main view controller to show (image 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.
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 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
}
}
}