Present UIPageViewController with Modal Presentation Style - ios

Currently i'm displaying modally a view controller, (embedded in in navigation) with a UIscrollView as subview. I would like to replace the scroll view with an UIPageViewController, but i cant resize the UIPageViewController neither displaying it modally. Solution, ideas?
This is the code that i have now:
UIViewController *viewController = [[UIViewController alloc] init];
UINavigationController *modalNav = [[UINavigationController alloc] initWithRootViewController:viewController];
UIBarButtonItem *dismiss = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(dismissModalViewControllerAnimated:)];
modalNav.navigationBar.topItem.leftBarButtonItem = dismiss;
modalNav.modalPresentationStyle = UIModalPresentationFormSheet;

View controllers can be embedded into one another by using the container view.
Remove your scroll view and put a container view in its place. You'll see that a view controller is embedded it in by default. Delete the embed segue, drag and drop a UIPageViewController into your storyboard and embed it in the container view.
It should look something like below.

The better way is to create a viewcontroller, add uipageviewcontrolller as its sub view.
Here is my code for Gallery Container Page. I made a push segue to Gallery Container Page so as to avoid navigation troubles (now the parent view controller for PageViewController is Gallery Container.)
import UIKit
class GalleryContainerPage: UIViewController,UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController?
private let contentImages = ["person-icon.jpg",
"3807343799880.jpg","4701365629867.jpg","4713011921017.jpg"]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBarHidden = false
// Do any additional setup after loading the view.
}
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") 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!)
pageViewController!.didMoveToParentViewController(self)
self.view.addSubview(pageViewController!.view)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
appearance.backgroundColor = UIColor.darkGrayColor()
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! PageItemController
if itemController.itemIndex > 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! PageItemController
if itemController.itemIndex+1 < contentImages.count {
return getItemController(itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> PageItemController? {
if itemIndex < contentImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as! PageItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = contentImages[itemIndex]
return pageItemController
}
return nil
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return contentImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
setupPageControl()
createPageViewController()
}
}

Related

UIPageViewController viewController disappears after transition

I've embedded a UIPageViewController in a UINavigationController, which in turn is embedded in a UITabBarController. I'm simply trying to make it so that the pageViewController loops through its viewControllers that are stored in an array. However every time I try to move to the next page, the first viewController snaps back into place before disappearing.
I've made the first viewController red and the second one blue and oddly enough when loading them in I'm presented with the second viewController.
This gif shows what I mean
I've tried to set up a pageViewController in the same manner in a new project and everything worked as expected so I can't see where the problem is.
import UIKit
final internal class TabBarController: UITabBarController, ApplicationLoginDelegate {
private let newsFeedTableViewController: NewsFeedTableViewController = NewsFeedTableViewController(style: UITableViewStyle.grouped)
private let substitutionPlanTableViewController: SubstitutionPlanTableViewController = SubstitutionPlanTableViewController(style: UITableViewStyle.grouped)
private let loginTableViewController: LoginTableViewController = LoginTableViewController(style: UITableViewStyle.grouped)
private let timeTableViewController: TimeTablePageViewController = TimeTablePageViewController(transitionStyle: UIPageViewControllerTransitionStyle.scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.horizontal, options: nil)
private let moreTableViewController: MoreTableViewController = MoreTableViewController(style: UITableViewStyle.grouped)
//
// MARK: - Override point
//
/**
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
*/
override func viewDidLoad() {
super.viewDidLoad()
self.setUpTabBar()
}
/**
Notifies the view controller that its view is about to be added to a view hierarchy.
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.
For more information about the how views are added to view hierarchies by a view controller, and the sequence of messages that occur, see Supporting Accessibility.
*/
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if UIApplication.boolForKey(UserDefaultKey.openSubstitutionPlanOnStartup) == true {
self.selectedViewController = self.viewControllers?[1]
}
}
//
// MARK: - Functions
//
private func setUpTabBar() {
// General
self.tabBar.tintColor = UIColor.applicationBaseColor
self.tabBar.unselectedItemTintColor = UIColor.lightGray
self.tabBar.backgroundColor = UIColor.white
// Create tab bar items
let newsFeedTabBarItem: UITabBarItem = UITabBarItem(title: "Aktuelles", image: #imageLiteral(resourceName: "News"), tag: 0)
let substitutionTabBarItem: UITabBarItem = UITabBarItem(title: "Vertretungen", image: #imageLiteral(resourceName: "SubstitutionPlan"), tag: 1)
let timeTableTabBarItem: UITabBarItem = UITabBarItem(title: "Stundenplan", image: #imageLiteral(resourceName: "TimeTable"), tag: 2)
let moreTabBarItem: UITabBarItem = UITabBarItem(title: "Entdecken", image: #imageLiteral(resourceName: "MoreMenu"), tag: 3)
// Link items and controllers
self.newsFeedTableViewController.tabBarItem = newsFeedTabBarItem
self.substitutionPlanTableViewController.tabBarItem = substitutionTabBarItem
self.loginTableViewController.tabBarItem = substitutionTabBarItem
self.timeTableViewController.tabBarItem = timeTableTabBarItem
self.moreTableViewController.tabBarItem = moreTabBarItem
// Set delegates
self.loginTableViewController.delegate = self
// Set tab bar view controllers
var viewControllers: [UIViewController] = []
if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == true {
viewControllers = [newsFeedTableViewController, substitutionPlanTableViewController, timeTableViewController, moreTableViewController]
} else {
viewControllers = [newsFeedTableViewController, timeTableViewController, moreTableViewController]
}
self.viewControllers = viewControllers.map({ (controller) -> UIViewController in
controller.navigationItem.largeTitleDisplayMode = .always
let navigationController = UINavigationController(rootViewController: controller)
navigationController.navigationBar.prefersLargeTitles = true
return navigationController
})
if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == false {
self.viewControllers?.insert(self.loginTableViewController, at: 1)
}
}
}
The UITabBarController and the UIPageViewController:
class TimeTablePageViewController: UIPageViewController, UIPageViewControllerDataSource {
private var timeTableViewControllers: [UIViewController]!
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)
self.timeTableViewControllers[0].view.backgroundColor = .red
self.timeTableViewControllers[1].view.backgroundColor = .blue
self.setViewControllers([self.timeTableViewControllers[0]], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = self.timeTableViewControllers.index(of: viewController) {
if viewController == self.timeTableViewControllers.first {
return self.timeTableViewControllers.last
} else {
return self.timeTableViewControllers[index - 1]
}
} else {
return nil
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = self.timeTableViewControllers.index(of: viewController) {
if viewController == self.timeTableViewControllers.last {
return self.timeTableViewControllers.first
} else {
return self.timeTableViewControllers[index + 1]
}
} else {
return nil
}
}
}
The repeatedValue parameter of Array.init(repeating repeatedValue: Array.Element, count: Int) is not a closure. It's a single object that will be used to fill the array.
The code won't call UIViewController() for each element it creates. You are creating an array that contains the same UIViewController instance two times. A view can't have two superViews, so when you scroll to the second page, the UIPageViewController adds the view of the only viewController to its view, which means that it will be removed from its view as well.
Replace
self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)
with
self.timeTableViewControllers = [UIViewController(), UIViewController()]

Tap UITabBarItem To Scroll To Top Like Instagram and Twitter

I'm having issues making this feature work and i would like to get some help.
My hierarchy is TabBarController -> Navigation Controller -> TableViewController
What i want is if you are on current tab and you scrolled down you will be able to tap the current View's UITabBarItem and you will be scrolled back to the top,Like Instagram and Twitter does for example.
I have tried many things right here :
Older Question
but sadly non of the answers did the job for me.
I would really appreciate any help about this manner ,
Thank you in advance!
Here is my TableView`controller's Code :
import UIKit
class BarsViewController: UITableViewController,UISearchResultsUpdating,UISearchBarDelegate,UISearchDisplayDelegate,UITabBarControllerDelegate{
//TableView Data & non related stuff....
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.searchController.searchBar.resignFirstResponder()
self.searchController.searchBar.endEditing(true)
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = IndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? BarsViewController
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
TabBarController.Swift Code ( Code doesn't work ) :
import UIKit
class TabBarController: UITabBarController,UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
Here you go, this should work:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? ZBNavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
and then...
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
// Changed this
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
There's a bit extra in this function so that if the UIViewController is already at the top it will pop to the previous controller
Try this code in your TabViewController:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
Also, your TabViewController should inherit from UITabBarControllerDelegate
final code:
import UIKit
class tabViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
let indexPath = NSIndexPath(row: 0, section: 0)
let navigVC = viewController as? UINavigationController
let finalVC = navigVC?.viewControllers[0] as? YourVC
finalVC?.tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
}
}
Remember to change tabBarIndex and set self.delegate = self in viewDidLoad
You have just to create a file TabBarViewController with this code :
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let viewControllers = viewControllers else { return false }
if viewController == viewControllers[selectedIndex] {
if let nav = viewController as? UINavigationController {
guard let topController = nav.viewControllers.last else { return true }
if !topController.isScrolledToTop {
topController.scrollToTop()
return false
} else {
nav.popViewController(animated: true)
}
return true
}
}
return true
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
var isScrolledToTop: Bool {
if self is UITableViewController {
return (self as! UITableViewController).tableView.contentOffset.y == 0
}
for subView in view.subviews {
if let scrollView = subView as? UIScrollView {
return (scrollView.contentOffset.y == 0)
}
}
return true
}
}
And then in your storyboard, set custom class TabBarController like this :
Great examples! I've tried some things also and I guess this small piece of code for our delegate is all we need:
extension AppDelegate: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// Scroll to top when corresponding view controller was already selected and no other viewcontrollers are pushed
guard tabBarController.viewControllers?[tabBarController.selectedIndex] == viewController,
let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1,
let topViewController = navigationController.viewControllers.first,
let scrollView = topViewController.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView else {
return true
}
scrollView.scrollRectToVisible(CGRect(origin: .zero, size: CGSize(width: 1, height: 1)), animated: true)
return false
}
}
I just had to implement this and was surprised to find out it wasn't as easy as I thought it would be. This is how I implemented it:
A few notes on my application's setup
The MainTabBarcontroller is our root view controller and we generally access it from the UISharedApplication.
Our navigation structure is MainTabBarController -> Nav Controller -> Visible View Controller
The main issue I found with implementing this is I only wanted to scroll to the top if I was already on the first screen in the tab bar but once you try to make this check from tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) you've already lost a reference to the view controller that was visible before you tapped it. It is also a problem to compare view controllers when you're switching tabs since the visible view controller might be in another tab.
So I created a property in my TabBar class to hold a reference to the previousTopVC and created a protocol to help me set this property from the currently visible VC.
protocol TopScreenFindable {
func setVisibleViewController()
}
extension TopScreenFindable where Self: UIViewController {
func setVisibleViewController() {
guard let tabController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }
tabController.previousTopVC = self
}
}
I then called conformed to this protocol and setVisibleViewController from the View Controllers' viewDidAppear and now had a reference to the previous visible VC when any screen showed up.
I created a delegate for my MainTabBarController class
protocol MainTabBarDelegate: class {
func firstScreenShouldScrollToTop()
}
Then called it from the UITabBarControllerDelegate method
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let navController = viewController as? UINavigationController, let firstVC = navController.viewControllers.first, let previousVC = previousTopController else { return }
if firstVC == previousVC {
mainTabBarDelegate?.firstScreenShouldScrollToTop()
}
}
And in the First View Controllers conformed to the MainTabBarDelegate
extension FirstViewController: MainTabBarDelegate {
func firstScreenShouldScrollToTop() {
collectionView.setContentOffset(CGPoint(x: 0, y: collectionView.contentInset.top), animated: true)
}
I set the tabBar.mainTabBarDelegate = self on the FirstViewController's viewDidAppear so that the delegate was set every time the screen showed up. If I did it on viewDidLoad it wouldn't work when switching tabs.
A couple things I didn't like about my approach.
Having a reference to the tab bar in my view controllers just so it can set itself as its delegate.
Making every relevant screen in the app conform to the TopScreenFindable protocol

Duplicate view controllers when using nav controllers inside page view controller

Courtesy of Using UIPageViewController with swift and multiple view controllers, I'm embedding navigation controllers in a UIPageViewController so I can horizontally scroll thru them (like swipe nav). Problem:
When I reach the first or last nav controller, and then swipe in the opposite direction, the 2nd-to-first/last nav controller will duplicate. So I'll have 5 nav controllers. For example:
Starting at FirstNav, swipe left all the way to FourthNav. When I swipe right through the array of controllers from FourthNav, the sequence will be: ThirdNav, ThirdNav, SecondNav, FirstNav. Can anyone find out what's going on?
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var index = 0
var identifiers: NSArray = ["FirstNav", "SecondNav", "ThirdNav", "FourthNav"]
override func viewDidLoad() {
super.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! {
if index == 0 {
return self.storyboard!.instantiateViewControllerWithIdentifier("FirstNav") as! UINavigationController
}
if index == 1 {
return self.storyboard!.instantiateViewControllerWithIdentifier("SecondNav") as! UINavigationController
}
if index == 2 {
return self.storyboard!.instantiateViewControllerWithIdentifier("ThirdNav") as! UINavigationController
}
if index == 3 {
return self.storyboard!.instantiateViewControllerWithIdentifier("FourthNav") 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)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
The issue has to do with your calculation of the index variable in your pageViewController(_:viewControllerBeforeViewController:) and pageViewController(_:viewControllerAfterViewController:) methods. To simplify that logic and the overall logic of your page view controller, you should change your implementation to the following:
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
private var pages: [UIViewController]!
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
self.pages = [
self.storyboard!.instantiateViewControllerWithIdentifier("FirstNav") as! UINavigationController,
self.storyboard!.instantiateViewControllerWithIdentifier("SecondNav") as! UINavigationController,
self.storyboard!.instantiateViewControllerWithIdentifier("ThirdNav") as! UINavigationController,
self.storyboard!.instantiateViewControllerWithIdentifier("FourthNav") as! UINavigationController
]
let startingViewController = self.pages.first! as UIViewController
self.setViewControllers([startingViewController], direction: .Forward, animated: false, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index = (self.pages as NSArray).indexOfObject(viewController)
// if currently displaying last view controller, return nil to indicate that there is no next view controller
return (index == self.pages.count - 1 ? nil : self.pages[index + 1])
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = (self.pages as NSArray).indexOfObject(viewController)
// if currently displaying first view controller, return nil to indicate that there is no previous view controller
return (index == 0 ? nil : self.pages[index - 1])
}
}
You don't even need to maintain an index instance variable, and this implementation does not do so. But you could, if you wanted. This solution also instantiates a single instance of each UINavigationController instead of instantiating a new one every time the user attempts to scroll to a different page, which conserves memory and preserves the state of the view controllers as the user scrolls between them.
Please excuse the non-descriptive pages variable name. I didn't want to create a conflict with UIPageViewController's viewControllers property.

iOS Swift - Change UIPageViewController page using UISegmentedControl

I finnaly synchronized my segmentedController to the UIPageViewController in this way: when I swipe between pages, the segmented controller changes it's segment index. I want to know how to do the reverse too, when segmentedController's segment is tapped, to change the pageViewController page by segmentedController index. Here is my code:
class photoPageViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UINavigationControllerDelegate {
#IBOutlet var segmentedControl: UISegmentedControl!
private var pageViewController: UIPageViewController?
var controllers = [thePageViewController]()
var thePage = thePageViewController()
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
}
private func createPageViewController() {
var pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as! UIPageViewController
pageController.dataSource = self
pageController.delegate = self
var firstController = getItemController(thePage.itemIndex)!
var 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()
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! thePageViewController
if itemController.itemIndex > 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! thePageViewController
if itemController.itemIndex+1 < 3 {
return getItemController(itemController.itemIndex+1)
}
return nil
}
func getItemController(itemIndex: Int) -> UIViewController? {
var vc: thePageViewController? = nil
switch itemIndex {
case 0:
vc = self.storyboard!.instantiateViewControllerWithIdentifier("ViewController0") as! firstPhotoViewController
vc?.itemIndex = itemIndex
case 1:
vc = self.storyboard!.instantiateViewControllerWithIdentifier("ViewController1") as! secondPhotoViewController
vc?.itemIndex = itemIndex
case 2:
vc = self.storyboard!.instantiateViewControllerWithIdentifier("ViewController2") as! thirdPhotoViewController
vc?.itemIndex = itemIndex
default:
return nil
}
return vc
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return 3
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
func pageViewController(photoPageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers pageViewController: [AnyObject],
transitionCompleted completed: Bool)
{
if (!completed)
{
return;
}
segmentedControl.selectedSegmentIndex = thePageIndex
//thePageIndex is a global variable that changes when views from pageViewController appear
}
#IBAction func segmentedFunction(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex
{
case 0:
getItemController(0)
case 1:
getItemController(1)
case 2:
getItemController(2)
default:
println("...")
}
}}
I think you need to change your code a little bit! In my case I create several vc and put them to one array.
func createArrayOfControllers(){
newsLenta = self.storyboard?.instantiateViewControllerWithIdentifier("NewsLentaTableViewController") as NewsLentaTableViewController
mainPage = self.storyboard?.instantiateViewControllerWithIdentifier("MainPageTableViewController") as MainPageTableViewController
onlinetranslation = self.storyboard?.instantiateViewControllerWithIdentifier("SingleTopicTableViewController") as SingleTopicTableViewController
tableViewControllers = [newsLenta, mainPage, onlinetranslation]
}
where tableViewController is global var var tableViewControllers = [UITableViewController]() ! I signed type TableViewController because all my vs's are type of table View controller. So the next function you need to change is
func viewControllerAtIndex(index : Int) -> UIViewController? {
if index > 2 || index < 0 {
return nil
}
return tableViewControllers[index]
}
In the end you just need to create action of segmentControl and reset your vc by index. Using this function:
func resetToMainPage(index: Int!) {
/* Getting the page View controller */
mainPageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("MainPageViewController") as UIPageViewController
self.mainPageViewController.dataSource = self
self.mainPageViewController.delegate = self
let pageContentViewController = self.viewControllerAtIndex(index)
segmentedControl.selectedSegmentIndex = index
self.mainPageViewController.setViewControllers([pageContentViewController!], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
self.mainPageViewController.view.frame = CGRectMake(0, 95, self.view.frame.width, self.view.frame.height)
self.addChildViewController(mainPageViewController)
self.view.addSubview(mainPageViewController.view)
self.mainPageViewController.didMoveToParentViewController(self)
}
My question for your how do you update thePageIndex... I dont get that...
I found the way how to change UIPageViewController's content viewcontroller. I dont know is it right way or wrong but it works. Here is a code:
put this to viewdidload:
segmentControl.addTarget(self, action: "selectPageIndexBySegmentControl", forControlEvents: UIControlEvents.ValueChanged)
put it separte from viewdieload:
func selectPageIndexBySegmentControl(){
switch segmentControl.selectedIndex {
case 0:
println("zero index were selected")
pageContentViewController = self.viewControllerAtIndex(0)
mainPageViewController.setViewControllers([pageContentViewController!], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
case 1:
println("first element were selected")
pageContentViewController = self.viewControllerAtIndex(1)
mainPageViewController.setViewControllers([pageContentViewController!], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
case 2:
println("second element were selected")
pageContentViewController = self.viewControllerAtIndex(2)
mainPageViewController.setViewControllers([pageContentViewController!], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
default:
break
}
}

Swift - Implementing PageViewController in one UIViewController

Nowhere in internet is explain how to implement PageViewController in your UIViewController
I take this code from default XCode Page-Based Application
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController?
override func viewDidLoad() {
self.pageViewController = UIPageViewController(transitionStyle: .PageCurl, navigationOrientation: .Horizontal, options: nil)
self.pageViewController!.delegate = self
self.view.addSubview(self.pageViewController!.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect = self.view.bounds
self.pageViewController!.view.frame = pageViewRect
self.pageViewController!.didMoveToParentViewController(self)
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController!.gestureRecognizers
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
//?? what controller should I return
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
//?? what controller should I return
}
}
I want to have 6 pages and I want to add text in different pages how to recognize in which page I am and show the info that I want? I want to use only 1 ViewControler, only this view controller.
UIPageViewController requires that you use different view controllers. You may need to refactor your code to have a "parent" or "root" view controller, whose only job is to present other view controllers that implement the logic for specific pages. Here's some sample code. It's oversimplified but should give you an idea of how to get going.
class PageContent {
let text: String
let index: Int
init(text: String, index: Int) {
self.text = text
self.index = index
}
}
class MyPageViewController : UIViewController {
var page: PageContent!
override func viewDidLoad() {
// Load page data for self.page
}
}
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController?
var pages: [PageContent]!
override func viewDidLoad() {
let firstPage = PageContent(text: "...", index: 0)
let secondPage = PageContent(text: "...", index: 1)
// ...
self.pages = [firstPage, secondPage] // add more as necessary
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let beforeController = viewController as MyPageViewController
let newPageIndex = beforeController.page.index - 1
if newPageIndex >= 0 {
let newController = MyPageViewController()
newController.page = self.pages[newPageIndex]
return newController
} else {
return nil
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let afterController = viewController as MyPageViewController
let newPageIndex = afterController.page.index + 1
if newPageIndex < self.pages.count {
let newController = MyPageViewController()
newController.page = self.pages[newPageIndex]
return newController
} else {
return nil
}
}
}

Resources