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).
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 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
Double tapping anywhere at the bottom of a page control area (where the dots are) pauses the transition from one view controller to another.
Example of double tapping:
I simplified a UIPageViewController project to demonstrate this:
Storyboard
PageViewController.swift
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages: [UIViewController] = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
pages.append(UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Page1"))
pages.append(UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Page2"))
setViewControllers([pages.first!], direction: .forward, animated: true, completion: nil)
}
// This allows the dots to appear on top of the views
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
}
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let viewControllerIndex = pages.index(of: viewController)
if viewControllerIndex == 1 {
return pages[0]
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let viewControllerIndex = pages.index(of: viewController)
if viewControllerIndex == 0 {
return pages[1]
}
return nil
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
}
The source of the problem lies in this code:
// This allows the dots to appear on top of the views
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
}
}
When this code is removed there is no partial transitions but you will now see a black bar which is also not desired.
This is a common solution I have found throughout the web and Stackoverflow to get the Page Control (dots) to show on top of the views instead of within its own bar at the bottom of the screen.
So that's all I'm looking for. A solution that:
Shows the dots on top of the views (no black bar)
No problems with transitioning.
Thanks in advance for any help!
There is very nice tutorial How to Use UIPageViewController in Swift which explains how to configure UIPageViewController
Second part of this tutorial How to Move Page Dots in a UIPageViewController which i believe is answer to your question.
You can use Container View to embed UIPageViewController in it and then you can keep any view top of the page view controller which does not scroll with UIPageViewController
I hope this helps.
Thanks, Jay.
For me the container view was the simplest solution. What I did was cover up the Page Control at the bottom of the page (dots) with a UIView that had the background color set to clear color. So I'm preventing the user from double tapping in that area. It totally feels like a hack but I honestly didn't want to implement all that custom code in that second link you posted.
Then I went to https://bugreport.apple.com and requested an Xcode Enhancement for the UIPageViewController that gives developers a checkbox to overlay the page control over the view or use the black bar. :D
Jay, thanks again for helping me on this. I didn't know you could reference the UIPageControl in the UIPageViewController but I did some research and found a way. I changed the viewDidLoad to this and it worked! No hack needed:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
setViewControllers([pages.first!], direction: .forward, animated: true, completion: nil)
let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [PageViewController.self])
pageControl.isUserInteractionEnabled = false
}
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.
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)