14

I'm following this tutorial: http://swiftiostutorials.com/ios-tutorial-using-uipageviewcontroller-create-content-slider-objective-cswift/ to create an app that shows multiple sliders.

Even though i've got this tutorial to work, This example only changes an image based on those that are stored in an array.

How can I get it to load ViewControllers instead of images

I have 4 ViewControllers:

  • ViewController1
  • ViewController2
  • ViewController3
  • ViewController4

I would like slide one to show ViewController1 and slide2 to load ViewController2 etc....

Here is my main ViewController:

 import UIKit

class ViewController: UIViewController, UIPageViewControllerDataSource {

// MARK: - Variables
private var pageViewController: UIPageViewController?

// Initialize it right away here
private let contentImages = ["nature_pic_1.png",
                             "nature_pic_2.png",
                             "nature_pic_3.png",
                             "nature_pic_4.png"];

// MARK: - View Lifecycle
override func viewDidLoad() {
    super.viewDidLoad()
    createPageViewController()
    setupPageControl()
}

private func createPageViewController() {

    let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
    pageController.dataSource = self

    if contentImages.count > 0 {
        let firstController = getItemController(0)!
        let startingViewControllers: NSArray = [firstController]
        pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
    }

    pageViewController = pageController
    addChildViewController(pageViewController!)
    self.view.addSubview(pageViewController!.view)
    pageViewController!.didMoveToParentViewController(self)
}

private func setupPageControl() {
    let appearance = UIPageControl.appearance()
    appearance.pageIndicatorTintColor = UIColor.grayColor()
    appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
    appearance.backgroundColor = UIColor.darkGrayColor()
}

// MARK: - UIPageViewControllerDataSource

func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {

    let itemController = viewController as PageItemController

    if itemController.itemIndex > 0 {
        return getItemController(itemController.itemIndex-1)
    }

    return nil
}

func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

    let itemController = viewController as PageItemController

    if itemController.itemIndex+1 < contentImages.count {
        return getItemController(itemController.itemIndex+1)
    }

    return nil
}

private func getItemController(itemIndex: Int) -> PageItemController? {

    if itemIndex < contentImages.count {
        let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as PageItemController
        pageItemController.itemIndex = itemIndex
        pageItemController.imageName = contentImages[itemIndex]
        return pageItemController
    }

    return nil
}

// MARK: - Page Indicator

func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
    return contentImages.count
}

func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
    return 0
}

and here is my PageItemController:

import UIKit

class PageItemController: UIViewController {

// MARK: - Variables
var itemIndex: Int = 0
var imageName: String = "" {

    didSet {

        if let imageView = contentImageView {
            imageView.image = UIImage(named: imageName)
        }

    }
}

@IBOutlet var contentImageView: UIImageView?

// MARK: - View Lifecycle
override func viewDidLoad() {
    super.viewDidLoad()
    contentImageView!.image = UIImage(named: imageName)
}
}

I'm new to Swift/iOS Development and really trying to get into it by developing. Thank you in advance for your answers :)

EDIT: To Make Question Clear

How do I make it so that there is an array of view controllers that correspond to the slide left/right of the UIPageViewController?

So when I swipe left on ViewController1 - the UIViewController2 is loaded and reverse for swipe right.

computingfreak
  • 4,939
  • 1
  • 34
  • 51
DannieCoderBoi
  • 728
  • 2
  • 12
  • 31
  • UIPageViewController _does_ load view controllers. You _are_ loading view controllers. What's the question? – matt Feb 09 '15 at 02:54
  • @matt each of the ViewControllers I specified above have content like forms/graphs/images etc... How do I specifiy which one to show based on the scroll. At the moment its only looping through an array of images. – DannieCoderBoi Feb 09 '15 at 02:56
  • I don't know how because I don't know what you want to do. Only one thing matters: each time `viewControllerBeforeViewController` or `viewControllerAfterViewController` is called, you get to decide, right then and there, what view controller to load. So decide! – matt Feb 09 '15 at 03:00
  • See also this (probably yours is just a duplicate): http://stackoverflow.com/questions/21641373/how-to-implement-uipageviewcontroller-that-utilizes-multiple-viewcontrollers – matt Feb 09 '15 at 03:02
  • so the line: let itemController = viewController as PageItemController - change the end to the one I want? Apologies I am new to swift and I think the bad grammar and explanation of the above tutorial has confused me even more. – DannieCoderBoi Feb 09 '15 at 03:03
  • I saw that one but that is in Obj-C I need swift. :( – DannieCoderBoi Feb 09 '15 at 03:04
  • 1
    That's utter nonsense. You can read the Objective-C perfectly well. Language has nothing to do with this. – matt Feb 09 '15 at 03:04
  • How does it not? If I'm creating a Swift application do I not need to use Swift? – DannieCoderBoi Feb 09 '15 at 03:05
  • 2
    But you can understand the _concepts_ no matter what the language of discussion is. Reading and writing are two different things. Thinking and writing are two different things. Read. Think. Then write (your code, in Swift). No one is going to write it for you. Do not look for code to copy. Look for _knowledge_. – matt Feb 09 '15 at 03:06
  • BTW: The bad grammar comment etc was for the link I provided not your link. – DannieCoderBoi Feb 09 '15 at 03:07
  • The link you provided is coded for a Master View Application. Does this make a difference as mine is Single View? – DannieCoderBoi Feb 09 '15 at 03:10
  • The point is not his question but my answer. – matt Feb 09 '15 at 03:12
  • A 20 minute watch to this video answers your question. https://www.youtube.com/watch?v=8bltsDG2ENQ – Deepak Thakur Feb 03 '16 at 10:59
  • @DannieCoderBoi Hi could you please send me the code please . my email id:mmoidul.islam@gmail.com – Md Maidul Islam Jun 16 '16 at 14:17

4 Answers4

10

Assuming you have view controllers 1-4 defined in the same storyboard as your UIPageViewController, and you have their Storyboard IDs set as ViewController0, ViewController1, and et cetera, then create a method to populate your view controller array and call it in your viewDidLoad() before calling createPageViewController()

    override func viewDidLoad() {
        super.viewDidLoad()
        populateControllersArray()
        createPageViewController()
        setupPageControl()
    }

Implement the method like so:

    var controllers = [PageItemController]()

    func populateControllersArray() {
        for i in 0...3 {
            let controller = storyboard!.instantiateViewControllerWithIdentifier("ViewController\(i)") as PageItemController
            controller.itemIndex = i
            controllers.append(controller)
        }
    }

And define your createPageViewController() as the following

    private func createPageViewController() {

        let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
        pageController.dataSource = self

        if !controllers.isEmpty {
            pageController.setViewControllers([controllers[0]], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
        }

        pageViewController = pageController
        addChildViewController(pageViewController!)
        self.view.addSubview(pageViewController!.view)
        pageViewController!.didMoveToParentViewController(self)
    }

then in your two delegate before and after methods:

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        if let controller = viewController as? PageItemController {
            if controller.itemIndex > 0 {
                return controllers[controller.itemIndex - 1]
            }
        }
        return nil
    }
    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
        if let controller = viewController as? PageItemController {
            if controller.itemIndex < controllers.count - 1 {
                return controllers[controller.itemIndex + 1]
            }
        }
        return nil
    }

And in the count method

    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
        return controllers.count
    }

In fact, you can populate controllers with any view controllers you want to display, just set their class as PageItemController in storyboard (in order to have index property).

Or you can set each view controller as it's own class, and use runtime property getting and setting.

Use controller.valueForKey("itemIndex") as Int in the before and after method instead of controller.itemIndex

Use controller.setValue(i, forKey: "itemIndex") instead of controller.itemIndex = i in populateControllersArray().

Just ensure that each controller class has the Int property itemIndex, or your application will crash.

To bring it all together in your code, do the following:

import UIKit

import UIKit

class ViewController: UIViewController, UIPageViewControllerDataSource {

    // MARK: - Variables
    private var pageViewController: UIPageViewController?

    // MARK: - View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        populateControllersArray()
        createPageViewController()
        setupPageControl()
    }

    var controllers = [PageItemController]()


    func populateControllersArray() {
        for i in 0...3 {
            let controller = storyboard!.instantiateViewControllerWithIdentifier("ViewController\(i)") as PageItemController
            controller.itemIndex = i
            controllers.append(controller)
        }
    }

    private func createPageViewController() {

        let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
        pageController.dataSource = self

        if !controllers.isEmpty {
            pageController.setViewControllers([controllers[0]], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
        }

        pageViewController = pageController
        addChildViewController(pageViewController!)
        self.view.addSubview(pageViewController!.view)
        pageViewController!.didMoveToParentViewController(self)
    }

    private func setupPageControl() {
        let appearance = UIPageControl.appearance()
        appearance.pageIndicatorTintColor = UIColor.grayColor()
        appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
        appearance.backgroundColor = UIColor.darkGrayColor()
    }

    // MARK: - UIPageViewControllerDataSource
    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        if let controller = viewController as? PageItemController {
            if controller.itemIndex > 0 {
                return controllers[controller.itemIndex - 1]
            }
        }
        return nil
    }
    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
        if let controller = viewController as? PageItemController {
            if controller.itemIndex < controllers.count - 1 {
                return controllers[controller.itemIndex + 1]
            }
        }
        return nil
    }

    // MARK: - Page Indicator

    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
        return controllers.count
    }

    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
        return 0
}
Eric Ferreira
  • 1,811
  • 18
  • 20
2

You do load ViewControllers for every page. every image that you show is inside it's own ViewController. That is done through:

private func getItemController(itemIndex: Int) -> PageItemController?

if every page of yours uses the same layout, there is nothing left to do here, except designing this ViewController in the Interface Builder. If, however, every page uses a different layout and shows different data, you would first prototype/design those ViewControllers in Interface Builder.

then you would create classes for every ViewController and extend them from PageItemController. You only keep the index variable in PageItemController and move the rest of your logic to the subclasses.

import UIKit

class PageItemController: UIViewController {

    // MARK: - Variables
    var itemIndex: Int = 0
}

for example a viewController that holds an image

import UIKit

class PageImageViewController: PageItemController {
    // MARK: - Outlets
    @IBOutlet var contentImageView: UIImageView?

    // MARK: - Variables
    var imageName: String = "" {
        didSet {
            if let imageView = contentImageView {
                imageView.image = UIImage(named: imageName)
            }
        }
    }

    // MARK: - View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        contentImageView!.image = UIImage(named: imageName)
    }
}

finally you just change your getItemController function to return the correct ViewController for the specified index. here you either pass data to the ViewController or you just return it.

private func getItemController(itemIndex: Int) -> UIViewController? {
    var vc: PageItemController? = nil

    switch itemIndex {
    case 0:
        // show an ImageViewController and pass data if needed
        vc = self.storyboard!.instantiateViewControllerWithIdentifier("ImageController") as PageImageViewController

        vc.itemIndex = itemIndex
        vc.imageName = "any_image_file"
    case 1:
        // show any other ViewController and pass data if needed
        vc = self.storyboard!.instantiateViewControllerWithIdentifier("ANY_OTHERController") as ANY_OTHERController

        vc.PRESENTABLE_DATA = ANY_PRESENTABLE_DATA_SOURCE
        vc.itemIndex = itemIndex
    case 2:
        // show any other ViewController and pass data if needed
        vc = self.storyboard!.instantiateViewControllerWithIdentifier("ANY_OTHERController") as ANY_OTHERController

        vc.PRESENTABLE_DATA = ANY_PRESENTABLE_DATA_SOURCE
        vc.itemIndex = itemIndex
    }

    return vc
}
ergoon
  • 1,264
  • 9
  • 17
1

Here is a great repo for this:

https://github.com/goktugyil/EZSwipeController

private func setupPageViewController() {
    pageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
    pageViewController.dataSource = self
    pageViewController.delegate = self
    pageViewController.setViewControllers([stackPageVC[stackStartLocation]], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
    pageViewController.view.frame = CGRect(x: 0, y: Constants.StatusBarHeight, width: Constants.ScreenWidth, height: Constants.ScreenHeightWithoutStatusBar)
    pageViewController.view.backgroundColor = UIColor.clearColor()
    addChildViewController(pageViewController)
    view.addSubview(pageViewController.view)
    pageViewController.didMoveToParentViewController(self)
}
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
0

I got Eric Ferreira's code above to work (Xcode 6.4). I wanted to use a Page View Controller to display two completely unique view controllers with labels that displays different data from a Realm database.

I got it to work in a test project where I used 2 storyboards, each containing a single label that is set by its own class containing the IBOutlet to the label and code setting the label text -- this adequately simulates the way I am displaying my Realm database data. Each storyboard class inherits the PageItemController so as to have the "itemIndex" variable available to it. The PageItemController in turns inherits UIViewController completing the inheritance chain.

I hope this helps someone that is seeking to use completely unique storyboards.

Delete My Account
  • 745
  • 2
  • 9
  • 21