UIPageViewController and storyboard
2020. Swift updated.
2017 answer...
Nowadays it is dead easy to do this simply using Storyboard.
These sort of "swiping full-screen tutorials" were popular as app "intros" for awhile, so I called the class below IntroPages
.
Step 1, make a container view that is a UIPageViewController.
If new to iOS, here is a container view tutorial.
( Note: if you don't know how to "change" the container view to a UIPageViewController, scroll down to the section "How to change..." on that tutorial!
)
You can make the container any shape you want. As with any container view, it can be full-screen or a small part of the screen - whatever you want.
Step 2,
Make four straightforward, ordinary, view controllers which can be anything you want - images, text, tables, anything at all. (Purple in the example.)
Note that they simply sit there on your storyboard, do not link them to anything.
Step 3, you must Set the IDs of those four pages. "id1", "id2", "id3", "id4" is fine.
Step 4, copy and paste! Here's the class IntroPages,
import UIKit
class IntroPages: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let p1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id1")
let p2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id2")
let p3: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id3")
// etc ...
pages.append(p1)
pages.append(p2)
pages.append(p3)
// etc ...
setViewControllers([p1], direction: UIPageViewController.NavigationDirection.forward, animated: false, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController)-> UIViewController? {
let cur = pages.firstIndex(of: viewController)!
// if you prefer to NOT scroll circularly, simply add here:
// if cur == 0 { return nil }
var prev = (cur - 1) % pages.count
if prev < 0 {
prev = pages.count - 1
}
return pages[prev]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)-> UIViewController? {
let cur = pages.firstIndex(of: viewController)!
// if you prefer to NOT scroll circularly, simply add here:
// if cur == (pages.count - 1) { return nil }
let nxt = abs((cur + 1) % pages.count)
return pages[nxt]
}
func presentationIndex(for pageViewController: UIPageViewController)-> Int {
return pages.count
}
}
(Look at the comments - there is code for either looping or linear paging as you prefer.)
On the storyboard look at the UIPageViewController. Set the class to be IntroPages
.
That's all there is to it - you're done.
You simply set the transition style on the storyboard,
it is very likely you want "Scroll", not the other one.
You're done! And now ...
Introduction to adding UIPageControl ...
You add the UIPageControl
in the highest-level wrapper class, "Intro" in the above image example.
(So, surprisingly not in the page view controller, not in "IntroPages".)
Thus, on the storyboard, very simply drag a UIPageControl
on to "Intro".
Note! Bizarrely, in storyboards, you cannot move a UIPageControl.
When you drag a page control on to a view controller, it just sits in a fixed place. This is completely bizarre but that's how it is. Don't waste time trying to move it :)
From Intro, you will need to access the page view controller (IntroPages) in the usual way:
class Intro: UIViewController {
@IBOutlet var pageControl: UIPageControl!
var pageViewController: IntroPages!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueIntroPages" {
// if new to container views, identifier explained here:
// https://stackoverflow.com/a/23403979/294884
pageViewController = (segue.destination as! IntroPages)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.bringSubviewToFront(pageControl)
pageControl.currentPage = 0
}
Note this key line:
view.bringSubviewToFront(pageControl)
Add this function
@IBAction func userDidChangePageControl(_ sender: UIPageControl) {}
and then in storyboard click on the page control and drag valueChanged to that function.
The simplest outline version of the function is
@IBAction func userDidChangePageControl(_ sender: UIPageControl) {
pageViewController.setViewControllers(
[pageViewController.pages[newIndex]],
direction: (pageViewController.currentIndex < newIndex)
? .forward : .reverse,
animated: true, completion: nil)
}
However see this QA https://stackoverflow.com/questions/66545624 for a fuller investigation of that.
in IntroPages add the property...
var currentIndex: Int {
if let visibleViewController = viewControllers?.first,
let ci = pages.firstIndex(of: visibleViewController) {
return ci
}
else {
return 0
}
}
in IntroPages also add the following, so that, when the user swipes the screen the UIPageControl knows what to do:
func pageViewController(_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
(parent as? Intro)?.pageControl.currentPage = currentIndex
}
in IntroPages also add the following due to an Apple bug / unusual behavior:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let subViews = view.subviews
var scrollView: UIScrollView? = nil
var pageControl: UIPageControl? = nil
// maintain this code order...
for view in subViews {
if view.isKind(of: UIScrollView.self) {
scrollView = view as? UIScrollView
}
else if view.isKind(of: UIPageControl.self) {
pageControl = view as? UIPageControl
}
}
// maintain this code order...
if (scrollView != nil && pageControl != nil) {
scrollView?.frame = view.bounds
if let pageControl = pageControl {
view.bringSubviewToFront(pageControl) }
}
}
You're on the way.
Add UIPageControl to UIPageViewController via storyboard
Subclass UIPageViewController
is not the best idea. Better is to create a container view controller for UIPageViewController
that will conform to UIPageViewControllerDataSource
and UIPageViewControllerDelegate
. Using storyboard you can add to you container view controller's view a container view that load your UIPageViewController
. Get your UIPageViewController
from -prepareForSegue:sender:
method to configure.
Now you're able to manage your UIPageViewController
from container view controller. Good luck!
using the UIPageViewController in a storyboard
UPDATE: I managed to resolve this issue by making a new project in xcode using the default pagecontroller template. It used the storyboard and was easy to follow.
UIPageviewController using Storyboard crash
I found loadView() { }
declared at page ContentViewController
. However loadView()
remains empty without calling [super loadView];
. As a result from this, the view would remain nil
.
Now it appears to work after the removal of the method, loadView()
.
iOS/Swift/Storyboard: Add PageViewController using only *part* of screen?
When I start with a Single View Application, I go to Storyboard and try to drag a "Page View Controller" from the Object Library into the ViewController frame, it just "bounces back", i.e. it won't let me add the Page View Controller.
A page view controller is a view controller. Thus, to make its view occupy only part of the screen, you must obey the rules for view controllers in general: you need a custom parent view controller, and the page view controller must be its child view controller — and you must do the elaborate dance that this entails when you create the child and put its view into the interface.
To get the storyboard to do the dance for you, use a Container View and hook it by its embed segue to the page view controller:
(Still, in my opinion it is always better to learn to do the dance manually, in code, yourself.)
Using UIPageViewController in storyboards
It is fully configurable in Xcode. You'll connect the data-source
and delegate
outlets to classes that inherit the appropriate protocols (UIPageViewControllerDataSource
and UIPageViewControllerDelegate
).
But, you must do some implementation for the two protocols - usually by accessing your model's data. It is common, but not required, to have the controller for the UIPageView be handle these two protocols. As:
@interface MyPageViewController : UIPageViewController
...
@end
swift: How to create UIPageViewController?
Select your PageViewController on story board and set. Spine Location none
to Mid
. Also Check double sided
.
Modify your code as below. (Changes done on your own code)
import UIKit
class PageViewController: UIViewController {
@IBOutlet weak var PageControl: UIPageControl!
var pageViewController: UIPageViewController?
var images = ["01","02","03","04"]
var pendingIndex: Int?
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControll()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createPageViewController() {
let pageController = self.storyboard?.instantiateViewController(withIdentifier: "PageViewController") as! UIPageViewController
pageController.isDoubleSided = true
pageController.dataSource = self
pageController.delegate = self
if images.count > 1{
let firstController = getContentViewController(withIndex: 0)!
let secondController = getContentViewController(withIndex: 1)!
let contentControllers = [firstController,secondController]
pageController.setViewControllers(contentControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
}
pageViewController = pageController
self.addChildViewController(pageViewController!)
self.view.insertSubview(pageViewController!.view, at: 0)
pageViewController!.didMove(toParentViewController: self)
}
//Setup Pagination Icons and count
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return images.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
func setupPageControll(){
let apperance = UIPageControl.appearance()
apperance.pageIndicatorTintColor = UIColor.gray
apperance.currentPageIndicatorTintColor = UIColor.white
apperance.backgroundColor = UIColor.clear
}
func currentControllerIndex() -> Int{
let pageItemController = self.currentConroller()
if let controller = pageItemController as? ContentView {
return controller.itemIndex
}
return -1
}
///////////////////////////////////////////////
func currentConroller() -> UIViewController?{
if (self.pageViewController?.viewControllers?.count)! > 0{
return self.pageViewController?.viewControllers![0]
}
return nil
}
func getContentViewController(withIndex index: Int) -> ContentView? {
if index < images.count{
let contentVC = self.storyboard?.instantiateViewController(withIdentifier: "ContentViewController") as! ContentView
contentVC.itemIndex = index
//contentVC.imageName.image = self.images[index]
return contentVC
}
return nil
}
}
extension PageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
pendingIndex = (pendingViewControllers.first as! ContentView).itemIndex
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let contentVC = viewController as! ContentView
if contentVC.itemIndex > 0 {
return getContentViewController(withIndex: contentVC.itemIndex - 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let contentVC = viewController as! ContentView
if contentVC.itemIndex + 1 < images.count {
return getContentViewController(withIndex: contentVC.itemIndex + 1)
}
return nil
}
}
Github Full Project.
Related Topics
Uiactivityviewcontroller Crashing on iOS 8 Ipads
How to Load Storyboard Programmatically from Class
Change Uitableview Height Dynamically
How to Parse Json Response from Alamofire API in Swift
How to Create Local Notifications
Make Uinavigationbar Transparent
How to Wrap Text in a Uitableviewcell Without a Custom Cell
Changing Tint/Background Color of Uitabbar
React Native: How to Select the Next Textinput After Pressing the "Next" Keyboard Button
How to Know When Uitableview Did Scroll to Bottom in Iphone
Execute Action When Back Bar Button of Uinavigationcontroller Is Pressed
Reloaddata() of Uitableview With Dynamic Cell Heights Causes Jumpy Scrolling
How to Programmatically Sense the Iphone Mute Switch
Do Something Every X Minutes in Swift
Swift Saving and Retrieving Custom Object from Userdefaults
How to Convert from Degrees to Radians
How to Detect Whether a User Has an iPhone 6 Plus in Standard or Zoomed Mode