I have implemented a scrollView into my app..
It works fine except that when it first runs and I swipe the commands I posted below get called multiple times then the itemIndex is skipped ahead by 1, so none of my other code works.
class ViewController: UIViewController, UIPageViewControllerDataSource {
// MARK: - Variables
private var pageViewController: UIPageViewController?
// Initialize it right away here
private let contentImages = quizQuestion
// 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 as [AnyObject], 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
var index = itemController.itemIndex
if index == 0 || index == NSNotFound {
index = self.contentImages.count
}
index--
return getItemController(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! PageItemController
var index = itemController.itemIndex
if index == NSNotFound {
return nil
}
index++
if index == self.contentImages.count {
index = 0
}
return getItemController(index)
}
private func getItemController(itemIndex: Int) -> PageItemController? {
if itemIndex < contentImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as! PageItemController
// as you can see, everything is working fine
// 1 to 2, 2 to 3 etc.
// what you see here is your default PageViewController behaviour. When we scroll from view 1 to view 2, pageViewcontroller will automatically call 2 methods:viewControllerAfterViewController and viewControllerBeforeViewController
// if you want to get current index, let me think, you can get it here.
// are you clear now?
pageItemController.itemIndex = itemIndex
// pageItemController.imageName = contentImages[itemIndex]
//println(contentImages[itemIndex])
//println("item index is \(contentImages[itemIndex])")
return pageItemController
}
return nil
}
}
This is the code that gets repeated when ever I swipe when the page first loads.
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
println("item index is \(itemIndex)") <-
questionLabel.text = quizQuestion[itemIndex].question <-
println(quizQuestion[itemIndex].question) <-
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
The following image shows the index working correctly when the view first loads.
Now I have swiped left once and the index should be 1 but not only is it not 1 it has loaded the view atleast 3 more times
There is no guarantee that the UIPageViewCOntroller subsystem won't generate the view controller for the next view, and the view after that ahead of time. My guess is that is what is happening. You may want to keep track of the view controllers yourself. You could keep an array of PageItemController objects. You could then check the itemIndex of the viewController passed in to pageViewController(pageViewController:viewControllerAfterViewController:) and generate the next, or return the correct one from the array. But you may not need to or want to do that.
Related
I am trying to use PageViewController to generate new ViewControllers with data based on an array of names held in UserDefaults. When the user adds a new name into the array, the PageView should generate a new page on each swipe until the index is >= than the array.count.
The problem is while there are 5 items in the Array, I am able to swipe infinitely, the index works correctly for the first two pages, but it stays at 1 on each next swipe, so it never becomes == or >= so that it will return Nil. The array seems to be correct, when printing it prints the 5 names, but for some reason it is not indexing correctly in the pageView, can anyone Identify what the issue may be?
I hope this is enough information, Manager is the class where i hold the array called coins which contains at the moment 5 names of cryptocurrencies, the idea is to be able to add a new page, appending the array, and so that the pageView will generate a new page retrieving the data from an API to load the specific data for that coin, Very similar to the way the IOS weather app works. Thank you for the help, here is the code of the pageViewController:
import UIKit
class PageViewController: UIPageViewController,
UIPageViewControllerDataSource, UIPageViewControllerDelegate {
//page Control dots
var pageControl = UIPageControl()
func configurePageControl() {
// The total number of pages that are available is based on how
many available colors we have.
pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = (Manager.shared.coins.count)
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.gray
self.pageControl.currentPageIndicatorTintColor = UIColor.white
self.view.addSubview(pageControl)
}
// MARK: Delegate functions
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
self.pageControl.currentPage = (viewControllers?.index(of: pageContentViewController)!)!
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {
if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
return nil
}
let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController
templateViewController.dataObject = Manager.shared.coins[index]
print(Manager.shared.coins)
return templateViewController
}
func indexOfViewController(_ viewController: TemplateViewController) -> Int {
print(viewController.dataObject)
return Manager.shared.coins.index(of: viewController.dataObject) ?? NSNotFound
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if (index == 0) || (index == NSNotFound){
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == Manager.shared.coins.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
//List of View Controllers.
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
configurePageControl()
self.dataSource = self
if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!){
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
}
}
Coins:
class Manager {
var coins = [""]
let defaults = UserDefaults.standard
static let shared = Manager()
init() {
self.coins = self.defaults.stringArray(forKey: "SavedStringArray") ?? [String]()
}
func addCoin(coin:String) {
self.coins.append(coin)
self.defaults.set(self.coins, forKey: "SavedStringArray")
print ("coins from addCoin:")
print (self.coins)
}
}
and I add coins to the array from elsewhere like so:
#IBAction func goButton(_ sender: Any) {
self.choosePerm()
Manager.shared.coins.append(chosenCoin)
Manager.shared.addCoin(coin: chosenCoin)
print(Manager.shared.coins)
print(chosenCoin)
}
Edit: Above I've edited the way i append the array, choosePerm() gets the name of the coin after searching has been done, and chosenCoin is the coin chosen after search, it definitely is 1 name, but I am still getting it to append double each time, can you see anything In there that might be causing that? i.e. it prints
["Ethereum", "Boolberry", "Boolberry", "Bitcoin", "Bitcoin"]
You are appending the coin twice:
Manager.shared.coins.append(chosenCoin) // <-- appending for the first time
Manager.shared.addCoin(coin: chosenCoin) // <-- appending for the second time
func addCoin(coin:String) {
self.coins.append(coin) // <-- this is the second appending
This kind of error can be fixed but not allowing appending from outside the Manager class:
private(set) var coins = [""]
I have an Assertion Failure in UIPageViewController.
Assertion failure in -[UIPageViewController _flushViewController:animated:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIPageViewController.m
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Don't know about flushed view <UIView: 0x15a5bff30; frame = (0 0; 768 903); autoresize = W+H; layer = <CALayer: 0x15a5bfc30>>'
*** First throw call stack:
(0x181ebedb0 0x181523f80 0x181ebec80 0x182844154 0x1877a1c40 0x1877a1da8 0x18784e9c4 0x18784ebfc 0x187852318 0x18784dd98 0x1870101e4 0x1849a2994 0x18499d5d0 0x1870270a4 0x10028b620 0x100348b78 0x100379f54 0x100168878 0x18733d568 0x1870330b4 0x1870f1a00 0x18733e71c 0x1870f832c 0x18703536c 0x18700f7ac 0x18700ed40 0x18700eba8 0x1873283b4 0x18700d5e8 0x18784ebd4 0x187852318 0x18784df3c 0x1871db550 0x1871daf6c 0x101c9b768 0x1849f0234 0x1849f00e8 0x182135e54 0x181e5d030 0x181e757d4 0x181e74f0c 0x181e72c64 0x181d9cc50 0x183684088 0x18707e088 0x10033b200 0x18193a8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
I don't know why this error is occurring. Any clues on what's causing it or how to debug it?
The direct way to run into this assert is to use cycled source for UIPageController defined with scroll transition style.
When the source contains two pages each one is the previous and the next for another one. If you swipe UIPageController containing two pages and then try to set source with 3 pages you will get the assertion mentioned above with guarantee assuming that UIPageControllerDataSource before/after methods allow cycled transition in case of 2 pages.
The main rules of crash-free using UIPageController with scroll transition:
1) set dataSource before calling setViewControllers method
2) use setViewControllers method without animation (animated: false)
3) set dataSource to nil for single page mode
4) don't allow cycles for 2-page mode
All these recommendations together make UIPageController absolutely stable.
import UIKit
/// Convenient subclass of UIPageViewController
#objc class AMPageViewController: UIPageViewController {
/// Turn on/off PageControl at the bottom
#objc var showPageControl: Bool = true
/// Array of all viewControllers
#objc var source: [UIViewController]? {
didSet {
let count = source?.count ?? 0
if count > 0 {
dataSource = count > 1 ? self : nil
}
else {
dataSource = nil
delegate = nil
}
}
}
/// Index of the current viewController from source
#objc var pageIndex: Int {
get {
var currentPageIndex: Int = 0
if let vc = viewControllers?.first, let source = source, let pageIndex = source.index(of: vc) {
currentPageIndex = pageIndex
}
return currentPageIndex
}
set {
guard newValue >= 0, let source = source, newValue < source.count else { return }
let vc = source[newValue]
let direction: UIPageViewControllerNavigationDirection = newValue < pageIndex ? .reverse : .forward
setViewController(vc, direction: direction)
}
}
override weak var delegate: UIPageViewControllerDelegate? {
get { return super.delegate }
set {
if source?.count ?? 0 > 0 {
super.delegate = newValue
}
else {
super.delegate = nil
}
}
}
/// Initializer in scroll-mode with interPageSpacing
#objc init(navigationOrientation: UIPageViewControllerNavigationOrientation = .horizontal, interPageSpacing: Int = 0) {
let options = (interPageSpacing > 0) ? [UIPageViewControllerOptionInterPageSpacingKey : 5] : nil
super.init(transitionStyle: .scroll, navigationOrientation: navigationOrientation, options: options)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Set viewcontroller by index from source
#objc func setPageIndex(_ index: Int, completion: ((Bool) -> Void)? = nil) {
guard index > 0, let source = source, index < source.count else { return }
let vc = source[index]
let direction: UIPageViewControllerNavigationDirection = index < pageIndex ? .reverse : .forward
setViewController(vc, direction: direction, completion: completion)
}
private func setViewController(_ viewController: UIViewController, direction: UIPageViewControllerNavigationDirection = .forward, completion: ((Bool) -> Void)? = nil) {
super.setViewControllers([viewController], direction: direction, animated: false, completion: completion)
}
}
extension FFPageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let source = source, let index = source.index(of: viewController) else { return nil }
let count = source.count
if count == 2, index == 0 {
return nil
}
let prevIndex = (index - 1) < 0 ? count - 1 : index - 1
let pageContentViewController: UIViewController = source[prevIndex]
return pageContentViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let source = source, let index = source.index(of: viewController) else { return nil }
let count = source.count
if count == 2, index == 1 {
return nil
}
let nextIndex = (index + 1) >= count ? 0 : index + 1
let pageContentViewController = source[nextIndex]
return pageContentViewController
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return showPageControl ? (source?.count ?? 0) : 0
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard showPageControl else { return 0 }
return pageIndex
}
}
The overall implementation and usage examples one can find at GitHub project.
When UIPageViewController transition, ViewController inside it(ex: UITableViewController) transition will cause crash.
In my case (crash):
step1
self.pageViewController.setViewControllers([self.tableViewController2], direction: .forward, animated: true, completion: nil)
step2
Scroll the tableView while UIPageViewController transition.
My Solution
(disable scroll both target view controller and current view controller)
self.tableViewController1.tableView.isScrollEnabled = false
self.tableViewController2.tableView.isScrollEnabled = false
self.pageViewController.setViewControllers([self.tableViewController2], direction: .forward, animated: true, completion: { _ in
self.tableViewController1.tableView.isScrollEnabled = true
self.tableViewController2.tableView.isScrollEnabled = true
})
Move your pageViewController.setViewControllers function call inside DispatchQueue.main.async block if you are doing it in code.
I don't know why it works but it worked for me. For reference.
This happened to me too when I had textfields in child controller and didn't dismiss keyboard on scroll to next controller. If this is case just add endEditing in action where you programmatically change your controller or if you are scrolling on scrollViewDidScroll delegate method of pageViewController
This happens when your UIPageViewControllerTransitionStyle is set to scroll instead of pageCurl.
Are you dynamically creating View Controllers and setting them on UIPageViewController? In that case, you must ensure that the second call to setViewControllers is called after the first one completes animation because of a bug in UIKit. A delayed dispatch is a quick and dirty fix, though it is not a good practice
More details here.
https://forums.developer.apple.com/thread/6554
For me, the issue was using self.pageViewController as a member of the current view controller instead of pageViewController as parameter obtained in the didFinishAnimating delegate method.
I am trying to load appropriate images from an array into my UIPageViewController's Views.
I have 3 views which I cycle through in order to prevent crashes.
I keep track whether I am scrolling forward or backwards but sometimes when flipping forth and back really quick, I run into a bug that flips the direction and than I get the same view repeating for couple of scrolls.
Here is the function that updates the local variable index when scrolling completes
func pageViewController(pvc: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
let thisPage = pvc.viewControllers!.last as! PageContentViewController
let currentIndex = thisPage.pageIndex
let lastIndex = lastPage.pageIndex
if(currentIndex > urlIndex){
urlIndex += 1
}else if(currentIndex < urlIndex) {
urlIndex -= 1
}else{
}
}
}
And here is the rest of my UIPageViewController code:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
}
private(set) lazy var orderedViewControllers: [PageContentViewController] = {
return [self.newPageContentViewController(),
self.newPageContentViewController(),
self.newPageContentViewController()
]
}()
private func newPageContentViewController() -> PageContentViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
}
private func getNextViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeAtIndex(0)
orderedViewControllers.append(scrollView)
return scrollView
}
private func getPreviousViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeLast()
orderedViewControllers.insert(scrollView, atIndex: 0)
return scrollView
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
guard urlIndex > 0 else {
return nil
}
let previousUrlIndex = urlIndex - 1
return getViewControllerAtIndex(previousUrlIndex, next: false)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let nextUrlIndex = urlIndex + 1
guard arrayRouteName.count > nextUrlIndex else{
return nil
}
return getViewControllerAtIndex(nextUrlIndex, next: true)
}
func getViewControllerAtIndex(index: NSInteger, next: Bool) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
var pageContentViewController:PageContentViewController
if(next){
pageContentViewController = getNextViewController()
}else{
pageContentViewController = getPreviousViewController()
}
pageContentViewController.localImage = arrayLocalImage[index]
pageContentViewController.pageIndex = index
pageContentViewController.view.clipsToBounds = true
return pageContentViewController
}
I have managed to fix the problem. I have removed the pageViewControllerfunction from the UIPageControllerDelegate an simply chaged my pageViewController functions in my data source to get the index from viewController like so
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let index = ((viewController as! UIPageContentViewController).pageIndex) - 1
guard index < 0 {
return nil
}
return getViewControllerAtIndex(index, pageContentViewController: getPreviousViewController() )
}
and it behaves like it should.
This question already has answers here:
Disable UIPageViewController bounce
(16 answers)
Closed 5 years ago.
I've been making a sample app with scrolling between UIViewControllers but the point is i want to disable the bouncing effect at the end of scrolling and when going back to the first UIViewController.
here is my code :
class PageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as! UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as! UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
}
Take a look here :
I don't know if you can. UIPageController is not very customizable.
Personally, when I want to scroll between UIViewController, I prefer using a simple UIViewController, which will be a container, with an UIScrollView in it. Then I add programmatically all the controllers in the contentSize of the UIScrollView. You have to add all the controllers as child of the container.
UPDATE iOS 9
func setupDetailViewControllers() {
var previousController: UIViewController?
for controller in self.controllers {
addChildViewController(controller)
addControllerInContentView(controller, previousController: previousController)
controller.didMoveToParentViewController(self)
previousController = controller
}
}
func addControllerInContentView(controller: UIViewController, previousController: UIViewController?) {
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
// top
controller.view.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
// bottom
controller.view.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
// trailing
trailingContentViewConstraint?.active = false
trailingContentViewConstraint = controller.view.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
trailingContentViewConstraint?.active = true
// leading
let leadingAnchor = previousController?.view.trailingAnchor ?? contentView.leadingAnchor
controller.view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
// width
controller.view.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
}
PREVIOUS ANSWER
Like so :
scrollView is an IBOutlet with contraints to each edge of the ContainerViewController
class ContainerViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.bounces = false
scrollView.pagingEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLayoutSubviews() {
initScrollView()
}
func initScrollView(){
let viewController1 = storyboard?.instantiateViewControllerWithIdentifier("ViewController1") as! ViewController1
viewController1.willMoveToParentViewController(self)
viewController1.view.frame = scrollView.bounds
let viewController2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
viewController2.willMoveToParentViewController(self)
viewController2.view.frame.size = scrollView.frame.size
viewController2.view.frame.origin = CGPoint(x: view.frame.width, y: 0)
scrollView.contentSize = CGSize(width: 2 * scrollView.frame.width, height: scrollView.frame.height)
scrollView.addSubview(viewController2.view)
self.addChildViewController(viewController2)
viewController2.didMoveToParentViewController(self)
scrollView.addSubview(viewController1.view)
self.addChildViewController(viewController1)
viewController1.didMoveToParentViewController(self)
}}
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.
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
}
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
}
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)
}
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.