Programmatically advance UIPageViewController - Swift - ios

This is my first attempt at an IOS app, and I have no experience with swift, and a lot of the code is borrowed from the web and edited.
I am trying to create a set of slides. I go from the main Landing page to another View Controller, TestVC, that runs the slides. The landing page and the slides work. I can swipe back and forth. I am now trying to add a timer so that the slides auto advance every 5 or so seconds.
I believe that the code that needs to be run is:
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
I get an error :
test.swift:31:9: Ambiguous reference to member 'pageViewController(_:viewControllerBefore:)'.
I do not know how to interpret this error and move forward. The error is triggered in the test.swift, where a timer calls a function that tries to advance the slide. Advise is appreciated. If I am doing it wrong, please point me in the appropriate direction.
The landing page has a button, that opens a ViewController testVC. I have 2 files, test.swift and alphabetItemController.swift. The storyboard has, in addition to the landing page ViewController, a PageViewController called alphabetPVC, a ViewController called alphabetVC and a ViewController called TestVC.
Here is the code for alphabetItemController.swift ...
import UIKit
class alphabetItemController: UIViewController {
#IBOutlet weak var contentImageView2: UIImageView!
#IBOutlet weak var contentWordPn: UILabel!
var itemIndex: Int = 0
var imageName: String = ""
var wordPN: String = ""
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
contentImageView2!.image = UIImage(named: imageName)
contentWordPn!.text = wordPN
}
}
Here is the code for test.swift ...
import Foundation
import UIKit
class testItemController: UIViewController, UIPageViewControllerDataSource {
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(changeSlide), userInfo: nil, repeats: true)
//tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(goToNextPage), userInfo: nil, repeats: true)
}
func changeSlide() {
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
}
// MARK: - Variables
private var varPageVC: UIPageViewController?
private let contentTextWordPN = ["A", "B", "C", "D", "E"]
private let contentCount = 5 //TODO ADJUST THIS FOR EACH COLLECTION
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetPVC") as! UIPageViewController
pageController.dataSource = self
if contentCount > 0 {
let firstController = getItemController(itemIndex: 0)!
let startingViewControllers = [firstController]
pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
varPageVC = pageController
addChildViewController(varPageVC!)
self.view.addSubview(varPageVC!.view)
varPageVC!.didMove(toParentViewController: self)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.gray
appearance.currentPageIndicatorTintColor = UIColor.white
appearance.backgroundColor = UIColor.darkGray
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex > 0 {
return getItemController(itemIndex: itemController.itemIndex-1)
}
return nil
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex+1 < contentCount {
return getItemController(itemIndex: itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> alphabetItemController? {
if itemIndex < contentCount {
let pageItemController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetVC") as! alphabetItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = "alphabet_" + String(format: "%02d", (itemIndex + 1)) //alphabet_01
pageItemController.wordPN = contentTextWordPN[itemIndex]
return pageItemController
}
return nil
}
func presentationCountForPageViewController(varPageVC: UIPageViewController) -> Int {
return contentCount
}
func presentationIndexForPageViewController(varPageVC: UIPageViewController) -> Int {
return 0
}
func currentControllerIndex() -> Int {
let pageItemController = self.currentController()
if let controller = pageItemController as? alphabetItemController {
return controller.itemIndex
}
return -1
}
func currentController() -> UIViewController? {
if (self.varPageVC?.viewControllers?.count)! > 0 {
return self.varPageVC?.viewControllers![0]
}
return nil
}
}
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}
There is even an extension UIPageViewController, but I do not know how to call the goToNextPage function.

I ended up changing the changeSlide() function... I had the next slide function in the extension already, and only the syntax to call it was eluding me... I found some examples on SO and used them as references:
func changeSlide() {
varPageVC?.goToNextPage()
}

Related

Child ViewController Not Receiving Delegate Calls from Parent PageViewController

I'm trying to send some data with a delegate call from a Parent PageViewController to a Child ViewController. However, at the moment, this doesn't work. I have my views set up like this:
ViewController (which contains a map view, in which data needs to be passed to the ParentPageVC)
ParentPageViewController (which then needs to pass the data received to ChildTwoVC)
ChildOneViewController
ChildTwoViewController (displays the data received from parent)
Interestingly, I can send data between the children inside the parent PageViewController. I can also send data from the initial ViewController to the ParentPageViewController just fine. I just cannot send the data from the Parent TO the Child.
Here's my Parent PageViewController:
protocol ParentPageViewDelegate: class {
func viewNeedsDismiss()
}
class ParentPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, ParentPageViewDelegate, MyMapDataViewDelegate {
lazy var childViewControllers = [UIViewController]()
weak var childTwoViewDelegate: ChildTwoViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
let childOneViewController = storyboard?.instantiateViewController(withIdentifier: "ChildOneViewController") as! ChildOneViewController
let childTwoViewController = storyboard?.instantiateViewController(withIdentifier: "ChildTwoViewController") as! ChildTwoViewController
childOneViewController.childOneDelegate = childTwoViewController
childTwoViewController.parentPageViewDelegate = self
self.childTwoViewDelegate = childTwoViewController
workoutViewControllers.append(childOneViewController)
workoutViewControllers.append(childTwoViewController)
if let firstViewController = workoutViewControllers.last {
setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [StartWorkoutPageViewController.self])
pageControl.currentPageIndicatorTintColor = .orange
pageControl.pageIndicatorTintColor = .gray
}
// MARK: Delegates
// ParentPageView Delegate
func viewNeedsDismiss() {
// Works fine
print("WillDismiss")
}
// MyMapDataView Delegate
func updateStats(distance: String, rawDistance: Double, speedPace: String) {
print("Method Was Called") // Yes, the method is called.
// However nothing happens in the below line... it doesn't call 'childTwoViewDelegate'
childTwoViewDelegate?.updateStats(distance: distance, rawDistance: rawDistance, speedPace: speedPace)
}
// MARK: PageView DataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = childViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return childViewControllers.last
}
guard childViewControllers.count > previousIndex else {
return nil
}
return childViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = childViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let childViewControllersCount = childViewControllers.count
guard childViewControllersCount != nextIndex else {
return workoutViewControllers.first
}
guard childViewControllersCount > nextIndex else {
return nil
}
return childViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return childViewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = workoutViewControllers.firstIndex(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
My Child ViewController:
protocol ChildTwoViewDelegate: class {
func updateStats(distance: String, rawDistance: Double, speedPace: String)
}
class ChildTwoViewController: UIViewController, ChildTwoViewDelegate {
weak var parentPageViewDelegate: ParentPageViewDelegate?
#IBAction func closeButton(_sender: Any) {
// Works fine
parentPageViewDelegate?.viewNeedsDismiss()
}
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: Delegates
// ChildTwoViewDelegate
func updateStats(distance: String, rawDistance: Double, speedPace: String) {
// This method does not get called.
print("Method Was Called in Child")
}
}
Thank you!
I just stripped the parts out that I couldn't use and ran it in Xcode and it worked for me. I'll post the functioning part and maybe it'll help you deduce the problem. I don't see anything conspicuously wrong with your code. It may be a problem with the storyboard. I don't use storyboards so I can't comment on that.
protocol ParentPageViewDelegate: class {
func viewNeedsDismiss()
}
class ParentPageViewController: UIViewController, ParentPageViewDelegate {
// lazy var childViewControllers = [UIViewController]()
weak var childTwoViewDelegate: ChildTwoViewDelegate?
let childTwoViewController = ChildTwoViewController()
override func viewDidLoad() {
super.viewDidLoad()
// let childOneViewController = storyboard?.instantiateViewController(withIdentifier: "ChildOneViewController") as! ChildOneViewController
// let childTwoViewController = storyboard?.instantiateViewController(withIdentifier: "ChildTwoViewController") as! ChildTwoViewController
// childOneViewController.childOneDelegate = childTwoViewController
childTwoViewController.parentPageViewDelegate = self
self.childTwoViewDelegate = childTwoViewController
childTwoViewDelegate?.updateStats(distance: "test", rawDistance: 3.4, speedPace: "none")
// workoutViewControllers.append(childOneViewController)
// workoutViewControllers.append(childTwoViewController)
//
// if let firstViewController = workoutViewControllers.last {
// setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
// }
// let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [StartWorkoutPageViewController.self])
// pageControl.currentPageIndicatorTintColor = .orange
// pageControl.pageIndicatorTintColor = .gray
}
// MARK: Delegates
// ParentPageView Delegate
func viewNeedsDismiss() {
// Works fine
print("WillDismiss")
}
// MyMapDataView Delegate
func updateStats(distance: String, rawDistance: Double, speedPace: String) {
print("Method Was Called") // Yes, the method is called.
// However nothing happens in the below line... it doesn't call 'childTwoViewDelegate'
childTwoViewDelegate?.updateStats(distance: distance, rawDistance: rawDistance, speedPace: speedPace)
}
// MARK: PageView DataSource
// func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
// guard let viewControllerIndex = childViewControllers.firstIndex(of: viewController) else {
// return nil
// }
//
// let previousIndex = viewControllerIndex - 1
//
// guard previousIndex >= 0 else {
// return childViewControllers.last
// }
//
// guard childViewControllers.count > previousIndex else {
// return nil
// }
//
// return childViewControllers[previousIndex]
// }
//
// func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
// guard let viewControllerIndex = childViewControllers.firstIndex(of: viewController) else {
// return nil
// }
//
// let nextIndex = viewControllerIndex + 1
// let childViewControllersCount = childViewControllers.count
//
//// guard childViewControllersCount != nextIndex else {
//// return workoutViewControllers.first
//// }
//
// guard childViewControllersCount > nextIndex else {
// return nil
// }
//
// return childViewControllers[nextIndex]
// }
// func presentationCount(for pageViewController: UIPageViewController) -> Int {
// return childViewControllers.count
// }
// func presentationIndex(for pageViewController: UIPageViewController) -> Int {
// guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = workoutViewControllers.firstIndex(of: firstViewController) else {
// return 0
// }
//
// return firstViewControllerIndex
// }
}
protocol ChildTwoViewDelegate: class {
func updateStats(distance: String, rawDistance: Double, speedPace: String)
}
class ChildTwoViewController: UIViewController, ChildTwoViewDelegate {
weak var parentPageViewDelegate: ParentPageViewDelegate?
#IBAction func closeButton(_sender: Any) {
// Works fine
parentPageViewDelegate?.viewNeedsDismiss()
}
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: Delegates
// ChildTwoViewDelegate
func updateStats(distance: String, rawDistance: Double, speedPace: String) {
// This method does not get called.
print("Method Was Called in Child")
}
}
Let me know if this helps.

Swift - RootPageViewController, add buttons from different ViewControllers

I've got my RootPageViewController set up and all I need is to add UIButtons. I've already added the button and its function from the UIViewController which is to be displayed first.
How am I able to also add UIButtons from f.e. the third UIViewController and assign them a function?
Just like with the button from the secondViewController which is displayed first: secondViewController.buttonAdd.action = #selector(addData(_:))
If I solely use let's say firstViewController.buttonBack.action = #selector(backData(_:)) - I'll get an error saying:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I assume thats because, there's no if let firstViewController ...
But let's say I'd add:
if let firstViewController = viewControllerList.first as? TimelineViewController {
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
firstViewController.buttonBack.action = #selector(backData(_:))
}
In this case the order which view will be shown initially and which next etc. will be mixed up.
I'm open for suggestion and better approaches, I just want this to work,
RootPageViewController:
import UIKit
class RootPageViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "timelineView")
let vc2 = sb.instantiateViewController(withIdentifier: "mainView")
let vc3 = sb.instantiateViewController(withIdentifier: "addView")
return [vc1, vc2, vc3]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let secondViewController = viewControllerList[1] as? MainViewController {
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
secondViewController.buttonAdd.action = #selector(addData(_:))
}
}
#objc func addData(_ sender: Any) {
if let thirdViewController = viewControllerList.last {
self.setViewControllers([thirdViewController], direction: .forward, animated: true, completion: nil)
}
}
#objc func backData(_ sender: Any) {
let secondViewController = viewControllerList[1]
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else { return nil }
guard viewControllerList.count > previousIndex else { return nil }
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else { return nil }
guard viewControllerList.count > nextIndex else { return nil }
return viewControllerList[nextIndex]
}
}
The solution was to call this in any UIViewControllers button action:
// get parent view controller
let parentVC = self.parent as! UserDetailsPVC
// change page of PageViewController
parentVC.setViewControllers([parentVC.pages[1]], direction: .forward, animated: true, completion: nil)

Pass data in UIPageViewController between different swipe views

I am working on a project with 3 views that are accessed by swiping.
The following code implements the PageViewController I am using to handle the swiping motion:
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
lazy var orderedViewControllers : [UIViewController] = {
return [self.newVC(viewController : "ClimaVC"),
self.newVC(viewController : "WeatherVC"),
self.newVC(viewController : "ClosetVC")]
}()
var myViewControllers = [UIViewController]()
var currentIndex : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.dataSource = self
setViewControllers([orderedViewControllers[1]],
direction: .forward, animated: true, completion: nil)
self.delegate = self
}
func newVC(viewController : String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
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 orderedViewControllers.last
//Return nil to avoid swiping forever.
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
currentIndex = previousIndex
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;
guard orderedViewControllers.count != nextIndex else {
//return orderedViewControllers.first
//Return nil to avoid swiping forever.
return nil
}
guard orderedViewControllers.count > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
The main View Controller is WeatherVC. In this view, time data is retrieved and stored in an integer variable named timeOfDay.
Depending on the value of timeOfDay, the background displayed will differ. While I was able to use this variable in WeatherVC, I need to pass this variable timeOfDay to the two other view controllers ClimaVC and ClosetVC.
I tried setting up a delegate protocol but failed, explored using the different functions provided by UIPageViewControllerDelegate and it did not get me anywhere.
I would really appreciate some help on this,
Thank you very much!
It's not complicated, just assign the values at the pageviewcontroller datasources.
import UIKit
class ClimaViewController: UIViewController{
var timeOfDayClima: Int = 0
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print (timeOfDayClima)
}
}
class WeatherViewController: UIViewController{
var timeOfDayClima: Int = 0
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timeOfDayClima += 1
}
}
class ClosetViewController: UIViewController{
var timeOfDayClima: Int = 0
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print (timeOfDayClima)
}
}
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
lazy var orderedViewControllers : [UIViewController] = {
return [self.newVC(viewController : "ClimaVC"),
self.newVC(viewController : "WeatherVC"),
self.newVC(viewController : "ClosetVC")]
}()
var myViewControllers = [UIViewController]()
var currentIndex : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.dataSource = self
setViewControllers([orderedViewControllers[1]],
direction: .forward, animated: true, completion: nil)
self.delegate = self
}
func newVC(viewController : String) -> UIViewController {
return UIStoryboard(name: "Second", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//Guard = some kind of if statement.
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1;
guard previousIndex >= 0 else {
// return orderedViewControllers.last
//Return nil to avoid swiping forever.
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
currentIndex = previousIndex
if(currentIndex == 0) {
(orderedViewControllers.first as! ClimaViewController).timeOfDayClima = (orderedViewControllers[1] as! WeatherViewController).timeOfDayClima
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
//Guard = some kind of if statement.
guard let viewControllerIndex = orderedViewControllers.index(of: viewController)
else {
return nil
}
let nextIndex = viewControllerIndex + 1;
guard orderedViewControllers.count != nextIndex else {
//return orderedViewControllers.first
//Return nil to avoid swiping forever.
return nil
}
guard orderedViewControllers.count > nextIndex else {
return nil
}
currentIndex = nextIndex
if(currentIndex == orderedViewControllers.count - 1) {
(orderedViewControllers.last as! ClosetViewController).timeOfDayClima = (orderedViewControllers[1] as! WeatherViewController).timeOfDayClima
}
if(nextIndex == 0) {
let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "ClimaVC") as! ClimaViewController
secondVC.timeOfDayClima = 1
}
return orderedViewControllers[nextIndex]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Maybe you should try explaining what has failed in setting up your delegate protocol as well because it seems like it is likely the solution you are looking for. I'm writing a protocol here for reference but you have to change it according to your needs.
protocol WeatherVCDelegate {
func changedTime(timeOfDay: Int)
}
Here comes the tricky part. When you declare a delegate in WeatherVC, you need to use the delegate to call the function to relay the timeOfDay to other weatherVC. How this works is that anything that conforms to the delegate protocol will be triggered to use the function.
class WeatherVC {
weak var delegate: WeatherVCDelegate?
func functionName() {
delegate?.changedTime(timeOfDay: value)
}
Now, when you initialize weatherVC in your PageViewController, you need PageViewController to conform to weatherVC delegate.
In PageViewController...
weatherVC.delegate = self
extension PageViewController: WeatherVCDelegate {
...
}
Here is more read up about delegates if my answer is not clear enough
https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef

Best approach: Swipe between scrollable views or view controllers

I have a few screens in my app that look like this (not from my app, found it online):
One of them is an actual intro screen, like the one above and the other will display some data and an "I AGREE" button on the last view.
In all cases the user will have to swipe between the views / view controllers.
I know I can swipe between view controllers like this: Setting up UIScrollView to swipe between 3 view controllers and I can also swipe between views using this:
func respondToSwipeGesture(_ gesture: UIGestureRecognizer)
{
print("respondToSwipeGesture")
if let swipeGesture = gesture as? UISwipeGestureRecognizer
{
switch swipeGesture.direction
{
case UISwipeGestureRecognizerDirection.right:
if(isLeftToRightActive == true)
{
print("Swiped right")
moveTheMainView()
}
case UISwipeGestureRecognizerDirection.left:
if(isLeftToRightActive == false)
{
print("Swiped left")
moveTheMainView()
}
default:
break
}
}
}
func moveTheMainView()
{
print("moveTheMainView")
let mainViewContainerHeight = self.vMainViewContainer.frame.height
let mainViewContainerWidth = self.vMainViewContainer.frame.width
let mainViewContainerModifiedLeft : CGFloat = mainViewContainerWidth / 2
if(isLeftToRightActive == false)
{
print("Move the main view to the right")
let urlCRTable = Bundle.main.url(forResource: "CRCharts", withExtension: "html")
let crTableRequestObj = URLRequest(url: urlCRTable!)
self.wvCRTable.loadRequest(crTableRequestObj)
ivIndicator.image = UIImage(named: "SecondOn")
isLeftToRightActive = true
}
else
{
print("Move the main view to the left")
let urlCRTable = Bundle.main.url(forResource: "CRTable", withExtension: "html")
let crTableRequestObj = URLRequest(url: urlCRTable!)
self.wvCRTable.loadRequest(crTableRequestObj)
ivIndicator.image = UIImage(named: "FirstOn")
isLeftToRightActive = false
}
}
So I was wondering what would be the best / correct approach to do this: to use multiple view controllers or to use multiple views in the same view controller? Or is there any other way that is considered corect?
I usually do walkthroughs like this, using PageViewController. You should create scenes like this on screenshots. And classes:
class WalkthroughContentViewController: UIViewController {
#IBOutlet var headingLabel: UILabel!
#IBOutlet var contentLabel: UILabel!
#IBOutlet var contentImageView: UIImageView!
#IBOutlet var pageControl: UIPageControl!
#IBOutlet var forwardButton: UIButton!
var index = 0
var heading = ""
var imageFile = ""
var content = ""
override func viewDidLoad() {
super.viewDidLoad()
headingLabel.text = heading
contentLabel.text = content
contentImageView.image = UIImage(named: imageFile)
pageControl.currentPage = index
switch index {
case 0...1: forwardButton.setTitle("NEXT", for: .normal)
case 2: forwardButton.setTitle("DONE", for: .normal)
default: break
}
}
#IBAction func nextButtonTapped(sender: UIButton) {
switch index {
case 0...1: // Next Button
let pageViewController = parent as! WalkthroughPageViewController
pageViewController.forward(index: index)
case 2: // Done Button
UserDefaults.standard.set(true, forKey: "hasViewedWalkthrough")
// Add Quick Actions
if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
let bundleIdentifier = Bundle.main.bundleIdentifier
let shortcutItem1 = UIApplicationShortcutItem(type: "\(bundleIdentifier).OpenFavorites", localizedTitle: "Show Favorites", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "favorite-shortcut"), userInfo: nil)
let shortcutItem2 = UIApplicationShortcutItem(type: "\(bundleIdentifier).OpenDiscover", localizedTitle: "Discover Restaurants", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "discover-shortcut"), userInfo: nil)
let shortcutItem3 = UIApplicationShortcutItem(type: "\(bundleIdentifier).NewRestaurant", localizedTitle: "New Restaurant", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(type: .add), userInfo: nil)
UIApplication.shared.shortcutItems = [shortcutItem1, shortcutItem2, shortcutItem3]
}
dismiss(animated: true, completion: nil)
default: break
}
}
}
class WalkthroughPageViewController: UIPageViewController, UIPageViewControllerDataSource {
var pageHeadings = ["Personalize", "Locate", "Discover"]
var pageImages = ["foodpin-intro-1", "foodpin-intro-2", "foodpin-intro-3"]
var pageContent = ["Pin your favorite restaurants and create your own food guide",
"Search and locate your favourite restaurant on Maps",
"Find restaurants pinned by your friends and other foodies around the world"]
override func viewDidLoad() {
super.viewDidLoad()
// Set the data source to itself
dataSource = self
// Create the first walkthrough screen
if let startingViewController = contentViewController(at: 0) {
setViewControllers([startingViewController], direction: .forward, animated: true, completion: nil)
}
}
// MARK: - UIPageViewControllerDataSource Methods
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = (viewController as! WalkthroughContentViewController).index
index -= 1
return contentViewController(at: index)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = (viewController as! WalkthroughContentViewController).index
index += 1
return contentViewController(at: index)
}
// MARK: - Helper Methods
func contentViewController(at index: Int) -> WalkthroughContentViewController? {
if index < 0 || index >= pageHeadings.count {
return nil
}
// Create a new view controller and pass suitable data.
if let pageContentViewController = storyboard?.instantiateViewController(withIdentifier: "WalkthroughContentViewController") as? WalkthroughContentViewController {
pageContentViewController.imageFile = pageImages[index]
pageContentViewController.heading = pageHeadings[index]
pageContentViewController.content = pageContent[index]
pageContentViewController.index = index
return pageContentViewController
}
return nil
}
func forward(index: Int) {
if let nextViewController = contentViewController(at: index + 1) {
setViewControllers([nextViewController], direction: .forward, animated: true, completion: nil)
}
}
}
If you have more items, you should use UICollectionView, each item (with red background in your image) will be a cell.
This way the views will be reused, better performance.
You should use UIPageViewController for this approach .
How to use check : https://stackoverflow.com/a/25416608/3901620
Third Library for this

PageViewController after go to View Controller

I have a pageViewController with 3 viewControllers which is embedded in a view of a viewcontroller.
i am setting the view controllers programatically:
lazy var vcArr: [UIViewController] = {
return [self.vcInstance(name: "vc1"),
self.vcInstance(name: "vc2"),
self.vcInstance(name: "vc3"),
]
}()
i am programatically setting the number of pages to 4
let pageController = UIPageControl.appearance()
pageController.numberOfPages = 4
what i want to happen is that if the user tries to swipe right from the 3rd page it will segue to a signup viewController.
I'm currently trying to achieve this in the viewcontroller after function testing for it the next item is greater than or equal to the total count.
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
if nextIndex >= vcArr.count {
infoClick()
return nil
} else {
guard nextIndex < vcArr.count else {
return self.vcArr.last
}
guard self.vcArr.count > nextIndex else {
return nil
}
return self.vcArr[nextIndex]
}
}
with this test the way it is sometimes it stops on page 3 and then when swiped right it will segue off with my function infoClick(). however somethings it goes right off when it hits the 3rd page as you would think because its > or = the number of pages which is 3.
when i change this to just > it doesn't display anything on the third page.
I've searched heaps for a way around this but to no avail. does anyone have any ideas how to achieve this?
attaching entire class below:
import UIKit
import Parse
class pageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
#IBOutlet var createAccountBarButton: UIBarButtonItem!
lazy var vcArr: [UIViewController] = {
return [self.vcInstance(name: "vc1"),
self.vcInstance(name: "vc2"),
self.vcInstance(name: "vc3"),
]
}()
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
//---------------------------------------------- autoLogin
/// temporary location, needs to be launched during the loading screen
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
// if (PFUser.current() != nil) {
// let vc = self.storyboard!.instantiateViewController(withIdentifier: "PackViewController")
// self.present(vc, animated: true, completion: nil)
// if (PFUser.current() != nil) {
// let tbc = self.storyboard!.instantiateViewController(withIdentifier: "MyTabController") as! UITabBarController
// tbc.selectedIndex = 1
// self.present(tbc, animated: true, completion: nil)
// }
}
//---------------------------------------------- set the viewcontrollwe instance
private func vcInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
//---------------------------------------------- set the direction and first page of the page controller
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.isHidden = true
let pageController = UIPageControl.appearance()
pageController.pageIndicatorTintColor = UIColor.lightGray
pageController.currentPageIndicatorTintColor = GeneralFunctions.UIColorFromHEX(hexValue: 0xbb0d2a)
pageController.backgroundColor = UIColor.clear
pageController.bounds = CGRect(x: 0, y: 0, width: 0, height: 0)
pageController.numberOfPages = 4
//GeneralFunctions.commonButtonSettings(buttonName: signUpButton, hexValue: 0x0c1537)
self.dataSource = self
self.dataSource = self
if let firstVC = vcArr.first {
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
//---------------------------------------------- page view controller before settings
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
if previousIndex < 0 {
return nil
} else {
guard previousIndex >= 0 else {
return self.vcArr.last
}
guard self.vcArr.count > previousIndex else {
return nil
}
return self.vcArr[previousIndex]
}
}
//---------------------------------------------- page view controller after settings
public func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = vcArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
if nextIndex >= vcArr.count {
infoClick()
return nil
} else {
guard nextIndex < vcArr.count else {
return self.vcArr.last
}
guard self.vcArr.count > nextIndex else {
return nil
}
return self.vcArr[nextIndex]
}
}
//---------------------------------------------- presentation count
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
return self.vcArr.count
}
//---------------------------------------------- presentation index
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
let firstViewControllerIndex = vcArr.index(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//---------------------------------------------- launch the new view controller - still launching before swipped to 4th window sometimes
// not sure if this is the best way to do this need to look at this a bit more
func infoClick() {
let storyboard: UIStoryboard = UIStoryboard (name: "Main", bundle: nil)
let vc: SignupViewController = storyboard.instantiateViewController(withIdentifier: "SignupViewController") as! SignupViewController
let currentController = getCurrentViewController()
currentController?.present(vc, animated: false, completion: nil)
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}

Resources