Similar questions to mine have been asked numerous times, however, in my situation I set up the UIPageViewController in an unconventional method. And over the past day and a half of trying to implement the other questions solutions into my program, I have come to believe that they only work with a more conventional method of setting up the UIPageViewController. For some background, I have two UIViewControllers that are being transitioned between in a UIPageViewController. I would like to be able to programmatically change the page of the UIPageViewController from one of the views controllers.
Here is my UIPageViewController class:
class transitionController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
lazy var arrayVC: [UIViewController] = {
return [
self.VCInstance(name: "VC1"),
self.VCInstance(name: "VC2")
]
}()
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let VC1 = arrayVC.first{
setViewControllers([VC1], direction: .forward, animated: true, completion: nil)
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = arrayVC.index(of: viewController) else{
return nil
}
guard FIRAuth.auth()?.currentUser?.uid != nil else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard arrayVC.count > previousIndex else{
return nil
}
return arrayVC[previousIndex]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = arrayVC.index(of: viewController) else{
return nil
}
guard FIRAuth.auth()?.currentUser?.uid != nil else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < arrayVC.count else {
return nil
}
guard arrayVC.count > nextIndex else{
return nil
}
return arrayVC[nextIndex]
}
}
I have tried to flip the pages with this block of code in my VC1:
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VC2")
transitionController().setViewControllers([vc]?, direction: .Forward, animated: true, completion: nil)
I believe that this doesn't work as I am creating a new instance of the transition controller(I could be wrong).
How can I programmatically flip pages of the UIPageViewController from within the VC1 or VC2 classes using my method of setting up the UIPageViewController?
Thanks in advance.
Related
The UIPageViewControllerDataSource is not being called from ViewDidLoad, hence only the first page is displayed and swipe doesn't do anything.
I have followed this tutorial:
https://spin.atomicobject.com/2015/12/23/swift-uipageviewcontroller-tutorial/
The answers in questions similar to this do not work. I have viewed 5 similar questions but none of them work for me.
class SetupViewController: UIPageViewController {
private(set) lazy var pages : [UIViewController] = {
return [self.newPage(1), self.newPage(2), self.newPage(3), self.newPage(4)]
}()
private func newPage(_ page: Int) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Page\(page)")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstPage = pages.first {
print(#function, "if let firstPage", firstPage, pages) //viewDidLoad() if let firstPage page1name [page1, page2, page3, page4]
setViewControllers([firstPage], direction: .forward, animated: true, completion: nil)
}
}
}
extension SetupViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
print(#function) //It doesn't print
guard let pageIndex = pages.firstIndex(of: viewController) else { return nil }
let nextIndex = pageIndex + 1
let pageCount = pages.count
guard nextIndex != pageCount else { return nil }
guard pageCount > nextIndex else { return nil }
return pages[nextIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
print(#function) //It doesn't print
guard let pageIndex = pages.firstIndex(of: viewController) else { return nil }
let previousIndex = pageIndex - 1
guard previousIndex >= 0 else { return nil }
guard pages.count > previousIndex else { return nil }
return pages[previousIndex]
}
}
Storyboard IDs are: Page1, Page2, Page3 & Page4
What am I doing wrong?
May be you have a configuration problem i have created a demo here
https://github.com/ShKhan9/PagerShow
let wind = (UIApplication.shared.delegate as! AppDelegate).window!
wind.rootViewController = SetupViewController()
I've got my RootPageViewController set up and all I need is to add UIButtons. I've already added the button and its function from the UIViewController which is to be displayed first.
How am I able to also add UIButtons from f.e. the third UIViewController and assign them a function?
Just like with the button from the secondViewController which is displayed first: secondViewController.buttonAdd.action = #selector(addData(_:))
If I solely use let's say firstViewController.buttonBack.action = #selector(backData(_:)) - I'll get an error saying:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I assume thats because, there's no if let firstViewController ...
But let's say I'd add:
if let firstViewController = viewControllerList.first as? TimelineViewController {
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
firstViewController.buttonBack.action = #selector(backData(_:))
}
In this case the order which view will be shown initially and which next etc. will be mixed up.
I'm open for suggestion and better approaches, I just want this to work,
RootPageViewController:
import UIKit
class RootPageViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "timelineView")
let vc2 = sb.instantiateViewController(withIdentifier: "mainView")
let vc3 = sb.instantiateViewController(withIdentifier: "addView")
return [vc1, vc2, vc3]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let secondViewController = viewControllerList[1] as? MainViewController {
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
secondViewController.buttonAdd.action = #selector(addData(_:))
}
}
#objc func addData(_ sender: Any) {
if let thirdViewController = viewControllerList.last {
self.setViewControllers([thirdViewController], direction: .forward, animated: true, completion: nil)
}
}
#objc func backData(_ sender: Any) {
let secondViewController = viewControllerList[1]
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else { return nil }
guard viewControllerList.count > previousIndex else { return nil }
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else { return nil }
guard viewControllerList.count > nextIndex else { return nil }
return viewControllerList[nextIndex]
}
}
The solution was to call this in any UIViewControllers button action:
// get parent view controller
let parentVC = self.parent as! UserDetailsPVC
// change page of PageViewController
parentVC.setViewControllers([parentVC.pages[1]], direction: .forward, animated: true, completion: nil)
I have a pageViewController with 3 viewControllers which is embedded in a view of a viewcontroller.
i am setting the view controllers programatically:
lazy var vcArr: [UIViewController] = {
return [self.vcInstance(name: "vc1"),
self.vcInstance(name: "vc2"),
self.vcInstance(name: "vc3"),
]
}()
i am programatically setting the number of pages to 4
let pageController = UIPageControl.appearance()
pageController.numberOfPages = 4
what i want to happen is that if the user tries to swipe right from the 3rd page it will segue to a signup viewController.
I'm currently trying to achieve this in the viewcontroller after function testing for it the next item is greater than or equal to the total count.
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
if nextIndex >= vcArr.count {
infoClick()
return nil
} else {
guard nextIndex < vcArr.count else {
return self.vcArr.last
}
guard self.vcArr.count > nextIndex else {
return nil
}
return self.vcArr[nextIndex]
}
}
with this test the way it is sometimes it stops on page 3 and then when swiped right it will segue off with my function infoClick(). however somethings it goes right off when it hits the 3rd page as you would think because its > or = the number of pages which is 3.
when i change this to just > it doesn't display anything on the third page.
I've searched heaps for a way around this but to no avail. does anyone have any ideas how to achieve this?
attaching entire class below:
import UIKit
import Parse
class pageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
#IBOutlet var createAccountBarButton: UIBarButtonItem!
lazy var vcArr: [UIViewController] = {
return [self.vcInstance(name: "vc1"),
self.vcInstance(name: "vc2"),
self.vcInstance(name: "vc3"),
]
}()
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
//---------------------------------------------- autoLogin
/// temporary location, needs to be launched during the loading screen
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
// if (PFUser.current() != nil) {
// let vc = self.storyboard!.instantiateViewController(withIdentifier: "PackViewController")
// self.present(vc, animated: true, completion: nil)
// if (PFUser.current() != nil) {
// let tbc = self.storyboard!.instantiateViewController(withIdentifier: "MyTabController") as! UITabBarController
// tbc.selectedIndex = 1
// self.present(tbc, animated: true, completion: nil)
// }
}
//---------------------------------------------- set the viewcontrollwe instance
private func vcInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
//---------------------------------------------- set the direction and first page of the page controller
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.isHidden = true
let pageController = UIPageControl.appearance()
pageController.pageIndicatorTintColor = UIColor.lightGray
pageController.currentPageIndicatorTintColor = GeneralFunctions.UIColorFromHEX(hexValue: 0xbb0d2a)
pageController.backgroundColor = UIColor.clear
pageController.bounds = CGRect(x: 0, y: 0, width: 0, height: 0)
pageController.numberOfPages = 4
//GeneralFunctions.commonButtonSettings(buttonName: signUpButton, hexValue: 0x0c1537)
self.dataSource = self
self.dataSource = self
if let firstVC = vcArr.first {
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
//---------------------------------------------- page view controller before settings
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
if previousIndex < 0 {
return nil
} else {
guard previousIndex >= 0 else {
return self.vcArr.last
}
guard self.vcArr.count > previousIndex else {
return nil
}
return self.vcArr[previousIndex]
}
}
//---------------------------------------------- page view controller after settings
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
if nextIndex >= vcArr.count {
infoClick()
return nil
} else {
guard nextIndex < vcArr.count else {
return self.vcArr.last
}
guard self.vcArr.count > nextIndex else {
return nil
}
return self.vcArr[nextIndex]
}
}
//---------------------------------------------- presentation count
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
return self.vcArr.count
}
//---------------------------------------------- presentation index
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
let firstViewControllerIndex = vcArr.index(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//---------------------------------------------- launch the new view controller - still launching before swipped to 4th window sometimes
// not sure if this is the best way to do this need to look at this a bit more
func infoClick() {
let storyboard: UIStoryboard = UIStoryboard (name: "Main", bundle: nil)
let vc: SignupViewController = storyboard.instantiateViewController(withIdentifier: "SignupViewController") as! SignupViewController
let currentController = getCurrentViewController()
currentController?.present(vc, animated: false, completion: nil)
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}
I created a PageViewController and added three view controllers as a walkthrough for my app, but i want them to be displayed only once, and i can't figure out how.
here is my code for the pageViewController:
import UIKit
class MyPageViewController: UIPageViewController{
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
// Do any additional setup after loading the view.
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//array for the ordered view controllers
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("First"),
self.newColoredViewController("Second"),
self.newColoredViewController("Third")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)ViewController")
}
}
extension MyPageViewController: UIPageViewControllerDataSource {
//returning index of previous view controller
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
//returning index of the next view controller
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
how do i edit the code to display it only once?
From what you've said I'm guessing that you only want it to show the first time the user opens the app and then not appear again?
Theres a few ways of doing this, the simplest would likely be to set a property on NSUserDefaults, this will remember the setting in between app launches. try something like:
// this should be your initial view controller
let defaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
if !defaults.boolForKey("walkthroughSeen") {
// create page view controller and display
let walkthroughVC = UIStoryboard(name: "Main", bundle: nil).
instantiateViewControllerWithIdentifier("MyPageViewController")
self.presentViewController(walkthroughVC, animated: true)
defaults.setBool(true, forKey: "walkthroughSeen")
}
}
I recently set up a ContainerView with a PageViewController that cycles through views (images) as a sort of banner on top of a TableView that I have. Now, I'm having difficulty hooking that up to an NSTimer (so that it will cycle through the views/images automatically). Everything works, except for the timer piece that I've tried to hook up. I've tried to convert some Objective-C tutorials/SO questions but am still having difficulty.
I have looked at this: Set timer to UIPageViewController, but I can't get that provided code to work for me at all (nor any variation of it).
See below for my code:
class ImageSwapViewController: UIPageViewController {
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("First"),
self.newColoredViewController("Second"),
self.newColoredViewController("Third")]
}()
private func newColoredViewController(order: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(order)ViewController")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(ImageSwapViewController.animation), userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animation() {
[NEED HELP WITH WHAT GOES HERE]
}
}
extension ImageSwapViewController : UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return orderedViewControllers.last
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return orderedViewControllers.first
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
Any help is greatly appreciated.
A couple of notes:
1) The general idea is that you need to set up a timer in some reasonable place--viewWillAppear is a common location, and remember to invalidate it when the view is no longer displaying (as in viewWillDisappear). Failing to do so may result in memory growth because the run loop holds a strong reference to the target--in this case, self. I'd recommend moving your NSTimer.scheduledTimer... invocation to viewWillAppear.
2) The animation code above is pretty simple. Basically, you need to get the current "page", find out what the next "page" should be, and then ask your UIPageViewController to display that next "page".
func animation() {
guard let currentViewController = viewControllers?.first else {
return
}
// Use delegate method to retrieve the next view controller
guard let nextViewController = pageViewController(self, viewControllerAfterViewController: currentViewController) else {
return
}
setViewControllers([nextViewController], direction: .Forward, animated: true, completion: nil)
}
You could also simplify your before and after methods slightly:
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let i = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = (i + 1) % AutoAnimatingPageViewController.colors.count
return orderedViewControllers[nextIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let i = orderedViewControllers.indexOf(viewController) else {
return nil
}
let prevIndex = (i - 1) < 0 ? AutoAnimatingPageViewController.colors.count - 1 : (i - 1)
return orderedViewControllers[prevIndex]
}