Is there a way to keep the navigation bar across multiple views? - ios

So I have 2 views and when I click on "Go to second view", The navigation bar should be fixed and the only part that should be moving is the view below the Navigation Bar. Is there a possibility that swift allows this either with storyboards or programmatically?

Yes. There is.
1. Create PageViewController.swift and paste the code below there
//
// Copyright © 2018 fewlinesofcode.com All rights reserved.
//
import UIKit
class PageViewController: UIPageViewController {
fileprivate(set) var currentPageIndex: Int = 0
var pages = [UIViewController]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let firstViewController = page(at: currentPageIndex) {
setViewControllers([firstViewController], direction: .forward, animated: false, completion: { completed in })
}
}
func resetIndex() { currentPageIndex = 0 }
func page(at index: Int) -> UIViewController? {
guard index >= 0 && index < pages.count else { return nil }
return pages[index]
}
}
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let pageIndex = pages.index(of: viewController), currentPageIndex != 0 else {
return nil
}
currentPageIndex = pageIndex
currentPageIndex -= 1
return page(at: currentPageIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let pageIndex = pages.index(of: viewController) else {
return nil
}
currentPageIndex = pageIndex + 1
if currentPageIndex == pages.count {
return nil
}
return page(at: currentPageIndex)
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return currentPageIndex
}
}
extension PageViewController {
func showPrev(completion: ((Bool) -> Void)? = nil) {
guard currentPageIndex > 0 else { return }
currentPageIndex -= 1
setViewControllers([pages[currentPageIndex]], direction: .reverse, animated: true, completion: completion)
}
func showNext(completion: ((Bool) -> Void)? = nil) {
guard currentPageIndex < pages.count - 1 else { return }
currentPageIndex += 1
setViewControllers([pages[currentPageIndex]], direction: .forward, animated: true, completion: completion)
}
}
2. Subclass it like in the sample below:
class ViewController: PageViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupChildViewControllers()
}
private func setupChildViewControllers() {
let vc1 = <Instantiate your VC 1>
let vc2 = <Instantiate your VC 2>
pages = [
vc1, vc2
]
}
}
3. Setup navigation
Just embed ViewController in UINavigationController.

Each view controller has it's own navigation bar execution. The only way this would be possible is instead of moving to another view controller, you could create a subclass of UIView, and present a new UIView upon clicking that button by adding it to the subview of the base view controller.

Related

How to Reset/reload the UI Page Controller views in swift [duplicate]

This question already has answers here:
Refresh UIPageViewController - reorder pages and add new pages
(4 answers)
Closed 4 years ago.
I am looking to how to reload the UI page controller.
On a table VC I have added UIHeaderView and embedded the UIPage VC.
Page VC is loading images dynamically from firebase.
First time it is loading the right set of images but when on the table VC I selecting other cel the Page VC need to refresh its data.
How to achieve this functionality
Here is the coed I have written for PAGE VC
//-----PAGE VC CODE------
import UIKit
import Firebase
protocol ProductImagesPageVCDelegate: class
{
func setupPageController(numberOfPages: Int)
func turnPageController(to index: Int)
}
class ProductImagesPageVC: UIPageViewController {
var product: Product!
weak var pageViewControllerDelegate: ProductImagesPageVCDelegate?
struct StoryBoard {
static let productImageVC = "ProductImageVC"
}
lazy var controllers: [UIViewController] = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var controllers = [UIViewController]()
if let imageLinks = self.product.imageLinks
{
for imageLink in imageLinks
{
let productImageVC = storyboard.instantiateViewController(withIdentifier: StoryBoard.productImageVC)
controllers.append(productImageVC)
}
}
self.pageViewControllerDelegate?.setupPageController(numberOfPages: controllers.count)
return controllers
}()
override func viewDidLoad() {
super.viewDidLoad()
// if #available(iOS 11.0, *) {
// contentInsetAdjustmentBehavior = .never
// } else {
// automaticallyAdjustsScrollViewInsets = false
// }
automaticallyAdjustsScrollViewInsets = false
dataSource = self
delegate = self
self.turnToPage(index: 0)
}
func turnToPage(index: Int)
{
let controller = controllers[index]
var direction = UIPageViewControllerNavigationDirection.forward
if let currentVC = viewControllers?.first
{
guard let currentIndex = controllers.index(of: currentVC) else {return}
if currentIndex > index
{
direction = .reverse
}
}
self.configuewDisplaying(viewController: controller)
setViewControllers([controller], direction: direction, animated: true, completion: nil)
}
func configuewDisplaying(viewController: UIViewController)
{
for (index, vc) in controllers.enumerated()
{
if viewController === vc {
if let productImageVC = viewController as? ProductImageVC
{
productImageVC.imageLink = self.product.imageLinks?[index]
self.pageViewControllerDelegate?.turnPageController(to: index)
}
}
}
}
}
extension ProductImagesPageVC: UIPageViewControllerDataSource
{
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = controllers.index(of: viewController)
{
if index < controllers.count - 1
{
return controllers[index + 1]
}
}
return controllers.first
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = controllers.index(of: viewController)
{
if index > 0
{
return controllers[index - 1]
}
}
return controllers.last
}
}
extension ProductImagesPageVC: UIPageViewControllerDelegate
{
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
self.configuewDisplaying(viewController: pendingViewControllers.first as! ProductImageVC)
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if !completed
{
self.configuewDisplaying(viewController: previousViewControllers.first as! ProductImageVC)
}
}
}
Try this-
let viewControllers: [UIViewControllers] = [UIViewController()]
if let pageViewController = parentViewController as? UIPageViewController {
pageViewController.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: nil)}

UIPageController: Turning the page forward then backward quickly only updates the first page

I have the class SliderPgaeViewController: UIPageViewController with scroll transition style as follows:
class SliderPgaeViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, PlayerUpdatePageControllerDelegate {
var lastPendingIndex: Int = 0
var sliderPageDelegate: SliderPageDelegate? = nil
let playerManager = PlayerManager.getInstance()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
setViewControllers([createViewController(index: playerManager.getCurrentIndex())!], direction: .forward, animated: true, completion: nil)
lastPendingIndex = playerManager.getCurrentIndex()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return playerManager.getSongsCount()
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let vc = viewController as? PlayerImageViewController {
if (vc.index == 0){
return nil
}
return createViewController(index: vc.index! - 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let vc = viewController as? PlayerImageViewController {
if (vc.index == playerManager.getSongsCount() - 1){
return nil
}
return createViewController(index: vc.index! + 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){
if let vc = pendingViewControllers[0] as? PlayerImageViewController {
self.lastPendingIndex = vc.index!
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool) {
print("before completion : \(self.lastPendingIndex)")
if(completed){
print("completed : \(self.lastPendingIndex)")
if (viewControllers?.first as? PlayerImageViewController) != nil {
sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0)
}
}
}
private func createViewController(index i: Int) -> UIViewController?{
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlayerImageController") as! PlayerImageViewController
vc.index = i
vc.image = playerManager.getSong(index: i).image
return vc
}
...
I am using this page controller to display the thumbnail of a song in a music player. And when the user turn a page the player changes the song playing by calling sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0) in pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool)
When I'm turning the page forward and then immediately backward, the page is turning correctly (first forward then backward); however, sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0) is only being called in the forward direction.
So, if we have list of songs (A, B, C, ...) and we are currently at song A. When the user swipes forward, the thumbnail changes to B's thumbnail and the player updates the song to B. However, if the forward swipe was followed by a backward swipe quickly, then the thumbnail changes to A but the song remains B
Update:
if A has index = 0 and B has index = 1, moving A->B->A quickly will print the following:
before completion : 1
before completion : 1
completed : 1
I have added PageControllerDelegate in your code and introduce two new methods. Then implement these delegates in your UIPageViewController class and then in PlayerImageViewController class invoke both methods in viewWillAppear and viewWillDisappear. Now remove you didFinishAnimation method and write that code in viewControllerIsBeingDisplay.
For deeper knowledge review this code.
class PlayerImageViewController: UIViewController {
var index: Int?
var image: UIImage?
weak var delegate: PageControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let del = self.delegate {
del.viewControllerIsBeingHide(self)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let del = self.delegate {
del.viewControllerIsBeingDisplay(self)
}
}
}
protocol SliderPageDelegate {
func updateSong(index: Int, dir: Int)
}
protocol PageControllerDelegate {
func viewControllerIsBeingHide(_ viewController: PlayerImageViewController)
func viewControllerIsBeingDisplay(_ viewController: PlayerImageViewController)
}
class SliderPgaeViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, PageControllerDelegate {
var lastPendingIndex: Int = 0
var sliderPageDelegate: SliderPageDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
setViewControllers([createViewController(index: playerManager.getCurrentIndex())!], direction: .forward, animated: true, completion: nil)
lastPendingIndex = playerManager.getCurrentIndex()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return playerManager.getSongsCount()
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let vc = viewController as? PlayerImageViewController {
if (vc.index == 0){
return nil
}
return createViewController(index: vc.index! - 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let vc = viewController as? PlayerImageViewController {
if (vc.index == playerManager.getSongsCount() - 1){
return nil
}
return createViewController(index: vc.index! + 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){
if let vc = pendingViewControllers[0] as? PlayerImageViewController {
self.lastPendingIndex = vc.index!
}
}
private func createViewController(index i: Int) -> UIViewController?{
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlayerImageController") as! PlayerImageViewController
vc.index = i
vc.delegate = self
// vc.image = playerManager.getSong(index: i).image //Commented becoz i do not have this file
return vc
}
var previousVCIndex = 0
func viewControllerIsBeingHide(_ viewController: PlayerImageViewController) {
previousVCIndex = viewController.index!
}
func viewControllerIsBeingDisplay(_ viewController: PlayerImageViewController) {
if let del = sliderPageDelegate {
del.updateSong(index: previousVCIndex, dir: 0)
}
}
}

How do I hide the status bar on my UIPageViewController? (All view controllers)

I've searched every question/answer I can find on here, but I can't figure out how to hide the status bar for all of the view controllers in my UIPageViewController. Here's the code from my UIPageViewController class:
class TipsVC: UIPageViewController, UIPageViewControllerDelegate {
lazy var VCArr: [UIViewController] = {
return [self.VCInstance(name: "T1"),
self.VCInstance(name: "T2"),
self.VCInstance(name: "T3")]
}()
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
override public func viewDidLoad() {
super.viewDidLoad()
//self.dataSource = self
self.delegate = self
if let OB1 = VCArr.first {
setViewControllers([OB1], direction: .forward, animated: true, completion: nil)
let pageController = UIPageControl.appearance()
pageController.pageIndicatorTintColor = UIColor(red:1.00, green:0.88, blue:0.92, alpha:1.0)
pageController.currentPageIndicatorTintColor = UIColor(red:1.00, green:0.25, blue:0.51, alpha:1.0)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIScrollView {
view.frame = UIScreen.main.bounds
} else if view is UIPageControl {
view.backgroundColor = UIColor.clear
}
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?{
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return VCArr.last
}
guard VCArr.count > previousIndex else {
return nil
}
return VCArr[previousIndex]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?{
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCArr.count else {
return VCArr.first
}
guard VCArr.count > nextIndex else {
return nil
}
return VCArr[nextIndex]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int{
return VCArr.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int{
guard let OB1 = viewControllers?.first,
let OB1Index = VCArr.index(of: OB1) else {
return 0
}
return OB1Index
}
public func nextPageWithIndex(index: Int)
{
let nextVC = VCArr[index]
setViewControllers([nextVC], direction: .forward, animated: true, completion: nil)
}
}
and here's a code sample from one of my viewControllers:
class T1: UIViewController {
#IBOutlet var nextBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
UIApplication.shared.isStatusBarHidden = true
}
#IBAction func nextBtnDidPress(_ sender: Any) {
let next = self.parent as! TipsVC
next.nextPageWithIndex(index: 1)
}
}
I'm well aware of how to hide the status bar on regular view controllers, but I'm unable to get the same results when I'm using a UIPageViewController. What's going on?
To hide statusbar from particluar viewController you need to hide status bar in viewWillAppear and hide status bar in viewWillDisAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIApplication.shared.isStatusBarHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.isStatusBarHidden = false
}
viewWillAppear will call before opening particular viewController and viewWillDisAppear call after dismiss particular viewController
if you face problem with UIPageViewController use UIViewController and embed UIPageViewController inside that for reference check below code
var pageContainer: UIPageViewController!
pageContainer = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageContainer.delegate = self
pageContainer.dataSource = self
pageContainer.setViewControllers([getViewControllerAtIndex(index: 0)] as [UIViewController], direction: .forward, animated: false, completion: nil)
pageContainer.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.view.addSubview(pageContainer.view)
Hope this will help you

Swipe direction PageViewController

My problem here it's the swipe movement when I use the pageViewController implementation. I want it to go from right to left but it's going from left to right who's not naturally for the UI experience !
It was working perfectly when I was using Xcode 7 and Swift 2.2 ...
So since I copied/pasted my code and modifiying with the Xcode 8 suggestion, it has this strange behavior.
import UIKit
class FirstEventViewController: UIViewController {
// FIXME: - The swipe gesture has to be from right to left when the view appears (PageControl not showing)
var pageViewController: UIPageViewController!
var titleEvents = [String]()
var pageImages: NSArray!
override func viewDidLoad() {
super.viewDidLoad()
pageImages = NSArray(objects: "supersmashbros","spiritodjsession","thomasdelortrio")
titleEvents = ["supersmashbros","spiritodjsession","thomasdelortrio"]
initiatePgVC()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//TODO: CODE HERE
}
// MARK: - Private functions
/**
Function initializing the logic of the pageviewController
*/
func initiatePgVC() {
pageViewController = UIStoryboard(name:"Main", bundle:nil).instantiateViewController(withIdentifier: "PageViewController") as! UIPageViewController
pageViewController.dataSource = self
let startVC = viewControllerAtIndex(index: 0)
let viewControllers = NSArray(object: startVC) as! [UIViewController]
pageViewController.setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil)
//pageViewController.view.frame = CGRect(x:0, y:65, width:self.view.frame.width, height:self.view.frame.size.height - 140)
pageViewController.view.frame = self.view.bounds
addChildViewController(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParentViewController: self)
}
/**
Function associating the viewControllers
*/
func viewControllerAtIndex(index: Int)->SecondEventViewController{
if ((titleEvents.count == 0) || (index >= titleEvents.count)){
return SecondEventViewController()
}
let vc:SecondEventViewController = storyboard?.instantiateViewController(withIdentifier: "SecondEventViewController") as! SecondEventViewController
vc.imageFile = pageImages[index] as! String
vc.labelTitle = titleEvents[index]
vc.pageIndex = index
return vc
}
}
extension FirstEventViewController:UINavigationBarDelegate{
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
extension FirstEventViewController:UIPageViewControllerDataSource{
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let vc = viewController as! SecondEventViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound){
return nil
}
index = index-1
return viewControllerAtIndex(index: index)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let vc = viewController as! SecondEventViewController
var index = vc.pageIndex as Int
if (index == NSNotFound){
return nil
}
index = index+1
if (index == titleEvents.count){
return nil
}
return viewControllerAtIndex(index: index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return titleEvents.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
I once suspected the pageViewController.setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil) because it implements the direction but even if I change with reverse, nothing happens.
Have you got the changing of the indexes the wrong way around?
for after controller i'd expect the index to increase but you are decreasing it, and vice versa.
Try swapping these, so after should be + 1 and before -1
index = index+1

PageViewController current page index in Swift

I want to get current index of a pageViewController, I don't know how I get the visible pages index.
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool)
{
// How to get it?
}
You can use didFinishAnimating, and set tags to viewcontrollers. try this
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
{
if (!completed)
{
return
}
self.pageControl.currentPageIndex = pageViewController.viewControllers!.first!.view.tag //Page Index
}
Add this code to your UIPageViewController.
var pages = [UIViewController]()
var currentIndex: Int {
guard let vc = viewControllers?.first else { return 0 }
return pages.firstIndex(of: vc) ?? 0
}
In Swift 3
Override didFinishAnimating function of UIPageViewControllerDelegate like this:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
if let currentViewController = pageViewController.viewControllers![0] as? WalkthroughContentViewController {
pageControl.currentPage = currentViewController.index
}
}
}
where WalkthroughContentViewController is the UIViewController presented by UIPageViewController
Remember to keep an index variable inside the WalkthroughContentViewController. Also, in the viewDidLoad method set:
delegate = self
Swift 3: full programmatic PageViewController example to get/set the current page index without view tagging:
enum PageViewType:String {
case green = "greenView"
case blue = "blueView"
case red = "redView"
}
class MyPageViewController: UIPageViewController {
private (set) lazy var orderedViewControllers:[UIViewController] = {
return [self.newPageView(.green),
self.newPageView(.blue),
self.newPageView(.red)
]
}
var currentIndex:Int {
get {
return orderedViewControllers.index(of: self.viewControllers!.first!)!
}
set {
guard newValue >= 0,
newValue < orderedViewControllers.count else {
return
}
let vc = orderedViewControllers[newValue]
let direction:UIPageViewControllerNavigationDirection = newValue > currentIndex ? .forward : .reverse
self.setViewControllers([vc], direction: direction, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
private func newPageView(_ viewType:PageViewType) -> UIViewController {
let vc = self.storyboard?.instantiateViewController(withIdentifier: viewType.rawValue)
return vc
}
}
UIPageViewController DataSource implementation:
extension MyPageViewController:UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let previousIndex = currentIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let nextIndex = currentIndex + 1
guard orderedViewControllers.count != nextIndex else {
return nil
}
guard orderedViewControllers.count > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return orderedViewControllers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return currentIndex
}
}
First, have your UIPageViewController implement the UIPageViewControllerDataSource method presentationIndex(for pageViewController: UIPageViewController) -> Int to return the index for each of the PageViewController's ViewControllers.
Then in your delegate, access the datasource through the passed-in PageViewController:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let dataSource = pageViewController.dataSource,
let currentIndex = dataSource.presentationIndex?(for: pageViewController) else { preconditionFailure() }
}
Dont forget to set pageviewcontroller's delegate.
func createPageViewController() {
// Create page view controller
pageViewController = storyboard?.instantiateViewController(withIdentifier: "PageViewController") as? UIPageViewController
pageViewController?.delegate = self
pageViewController?.dataSource = self
let startingViewController: ChildViewController = viewControllerAtIndex(index: 0)!
let viewControllers: Array = [startingViewController]
pageViewController?.setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil)
self.addChildViewController(pageViewController!)
self.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)
self.view.addSubview((pageViewController?.view)!)
self.pageViewController?.didMove(toParentViewController: self)
}
func viewControllerAtIndex(index: Int) -> ChildViewController? {
// return nil here, if there won't be any page in pageviewcontroller
// Create a new view controller and pass suitable data.
let pageContentViewController: ChildViewController = storyboard?.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController
pageContentViewController.pageIndex = index
return pageContentViewController
}
//Also add viewControllerAfter and viewControllerBefore methods
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
self.pendingIndex = (pendingViewControllers.first as! ChildViewController).pageIndex
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
self.currentIndex = self.pendingIndex!
//Perform your task here
}
}
You can use next method:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard
completed,
let viewControllerIndex = tutorialViews.index(of: pageViewController.viewControllers!.first!) else
return
}
self.currentIndex = viewControllerIndex
}
Where tutorialViews is an array of pages (ViewControllers).
And initialize currentIndex like that:
var currentIndex = 0 {
didSet {
self.updateContentForPage(withIndex: currentIndex)
}
}
Try it..
func pageViewController(pageViewController: UIPageViewController,didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool){
guard completed else { return }
self.pageControl.currentPage = pageViewController.viewControllers!.first!.view.tag
}
Swift 4 version
Extending the ViewController class with these protocols (UIPageViewControllerDelegate, UIPageViewControllerDataSource) and adding the following functions helped me to make my page control work correctly.
class ViewController : UIPageViewControllerDelegate,UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
if let viewController = pendingViewControllers[0] as? DataViewController {
self.lastPendingViewControllerIndex = viewController.index!
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
pageControl.currentPage = self.lastPendingViewControllerIndex
if lastPendingViewControllerIndex == 4 {
self.getstartedButton.isHidden=false
} else {
self.getstartedButton.isHidden=true
}
}
}
}
Create a method within, for example, class -> WalkthroughPageViewController), such that:
/* The method takes in a page index and creates the next content view controller. If the controller can
be created, we call the built-in setViewControllers method and navigate to the next view controller.*/
func forward(index: Int) {
if let nextViewController = contentViewController(at: index + 1) {
setViewControllers([nextViewController], direction: .forward, animated: true, completion: nil)
}
}
And in the class that controls said UIPageController, which will be a controller view (class -> WalkthroughContentViewController) and that contains a following button that passes to the next page and updates the property "pageControl.currentPage" (which is itself the UIpageControl), implements:
class WalkthroughContentViewController: UIViewController {
#IBOutlet var headingLabel: UILabel!
#IBOutlet var contentLabel: UILabel!
#IBOutlet var forwardButton: UIButton!
#IBOutlet var pageControl: UIPageControl!
#IBOutlet var contentImageView: UIImageView!
var index = 0
var heading = ""
var content = ""
var imageFile = ""
override func viewDidLoad() {
super.viewDidLoad()
headingLabel.text = heading
contentLabel.text = content
contentImageView.image = UIImage(named: imageFile)
// Update the 'currentPage' property of the page control.
pageControl.currentPage = index
if case 0...1 = index {
forwardButton.setTitle("NEXT", for : .normal)
} else if case 2 = index {
forwardButton.setTitle("DONE", for : .normal)
}
}
// MARK: - Actions
#IBAction func nextButtonTapped(sender: UIButton) {
switch index {
case 0...1: /* Get the parent controller & call to the "forward" method from the 'WalkthroughPageViewController'. */
let pageViewController = parent as! WalkthroughPageViewController
pageViewController.forward(index: index)
case 2: /* Dismiss the page view controller and show the main screen of the app*/
dismiss(animated: true, completion: nil)
default: break
}
}
}
Just check apple's docs (https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit) -> Section 3 -> Step 5
You can get the current index like this
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = parent.controllers.firstIndex(of: visibleViewController)
{
parent.currentPage = index
}
}
or if you are keeping viewControllers in an array you can do this
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = orderedViewControllers.firstIndex(of: visibleViewController)
{
statisticsPageDelegate?.statisticsPageViewController(statisticsPageViewController: self, didUpdatePageIndex: index)
}
}
This is the full code
class StatisticsPageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var currentIndex: Int?
var statisticsPageDelegate: StatisticsPageViewControllerDelegate?
private var pendingIndex: Int?
required init?(coder aDecoder: NSCoder) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
statisticsPageDelegate?.statisticsPageViewController(
statisticsPageViewController: self,
didUpdatePageCount: orderedViewControllers.count
)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of:viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of:viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = orderedViewControllers.firstIndex(of: visibleViewController)
{
statisticsPageDelegate?.statisticsPageViewController(statisticsPageViewController: self, didUpdatePageIndex: index)
}
}
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newStatisticsViewController(identifier: Identifiers.PROGRAM_VIEW_CONTROLLER),
self.newStatisticsViewController(identifier: Identifiers.LOGIN_VIEW_CONTROLLER)]
}()
private func newStatisticsViewController(identifier: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: identifier)
}
}
protocol StatisticsPageViewControllerDelegate: class {
func statisticsPageViewController(statisticsPageViewController:
StatisticsPageViewController, didUpdatePageCount count: Int)
func statisticsPageViewController(statisticsPageViewController:
StatisticsPageViewController, didUpdatePageIndex index: Int)
}
Use viewDidAppear that mark page as visible and viewDidDisappear that mark page as invisible
Item ViewController:
class PageItemViewController: UIViewController {
private(set) var isVisible: Bool = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
isVisible = true
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
isVisible = false
}
}
Get index of the visible page
class PageViewController: UIPageViewController, UIPageViewControllerDelegate {
var items: [PageItemViewController] = [] {
didSet {
if let last = items.last {
setViewControllers([last], direction: .forward, animated: true, completion: nil)
}
}
}
// ...
public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if finished {
guard let currentIndex = items.firstIndex(where: { $0.isVisible }) else { return }
print(currentIndex)
}
}
}

Resources