I have a QuizViewController which extends UIViewController, UIPageControllerDelegate
, and a UIPageViewControllerDataSource
.
Inside QuizViewController.swift
private var pageViewController: UIPageViewController?
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("QuizPageViewController") as! UIPageViewController
pageController.dataSource = self
if pageInfo.count > 0 {
let firstController = getItemController(0)!
let startingViewControllers: NSArray = [firstController]
pageController.setViewControllers(startingViewControllers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController!.didMoveToParentViewController(self)
}
private func getItemController(itemIndex: Int) -> QuizPageItem? {
if itemIndex < pageInfo.count {
let CurrentQuizPageItem = self.storyboard!.instantiateViewControllerWithIdentifier("QuizPageItem") as! QuizPageItem
CurrentQuizPageItem.itemIndex = itemIndex
CurrentQuizPageItem.thisPageInfo = pageInfo[itemIndex]
return CurrentQuizPageItem
}
return nil
}
/*
PageView Delegates
*/
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let CurrentQuizPageItem = viewController as! QuizPageItem
if CurrentQuizPageItem.itemIndex > 0 {
return getItemController(CurrentQuizPageItem.itemIndex - 1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let CurrentQuizPageItem = viewController as! QuizPageItem
if CurrentQuizPageItem.itemIndex + 1 < pageInfo.count {
return getItemController(CurrentQuizPageItem.itemIndex + 1)
}
return nil
}
My question is how do I get the next and back buttons to work properly?
Right now the pages can be changed via swipe. I don't want to use swipe. I want to control using the next and back buttons.
UPDATE
This is on the Main.storyboard
PageViewController is the middle one in the storyboard.
PageViewController has the storyboard id as QuizPageViewController.
QuizViewController is the left one in the storyboard.
QuizViewController instantiates a PageViewController using the storyboard id QuizPageViewController
which is done by this block
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("QuizPageViewController") as! UIPageViewController
pageController.dataSource = self
When this instantiation happens, it also creates the front page for the QuizPageItem.
QuizPageItem is the right most view in the Main.storyboard.
So if you see the 2 mockups, they are both QuizPageItems.
The first mockup should have a itemIndex of 0.
The second mockup should have itemIndex of 1.
let CurrentQuizPageItem = self.storyboard!.instantiateViewControllerWithIdentifier("QuizPageItem") as! QuizPageItem
CurrentQuizPageItem.itemIndex = itemIndex
Most of the answers I have received suggests solving it via the QuizViewController aka the left most view in my Main.storyboard.
However, the buttons are in the QuizPageItem and not accessible via the QuizViewController.
I want to know how I can connect the back/next buttons in the QuizPageItem to execute the pagination which is controlled in the QuizViewController assuming the way I am wiring up the various views in the Main.storyboard is correct
At the same time, I allow the possibility that the current way I am wiring up the various views in the Main.storyboard is not ideal.
If so, please advise an alternative way.
This is the tutorial I follow to get to where I am currently at.
http://shrikar.com/ios-swift-tutorial-uipageviewcontroller-as-user-onboarding-tool/
UPDATE 2 I apologise that I am seen as arguing. I genuinely want to learn how to do this. I am pretty sure I am lacking some fundamental knowledge hence I am unable to understand Michael Dautermann's answer.
I assume there is a function in the QuizViewController that will trigger the page turning.
I am not sure what that function is.
These are the functions that I know will get triggered when the buttons are pressed.
I have the following inside the QuizPageItem class
@IBAction func pageBackButton(sender: AnyObject) {
}
@IBAction func pageNextButton(sender: AnyObject) {
}
However, they are not in the QuizViewController class but in the QuizPageItem class.
Am I supposed to put the setViewController method in these two functions inside QuizPageItem class?
And if so, how do I even access the QuizViewController instance from inside the QuizPageItem class?
UPDATE 3:
My file structure is
The QuizViewController controls which QuizPageItem you see. A QuizPageItem represents a different view as designed by the mockup.
UPDATE4:
matt's answer helped me a lot with understanding this FirstResponder which I was totally unfamiliar with in the first place.
When I tried to implement it, I was faced with this error.
I have googled around and I have tried to remedy it to no avail.
I kept triggering this error.
Attached is the code snippet for the QuizViewPageItemController.swift
import UIKit
class QuizPageItemViewController: UIViewController, CheckboxDelegate {
@IBOutlet weak var pageHeadingLabel: UILabel!
@IBOutlet weak var pageInstructionLabel: UILabel!
@IBOutlet weak var pageProgressView: UIProgressView!
@IBOutlet weak var pageQuestionLabel: UILabel!
@IBOutlet weak var pageAnswerView: UIView!
@IBOutlet weak var pageBackButton: UIButton!
@IBOutlet weak var pageNextButton: UIButton!
let pageNo: Int
let maxPageNo: Int
let thisPageInfo: [String]
let interestsList = [
"Blue Chips", "Small Caps", "Pharmaceuticals", "Agriculture",
"Telecommunications", "Manufacturing", "Finance", "Banks",
"Retail", "Travel", "Airlines", "Tourism"]
init(pageNo: Int, maxPageNo: Int, thisPageInfo: [String]) {
self.pageNo = pageNo
self.maxPageNo = maxPageNo
self.thisPageInfo = thisPageInfo
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.pageBackButton.hidden = pageNo == 0
self.pageNextButton.hidden = pageNo == maxPageNo
pageHeadingLabel.text = thisPageInfo[0]
pageInstructionLabel.text = thisPageInfo[1]
pageQuestionLabel.text = thisPageInfo[2]
if thisPageInfo[0] == "Welcome" {
createCheckboxes()
pageProgressView.setProgress(0.33, animated: true)
} else if thisPageInfo[0] == "Awesome!" {
createSlider()
pageProgressView.setProgress(0.67, animated: true)
} else if thisPageInfo[0] == "Almost there..." {
createSlider()
pageProgressView.setProgress(0.95, animated: true)
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createCheckboxes() {
let fWidth = (self.view.frame.width - 40) / 2
var frame = CGRectMake(0, 0, fWidth, 40)
var contentSize = CGRectMake(0, 0, self.view.frame.width - 40, 0)
for (var counter = 0; counter < interestsList.count; counter++) {
let checkbox = Checkbox(frame: frame, title: interestsList[counter], selected: false)
checkbox.mDelegate = self
checkbox.tag = counter
checkbox.backgroundColor = UIColor.redColor()
if counter % 2 == 0 {
frame.origin.x += fWidth
} else{
frame.origin.x -= fWidth
frame.origin.y += frame.size.height
contentSize.size.height += frame.size.height
}
checkbox.titleLabel?.adjustsFontSizeToFitWidth = true
pageAnswerView.addSubview(checkbox)
}
}
func didSelectCheckbox(state: Bool, identifier: Int, title: String) {
print("checkbox '\(title)' has state \(state)")
}
func createSlider() {
let slider = UISlider(frame:CGRectMake(0, 20, self.view.frame.width - 40, 20))
slider.minimumValue = 0
slider.maximumValue = 10
slider.continuous = true
slider.tintColor = ChatQColours().chatQBlue
slider.value = 5
// slider.addTarget(self, action: "sliderValueDidChange:", forControlEvents: .ValueChanged)
pageAnswerView.addSubview(slider)
let leftlabel = UILabel(frame: CGRectMake(0, 40, 0, 0))
leftlabel.text = "Strongly Avoid"
leftlabel.sizeToFit()
pageAnswerView.addSubview(leftlabel)
let rightlabel = UILabel(frame: CGRectMake(0, 40, 0, 0))
rightlabel.text = "Strongly Prefer"
rightlabel.sizeToFit()
rightlabel.frame.origin.x = slider.frame.width - rightlabel.frame.width
pageAnswerView.addSubview(rightlabel)
}
}
Michael Dautermann's answer is perfectly correct, as this screencast shows:
What you're seeing is a page view controller with multiple pages (numbered so you can see the order), each page containing a Next button, and I'm repeatedly pressing the Next button to navigate to the next page.
Like yours, my project, illustrated in the screencast above, has a view controller hierarchy:
UITabBarController
ViewController
UIPageViewController
Page (which has a main view, and the label and buttons are subviews of that)
It appears that the heart of your question is not so much what method causes a page view controller to navigate to its next or previous page — that, as you have already been told, is simply setViewControllers:...
— but how the button communicates up the view controller hierarchy. In my example, that means sending a message from the button inside Page's view, past the Page view controller, past the UIPageViewController, and up to the ViewController, which then tells th UIPageViewController what to do.
I can think of numerous ways to do that:
The button posts a notification for which the ViewController is registered
The button sends a nil-targeted action for which the ViewController has a handler
The button sends a message to the tab bar controller (its tabBarController
property), which then sends a message down to its currently selected view controller, the ViewController
The button sends a message to its view controller (configured in the nib or storyboard as an action), which sends a message to its parentViewController!.parentViewController!
, which is the ViewController.
Which do I prefer? Personally, I like the nil-targeted action best, because it requires no extra code. The only func pageNextButton()
implementation is in ViewController. That is beauty of a nil-targeted action: it walks up the responder chain, looking for a recipient, automatically. The Page view controller and the UIPageViewController have no code at all in this regard.
I like this much better than parentViewController!.parentViewController!
, because the latter requires ultimately that the Page view controller knows the name of a method in the ViewController that it can call, and it must cast down to a ViewController — which is not very portable and gives the Page view controller too much knowledge about its environment, in my opinion. With a nil-targeted action, on the other hand, the sender is totally agnostic about who the actual target will turn out to be! So I like nil-target action best, and notification second best.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With