Programmatically-added page control disappears when setting view controllers after setup - ios

Problem
When tapping the skip button on page i (which calls setViewControllers(_:animated:) and transitions the user to the last page in the page view controller), and then swiping back to page i again, the page control disappears.
Wanted result
I want to programmatically add and show a custom page control on the bottom of each view controller in a page view controller when said page view controller contains different types of view controllers.
Efforts so far to resolve the issue
Adding the page control to the base view controller each time it appears.
Calling loadView() on the view controller that contains the missing page control.
Code
I have a WalkthroughRootViewController that contains a UIPageViewController. The type of the view controllers in the page view controller are two subclasses of type WalkthroughBaseViewController, the first n-1 of one type, and the last of the other. I have not included code of the last type, as that's working as far as I can see.
I have this code in WalkthroughBaseViewController:
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
The page control is added to the view in viewDidLoad():
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
If the user is on any of the first n-1 view controllers, there is a skip button the user can tap to skip forward to the last view controller. The code for this is
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
Reference
I have highlighted the code I believe is important, but here are all files related to the walkthrough of the application.
WalkthroughRootViewController
import UIKit
class WalkthroughRootViewController: UIViewController {
// MARK: Regular Properties
var walkthroughPageViewController: UIPageViewController!
var walkthroughImages = [
Images.w1,
Images.w2
]
var walkthroughStrings: [String] = [
.localized(.walkthroughTitle1),
.localized(.walkthroughZipCodeTitle)
]
// MARK: Lazy Properties
lazy var walkthroughPageViewControllerDataSource: WalkthroughPageViewControllerDataSource = {
var dataSource = WalkthroughPageViewControllerDataSource()
dataSource.walkthroughRootViewController = self
return dataSource
}()
// MARK: Computed Properties
var lastIndex: Int {
return walkthroughImages.count - 1
}
var temporaryUserInput: String?
var temporarySwitchPosition = false
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
// MARK: View Controller Life Cycle
extension WalkthroughRootViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Stop gray shadow from appearing under transition.
navigationController?.view.backgroundColor = .white
configurePageViewController()
}
}
// MARK: Helper Methods
extension WalkthroughRootViewController {
func configurePageViewController() {
walkthroughPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
walkthroughPageViewController.dataSource = walkthroughPageViewControllerDataSource
walkthroughPageViewController.delegate = walkthroughPageViewControllerDataSource
let startingViewController = storyboard!.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as! WalkthroughImageViewController
let startIndex = 0
startingViewController.delegate = self
startingViewController.pageIndex = startIndex
startingViewController.text = walkthroughStrings[startIndex]
startingViewController.image = walkthroughImages[startIndex]
startingViewController.numberOfPages = walkthroughImages.count
walkthroughPageViewController.setViewControllers([startingViewController], direction: .forward, animated: true)
walkthroughPageViewController.view.frame = view.bounds
add(walkthroughPageViewController)
}
}
extension WalkthroughRootViewController: WalkthroughDelegate {
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
}
extension WalkthroughRootViewController: WalkthrouZipCodeViewControllerDelegate {
func walkththroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userEnteredText enteredText: String) {
temporaryUserInput = enteredText
}
func walkthroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userChangedSwitchPosition position: Bool) {
temporarySwitchPosition = position
}
}
WalkthroughBaseViewController
import UIKit
protocol WalkthroughDelegate: class {
func skipWalkthrough()
}
class WalkthroughBaseViewController: UIViewController {
// MARK: Regular Properties
var pageIndex = 0
var text = ""
var delegate: WalkthroughDelegate?
var numberOfPages = 0
// Lazy Properties
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
}
// MARK: View Controller Life Cycle
extension WalkthroughBaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.silver
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
view.accessibilityIdentifier = Strings.AccessibilityIdentifiers.walkthrough
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
pageControl.currentPage = pageIndex
}
}
WalkthroughImageViewController
import UIKit
class WalkthroughImageViewController: WalkthroughBaseViewController {
// MARK: #IBOutlets
#IBOutlet weak var titleLabel: UILabel! {
didSet {
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.textColor = Colors.silver
titleLabel.numberOfLines = 0
}
}
#IBOutlet weak var skipWalkthroughButton: UIButton! {
didSet {
skipWalkthroughButton.setTitleColor(Colors.silver, for: .normal)
skipWalkthroughButton.titleLabel?.font = UIFont.preferredBoldFont(for: .body)
skipWalkthroughButton.setTitle(.localized(.skip), for: .normal)
}
}
#IBOutlet weak var imageView: UIImageView! {
didSet {
imageView.layer.shadowColor = Colors.brown.cgColor
imageView.layer.shadowOffset = CGSize(width: 0, height: 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0
imageView.clipsToBounds = false
imageView.contentMode = .scaleAspectFill
}
}
// MARK: Regular Properties
var image: UIImage?
// MARK: View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = image
titleLabel.text = text
}
}
// MARK: #IBActions
extension WalkthroughImageViewController {
#IBAction func skipWalkthrough(_ sender: UIButton) {
delegate?.skipWalkthrough()
}
}
WalkthroughPageViewControllerDataSource
import UIKit
class WalkthroughPageViewControllerDataSource: NSObject {
// MARK: Regular Properties
var walkthroughRootViewController: WalkthroughRootViewController!
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound || index == 0 {
return nil
}
index -= 1
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == walkthroughRootViewController.walkthroughImages.count {
return nil
}
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
}
extension WalkthroughPageViewControllerDataSource {
func viewController(at index: Int, storyboard: UIStoryboard) -> WalkthroughBaseViewController? {
if walkthroughRootViewController.walkthroughImages.count == 0 || index >= walkthroughRootViewController.walkthroughImages.count {
return nil
}
var viewController: WalkthroughBaseViewController?
if index == walkthroughRootViewController.lastIndex {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughZipCode) as? WalkthroughZipCodeViewController
if let viewController = viewController as? WalkthroughZipCodeViewController {
viewController.pageIndex = index
viewController.walkthroughZipCodeDelegate = walkthroughRootViewController
viewController.temporaryUserInput = walkthroughRootViewController.temporaryUserInput
viewController.temporarySwitchPosition = walkthroughRootViewController.temporarySwitchPosition
viewController.numberOfPages = walkthroughRootViewController.walkthroughImages.count
viewController.image = walkthroughRootViewController.walkthroughImages[index]
}
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as? WalkthroughImageViewController
if let viewController = viewController as? WalkthroughImageViewController {
viewController.delegate = walkthroughRootViewController
viewController.pageIndex = index
viewController.image = walkthroughRootViewController.walkthroughImages[index]
viewController.text = walkthroughRootViewController.walkthroughStrings[index]
}
}
return viewController
}
func indexOfViewController(_ viewController: WalkthroughBaseViewController) -> Int {
return viewController.pageIndex
}
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDelegate {
}

Create a single UIPageControl that you put in the WalkthroughRootViewController and update it when you navigate the pages - don't create a page control for each child.
Try not to use extensions to override methods - it can cause you trouble - see this blog entry.

Related

Child View Controllers in Page View Controller Failing to Receive Delegate Calls

I am having an issue with my two child view controllers inside a parent PageViewController, where a delegate called by one of the children is not received by the other child.
My first child contains buttons, and when a button is pressed, a delegate is triggered in the other child to pause the timer. However, it fails to receive the call and the timer continues to run.
Here is my PageViewController:
class StartMaplessWorkoutPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
lazy var workoutViewControllers: [UIViewController] = {
return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.dataSource = self
// Saw this from another answer, doesn't do anything that helps (at the moment)
let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayMaplessViewController") as! DisplayMaplessViewController
buttonsViewController.buttonsDelegate = displayMaplessViewController
if let firstViewController = workoutViewControllers.last {
setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [StartWorkoutPageViewController.self])
pageControl.currentPageIndicatorTintColor = .orange
pageControl.pageIndicatorTintColor = .gray
}
func getNewViewController(viewController: String) -> UIViewController {
return (storyboard?.instantiateViewController(withIdentifier: viewController))!
}
// MARK: PageView DataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return workoutViewControllers.last
}
guard workoutViewControllers.count > previousIndex else {
return nil
}
return workoutViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let workoutViewControllersCount = workoutViewControllers.count
guard workoutViewControllersCount != nextIndex else {
return workoutViewControllers.first
}
guard workoutViewControllersCount > nextIndex else {
return nil
}
return workoutViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return workoutViewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = workoutViewControllers.firstIndex(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
My ChildViewController with Buttons:
protocol ButtonsViewDelegate: class {
func onButtonPressed(button: String)
}
class ButtonsViewController: UIViewController {
weak var buttonsDelegate: ButtonsViewDelegate?
var isPaused: Bool = false
#IBOutlet weak var startStopButton: UIButton!
#IBOutlet weak var optionsButton: UIButton!
#IBOutlet weak var endButton: UIButton!
#IBAction func startStopButton(_ sender: Any) {
if isPaused == true {
buttonsDelegate?.onButtonPressed(button: "Start")
isPaused = false
} else {
buttonsDelegate?.onButtonPressed(button: "Pause")
isPaused = true
}
}
#IBAction func endButton(_ sender: Any) {
let menu = UIAlertController(title: "End", message: "Are you sure you want to end?", preferredStyle: .actionSheet)
let end = UIAlertAction(title: "End", style: .default, handler: { handler in
self.buttonsDelegate?.onButtonPressed(button: "End")
})
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
menu.addAction(end)
menu.addAction(cancelAction)
self.present(menu, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
My other ChildViewController, which should be receiving the calls of the ButtonsViewDelegate:
import UIKit
class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {
var timer = Timer()
var currentTime: TimeInterval = 0.0
var isCountdown: Bool = false
var isInterval: Bool = false
var currentRepeats: Int = 0
var currentActivity: Int = 0
var count: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
startIntervalTimer(withTime: 0)
}
// Currently not being called
func onButtonPressed(button: String) {
switch button {
case "Start":
restartIntervalTimer()
case "Pause":
pauseIntervalTimer()
case "End":
stop()
default:
break
}
}
func startIntervalTimer(withTime: Double) {
if withTime != 0 {
currentTime = withTime
if isInterval != true {
isCountdown = true
}
}
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
}
func pauseIntervalTimer() {
timer.invalidate()
}
func restartIntervalTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
}
// Currently Not being called
func stop() {
timer.invalidate()
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = [.pad]
let timeString = formatter.string(from: currentTime)
// save the data etc
print("Stop is called")
}
#objc func intervalTimerUpdate() {
currentTime += 1.0
print(currentTime)
}
}
Sorry that this is so long winded, been trying for quite a while and really annoyed that it doesn't work! Thanks!
I'll try to be clear, hopefully i'll be so as english is not my native language.
It seems to me that you are instantiating your ViewControllers to be presented in the getNewViewController() method and storing them in the workoutViewControllers array, but you are setting the delegate as a separate instance that you never set in your PageVC. You need to set the delegates using the same instances.
These two are two instances of two VC classes (also not sure if the identifier "DisplayViewController" is right, i expected "DisplayMaplessViewController", hard to tell without the storyboard):
let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayViewController") as! DisplayMaplessViewController
buttonsViewController.buttonsDelegate = displayMaplessViewController
And these in the array two other instances, unrelated from the ones above, of the same two classes:
lazy var workoutViewControllers: [UIViewController] = {
return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
}()
To better understand what i mean, i refactored from scratch and semplified your project (had to do it programmatically as i'm not used to storyboards).
It now consists of a PageController that displays a buttonsVC with a red button and a displayMaplessVC with a blue background.
Once you press the red button, the delegate method is called which causes the blue background to turn green.
Take a look at what i'm doing, as i'm appending the same instances of which i set the delegate:
instantiate a DisplayMaplessViewController object and ButtonsViewController object;
set buttonsVC.buttonsDelegate = displayMaplessVC;
append both ViewControllers to the array.
This is a way to get it done but for sure there are several other ways to achieve the same result, once you get the point and understand your mistake you can pick the one you like the most.
Just copy and paste it into a new project, build and run (you have to set the class of the starting ViewController in the Storyboard as StartMaplessWorkoutPageViewController):
import UIKit
class StartMaplessWorkoutPageViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var workoutViewControllers = [UIViewController]()
private let pageController: UIPageViewController = {
let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
return pageController
}()
override func viewDidLoad() {
super.viewDidLoad()
pageController.delegate = self
pageController.dataSource = self
let buttonsVC = ButtonsViewController()
let displayMaplessVC = DisplayMaplessViewController()
buttonsVC.buttonsDelegate = displayMaplessVC
workoutViewControllers.append(buttonsVC)
workoutViewControllers.append(displayMaplessVC)
self.addChild(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.setViewControllers([displayMaplessVC], direction: .forward, animated: true, completion: nil)
self.pageController.didMove(toParent: self)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pageController.view.frame = view.bounds
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return workoutViewControllers.last
}
guard workoutViewControllers.count > previousIndex else {
return nil
}
return workoutViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let workoutViewControllersCount = workoutViewControllers.count
guard workoutViewControllersCount != nextIndex else {
return workoutViewControllers.first
}
guard workoutViewControllersCount > nextIndex else {
return nil
}
return workoutViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return workoutViewControllers.count
}
}
.
protocol ButtonsViewDelegate: class {
func onButtonPressed()
}
import UIKit
class ButtonsViewController: UIViewController {
weak var buttonsDelegate: ButtonsViewDelegate?
let button: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.addTarget(self, action: #selector(onButtonPressed), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.frame = CGRect(x: 50,
y: 50,
width: 100,
height: 100)
}
#objc private func onButtonPressed() {
buttonsDelegate?.onButtonPressed()
}
}
.
import UIKit
class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {
private let testView: UIView = {
let view = UIView()
view.backgroundColor = .blue
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(testView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
testView.frame = view.bounds
}
internal func onButtonPressed() {
testView.backgroundColor = .green
}
}

Swift Class has another class from Pod with UiViewController and get Nil on outlets because of that

First of all, sorry the english.
I have a project in Swift 4.2. One of the Pods is imported and added to a class "Principal View Controller". This Pod has de UIViewController and i cannot add it on the class because it's used on the Pod and Xcode doesn't alow multiple inheritance.
If I remove the Pod from the class the Iboutlets works without problem but the pod is not working ( it's used for side menu ). If i leave the Pod added, the Iboutlets just get nil value.
I track in a debug the code and pass on 2 of the ViewDidLoad and nothing load value on Outlets if Pod is added.
Maybe someone knows the way to fix this?
**I remove some code from classes because it's way more than the 300000 characters that stackoverflow allow...
//
// PrincipalViewController.swift
//
import UIKit
import SwiftyJSON
import JLSlideMenuController
import Alamofire
class PrincipalViewController: JLSlideNavigationController, UIImagePickerControllerDelegate, URLSessionDownloadDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var scrollViewWeather: UIScrollView!
#IBOutlet var profileImage:UIImageView!
#IBOutlet var parentView: UIView!
#IBOutlet var lblUserName:UILabel!
#IBOutlet var menuButton:UIButton!
var jsonVar:JSON!
var rutasDisponibles : [Ruta]!
var userDefaults = UserDefaults.standard
var viewDayArray = [UIView](repeating: UIView(), count: 7)
var labelDayArray = [UILabel](repeating: UILabel(), count: 7)
var labelTempDayArray = [UILabel](repeating: UILabel(), count: 7)
var ivDayArray = [UIImageView](repeating: UIImageView(), count: 7)
var taskDescargaKmls : URLSessionTask!
var contador = 1
#IBOutlet weak var btMisRutas: UIButton!
#IBOutlet weak var btRutas: UIButton!
#IBOutlet weak var btInfoMunicipio: UIButton!
var jlSlide: JLSlideNavigationController!
lazy var session : Foundation.URLSession = {
let config = URLSessionConfiguration.ephemeral
config.allowsCellularAccess = false
let session = Foundation.URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
return session
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
UIApplication.shared.isStatusBarHidden = false
profileImage.layer.borderColor = UIColor.lightGray.cgColor
let lang = UserDefaults.standard.string(forKey: "LANG");
btRutas.setTitle(Languages.translateText("rutas", language: lang!), for: UIControl.State())
btInfoMunicipio.setTitle(Languages.translateText("info_municipio", language: lang!), for: UIControl.State())
btMisRutas.setTitle(Languages.translateText("mis_rutas", language: lang!), for: UIControl.State())
if(!OnlineStatus.isConnectedToNetwork()){
scrollViewWeather.isHidden = true
}
}
#IBAction func sideMenu(_ sender: UIButton) {
if menuIsPresented(){
self.hideMenu(animated: true)
}
else{
self.showMenu(animated: true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let configuration = URLSessionConfiguration.background(withIdentifier: "com.website.background")
let gesture = UITapGestureRecognizer(target: self, action: #selector(PrincipalViewController.closeMenu(_:)))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
gesture.delegate = self
self.parentView.addGestureRecognizer(gesture)
addSlideMenu("MenuViewController",storyboardName: "Main",distToTop: 0, widthAspectRatio: 0.8, distToBottom: 0,comeFromLeft: true)
}
The pod is JSlideMenuController : https://cocoapods.org/pods/JLSlideMenuController
//
// JLSlideMenuViewController.swift
// Pods
//
// Created by José Lucas Souza das Chagas on 20/03/16.
//
//
import UIKit
public class JLSlideMenuViewController: UIViewController {
public var attachedNavController:JLSlideNavigationController!
//constraints
var sideDist:NSLayoutConstraint!
var distToTopC:NSLayoutConstraint!
var distToBottomC:NSLayoutConstraint!
var widthC:NSLayoutConstraint!
//
/**
TRUE to enable the menu FALSE to disable
The value of this enable or disable the animations and gestures to show and hide the menu
*/
public var enabled:Bool = true{
didSet{
if enabled == false{
attachedNavController.hideMenu(animated: false)
}
}
}
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.layoutIfNeeded()
}
override public func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
//print(parent!.childViewControllers)
}
//public override func canBecomeFirstResponder() -> Bool {
public func canBecomeFirstResponder() -> Bool {
return true
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//override public func prefersStatusBarHidden() -> Bool {
public func prefersStatusBarHidden() -> Bool {
return true
}
func removeConstraints(){
self.view.window!.removeConstraint(sideDist)
self.view.window!.removeConstraint(distToTopC)
self.view.window!.removeConstraint(distToBottomC)
self.view.removeConstraint(widthC)
}
/**
Call this method to present modally the correct view controller accordingly to some interaction
with the menu view controller
- parameter VCId: The Id of the view controller to show
- parameter storyboardName: the name of the storyboard where the view controller is
- parameter animated: true to transition with some animation or not
*/
public func presentControllerModally(VCId:String,storyboardName:String,animated:Bool){
let destinyVC = JLSlideNavigationController.loadMenuVC(identifier: VCId, storyboardName: storyboardName)
destinyVC.view.frame = self.attachedNavController.view.frame
self.attachedNavController.hideMenu(animated: false)
let menuSegue = UIStoryboardSegue(identifier: "actualViewToAnother", source: self.attachedNavController,
destination: destinyVC, performHandler: { () -> Void in
DispatchQueue.global(qos: .background).async {
// Background Thread
DispatchQueue.main.async {
// Run UI Updates
self.attachedNavController.topViewController!.present(destinyVC, animated: animated, completion: nil)
}
}
/*
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.attachedNavController.topViewController!.presentViewController(destinyVC, animated: animated, completion: nil)
})
*/
})
//self.prepareForSegue(menuSegue, sender: nil)
self.attachedNavController.topViewController!.prepare(for: menuSegue, sender: nil)
menuSegue.perform()
}
/**
Call this method to show the correct view controller accordingly to some interaction
with the menu view controller
Use this one if the view controller where the menu is right now have some navigation controller
- parameter VCId: The Id of the view controller to show
- parameter storyboardName: the name of the storyboard where the view controller is
- parameter animated: true to transition with some animation or not
*/
public func showController(VCId:String,storyboardName:String,animated:Bool){
let destinyVC = JLSlideNavigationController.loadMenuVC(identifier: VCId, storyboardName: storyboardName)
destinyVC.view.frame = self.attachedNavController.view.frame
self.attachedNavController.hideMenu(animated: false)
let menuSegue = UIStoryboardSegue(identifier: "actualViewToAnother", source: self.attachedNavController,
destination: destinyVC, performHandler: { () -> Void in
self.attachedNavController.pushViewController(destinyVC, animated: animated)
})
//self.prepareForSegue(menuSegue, sender: nil)
self.attachedNavController.prepare(for: menuSegue, sender: nil)
menuSegue.perform()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
//
// JLSlideNavigationController.swift
// Pods
//
// Created by José Lucas Souza das Chagas on 24/04/16.
//
//
import UIKit
open class JLSlideNavigationController: UINavigationController,UINavigationControllerDelegate {
/**
menuVCStoryboardID: The Id of your menu View Controller.
*/
#IBInspectable private(set) var menuVCStoryboardID:String?
/**
the name of the storyboard where your menu View Controller is.
*/
#IBInspectable private(set) var storyboardName:String?
/**
the value of the constraint that indicates the distance between your menu top and this VC top.
*/
#IBInspectable private(set) var distToTop:CGFloat = 0
/**
the value of the constraint that determine your menu Width.
*/
#IBInspectable private(set) var width:CGFloat = 250
/**
the value of the constraint that indicates the distance between your menu bottom and this VC bottom.
*/
#IBInspectable private(set) var distToBottom:CGFloat = 0
/**
the bolean value that indicates if your menu will come from left or from right of the window.
*/
#IBInspectable private(set) var comeFromLeft:Bool = true
/**
A bolean value that indicates to use or not shadow effects
*/
#IBInspectable private(set) var useShadowEffects:Bool = false
private var menuContainerView: UIView?
/**
The instace of your menu View Controller associated to this View Controller
*/
private(set) static var myMenuVC:JLSlideMenuViewController?
public static var panGes:UIPanGestureRecognizer?
public static var screeEdgePanGes:UIScreenEdgePanGestureRecognizer?
/**
Incicates that the pan gesture that were started enabled or not
*/
private var panEnabled:Bool = false
private var lastPanTouch:CGPoint?
override open func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
if let pan = JLSlideNavigationController.panGes{
pan.isEnabled = true
}
if let pan = JLSlideNavigationController.screeEdgePanGes{
pan.isEnabled = true
}
// Do any additional setup after loading the view.
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let _ = self.view.window{
checkIfShouldAddOrUpdateMenu()
}
print(self.view.window!.gestureRecognizers!.count)
}
open override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
if let pan = JLSlideNavigationController.panGes{
pan.isEnabled = false
}
if let pan = JLSlideNavigationController.screeEdgePanGes{
pan.isEnabled = false
}
}
class func loadMenuVC(identifier:String,storyboardName:String)->UIViewController{
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
return storyboard.instantiateViewController(withIdentifier: identifier)
}
//MARK: - NavigationController delegate methods
public func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let menuView = menuContainerView , let window = self.view.window{
window.bringSubviewToFront(menuView)
}
let subViews = viewController.view.subviews
if let screenEdgeGes = JLSlideNavigationController.screeEdgePanGes{
for subView in subViews{
if let gestures = subView.gestureRecognizers{
for gesture in gestures{
gesture.require(toFail: screenEdgeGes)
}
}
}
}
}
//MARK: - Gestures methods
private func addGestures(){
//Pan
if let window = self.view.window , let pan = JLSlideNavigationController.panGes{
window.removeGestureRecognizer(pan)
}
JLSlideNavigationController.panGes = UIPanGestureRecognizer(target: self, action: #selector(JLSlideNavigationController.panAction(panGes:)))
self.view.window?.addGestureRecognizer(JLSlideNavigationController.panGes!)
//Screen Edge pan
if let window = self.view.window , let pan = JLSlideNavigationController.screeEdgePanGes{
window.removeGestureRecognizer(pan)
}
JLSlideNavigationController.screeEdgePanGes = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(JLSlideNavigationController.screenEdgePanAction(edgePan:)))
if comeFromLeft{
JLSlideNavigationController.screeEdgePanGes!.edges = UIRectEdge.left
}
else{
JLSlideNavigationController.screeEdgePanGes!.edges = UIRectEdge.right
}
self.view.window?.addGestureRecognizer(JLSlideNavigationController.screeEdgePanGes!)
JLSlideNavigationController.panGes!.require(toFail: JLSlideNavigationController.screeEdgePanGes!)
if let screenEdgeGes = JLSlideNavigationController.screeEdgePanGes, let topViewC = topViewController{
let subViews = topViewC.view.subviews
for subView in subViews{
if let gestures = subView.gestureRecognizers{
for gesture in gestures{
gesture.require(toFail: screenEdgeGes)
}
}
}
}
}
#objc public func screenEdgePanAction(edgePan:UIScreenEdgePanGestureRecognizer){
let currenLocation = edgePan.location(in: self.view)
if let menuView = menuContainerView, JLSlideNavigationController.myMenuVC!.enabled{
if edgePan.state == UIGestureRecognizer.State.began{
panEnabled = false
if !menuIsPresented(){
panEnabled = true
lastPanTouch = currenLocation
}
}
else if edgePan.state == UIGestureRecognizer.State.ended{//at the end of pan gesture check if the menu is near to show or hide completelly and then finishes the movement
panEnabled = false
let menuFrame = menuView.frame
if comeFromLeft{
if menuFrame.origin.x <= -menuFrame.size.width/2{
self.hideMenu(animated: true)
}
else if menuFrame.origin.x > -menuFrame.size.width/2{
self.showMenu(animated: true)
}
}
else{
if menuFrame.origin.x >= self.view.frame.width - menuFrame.size.width/2{
self.hideMenu(animated: true)
}
else if menuFrame.origin.x < self.view.frame.width - menuFrame.size.width/2{
self.showMenu(animated: true)
}
}
}
if panEnabled{//if the pan started at a right point continue
menuView.alpha = 1
let xDeslocamento = currenLocation.x - lastPanTouch!.x
lastPanTouch = currenLocation
if comeFromLeft{
if menuView.frame.origin.x + xDeslocamento >= -menuView.frame.width && menuView.frame.origin.x + xDeslocamento <= 0{
menuView.frame.origin = CGPoint(x:menuView.frame.origin.x + xDeslocamento ,y:menuView.frame.origin.y)
}
}
else{
if menuView.frame.origin.x + xDeslocamento >= self.view.frame.width - menuView.frame.width && menuView.frame.origin.x + xDeslocamento <= self.view.frame.width{
menuView.frame.origin = CGPoint(x:menuView.frame.origin.x + xDeslocamento ,y: menuView.frame.origin.y)
}
}
}
}
}
}

Using Page View Controller inside Tab Swift

MY WORK SO FAR:
So I have a Tab bar that looks like this:
When I click on "Canteen" I want to be directed to a Page View Controller where I can swipe between different pages but stay on the same tab.
I have this somewhat working:
I have the Storyboard setup like this:
As you can see that segue above is coming from the Tab Bar Controller.
The third view (Can Page Item Controller, ID: "CanItemController) is used for all pages in the page view.
The second view above (Page View Controller, ID: "CanPageController) is used for controlling the Pages (duh)
The first view (CanteenViewController) contains all the code and makes all the connections. This is where everything goes on. The code inside this class is here:
import UIKit
class CanteenViewController: UIViewController, UIPageViewControllerDataSource {
// MARK: - Variables
private var pageViewController: UIPageViewController?
private let contentImages = ["Radar-512.png",
"dartp.png",
"roomp.png",
"abnews.png",
"canteenp.png"];
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("CanPageController") 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! CanPageItemController
if itemController.itemIndex > 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! CanPageItemController
if itemController.itemIndex+1 < contentImages.count {
return getItemController(itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> CanPageItemController? {
if itemIndex < contentImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("CanItemController") as! CanPageItemController
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
}
}
I HAVE 2 PROBLEMS:
I can't see any page indicators at all.
This comes from the following code:
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
appearance.backgroundColor = UIColor.darkGrayColor()
}
Is there a way I can add a page indicator in the storyboard and reference that programatically. That way maybe I could add constraints and have more control. I think the page indicator might be hidden behind the Tab Bar. Though constraints are also giving me issues, which leads me to problem 2
As you can see in the Item Controller, I have a UIImageView and the constraints are all set right. But when I run the app the image appears for a second (completely out of proportion) and then disappears. i.e - my constraints simply don't work properly
Question
Is my approach in general just wrong? Or is there a few little changes I can make to fix the above problems. I I've been following a tutorial (on Ray Wenderlich I think), and it all worked fine until I tried to integrate it with my Tab Bar.
Leave all above thing, just do as following.
Edited : As per Swift 5
class CanteenViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrHelp: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
var page = 0
let arrContent: [[String: Any]] = [["name" : "Title1", "icon" : "Radar-512"],
["name" : "Title2", "icon" : "dartp"],
["name" : "Title3", "icon" : "roomp"],
["name" : "Title4", "icon" : "abnews"],
["name" : "Title5", "icon" : "canteenp"]]
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Canteen"
// Do any additional setup after loading the view, typically from a nib.
self.createHelpView()
self.pageControl.backgroundColor = UIColor.clear
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
self.pageControl.currentPageIndicatorTintColor = UIColor(red: 251/255, green: 108/255, blue: 108/255, alpha: 1.0)
self.pageControl.tintAdjustmentMode = UIView.TintAdjustmentMode.dimmed
self.pageControl.numberOfPages = self.arrContent.count
self.pageControl.currentPage = 0
}
func createHelpView() {
var x = 50
var i = 0
for item in self.arrContent {
let lblTitle = UILabel(frame: CGRect(origin: CGPoint(x: CGFloat(x), y: 10), size: CGSize(width: CGFloat(self.scrHelp.frame.width-100), height: 25)))
lblTitle.autoresizingMask = UIView.AutoresizingMask.flexibleBottomMargin
lblTitle.backgroundColor = UIColor.clear
lblTitle.font = UIFont.systemFont(ofSize: 17)
lblTitle.textAlignment = NSTextAlignment.center
lblTitle.textColor = UIColor.black
lblTitle.text = item["name"] as? String //self.arrTitle[i]
self.scrHelp.addSubview(lblTitle)
let imgView = UIImageView(frame: CGRect(origin: CGPoint(x: CGFloat(x), y: 50), size: CGSize(width: CGFloat(self.scrHelp.frame.width-100), height: CGFloat(self.scrHelp.frame.height-150))))
imgView.autoresizingMask = UIView.AutoresizingMask.flexibleBottomMargin
imgView.backgroundColor = UIColor.clear
imgView.image = UIImage(named: (item["icon"] as! String))
imgView.contentMode = UIView.ContentMode.scaleAspectFit
self.scrHelp.addSubview(imgView)
x = x + Int(self.scrHelp.frame.width)
i = i + 1
}
self.scrHelp.contentSize = CGSize(width: (CGFloat(self.arrContent.count) * self.view.frame.width), height: 0)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = CGFloat(self.scrHelp.frame.width)
let fractionalPage = self.scrHelp.contentOffset.x / pageWidth
self.page = lround(CDouble(fractionalPage))
self.pageControl.currentPage = self.page
}
}
At last, add UIScrollView and UIPageControl to you storyboard and set respective outlet and constraint.

Disable UIPageViewController bouncing - Swift [duplicate]

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)
}}

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.
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.

Resources