I'm creating an onboarding app for New Patients of a physical therapy clinic. The new patient will answer questions on each view controller within the UIPageViewController and tap a button to go to the next question.
I followed a tutorial and set up my UIPageViewController PageVC along with 3 view controllers. PageVC currently changes pages by swiping, but I want to be able to navigate backward and forward through view controllers using two buttons that are subviews of PageVC itself. How do I accomplish this?
I like using UIPageViewController and want to understand it a bit more. If there is a more effective method besides using UIPageViewController to accomplish this same task I'm happy to consider it.
class PageVC: UIPageViewController, UIPageViewControllerDataSource {
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.VCInstance(name: "BodyChart"),
self.VCInstance(name: "Symptoms"),
self.VCInstance(name: "HadSurgery")]
}()
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: name)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
}
}
I think what you're looking for is here. But this is another way.
UIPageViewController has an instance method transition. The snippet below will move from the first to the second page child view controller.
pageViewController.transition(from: pageViewController.viewControllers[0], to: pageViewController.viewControllers[1], duration: 0.3, options: UIViewAnimationOptions.curveEaseInOut, animations: nil, completion: nil)
PageVC currently changes pages by swiping, but I want to be able to navigate backward and forward through view controllers using two buttons
Add your buttons to page content, then disable horizontal scrolling on PageVC's scrollview or disable views' user interaction in a mannered fashion.
Related
I trying to use UIPageViewController to move between controllers. Everything is working fine, but the only issue is that I am changing the transition style in the storyboard from page-curl to scroll. but it is not working. When I run the app and move between controllers, the animation is page-curl !!!
my code is simple, you can check it:
override func viewDidLoad() {
super.viewDidLoad()
let firstViewController = orderedViewControllers[1]
setViewControllers([firstViewController],
direction: .forward,
animated: false,
completion: nil)
self.delegate = self
self.dataSource = self
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
```
You need to change this transitionStyle while instantiating your controller
PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
I have a simple UIPageViewController with 3 pages, which are all ViewControllers embedded in NavigationControllers.
When I scroll through these pages, the page on the right has a black background behind it during the entire scroll. When the controller snaps in place, it goes back to normal.
Here's how it looks.
How do I fix this??
SOLVED: in subclass of UIPageViewController: self.view.backgroundColor = .white
Code:
lazy var viewControllerList: [UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let yesterdayVC = sb.instantiateViewController(withIdentifier: "Yesterday")
let todayVC = sb.instantiateViewController(withIdentifier: "Today")
let tomorrowVC = sb.instantiateViewController(withIdentifier: "Tomorrow")
return [yesterdayVC, todayVC, tomorrowVC]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
setViewControllers([viewControllerList[1]], 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 }
return viewControllerList[nextIndex]
}
Edit: putting a view behind NavigationControllers or PageViewController doesn't help. The color's always black no matter what the color of PageViewController background is.
Subclass the UIPageViewController and in viewDidLoad() call self.view.backgroundColor = .white. This should fix your problem.
UIPageViewController's view is transparent so the black you are seeing is the window. Thus just change the windows background color in your app delegate or scene delegate, e.g.
self.window.backgroundColor = UIColor.whiteColor;
I have a walkthrough/onboarding process when a user first uses the app. I am doing this with a Page View Controller. On the last screen I have a button which I just dragged to a tab bar view controller to create a segue. But I actually need an IBaction now for the button tap. I have tried CTRL dragging the button everywhere but the only option I get is is "Exit" instead of an action. How can I create an IBaction for this button?
Here is the page view controller code
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var pageControl = UIPageControl()
// MARK: UIPageViewControllerDataSource
lazy var orderedViewControllers: [UIViewController] = {
return [self.newVc(viewController: "sbBlue"),
self.newVc(viewController: "sbRed"),
self.newVc(viewController: "sbGreen")]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
// This sets up the first view that will show up on our page control
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
configurePageControl()
// Do any additional setup after loading the view.
}
func configurePageControl() {
// The total number of pages that are available is based on how many available colors we have.
pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = orderedViewControllers.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.white
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
self.pageControl.currentPageIndicatorTintColor = UIColor.white
self.view.addSubview(pageControl)
}
func newVc(viewController: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
// MARK: Delegate methords
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
self.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewController)!
}
// MARK: Data source functions.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
//return orderedViewControllers.last
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
//return orderedViewControllers.first
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
Since you just want to set a value in user defaults showing that the user has "seen the 3rd screen" you could:
set the value in viewDidLoad() of the 3rd VC
set the value in `prepareForSegue in the 3rd VC
set the value in viewDidLoad() of the 1st tab's VC of the tabViewController
From your comments, looks like you settled on option "A"
Make sure the page view controller in the storyboard is connected to the corresponding controller file.
I’m having trouble with a UITableviewController embedded in a UIPageViewController…the system can’t distinguish between a swipe left to change pages and a swipe to delete an item in the UITableView. This particular UITableViewController happens to be on the last page of the PageViewController, so since swiping left doesn’t really do anything (nothing to the right) can I disable PageViewController from detecting swipes when current page == 3. The tricky part is I only want to ignore swipes to the left (so that the tableView reads the left swipe as a swipe to delete), whereas I want to still detect a swipe right so that the user can navigate back to page 2.
UIPageViewController code:
class WorkoutPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
//var pageControl = UIPageControl()
var referenceToNewWorkoutKingVC: NewWorkoutKingViewController?
// MARK: UIPageViewControllerDataSource
lazy var orderedViewControllers: [UIViewController] = {
var viewControllers = [UIViewController]()
if referenceToNewWorkoutKingVC?.sessionType == goalieSessionString || referenceToNewWorkoutKingVC?.sessionType == refSessionString || referenceToNewWorkoutKingVC?.sessionType == coachSessionString || referenceToNewWorkoutKingVC?.sessionType == openSkateSessionString {
viewControllers = [self.newVc(viewController: "NewIceDataTableViewController"),
self.newVc(viewController: "NewHealthDataTableViewController")]
} else {
viewControllers = [self.newVc(viewController: "NewIceDataTableViewController"),
self.newVc(viewController: "NewHealthDataTableViewController"),
self.newVc(viewController: "NewShiftTableViewController")]
}
return viewControllers
}()
let impact = UIImpactFeedbackGenerator(style: UIImpactFeedbackGenerator.FeedbackStyle.medium)
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
// This sets up the first view that will show up on our page control
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
func newVc(viewController: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
// MARK: Delegate methods
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
referenceToNewWorkoutKingVC?.pageControl.numberOfPages = orderedViewControllers.count
referenceToNewWorkoutKingVC?.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewController)!
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
//Haptic when pages switch, code from: https://www.hackingwithswift.com/example-code/uikit/how-to-generate-haptic-feedback-with-uifeedbackgenerator
impact.prepare()
impact.impactOccurred()
}
// MARK: Data source functions.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
//return orderedViewControllers.last
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
//return orderedViewControllers.first
// Uncomment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
Try this
func managePageControllerBounces(isEnable : Bool) {
for view in pageViewController.view.subviews {
if view is UIScrollView {
let scrollView = view as? UIScrollView
scrollView?.bounces = isEnable
}
}
}
Toggle bounces as per requirement
self.manegePageControllerBounces(isEnable: false)
Hope it's help to you....
in my swift iOS app i would create a storyboard with two views. I want use the page view controller element, so, with one left / right swipe, the user can change the actual view. How can I do this?
IMPORTANT! I have searched several time for tutorials in the web. I have only found schema which allow to change images in the same image view. I don't want to change the image in the same image in the same view; i want change the entire view. I attach a image, to better explain.
Thank in advance]1
This explains how to create a paged view controller using storyboard:
Drag a UIPageViewController into storyboard and mark it as the initial view controller.
Then create a new UIPageViewController subclass - say call it TutorialPageViewController and assign it to the object you just dragged into storyboard.
Now drag in two new UIViewControllers into storyboard and create/assign a subclass for each, as you would do normally.
Give each of these two view controllers a Storyboard ID e.g RedViewController & GreenViewController.
Now add this code in your TutorialPageViewController which creates
and shows the correct view controller when you swipe:
TutorialPageViewController:
class TutorialPageViewController: UIPageViewController, UIPageViewControllerDataSource {
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("RedViewController"),
self.newColoredViewController("GreenViewController")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)ViewController")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
See the article for more details and a tutorial on this.
https://spin.atomicobject.com/2015/12/23/swift-uipageviewcontroller-tutorial/
Update: Page Dots
Read from point 7 on this part 2 article that shows how to add a UIPageControl and change the dots on swipe:
https://spin.atomicobject.com/2016/02/11/move-uipageviewcontroller-dots/
#IBOutlet weak var pageControl: UIPageControl!
func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
didUpdatePageCount count: Int) {
pageControl.numberOfPages = count
}
func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
didUpdatePageIndex index: Int) {
pageControl.currentPage = index
}