Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: UIPageViewController - Load separate views

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.

like image 951
DannieCoderBoi Avatar asked Feb 09 '15 02:02

DannieCoderBoi


3 Answers

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
}
like image 116
Eric F. Avatar answered Sep 28 '22 17:09

Eric F.


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
}
like image 24
ergoon Avatar answered Oct 01 '22 17:10

ergoon


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)
}
like image 35
Esqarrouth Avatar answered Sep 28 '22 17:09

Esqarrouth