pageController with multiple viewControllers - ios

I'm trying to add a pagecontroller with multiple viewControllers. I've started by creating a contentView in interfacebuilder and embedded a pageController in it. Then i've added below code, but it does not seem to show anything?
let initialViewController = self.viewControllerAtIndex(0)
self.pageViewController?.setViewControllers([initialViewController], direction: .Forward, animated: false, completion: nil)
PAgeViewController Extension
var viewControllerIdentifiers = ["BottomNoLogo", "TopNoLogo"]
extension CameraViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentText = viewController.restorationIdentifier
var currentIndex = viewControllerIdentifiers.indexOf(currentText!)
if currentIndex == viewControllerIdentifiers.count - 1 {
return viewControllerAtIndex(0)
} else {
return viewControllerAtIndex(++currentIndex!)
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentText = viewController.restorationIdentifier
var currentIndex = viewControllerIdentifiers.indexOf(currentText!)
if currentIndex == 0 {
return viewControllerAtIndex(viewControllerIdentifiers.count - 1)
} else {
return viewControllerAtIndex(--currentIndex!)
}
}
func viewControllerAtIndex(index: Int) -> UIViewController {
let contentViewController = storyboard?.instantiateViewControllerWithIdentifier(viewControllerIdentifiers[index])
return contentViewController!
}
}

The page view controller really just provides the dots: you've still got to add the controllers' views as subviews of the parent view and manage the scrolling. There are lots of examples out there. See http://www.appcoda.com/uipageviewcontroller-tutorial-intro/, for example.

Related

How to optimize UIPageViewController to have re-using mechanism similar to tableView.dequeueReusableCell(withIdentifier:for:)?

So far, most of the UIPageViewController tutorial I encounter, will create UIViewController on the fly, when being swiped to desired page.
Example of UIPageViewController, without optimization
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
let dataSource = ["View Controller One", "View Controller Two", "View Controller Three", "View Controller Four"]
var currentViewControllerIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configurePageViewController()
}
func configurePageViewController() {
guard let pageViewController = storyboard?.instantiateViewController(withIdentifier: String(describing: CustomPageViewController.self)) as? CustomPageViewController else {
return
}
pageViewController.delegate = self
pageViewController.dataSource = self
addChild(pageViewController)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(pageViewController.view)
let views: [String: Any] = ["pageView": pageViewController.view]
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[pageView]-0-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[pageView]-0-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
guard let startingViewController = detailViewControllerAt(index: currentViewControllerIndex) else {
return
}
pageViewController.setViewControllers([startingViewController], direction: .forward, animated: true)
}
func detailViewControllerAt(index: Int) -> DataViewController? {
if index >= dataSource.count || dataSource.count == 0 {
return nil
}
guard let detailViewController = storyboard?.instantiateViewController(withIdentifier: String(describing: DataViewController.self)) as? DataViewController else {
return nil
}
detailViewController.index = index
detailViewController.displayText = dataSource[index]
return detailViewController
}
}
extension ViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
print ("presentationIndex \(currentViewControllerIndex)")
return currentViewControllerIndex
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
print ("presentationCount \(dataSource.count)")
return dataSource.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let dataViewController = viewController as? DataViewController
guard var currentIndex = dataViewController?.index else {
return nil
}
print ("viewControllerBefore \(currentIndex)")
if (currentIndex == 0) {
return nil
}
currentIndex -= 1
currentViewControllerIndex = currentIndex
return detailViewControllerAt(index: currentIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let dataViewController = viewController as? DataViewController
guard var currentIndex = dataViewController?.index else {
return nil
}
print ("viewControllerBefore \(currentIndex)")
if currentIndex == dataSource.count-1 {
return nil
}
currentIndex += 1
currentViewControllerIndex = currentIndex
return detailViewControllerAt(index: currentIndex)
}
}
However, I hardly see any example, on how to optimize UIPageViewController, to handle large number of pages.
Re-creating new UIViewController for every page doesn't seem like a optimal solution as it will be slow. It also take up unnecessary memory space, as most of the offscreen UIViewController is not visible.
How can we have something similar to tableView.dequeueReusableCell(withIdentifier:for:) ?
Can we re-use those previous created, currently not visible UIViewController?
Any code example, or tutorial resource on how to implement this correctly, is very much appreciated. Thank you.
Creating controllers on the fly can cause some glitches, so I typically have some array like var viewControllers: [UIViewController] where I store my pages
and implement UIPageViewControllerDataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = viewControllers.firstIndex(of: viewController), index > 0 else {
return nil
}
currentViewControllerIndex = index - 1
return viewControllers[currentViewControllerIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = viewControllers.firstIndex(of: viewController), index < viewControllers.count - 1 else {
return nil
}
currentViewControllerIndex = index + 1
return viewControllers[currentViewControllerIndex]
}
To reduce memory usage you can just deallocate view of the view controller on page change:
var currentViewControllerIndex = 0 {
didSet {
let range = currentViewControllerIndex - 1...currentViewControllerIndex + 1
for (offset, element) in viewControllers.enumerated() {
if range ~= offset {
element.loadViewIfNeeded()
} else {
element.view = nil
}
}
}
}

UITableView in UIPageViewController in NavigationController with Page Control

I have the following set up:
UINavigationController -> UIPageViewController-> UITableViewController/UIViewController/UIViewController
my UIPageViewController looks like this:
import UIKit
class DailyRoutinePageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false;
self.delegate = self
self.dataSource = self
self.navigationItem.title = getActiveUserName()
let page1: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("dailyRoutinePage1")
let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("dailyRoutinePage2")
let page3: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("dailyRoutinePage3")
pages.append(page1)
pages.append(page2)
pages.append(page3)
setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
#IBAction func buttonKontakte(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("careTaker")
self.presentViewController(vc, animated: true, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let previousIndex = abs((currentIndex - 1) % pages.count)
return pages[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let nextIndex = abs((currentIndex + 1) % pages.count)
return pages[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
this works fine. I can scroll through all pages. Now I want to add a Page Control. I can add a Page Control to the UIPageView's. But how can I add one to the UITableViewController? And when I scroll through the pages the Page Control scroll with the pages. How can I make it stand still?
An other problem: The PageViewController doesn't respect the navigation title bar. So there is content behind the Naviagtion bar at the top. How can I fix this?

UIPageViewContoller in container view with page control icon

I am writing a Swift app with three views. I have achieved this by putting a UIPageViewController inside a container and having that switch when you swipe, from another Stack Overflow article: UIPageViewController and storyboard. I would also like to have dots on the bottom indicating the page you are on. But, the UIPageControl I am using can only be placed on the view controller with the container, and the code with the current page is in the page controller's code. Here is my code:
class ContainerView : UIViewController {
#IBOutlet var myPageController: UIPageControl!
}
class MyPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let page1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "Page1ID")
let page2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "Page2ID")
let page3: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "Page3ID")
pages.append(page1)
pages.append(page2)
pages.append(page3)
setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.index(of: viewController)!
if (currentIndex == 0) {
return pages[currentIndex]
}
let previousIndex = abs((currentIndex - 1) % pages.count)
//myPageControl.currentPage = previousIndex
return pages[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.index(of: viewController)!
if (currentIndex == 2) {
return pages[currentIndex]
}
let nextIndex = abs((currentIndex + 1) % pages.count)
//myPageControl.currentPage = nextIndex
return pages[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
How could I access the parent view controller (ContainerView) from the child in the container (MyPageViewController)? I'm new to Swift/Objective-C but have programmed in other languages (C++, Python). If you need the storyboard, please ask.

Disable swipe function in UIViewController

I have a UIPageViewController with two ViewControllers in it, one of them got a NavigationController connected so i can push to another ViewController using the Push-Segue embedded in a button. The ViewController that is being pushed have the UIPageViewController datasource enabled so i can swipe from the pushed ViewController back to the first ViewController.
I want to be able to disable the swip function in the pushed ViewController so i must press on the back button to return, not swipe.
I think that my UIPageViewController is not setting the correct view in the function before and after.
Code for UIPageViewController
class upAndDownViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page2")
let topPage: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("topPage")
pages.append(page2)
pages.append(topPage)
setViewControllers([page2], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let nextIndex = abs((currentIndex + 1) % pages.count)
if (nextIndex < 1)
{
return nil
}
return pages[nextIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let previousIndex = abs((currentIndex - 1) % pages.count)
if (previousIndex > 0)
{
return nil
}
return pages[previousIndex]
}

Swift: Type 'ViewController' does not conform to protocol 'UIPageViewControllerDataSource'

I'm using Xcode 6 GM. I'm attempting to implement this page view controller tutorial but in Swift instead of Objective-C but it's not working as expected.
I've actually managed to find a git repo where someone else is doing the same, but after cloning their project and opening it in Xcode, it has the same errors I'm getting. I've managed to resolve most of them except for the protocol conformance issue when implementing the UIPageViewControllerDataSource protocol.
To be honest, I don't completely understand the usage of ? and ! in Swift and if that's causing my issue. Removing the ! from the variables in the implementation of the protocol's methods causes other errors.
Could someone please assist?
class ViewController: UIViewController, UIPageViewControllerDataSource {
var pageViewController : UIPageViewController?
var pageTitles = ["Over 200 Tips and Tricks", "Discover Hidden Features", "Bookmark Favorite Tip", "FreeRegular Update"]
var pageImages = ["page1.png", "page2.png", "page3.png", "page4.png"]
var currentIndex = 0
#IBAction func startWalkthrough(sender: UIButton) {
var startingViewController : PageContentViewController = self.viewControllerAtIndex(0)!
var viewControllers : NSArray = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: false, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
//Create page view controller
self.pageViewController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
self.pageViewController!.dataSource = self
let startingViewController : PageContentViewController = self.viewControllerAtIndex(0)!
let viewControllers: NSArray = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: false, completion: nil)
// Change the size of page view controller
self.pageViewController!.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30);
self.addChildViewController(self.pageViewController!)
self.view.addSubview(self.pageViewController!.view)
self.pageViewController!.didMoveToParentViewController(self)
}
func pageViewController(pageViewController: UIPageViewController!,
viewControllerBeforeViewController viewController: UIViewController!) -> UIViewController! {
var index = (viewController as PageContentViewController).pageIndex
if index == 0 || index == NSNotFound {
return nil
}
index!--
println("Decreasing Index: \(index)")
return self.viewControllerAtIndex(index!)
}
func pageViewController(pageViewController: UIPageViewController!,
viewControllerAfterViewController viewController: UIViewController!) -> UIViewController! {
var index = (viewController as PageContentViewController).pageIndex
if index == NSNotFound {
return nil
}
index!++
println("Increasing Index: \(index)")
if index == self.pageTitles.count {
return nil;
}
return self.viewControllerAtIndex(index!);
}
func viewControllerAtIndex(index : Int) -> PageContentViewController? {
if self.pageTitles.count == 0 || index >= self.pageTitles.count {
return nil;
}
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as PageContentViewController
pageContentViewController.imageFile = self.pageImages[index]
pageContentViewController.titleText = self.pageTitles[index]
pageContentViewController.pageIndex = index
return pageContentViewController;
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
It's because the UIPageViewControllerDataSource protocol has updated method signatures - you're using:
func pageViewController(pageViewController: UIPageViewController!, viewControllerBeforeViewController viewController: UIViewController!) -> UIViewController!
func pageViewController(pageViewController: UIPageViewController!, viewControllerAfterViewController viewController: UIViewController!) -> UIViewController!
but now they are:
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
When you're in doubt on a non conforming protocol, a Command+Click on the protocol name will bring you to the protocol declaration, where you can see if you are implementing its interface correctly

Resources