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
}
Related
I have a Page View Controller in side my View Controller, which is infinite loop scroll pageViewController.
normaly, I can tracking index of current View Controller (ContentViewController in my code) with property indexController for each view controller in pageViewController.
class ContentViewController: UIViewController {
var indexController = 0
override func viewDidLoad() {
super.viewDidLoad()
}
}
and i tracking index via 2 function with 2 property nextIndex and currentIndex
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
if let vc = pendingViewControllers[0] as? ContentViewController {
nextIndex = vc.indexController
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let prevIndex = currentIndex
if completed {
currentIndex = nextIndex
}
else {
nextIndex = currentIndex
}
}
but when i scroll to left so fast or swipe left and right continuously very fast, 2 function above does not call correctly, and currentIndex not update correctly.
what can i do? Can i prevent swipe too fast? somebody help me to solve this problem, please!
try set delegate of uiscrollview of pageviewcontroller to your view controller.
for view in pageViewController!.view.subviews {
if let scrollView = view as? UIScrollView {
scrollView.delegate = self
}
}
then get current view controller of pageViewController in this func
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {}
then update your index correctly
I have a UIPagevViewController which is used to show 4 ViewControllers. I want the dots background color to change based on the view's background color I am currently into. While I have managed to get the same background color for both the dots and the first page I fail to do so for every other one. In other words I want to dynamically change the dots background based on the view that is shown
import UIKit
class PageViewController: UIPageViewController,UIPageViewControllerDelegate, UIPageViewControllerDataSource {
//my 4 viewControllers used in the UIPageViewController
fileprivate lazy var pages : [UIViewController] = {
return [
self.getViewController(withIdentifier: "firstViewControllerID"),
self.getViewController(withIdentifier: "secondViewControllerID"),
self.getViewController(withIdentifier: "thirdViewControllerID"),
self.getViewController(withIdentifier: "differentViewController")
]
}()
//function that accesses those view controllers used above from the storyboard
fileprivate func getViewController(withIdentifier identifier: String) -> UIViewController
{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: identifier)
}
/* needed function for the UIPageViewControllerDelegate, UIPageViewControllerDataSource protocols
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//Some Code. Unnecessary for the question
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
//Some Code. Unnecessary for the question
}
*/
//function where the dots colours are set
private func setupPageControl() {
let appearance = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self])
appearance.pageIndicatorTintColor = UIColor.gray
appearance.currentPageIndicatorTintColor = UIColor.red
appearance.backgroundColor = pages[appearance.currentPage].view.backgroundColor
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
setupPageControl()
return pages.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
delegate = self
if let firstVC = pages.first {
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
setupPageControl()
}
}
}
First image->first page. Second image->second page. Look at the color of the dots background
Unfortunately you have no direct access to the page control built into a UIPageViewController. Therefore you have to use the appearance proxy in order to change its appearance (as you are already doing). But the appearance proxy only affects future instances of a thing; once the page control is already present, therefore, setting the appearance proxy will have no affect (as you have discovered).
Therefore the simplest solution is to give up on the page control built into the UIPageViewController and just use your own UIPageControl whose dots you can change directly. You will have to coordinate it with the page view controller but that is not difficult.
Another possibility is to try to get direct access to the UIPageViewController's UIPageControl. That's fragile because there is no official access, but that technique has been discussed here, as for example in this answer.
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).
Swift language
Main issue
In the image I have a green and a blue view. I am able to swipe between those two views and now I want to implement a UITabBar so I know on which view I selected. I tried UIPageControlbut I can not customize the indicators in any way. I tried creating a UITabBarController but I do not know where to put it.
First attempt
If I connect the UITabBarController with the green and blue view using segues then the indicator will not load, just the bar.
Second attempt
If I connect the UITabBarController with the pageviewcontroller or the container view it will add them into the indicator and when I connect the blue and green view after I will not be able to slide.
Last attempt(not tried it)
The only thing I have not tried is doing it programmatically inside the pageviewcontrollers.swift file. I do not really know how to implement a TabBar programmatically and I would like to ask for some help. If you want to know why I would rather have a UITabBar instead of page control indicator is because I can edit the indicators to icons instead.
Code in pageviewcontroller
import UIKit
class secondPageViewController: UIPageViewController , UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let page4: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page4")
let page5: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page5")
pages.append(page4)
pages.append(page5)
setViewControllers([page4], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let previousIndex = abs((currentIndex - 1) % pages.count)
if (previousIndex > 0)
{
return nil
}
return pages[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
let nextIndex = abs((currentIndex + 1) % pages.count)
if (nextIndex < 1)
{
return nil
}
return pages[nextIndex]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Link to answer: https://github.com/uacaps/PageMenu
Fully functional PageMenu with options.
I am developing an application for iOS using swift.
I have already set up one UIPageViewController successfully in my project, so I know how it works.
But now I'm trying to set up a second PageViewController, and it just doesn't work - it doesn't throw any errors either, it just goes directly to black screen and doesn't even show any segues
What I have tried so far:
Resetting the iOS simulator
Reindexing the xcode files
Restarting xcode
Restarting my machine
None of these things have helped.
My code:
(BTW: This code is almost the exactly same as in my working UIPageViewController, the only difference is in the variables' names)
import UIKit
class SelectDestinationViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var destinationViewControllers : [UIViewController] = []
override func viewDidLoad() {
super.viewDidLoad()
println("View did appear")
// Do any additional setup after loading the view.
self.delegate = self
self.dataSource = self
let firstView = storyboard?.instantiateViewControllerWithIdentifier("firstView") as FirstDestinationViewController
let secondView = storyboard?.instantiateViewControllerWithIdentifier("secondView") as SecondDestinationViewController
destinationViewControllers = [firstView, secondView]
for var index = 0; index < destinationViewControllers.count; ++index {
println("\(destinationViewControllers[index])")
}
let startingViewController = self.viewControllerAtIndex(0)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: {(done: Bool) in})
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("View will appear!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewControllerAtIndex(index:NSInteger) -> UIViewController {
return destinationViewControllers[index]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index = find(destinationViewControllers, viewController)!
if (index == 0) || (index == NSNotFound) {
return nil
}
index--
if index == destinationViewControllers.count {
return nil
}
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index = find(destinationViewControllers, viewController)!
if index == NSNotFound {
return nil
}
index++
if index == destinationViewControllers.count {
return nil
}
return self.viewControllerAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return destinationViewControllers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
All the prints are showing up in the console - but nothing else is happening.
Does anyone know what the problem might be?
Thank you in advance :)
Believe it or not the UIPageViewController class is actually not supposed to be viewed by itself. As with the split view controller class (but unlike the tab controller class) you need to have a master view which wraps up the page view controller.
You also need to do some inelegant tricks to get the page view controllers to load without being linked in via the storyboard. When you see code where someone has got it working you can tell they had to know way too much about UIViewController internals to get something so simple working..
It would be a bit clearer if, as with the split view controller, it showed you a standard set-up when you put it into your storyboard, but unfortunately the tab view, split view, and page view all behave very differently.