UIPageViewController transition from control on child UIViewController - ios

I have a PageViewController, which has two UIViewControllers they are set up as follows:
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
self.pageViewController.dataSource = self
self.pageViewController.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-10)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
In one of the Child view controllers, I have a button which should transition to a specific index in the PageViewController:
#IBAction func createClick(sender: UIButton, forEvent event: UIEvent) {
let pageViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("NextViewController") as! UIViewController
let viewControllers: NSArray = [secondViewController]
pageViewController.self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
}
No errors, but the page does not transition. I'm thinking there is an issue with the delegation, but I'm not sure how to fix it.

The problem is that you aren't actually setting the viewControllers property on the UIPageViewController that your app is actually using - you are instantiating an entirely new UIPageViewController from the storyboard that has no relation to any of the controllers that are appearing on your screen.
One solution is to create a reference to your UIPageViewController in each of your child view controllers, like this:
weak var pageViewController: UIPageViewController?
In your dataSource, when you implement your "after" and "before" methods, i.e.: pageViewController:viewControllerBeforeViewController: and pageViewController:viewControllerAfterViewController: you will need to create the new child view controller, and then set the pageViewController property to a reference to the "parent" UIPageViewController.
Then, when your #IBAction method is invoked, you call the method on the pageViewController like this:
let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("NextViewController") as! UIViewController
let viewControllers: NSArray = [secondViewController]
pageViewController?.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
Something like that ought to work.

Related

UIPageViewController overlapping other view [Swift - UIStoryBoard]

I am trying add UIPageView with static position counter. But UIPageView page hiding Counter Label
Following code - I am creating PageViewController in my ControllerView.
func createPageViewController(){
let pageVC = self.storyboard?.instantiateViewController(identifier:
Constant.storyIdPageViewIdentifier) as! UIPageViewController
pageVC.dataSource = self
if carsArray.count>0 {
let imageController = getImageViewController(withIndex: 0)!
let imageControllers = [imageController]
pageVC.setViewControllers(imageControllers, direction: .forward, animated: true, completion: nil)
}
pageViewController = pageVC
self.addChild(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController?.didMove(toParent: self)
}
Try using self.view.bringSubviewToFront(counterlabel)

UIPageViewController subviewcontroller can't perform segue on viewDidAppear

I have a UIPageController which contains two subviewcontrollers. On start, I'm trying to perform a segue from SubViewControllerOne to LoginViewController when a user isn't authorized. I have a segue in viewDidAppear like so:
performSegue(withIdentifier: "authorizeSegue", sender: self);
This produces an unsuccessful segue and a warning
Warning: Attempt to present <Volley.LoginViewController: 0x7f98bd801e00> on <Volley.SubViewControllerOne: 0x7f98bd608400> whose view is not in the window hierarchy!
I've done this in the past with ViewControllers and TabBarViewControllers successfully.
When I wrap the segue in a timer, or trigger through an IBAction, the segue works perfectly fine. I believe it has something to do with the PageViewController.
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate {
var subViewControllers = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
self.edgesForExtendedLayout = [];
// Do any additional setup after loading the view.
let subViewControllerOne = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SubViewControllerOne") as! SubViewControllerOne
let subViewControllerTwo = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SubViewControllerTwo") as! SubViewControllerTwo
subViewControllers = [yourPodcastViewController, appearancesViewController]
setViewControllers([subViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
required init?(coder: NSCoder) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
The navigation controller and PageViewController
The segue from SubViewControllerOne to LoginViewController
I think because of view controller life cycle you should perform segue into viewWillApear
Welp, I've had this issue before when running segues via didSelect on tableviews. The solution is to wrap it in the main thread. Not sure why this is necessary, but it solves my problem.
DispatchQueue.main.async {
self.performSegue(withIdentifier: "authorizeSegue", sender: self);
}

Start ViewPagerController with second UIViewController

i have a viewpagercontroller and have it running correctly with 3 pre set UIViewControllers, however when i load the app it first displays the first UIViewController, i would instead, like to display the second UIViewController first, however i am having trouble achieving this.
Here is my ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
Here is my orderedViewControllers
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newProfileViewController(vc: "UserContentViewController"),
self.newProfileViewController(vc: "ProfileViewController"),
self.newProfileViewController(vc: "NotificationViewController")]
}()
you can this function with your desired index,
func scrollToViewController(index newIndex: Int) {
if let firstViewController = viewControllers?.first,
let currentIndex = orderedViewControllers.indexOf(firstViewController) {
let direction: UIPageViewControllerNavigationDirection = newIndex >= currentIndex ? .Forward : .Reverse
let nextViewController = orderedViewControllers[newIndex]
scrollToViewController(nextViewController, direction: direction)
}
}
All you need to do is change the first bit of code that sets the viewcontrollers:
So change this:
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
To this:
if orderedViewControllers.count > 0 {
let secondViewController = orderedViewControllers[1]
setViewControllers([secondViewController],
direction: .forward,
animated: false,
completion: nil)
}
This will set the page view controller to the second view controller straight away and then your delegate methods on the page view controller should handle the swiping before and after to load the correct view controller.

How do I change the UIPageViewController from WITHIN one of the UIViewControllers that is part of the UIPageViewController?

I have been trying to figure this out for hours, and I must be misunderstanding how UIPageViewController works.
I have 5 setup screens (separate view controllers that are loaded into the UIPageViewController) that I can swipe right + left to see.
BUT! I want to be able to programmatically show the next view controller in line after they dismiss an alert on one of those view controllers.
And so far, the only solution that I've found that gets close, is this solution here ( https://github.com/jeffaburt/UIPageViewController-Post ) that allows a "next" button, but the button isn't on the view controller that's showing, it's on the root view controller that holds the UIPageViewController.
I have the UIPageViewController set up to show 5 different view controllers, with this code.
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("setup1"),
self.newColoredViewController("setup2"),
self.newColoredViewController("setup3"),
self.newColoredViewController("setup4"),
self.newColoredViewController("setup5")]
}()
Then the newColoredViewController function does this:
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)")
}
and here's my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let SetupViewController = orderedViewControllers.first {
setViewControllers([SetupViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
I must be missing some fundamental understanding, because I've tried so many different ways to change the page programmatically, and nothing happens.
I've tried doing this, but it does nothing:
setViewControllers([orderedViewControllers[3]],
direction: .Forward,
animated: true,
completion: nil)
I've tried doing this, but it does nothing:
func scrollToNextViewController() {
if let visibleViewController = viewControllers?.first,
let nextViewController = pageViewController(self,
viewControllerAfterViewController: visibleViewController) {
scrollToViewController(nextViewController)
}
}
Please point me in the right direction!! I can research all day and usually can figure it out thanks to previous stackoverflow questions that are already answered, but here I'm stumped.
Thanks in advance!!
I figured out an easier way to do it within the structure I'd already set up. In the UIPageViewController, added this function:
func nextPageWithIndex(index: Int)
{
// let nextWalkthroughVC = newColoredViewController("setup4")
let nextWalkthroughVC = orderedViewControllers[index]
setViewControllers([nextWalkthroughVC], direction: .Forward, animated: true, completion: nil)
}
and then in one of the setup View Controllers I created a button action that ran this code:
#IBAction func yeaaa(sender: AnyObject) {
let parent = self.parentViewController as! SetupViewController
parent.nextPageWithIndex(3)
}
(where "SetupViewController" is the UIPageViewController)
YEEHAWWWW that was driving me nuts.
Here's the video that gave me the idea of "self.parentViewController as! SetupViewController" and the "nextPageWithIndex" code: https://www.youtube.com/watch?v=K3CSBxX5VXA
I guess in newColoredViewController you create the instances of the view controllers that are to be shown by the pageViewController.
There you shoud pass into a reference to the showing view controller. Something like
newSetupViewConroller.setNextDelegate(self).
Define a protocol NextDelegate that has a method performNext
That should be the type of the parameter (and the correlating instance variable) of setNextDelegate.
(Alternatively to a setter method you can of course declare some property of type NextDelegate)
Your presenting view controller should conform to this protocol and implement the performNext method.
When you did that then you have access to the presenting view controller (as an object that conforms to the protocol) and you can call its method performNext.
Within the method performNext you are in your presenting view controller and have access to the pageViewController and go from there.
(There are easier ways of doing this, but I think this is a proper one. That is what protocols are made for.)
First, we need to access parent class as PageViewController
Swift 5 :
if let controller = self.parent as? yourPageViewController
controller.goToNextPage()
}
PageViewController Extension can be used like that
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}

UIPageViewController + UIImagePicker with custom controls not working - view is not in window hierarchy

I'm trying to create effect similar to Snapchat in Swift - swiping between UIImagePicker with custom controls and other VCs.
The problem is:
when CameraVC is presented for the first time background is black and swipe between VCs works only on controls (on empty space where should be image from camera it isn't) and warning shows up "Attempt to present UIImagePickerController on CameraVC whose view is not in the window hierarchy!"
when I swipe to another VC and then back to CameraVC UIImagePicker is presented properly and everything works great instead of swiping between VCs which is not working at all. There's also no "window hierarchy" warning
So I think the reason why it's not working is that UIImagePicker is presenting over PageViewController not "in" it, but I have no idea how to fix this.
I'm presenting PageViewController like this:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vcPageView = storyboard.instantiateViewControllerWithIdentifier("PageViewID") as! UIViewController
self.presentViewController(vcPageView, animated: false, completion: nil)
}
Loading VCs to table in PageViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc0 = storyboard.instantiateViewControllerWithIdentifier("CameraID") as! UIViewController
var vc1 = storyboard.instantiateViewControllerWithIdentifier("vc2ID") as! UIViewController
self.myViewControllers = [vc0, vc1]
self.setViewControllers([myViewControllers[0]], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
And finally CameraVC:
#IBOutlet var cameraOverlay: UIView!
var camera = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera){
self.camera.delegate = self
self.camera.sourceType = UIImagePickerControllerSourceType.Camera;
self.camera.mediaTypes = [kUTTypeImage]
self.camera.allowsEditing = false
self.camera.showsCameraControls = false
self.cameraOverlay.frame = self.camera.cameraOverlayView!.frame
self.cameraOverlay.bringSubviewToFront(self.cameraOverlay)
self.camera.cameraOverlayView = self.cameraOverlay
self.cameraOverlay = nil
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.topMostViewController().presentViewController(self.camera, animated: false, completion: nil)
}
topMostViewController code:
extension UIViewController {
func topMostViewController() -> UIViewController {
// Handling Modal views
if let presentedViewController = self.presentedViewController {
return presentedViewController.topMostViewController()
}
// Handling UIViewController's added as subviews to some other views.
else {
for view in self.view.subviews
{
// Key property which most of us are unaware of / rarely use.
if let subViewController = view.nextResponder() {
if subViewController is UIViewController {
let viewController = subViewController as! UIViewController
return viewController.topMostViewController()
}
}
}
return self
}
}
Try this, In the CameraVC move the camera code in the viewDidLoad() to the viewDidAppear()

Resources