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
ion here" src="https://imgs.xnip.cn/cj/l/93/1d71f5b4-a2f1-4026-9580-1dc3ff8f3d41.png" />
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.