Using Page View Controller inside Tab Swift - ios

MY WORK SO FAR:
So I have a Tab bar that looks like this:
When I click on "Canteen" I want to be directed to a Page View Controller where I can swipe between different pages but stay on the same tab.
I have this somewhat working:
I have the Storyboard setup like this:
As you can see that segue above is coming from the Tab Bar Controller.
The third view (Can Page Item Controller, ID: "CanItemController) is used for all pages in the page view.
The second view above (Page View Controller, ID: "CanPageController) is used for controlling the Pages (duh)
The first view (CanteenViewController) contains all the code and makes all the connections. This is where everything goes on. The code inside this class is here:
import UIKit
class CanteenViewController: UIViewController, UIPageViewControllerDataSource {
// MARK: - Variables
private var pageViewController: UIPageViewController?
private let contentImages = ["Radar-512.png",
"dartp.png",
"roomp.png",
"abnews.png",
"canteenp.png"];
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("CanPageController") as! UIPageViewController
pageController.dataSource = self
if contentImages.count > 0 {
let firstController = getItemController(0)!
let startingViewControllers: NSArray = [firstController]
pageController.setViewControllers(startingViewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController!.didMoveToParentViewController(self)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
appearance.backgroundColor = UIColor.darkGrayColor()
}
// MARK: - UIPageViewControllerDataSource
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! CanPageItemController
if itemController.itemIndex > 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! CanPageItemController
if itemController.itemIndex+1 < contentImages.count {
return getItemController(itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> CanPageItemController? {
if itemIndex < contentImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("CanItemController") as! CanPageItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = contentImages[itemIndex]
return pageItemController
}
return nil
}
// MARK: - Page Indicator
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return contentImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
I HAVE 2 PROBLEMS:
I can't see any page indicators at all.
This comes from the following code:
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
appearance.backgroundColor = UIColor.darkGrayColor()
}
Is there a way I can add a page indicator in the storyboard and reference that programatically. That way maybe I could add constraints and have more control. I think the page indicator might be hidden behind the Tab Bar. Though constraints are also giving me issues, which leads me to problem 2
As you can see in the Item Controller, I have a UIImageView and the constraints are all set right. But when I run the app the image appears for a second (completely out of proportion) and then disappears. i.e - my constraints simply don't work properly
Question
Is my approach in general just wrong? Or is there a few little changes I can make to fix the above problems. I I've been following a tutorial (on Ray Wenderlich I think), and it all worked fine until I tried to integrate it with my Tab Bar.

Leave all above thing, just do as following.
Edited : As per Swift 5
class CanteenViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrHelp: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
var page = 0
let arrContent: [[String: Any]] = [["name" : "Title1", "icon" : "Radar-512"],
["name" : "Title2", "icon" : "dartp"],
["name" : "Title3", "icon" : "roomp"],
["name" : "Title4", "icon" : "abnews"],
["name" : "Title5", "icon" : "canteenp"]]
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Canteen"
// Do any additional setup after loading the view, typically from a nib.
self.createHelpView()
self.pageControl.backgroundColor = UIColor.clear
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
self.pageControl.currentPageIndicatorTintColor = UIColor(red: 251/255, green: 108/255, blue: 108/255, alpha: 1.0)
self.pageControl.tintAdjustmentMode = UIView.TintAdjustmentMode.dimmed
self.pageControl.numberOfPages = self.arrContent.count
self.pageControl.currentPage = 0
}
func createHelpView() {
var x = 50
var i = 0
for item in self.arrContent {
let lblTitle = UILabel(frame: CGRect(origin: CGPoint(x: CGFloat(x), y: 10), size: CGSize(width: CGFloat(self.scrHelp.frame.width-100), height: 25)))
lblTitle.autoresizingMask = UIView.AutoresizingMask.flexibleBottomMargin
lblTitle.backgroundColor = UIColor.clear
lblTitle.font = UIFont.systemFont(ofSize: 17)
lblTitle.textAlignment = NSTextAlignment.center
lblTitle.textColor = UIColor.black
lblTitle.text = item["name"] as? String //self.arrTitle[i]
self.scrHelp.addSubview(lblTitle)
let imgView = UIImageView(frame: CGRect(origin: CGPoint(x: CGFloat(x), y: 50), size: CGSize(width: CGFloat(self.scrHelp.frame.width-100), height: CGFloat(self.scrHelp.frame.height-150))))
imgView.autoresizingMask = UIView.AutoresizingMask.flexibleBottomMargin
imgView.backgroundColor = UIColor.clear
imgView.image = UIImage(named: (item["icon"] as! String))
imgView.contentMode = UIView.ContentMode.scaleAspectFit
self.scrHelp.addSubview(imgView)
x = x + Int(self.scrHelp.frame.width)
i = i + 1
}
self.scrHelp.contentSize = CGSize(width: (CGFloat(self.arrContent.count) * self.view.frame.width), height: 0)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = CGFloat(self.scrHelp.frame.width)
let fractionalPage = self.scrHelp.contentOffset.x / pageWidth
self.page = lround(CDouble(fractionalPage))
self.pageControl.currentPage = self.page
}
}
At last, add UIScrollView and UIPageControl to you storyboard and set respective outlet and constraint.

Related

Best way to position UIToolbar programmatically (with or without UIToolbarDelegate)?

I'm implementing in Playgound a segmented control underneath the navigation bar.
This seems to be a classic problem, which has been asked:
UISegmentedControl below UINavigationbar in iOS 7
Add segmented control to navigation bar and keep title with buttons
In the doc of UIBarPositioningDelegate, it says,
The UINavigationBarDelegate, UISearchBarDelegate, and
UIToolbarDelegate protocols extend this protocol to allow for the
positioning of those bars on the screen.
And In the doc of UIBarPosition:
case top
Specifies that the bar is at the top of its containing view.
In the doc of UIToolbar.delegate:
You may not set the delegate when the toolbar is managed by a
navigation controller. The default value is nil.
My current solution is as below (the commented-out code are kept for reference and convenience):
import UIKit
import PlaygroundSupport
class ViewController : UIViewController, UIToolbarDelegate
{
let toolbar : UIToolbar = {
let ret = UIToolbar()
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
// toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: 44
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
//class Toolbar : UIToolbar {
// override var barPosition: UIBarPosition {
// return .topAttached
// }
//}
let vc = ViewController()
vc.title = "Try"
vc.view.backgroundColor = .red
// Another way to add toolbar...
// let segmented = UISegmentedControl(items: ["Good", "Bad"])
// let barItem = UIBarButtonItem(customView: segmented)
// vc.toolbarItems = [barItem]
// Navigation Controller
let navVC = UINavigationController(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self)
navVC.pushViewController(vc, animated: true)
navVC.preferredContentSize = CGSize(width: 375, height: 640)
// navVC.isToolbarHidden = false
// Page setup
PlaygroundPage.current.liveView = navVC
PlaygroundPage.current.needsIndefiniteExecution = true
As you can see, this doesn't use a UIToolbarDelegate.
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
#Leo Natan's answer in the first question link above mentioned the UIToolbarDelegate, but it seems the toolbar is placed in Interface Builder.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
Try this
UIView *containerVw = [[UIView alloc] initWithFrame:CGRectMake(0, 64, 320, 60)];
containerVw.backgroundColor = UIColorFromRGB(0xffffff);
[self.view addSubview:containerVw];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 124, 320, 1)];
bottomView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomView];
UISegmentedControl *sg = [[UISegmentedControl alloc] initWithItems:#[#"Good", #"Bad"]];
sg.frame = CGRectMake(10, 10, 300, 40);
[view addSubview:sg];
for (UIView *view in self.navigationController.navigationBar.subviews) {
for (UIView *subView in view.subviews) {
[subView isKindOfClass:[UIImageView class]];
subView.hidden = YES;
}
}
By setting the toolbar's delegate and by having the delegate method return .top, you get the normal shadow at the bottom of the toolbar. If you also adjust the toolbars frame one point higher, it will cover the navbar's shadow and the final result will be what appears to be a taller navbar with a segmented control added.
class ViewController : UIViewController, UIToolbarDelegate
{
lazy var toolbar: UIToolbar = {
let ret = UIToolbar()
ret.delegate = self
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height - 1 ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: toolbar.frame.height
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .top
}
}
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
I sincerely do not know how the UIToolbarDelegate comes into play, if you change the UINavigationController.toolbar it will crashes with "You cannot set UIToolbar delegate managed by the UINavigationController manually", moreover the same will happen if you try to change the toolbar's constraint or its translatesAutoresizingMaskIntoConstraints property.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
It seems to be a reasonable question. I guess the answer for this is that you have a UIView subclass which already has the behaviour of UIToolbar, so why would we create another class-like UIToolbar, unless you just want some view below the navigation bar.
There are 2 options that I'm aware of.
1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar
The first approach might help when you have to show the toolbar in other ViewControllers that are managed by your NavigationController.
You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.
import UIKit
private var context = 0
class NavigationController: UINavigationController {
private var inToolbarFrameChange = false
var observerBag: [NSKeyValueObservation] = []
override func awakeFromNib() {
super.awakeFromNib()
self.inToolbarFrameChange = false
}
override func viewDidLoad() {
super.viewDidLoad()
observerBag.append(
toolbar.observe(\.center, options: .new) { toolbar, _ in
if !self.inToolbarFrameChange {
self.inToolbarFrameChange = true
toolbar.frame = CGRect(
x: 0,
y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
width: toolbar.frame.width,
height: toolbar.frame.height
)
self.inToolbarFrameChange = false
}
}
)
}
override func setToolbarHidden(_ hidden: Bool, animated: Bool) {
super.setToolbarHidden(hidden, animated: false)
var rectTB = self.toolbar.frame
rectTB = .zero
}
}
2) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.
import UIKit
final class ViewController: UIViewController {
private let toolbar = UIToolbar()
private let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Op 1", "Op 2"])
control.isEnabled = false
return control
}()
override func loadView() {
super.loadView()
setupToolbar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBorderLine()
}
private func setupToolbar() {
let barItem = UIBarButtonItem(customView: segmentedControl)
toolbar.setItems([barItem], animated: false)
toolbar.isTranslucent = false
toolbar.isOpaque = false
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
}
}
private extension UINavigationBar {
func showBorderLine() {
findBorderLine().isHidden = false
}
func hideBorderLine() {
findBorderLine().isHidden = true
}
private func findBorderLine() -> UIImageView! {
return self.subviews
.flatMap { $0.subviews }
.compactMap { $0 as? UIImageView }
.filter { $0.bounds.size.width == self.bounds.size.width }
.filter { $0.bounds.size.height <= 2 }
.first
}
}

Programmatically-added page control disappears when setting view controllers after setup

Problem
When tapping the skip button on page i (which calls setViewControllers(_:animated:) and transitions the user to the last page in the page view controller), and then swiping back to page i again, the page control disappears.
Wanted result
I want to programmatically add and show a custom page control on the bottom of each view controller in a page view controller when said page view controller contains different types of view controllers.
Efforts so far to resolve the issue
Adding the page control to the base view controller each time it appears.
Calling loadView() on the view controller that contains the missing page control.
Code
I have a WalkthroughRootViewController that contains a UIPageViewController. The type of the view controllers in the page view controller are two subclasses of type WalkthroughBaseViewController, the first n-1 of one type, and the last of the other. I have not included code of the last type, as that's working as far as I can see.
I have this code in WalkthroughBaseViewController:
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
The page control is added to the view in viewDidLoad():
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
If the user is on any of the first n-1 view controllers, there is a skip button the user can tap to skip forward to the last view controller. The code for this is
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
Reference
I have highlighted the code I believe is important, but here are all files related to the walkthrough of the application.
WalkthroughRootViewController
import UIKit
class WalkthroughRootViewController: UIViewController {
// MARK: Regular Properties
var walkthroughPageViewController: UIPageViewController!
var walkthroughImages = [
Images.w1,
Images.w2
]
var walkthroughStrings: [String] = [
.localized(.walkthroughTitle1),
.localized(.walkthroughZipCodeTitle)
]
// MARK: Lazy Properties
lazy var walkthroughPageViewControllerDataSource: WalkthroughPageViewControllerDataSource = {
var dataSource = WalkthroughPageViewControllerDataSource()
dataSource.walkthroughRootViewController = self
return dataSource
}()
// MARK: Computed Properties
var lastIndex: Int {
return walkthroughImages.count - 1
}
var temporaryUserInput: String?
var temporarySwitchPosition = false
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
// MARK: View Controller Life Cycle
extension WalkthroughRootViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Stop gray shadow from appearing under transition.
navigationController?.view.backgroundColor = .white
configurePageViewController()
}
}
// MARK: Helper Methods
extension WalkthroughRootViewController {
func configurePageViewController() {
walkthroughPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
walkthroughPageViewController.dataSource = walkthroughPageViewControllerDataSource
walkthroughPageViewController.delegate = walkthroughPageViewControllerDataSource
let startingViewController = storyboard!.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as! WalkthroughImageViewController
let startIndex = 0
startingViewController.delegate = self
startingViewController.pageIndex = startIndex
startingViewController.text = walkthroughStrings[startIndex]
startingViewController.image = walkthroughImages[startIndex]
startingViewController.numberOfPages = walkthroughImages.count
walkthroughPageViewController.setViewControllers([startingViewController], direction: .forward, animated: true)
walkthroughPageViewController.view.frame = view.bounds
add(walkthroughPageViewController)
}
}
extension WalkthroughRootViewController: WalkthroughDelegate {
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
}
extension WalkthroughRootViewController: WalkthrouZipCodeViewControllerDelegate {
func walkththroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userEnteredText enteredText: String) {
temporaryUserInput = enteredText
}
func walkthroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userChangedSwitchPosition position: Bool) {
temporarySwitchPosition = position
}
}
WalkthroughBaseViewController
import UIKit
protocol WalkthroughDelegate: class {
func skipWalkthrough()
}
class WalkthroughBaseViewController: UIViewController {
// MARK: Regular Properties
var pageIndex = 0
var text = ""
var delegate: WalkthroughDelegate?
var numberOfPages = 0
// Lazy Properties
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
}
// MARK: View Controller Life Cycle
extension WalkthroughBaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.silver
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
view.accessibilityIdentifier = Strings.AccessibilityIdentifiers.walkthrough
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
pageControl.currentPage = pageIndex
}
}
WalkthroughImageViewController
import UIKit
class WalkthroughImageViewController: WalkthroughBaseViewController {
// MARK: #IBOutlets
#IBOutlet weak var titleLabel: UILabel! {
didSet {
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.textColor = Colors.silver
titleLabel.numberOfLines = 0
}
}
#IBOutlet weak var skipWalkthroughButton: UIButton! {
didSet {
skipWalkthroughButton.setTitleColor(Colors.silver, for: .normal)
skipWalkthroughButton.titleLabel?.font = UIFont.preferredBoldFont(for: .body)
skipWalkthroughButton.setTitle(.localized(.skip), for: .normal)
}
}
#IBOutlet weak var imageView: UIImageView! {
didSet {
imageView.layer.shadowColor = Colors.brown.cgColor
imageView.layer.shadowOffset = CGSize(width: 0, height: 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0
imageView.clipsToBounds = false
imageView.contentMode = .scaleAspectFill
}
}
// MARK: Regular Properties
var image: UIImage?
// MARK: View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = image
titleLabel.text = text
}
}
// MARK: #IBActions
extension WalkthroughImageViewController {
#IBAction func skipWalkthrough(_ sender: UIButton) {
delegate?.skipWalkthrough()
}
}
WalkthroughPageViewControllerDataSource
import UIKit
class WalkthroughPageViewControllerDataSource: NSObject {
// MARK: Regular Properties
var walkthroughRootViewController: WalkthroughRootViewController!
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound || index == 0 {
return nil
}
index -= 1
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == walkthroughRootViewController.walkthroughImages.count {
return nil
}
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
}
extension WalkthroughPageViewControllerDataSource {
func viewController(at index: Int, storyboard: UIStoryboard) -> WalkthroughBaseViewController? {
if walkthroughRootViewController.walkthroughImages.count == 0 || index >= walkthroughRootViewController.walkthroughImages.count {
return nil
}
var viewController: WalkthroughBaseViewController?
if index == walkthroughRootViewController.lastIndex {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughZipCode) as? WalkthroughZipCodeViewController
if let viewController = viewController as? WalkthroughZipCodeViewController {
viewController.pageIndex = index
viewController.walkthroughZipCodeDelegate = walkthroughRootViewController
viewController.temporaryUserInput = walkthroughRootViewController.temporaryUserInput
viewController.temporarySwitchPosition = walkthroughRootViewController.temporarySwitchPosition
viewController.numberOfPages = walkthroughRootViewController.walkthroughImages.count
viewController.image = walkthroughRootViewController.walkthroughImages[index]
}
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as? WalkthroughImageViewController
if let viewController = viewController as? WalkthroughImageViewController {
viewController.delegate = walkthroughRootViewController
viewController.pageIndex = index
viewController.image = walkthroughRootViewController.walkthroughImages[index]
viewController.text = walkthroughRootViewController.walkthroughStrings[index]
}
}
return viewController
}
func indexOfViewController(_ viewController: WalkthroughBaseViewController) -> Int {
return viewController.pageIndex
}
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDelegate {
}
Create a single UIPageControl that you put in the WalkthroughRootViewController and update it when you navigate the pages - don't create a page control for each child.
Try not to use extensions to override methods - it can cause you trouble - see this blog entry.

move the pagecontrol dots automatically

I implement the Page controller ..automatically moving the page in swift
but my issue is the page controller dots not getting changed not indicates the page any one help me to solve this issue
here is my code
override func viewDidLoad() {
super.viewDidLoad()
UpdateCounter = 0
arrPageTitle = ["In SignUp screen user can able to input the first name, last name, emailid and password.", "After SignUp email verification link has been send to his mail then add basic profile information and sport preferences.", "In Profile setting can view profile, privacy and notifications, friends, account and championships won."];
self.pageViewController = self.storyboard?.instantiateViewController(withIdentifier: "myPageviewcontroller") as! UIPageViewController
self.pageViewController.dataSource = self
let initialContentviewcontroller = self.getViewControllerAtIndex(index: 0) as PageContentViewController
let viewcontrollers = NSArray(object: initialContentviewcontroller)
self.pageViewController.setViewControllers(viewcontrollers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRect(x: 0, y: 50, width:self.view.frame.width,height: 350)
//pagecontroller.delegate = self
pagecontroller.numberOfPages = arrPageTitle.count
pagecontroller.currentPage = 0;
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMove(toParentViewController: self)
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: (#selector(StartUpPage.advancePage)), userInfo: nil, repeats: true)
}
func getViewControllerAtIndex(index: Int) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
return self.getViewControllerAtIndex(index: index)
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int
{
return arrPageTitle.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = self.getViewControllerAtIndex(index: 0)
let index = viewController.pageIndex as Int
return index
}
func advancePage ()
{
UpdateCounter += 1
if UpdateCounter > 2 {
UpdateCounter = 0
}
var nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
if (nextviewcontroller .isEqual(nil)) {
UpdateCounter = 0
nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
}
let startingViewControllers = [nextviewcontroller]
pageViewController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
pagecontroller.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
}
Any one help me how to solve this issues when auto scroll the page ..pagecontroll dots also get moved
Thanks in advance
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = pageViewController.viewControllers?[0] as! PageContentViewController
let index = viewController.pageIndex
pagecontroller.currentPage = index
UIPageControl.appearance().pageIndicatorTintColor = UIColor.lightGray
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.red
return index
}
add this code this is delegate method for page controller
It will Work fine
Happy Code:)
try change
pagecontroller.currentPage = 0
to
pagecontroller.currentPage = UpdateCounter
You reseted the current in func AdvancedPage
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
just delete "pagecontroller.currentPage = 0" is ok.
try this (I only know objective-c codes, try to use it in swift)
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if (finished) {
YourViewControllersInPageController *childViewController = [pageViewController.viewControllers firstObject];
[self.pageControl setCurrentPage:childViewController.index];
}
}
remember set delegate
pagecontroller.delegate = self
From Another related answer
A page indicator will be visible if both methods are implemented,
transition style is 'UIPageViewControllerTransitionStyleScroll', and
navigation orientation is
'UIPageViewControllerNavigationOrientationHorizontal'. Both methods
are called in response to a 'setViewControllers:...' call, but the
presentation index is updated automatically in the case of
gesture-driven navigation.
You also need to implement below functions to set the page counts for UIPageViewController
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The number of items reflected in the page indicator.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The selected item reflected in the page indicator.
have implemented a custom page control with the help of which you can change your size , color , shape and page number very easily ... here is the class
import UIKit
import PureLayout
extension PageControl {
func setupView() {
for subview in subviews {
subview.removeFromSuperview()
}
pages.removeAll(keepingCapacity: true)
container = UIView(frame: CGRect.zero)
self.addSubview(container!)
container?.autoCenterInSuperview()
container?.autoPinEdge(toSuperviewEdge: .top)
container?.autoPinEdge(toSuperviewEdge: .bottom)
for index in 0 ..< totalPages {
let page = UIView()
container?.addSubview(page)
pages.append(page)
page.autoMatch(.width, to: .height, of: page)
page.autoPinEdge(toSuperviewEdge: .top, withInset: padding)
page.autoPinEdge(toSuperviewEdge: .bottom, withInset: padding)
if index == 0 {
page.autoPinEdge(toSuperviewEdge: .left, withInset: padding, relation: .greaterThanOrEqual)
} else if index == totalPages-1 {
page.autoPinEdge(toSuperviewEdge: .right, withInset: padding, relation: .greaterThanOrEqual)
} else {
page.autoPinEdge(.left, to: .right, of: pages[index-1], withOffset: padding)
}
}
let size = frame.height - padding * 2
let width = padding * CGFloat(totalPages+1) + size * CGFloat(totalPages)
container?.autoSetDimension(.width, toSize: width)
//layoutIfNeeded()
setNeedsDisplay()
}
}
#IBDesignable class PageControl: UIControl {
var container:UIView?
var pages = [UIView]()
#IBInspectable var currentPage:Int = 0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var allPagesColor: UIColor = Colors.greyELight {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var currentPageColor: UIColor = Colors.green {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var totalPages: Int = 3 {
didSet {
if totalPages > 0 {
setupView()
}
}
}
#IBInspectable var padding: CGFloat = 2 {
didSet {
if padding > 0 {
setupView()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
let size = frame.height - padding * 2
for (index, view) in pages.enumerated() {
view.layer.cornerRadius = size / 2
view.layer.masksToBounds = true
view.backgroundColor = (index == currentPage) ? currentPageColor: allPagesColor
}
}
}
here is the implentation
var pageControl = PageControl()
and viewDidLoad add this code
let f = CGRect(x: CGFloat(0), y: CGFloat.adjustYAxis(91), width: CGFloat.adjustXAxis(100), height: CGFloat.adjustYAxis(6))
pageControl = PageControl(frame:f)
pageControl.totalPages = 5
pageControl.tag = 716
pageControl.delegate = self
pageControl.allPagesColor = UIColor(hexString:"#afc6de")
let firstMood = self.moodsArray[0] as! MoodsModel
pageControl.currentPageColor = UIColor(hexString: firstMood.MoodBackGroundColor!)
pageControl.currentPage = 0
pageControl.padding = 10
self.pageViewController.view.addSubview(pageControl)
and change you page number in delegate methods just like that
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}

Data source methods of UIPageViewController are not called

I have the following code in my iOS app:
class BannerTableViewCell: UITableViewCell, UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController!
private var pages: [UIViewController] = []
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clearColor()
self.backgroundView?.backgroundColor = UIColor.clearColor()
self.contentView.backgroundColor = UIColor.clearColor()
self.selectionStyle = UITableViewCellSelectionStyle.None
pageViewController = UIPageViewController();
self.pageViewController.dataSource = self
pageViewController.view.frame = CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 130)
self.addSubview(pageViewController.view)
//Example data
let v1 = UIViewController()
v1.view.frame = CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 130);
v1.view.backgroundColor = UIColor.blueColor()
let v2 = UIViewController()
v2.view.frame = CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 130);
v2.view.backgroundColor = UIColor.greenColor()
pages.append(v1)
pages.append(v2)
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = pages.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard pages.count > previousIndex else {
return nil
}
return pages[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = pages.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = pages.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return pages[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
I instantiate this cell in the table view, however page view controller in this cell is always empty, and the data source methods of pageViewController are not called. Do you have any idea why they are not called?
You should use the documented initializer to instantiate the UIPageViewController:
public init(transitionStyle style: UIPageViewControllerTransitionStyle, navigationOrientation: UIPageViewControllerNavigationOrientation, options: [String : AnyObject]?)
Also the ViewControllers you create at the end of awakeFromNib can be placed into the pageViewController right away.
pageViewController.setViewControllers([v1, v2], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
A page indicator will be visible if both methods are implemented, transition style is 'UIPageViewControllerTransitionStyleScroll', and navigation orientation is 'UIPageViewControllerNavigationOrientationHorizontal'.
Both methods are called in response to a 'setViewControllers:...' call, but the presentation index is updated automatically in the case of gesture-driven navigation.
It could also be because the Page View Controller is not your Root View Controller.
You could add the following code in your segue:
let x = (UIApplication.shared.delegate as! AppDelegate).window!
x.rootViewController = yourViewController()

Disable UIPageViewController bouncing - Swift [duplicate]

This question already has answers here:
Disable UIPageViewController bounce
(16 answers)
Closed 5 years ago.
I've been making a sample app with scrolling between UIViewControllers but the point is i want to disable the bouncing effect at the end of scrolling and when going back to the first UIViewController.
here is my code :
class PageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as! UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as! UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
}
Take a look here :
I don't know if you can. UIPageController is not very customizable.
Personally, when I want to scroll between UIViewController, I prefer using a simple UIViewController, which will be a container, with an UIScrollView in it. Then I add programmatically all the controllers in the contentSize of the UIScrollView. You have to add all the controllers as child of the container.
UPDATE iOS 9
func setupDetailViewControllers() {
var previousController: UIViewController?
for controller in self.controllers {
addChildViewController(controller)
addControllerInContentView(controller, previousController: previousController)
controller.didMoveToParentViewController(self)
previousController = controller
}
}
func addControllerInContentView(controller: UIViewController, previousController: UIViewController?) {
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
// top
controller.view.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
// bottom
controller.view.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
// trailing
trailingContentViewConstraint?.active = false
trailingContentViewConstraint = controller.view.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
trailingContentViewConstraint?.active = true
// leading
let leadingAnchor = previousController?.view.trailingAnchor ?? contentView.leadingAnchor
controller.view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
// width
controller.view.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
}
PREVIOUS ANSWER
Like so :
scrollView is an IBOutlet with contraints to each edge of the ContainerViewController
class ContainerViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.bounces = false
scrollView.pagingEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLayoutSubviews() {
initScrollView()
}
func initScrollView(){
let viewController1 = storyboard?.instantiateViewControllerWithIdentifier("ViewController1") as! ViewController1
viewController1.willMoveToParentViewController(self)
viewController1.view.frame = scrollView.bounds
let viewController2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
viewController2.willMoveToParentViewController(self)
viewController2.view.frame.size = scrollView.frame.size
viewController2.view.frame.origin = CGPoint(x: view.frame.width, y: 0)
scrollView.contentSize = CGSize(width: 2 * scrollView.frame.width, height: scrollView.frame.height)
scrollView.addSubview(viewController2.view)
self.addChildViewController(viewController2)
viewController2.didMoveToParentViewController(self)
scrollView.addSubview(viewController1.view)
self.addChildViewController(viewController1)
viewController1.didMoveToParentViewController(self)
}}

Resources