Swift language preferred.
I want UIPageViewController to have three viewControllers in it(check image).
I do not know how to implement the three viewControllers into the UIPageViewController correctly, before i posted this question i have looked around on the forum for similar questions but i did not find any.
On the white viewController i have connected other viewControllers onto it, i basically just want the same swipe navigation like "Youtube" and "SnapChat".
I am very new to Xcode and if possible a bit a detailed explanation so i will understand the concept of it.(I already read the about UIPageViewController on AppleĀ“s website.)
Swift language preferred.
Thanks for helping.
There is many ways you can use it. This is how i prefer to do it.
class PagingController: UIViewController, UIPageViewControllerDataSource {
let pageController:UIPageViewController
//Initializing PageController
required init(coder aDecoder: NSCoder) {
pageController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
//Assingning the protocol inplemetation
pageController.dataSource = self
//Adding the view
if let initialViewController = viewControllerAtIndex(0) {
pageController.setViewControllers([initialViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated:false, completion: nil)
addChildViewController(pageController)
view .addSubview(self.pageController.view)
pageController.didMoveToParentViewController(self)
}
}
//Adding the next view controller
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentDay = ((viewController as! UINavigationController).viewControllers[0] as! DietDayTableViewController).currentDay
let nextDay = (currentDay + 1) % 7
return viewControllerAtIndex(nextDay)
}
//Adding the previous view controller
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentDay = ((viewController as! UINavigationController).viewControllers[0] as! DietDayTableViewController).currentDay
let previousDay = ((currentDay - 1) + 7) % 7
return viewControllerAtIndex(previousDay)
}
//A function witch gives me the right view controller for each page
func viewControllerAtIndex(index:Int) -> UINavigationController?{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let vc = storyboard.instantiateViewControllerWithIdentifier("DietDayTableViewController") as? DietDayTableViewController {
vc.currentDay = index
return UINavigationController(rootViewController: vc)
}
return nil
}
}
I add comments for help. This pageController is for the days of the week. Each page is a day and i am using the same viewController for each page. The concept of pageviewcontroller is that you have asign the previous view controller and the next of the current one.
Related
I have been trying to use a UIPageViewController in my storyboard to create a set of sliding images, but I keep getting this error:
Type 'PageViewController' does not conform to protocol 'UIPageViewControllerDataSource'
And I don't understand why.
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
//for scroll view
lazy var subViewControllers:[UIViewController] = {
return[
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Slide_1") as! ViewController_0,
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Slide_2") as! ViewController_1,
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Slide_3") as! ViewController_2
]
}()
//after viewcontroller
func pageViewController(_pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex >= subViewControllers.count - 1) {
return nil
}
return subViewControllers[currentIndex + 1]
}
//before viewcontroller
func pageViewController(_pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex <= 0) {
return nil
}
return subViewControllers[currentIndex - 1]
}
override func viewDidLoad() {
super.viewDidLoad()
//setting the initial view for the slider
setViewControllers([subViewControllers[0]],direction: .forward, animated: true, completion: nil)
}
//making style a normal slide
required init?(coder: NSCoder) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
}
There should be a space between the _ and the pageViewController parameter in your two methods that are part of the protocol (before/after pages).
The underscore denotes that the method, when called, does not need a label for that parameter.
For example:
func setBlob(_ blob: Blob) -> Bool {
}
Would look like this when it's called:
let myBlob = Blob()
setBlob(myBlob)
If it didn't have the _ there it would expect setBlob(blob: blob). It's convenient to be able to change or ignore the parameter names for the sake of code cleanliness. But since you don't have a space between the parameter label and the actual parameter name, it thinks the parameter name is _pageViewController.
There should be a red icon on the same line where the error shows and you can click that to automatically fill in the missing methods so you can easily see what is missing / incorrect. Your class has to implement all the required methods of the protocol and follow the same method signatures or else you'll get this error.
I have a login flow that has a video playing in the background, and pages of text on top of that fixed video, and the final page is the login form.
I've managed to build the page scroll, but each page has its own background and I'm kinda lost to find out how to make a view fixed with a looping video on bg, and transparent pages scrolling horizontally on top of that.
UPDATE
My storyboard is like this:
And my BootViewController is implementing page handlers.
import UIKit
class BootViewController: UIPageViewController, UIPageViewControllerDataSource {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
dataSource = self
setViewControllers([UIStoryboard(name: "Boot", bundle: nil).instantiateViewController(withIdentifier: pages[0])], direction: .forward, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
let pages = [
"IntroPageOne",
"IntroPageTwo",
"LoginController"
]
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentViewControllerIdentifier = viewController.restorationIdentifier
var previousViewController = pages.index(of: currentViewControllerIdentifier!)! - 1
if previousViewController < 0 {
previousViewController = pages.count - 1
}
let previousViewControllerIdentifier = pages[previousViewController]
return UIStoryboard(name: "Boot", bundle: nil).instantiateViewController(withIdentifier: previousViewControllerIdentifier)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentViewControllerIdentifier = viewController.restorationIdentifier
var nextViewController = pages.index(of: currentViewControllerIdentifier!)! + 1
if nextViewController < 0 {
nextViewController = 0
}
if nextViewController >= pages.count {
nextViewController = 0
}
let nextViewControllerIdentifier = pages[nextViewController]
return UIStoryboard(name: "Boot", bundle: nil).instantiateViewController(withIdentifier: nextViewControllerIdentifier)
}
}
I'm lost on how to create a subview and add (link) to my bootController.
Did you just try the following setup?
UIViewController.view
- backgroundVideoView
- pageViewController.view
- page1
- page2
...
In this you add a background video view as a bottom view in view.subviews, and then you add a view of a pageViewController above it. All that you would need is to set pageViewController.view.backgroundColor = UIColor.clear and page1.view.backgroundColor = UIColor.clear, etc., to make sure that the background is visible.
UPDATE
In your case, maybe something like this will be ok:
class BackgroundViewController: UIViewController {
fileprivate var pageViewController: BootViewController!
override func viewDidLoad() {
super.viewDidLoad()
// configure here anything with the video that you have to configure
pageViewController = UIStoryboard(name: "Boot", bundle: nil).instantiateViewController(withIdentifier: "BootViewController") as! BootViewController
self.view.addSubview(pageViewController.view)
// set the frame, or use autolayout.. I will set the frame directly since it's easier here
pageViewController.view.frame = self.view.frame
}
}
There is no "backgroundView" of the UIPageViewController. To get something similar, you will use a different view controller to which you can include the view of your UIPageViewController. Then this parent view controller can become the background, and the page controller the foreground (because it is added as a topmost subview of the viewController).
I am trying to implement a PageViewController that switches to a different ViewController when a button is pressed. I can successfully load each "child" ViewController using the setViewControllers method during viewDidLoad().
However when I call the setViewControllers method outside viewDidLoad(), it does not change the current "child" ViewController.
Can you call the method setViewControllers at any point or just once during viewDidLoad()? Also is there a way to change a variable in the pageviewcontrollers dataSource extensions?
My project consists of a entry ViewController with a "Settings" button and a ContainerView. Inside the ContainerView I have the UIPageViewController.
Code for PageViewController:
import UIKit
class PageViewController: UIPageViewController {
var slides = [SlideItem]()
var currentIndex: Int!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
initSlides()
}
override func viewDidLoad() {
super.viewDidLoad()
if let viewController = viewSlideViewController(currentIndex ?? 0){
let viewControllers = [viewController]
setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil)
}
dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewSlideViewController(_ index: Int) -> SlideViewController? {
guard let storyboard = storyboard,
let page = storyboard.instantiateViewController(withIdentifier: "SlideViewController") as? SlideViewController else {
return nil
}
page.slide = slides[index]
page.slideIndex = index
return page
}
func viewSettingsViewController(_ index: Int) -> SettingsViewController? {
guard let storyboard = storyboard,
let page = storyboard.instantiateViewController(withIdentifier: "SettingsViewController") as? SettingsViewController else {
return nil
}
return page
}
func initSlides(){
slides.append(SlideItem.init(filename: "Slide 1", comparison: false, highYield: false, videoURL: nil,
groupOrganSystem: ["A"],
groupMedicalSpecialty: ["B"]))
slides.append(SlideItem.init(filename: "Slide 2", comparison: false, highYield: false, videoURL: nil,
groupOrganSystem: ["A"],
groupMedicalSpecialty: ["B"]))
}
func sidebarCommands(button: String, state: Bool){
switch button {
case "settings":
self.setViewControllers([viewSettingsViewController(0)!], direction: .forward, animated: false, completion: nil)
print("settings")
default:
return
}
}
}
Extensions:
extension PageViewController : UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let viewController = viewController as? SlideViewController,
let index = viewController.slideIndex,
index > 0 {
return viewSlideViewController(index - 1)
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let viewController = viewController as? SlideViewController,
let index = viewController.slideIndex,
(index + 1) < slides.count {
return viewSlideViewController(index + 1)
}
return nil
}
}
You can use UIPageviewController in this way: (inheritance)
https://medium.com/how-to-swift/how-to-create-a-uipageviewcontroller-a948047fb6af
You can call setViewControllers at any point (I've been doing that).
The fact that it does not produce expected results is probably a result of bug in your code, include your code if you want to help out with that.
Thank you for your help, I figured out what I was doing wrong. I didn't have the right reference for the active PageViewController in the ContainerView.
So of course called the setViewControllers function on random PageViewController did not produce the correct results.
I found this answer explains how to correctly get a reference from a ContainerView
There is a single viewController with some data in it. After swiping left or right I want to create new viewControllers having the same UI but having different data. What is the best method to do this ? Should I be using UIPageViewController ?
Here is a solution for Swift 3 setting up a UIPageViewController programmatically.
The trick is to declare a lazy array called pages on your UIPageViewController.
In viewDidLoad using this array you can set up the viewControllers. Also, your dataSource is able to work with this array, handling the logic of changing the viewControllers. Right now, the dataSource is being implemented to continuously display the viewControllers, like a carousel. Modify it to your needs ;)
import Foundation
import UIKit
struct DisplayableData {
let title: String
let description: String
// etc...
}
class DataViewController: UIViewController {
var data: DisplayableData?
// You can add data as an initalizer parameter, depending on your design needs
init() {
// If you would create a xib for your DataViewController, than replace nib name with DataViewController to make it work
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class PageViewController: UIPageViewController {
lazy internal var pages: [DataViewController] = {
// If the UI is the same, reuse the viewController, no need to create multiple ones
// You could create a xib, and draw the UI there
let firstVC = DataViewController()
// Assign your data structure to your viewController
firstVC.data = DisplayableData(title: "first", description: "desc")
let secondVC = DataViewController()
secondVC.data = DisplayableData(title: "second", description: "desc")
let thirdVC = DataViewController()
thirdVC.data = DisplayableData(title: "third", description: "desc")
return [firstVC, secondVC, thirdVC]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
// Set your viewControllers
setViewControllers([pages.first!], direction: .forward, animated: false, completion: nil)
}
}
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
// Lets check if the viewController is the right type
guard let viewController = viewController as? DataViewController else {
fatalError("Invalid viewController type in PageViewController")
}
// Load the next one, if it is the last, load the first one
let presentedVCIndex: Int! = pages.index(of: viewController)
if presentedVCIndex + 1 > pages.count - 1 {
return pages.first
}
return pages[presentedVCIndex + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
// Lets check if the viewController is the right type
guard let viewController = viewController as? DataViewController else {
fatalError("Invalid viewController type in PageViewController")
}
// Load the previous one, if it is the first, load the last one
let presentedVCIndex: Int! = pages.index(of: viewController)
if presentedVCIndex - 1 < 0 {
return pages.last
}
return pages[presentedVCIndex - 1]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return pages.count
}
}
I have two view controllers with an id of firstController and secondController. I want to be able to slide between them using a page controller but I don't know how to do this. I have looked at tutorials but they are for making little tutorial bits at the start of your apps not the actual app.
So how would I link them up so that I can swipe between them?
Here is the code wich you need. I also searched very long to find this. It works very fine. Maybe you want to change "as?" to "as!" to force a downcast of the destination ViewControllers, but don't do it if you are planning a segue later.
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var myViewControllers = Array(count: 4, repeatedValue:UIViewController())
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let pvc = segue.destinationViewController as? UIPageViewController
pvc?.dataSource = self
let storyboard = UIStoryboard(name: "Main", bundle: nil);
var vc0 = storyboard.instantiateViewControllerWithIdentifier("Infoseite") as! UIViewController
var vc1 = storyboard.instantiateViewControllerWithIdentifier("Anmeldung2") as! UIViewController
var vc2 = storyboard.instantiateViewControllerWithIdentifier("Anmeldung3") as! UIViewController
var vc3 = storyboard.instantiateViewControllerWithIdentifier("Anmeldung4") as! UIViewController
storyboard.instantiateViewControllerWithIdentifier("Anmeldung4")
self.myViewControllers = [ vc0, vc1, vc2, vc3]
pvc?.setViewControllers([myViewControllers[1]], direction:.Forward, animated:false, completion:nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var currentIndex = find(self.myViewControllers, viewController)!+1
if currentIndex >= self.myViewControllers.count {
return nil
}
return self.myViewControllers[currentIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var currentIndex = find(self.myViewControllers, viewController)!-1
if currentIndex < 0 {
return nil
}
return self.myViewControllers[currentIndex]
}
You can simply delete vc3 and vc4. You only have to watch out that every vc3 and vc4 is deleted and that you write "Array(count: 2,"...
Use a gesture recognizer to see when the user has swiped a direction...
Won't let me post images, so take a look here.
Then change the view controller using something like this...
let secondViewController = self.storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as SecondViewController
self.navigationController.pushViewController(secondViewController, animated: true)
You could also use a segue of course. (performSegueWithIdentifier method)