Add UIPageViewController in present app - ios

I have an app in working state in which I have a three screens 1,2,3 each of that screen is associated with a UICollectionView which are created programatically now I want to modify the current implementation & add those collection views to UIPageViewControllers.
I tried to find many tutorials related to PageViewController with CollectionViews but was not able to find anything. Can anyone help me out in implementing this or can give me a reference related to this.
I have also referred this tutorial, but hard luck for me :(

Just follow some of those page view controller tutorials, and when you get to the point of instantiating the child view controllers, make those children collection view controllers. Just as in your example tutorial where it is using UIImageView try replacing it with UICollectionViews & you will achieve what you want.
Do tell if you face any difficulties.
Happy Coding!

You can do this by:
1) Subclassing UIPageViewController and conforming to the UIPageViewControllerDataSource protocol. The data source will tell the pageViewController which view controllers will come next or before the current view controller being presented.
2) In viewDidLoad(), set the dataSource as self so the delegate methods get called. Also, call self.setViewControllers([collectionViewController], direction: .Forward, animated: true, completion: nil). collectionViewController will be an array populated with 1 collection view controller (the first view controller presented).
Example:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self //so our delegate methods get called
//use tags to reference each controller
let collectionViewOne = TestCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
collectionViewOne.view.tag = 0
let collectionViewTwo = TestCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
collectionViewTwo.view.tag = 1
let collectionViewThree = TestCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
collectionViewThree.view.tag = 2
collectionViewControllers = [collectionViewOne, collectionViewTwo, collectionViewThree]
self.setViewControllers([collectionViewOne], direction: .Forward, animated: true, completion: nil)
}
3) Implement the UIPageViewControllerDataSource methods:
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index = viewController.view.tag
if index == 0 {
return nil
}
if index > 0 {
index--
}
return collectionViewControllers[index] as? TestCollectionViewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index = viewController.view.tag
if index == 2 {
return nil
}
if index < collectionViewControllers.count - 1 {
index++
}
return collectionViewControllers[index] as? TestCollectionViewController
}
We return nil if the index will present an out of bounds view controller
Source code of TestCollectionViewController and the UIPageViewController subclass can be found here

I ran into a similar situation recently. I could never get the embedded UICollectionViews to work quite how I wanted them to in the UIPageViewController. I also felt that the implementation was overly complex, requiring a separate view controller for each UICollectionView. I did not feel this was appropriate for my dataset.
My solution was to use a single UIViewController with a horizontally scrolling, paged UICollectionView to handle the paging. Then I added vertically scrolling UICollectionViews for every page to contain my content. I also added an overlay that contained a UIPageControl that was fed scroll-position data to mock the page indicator. This vastly simplified my view controller to data source relationship, was easier to customize, and allowed greater control.
(I would post source but the code belongs to my employer and I used storyboards.)

Related

Swift: Call Function in PageViewController from other Viewcontroller

I got an PageViewController which loads two "child "ViewControllers in order to let the user "swipe" through them. I don't want this swipe gesture , but instead I want to have a function inside my ViewController which allows me to use setViewControllers in the PageViewController.
I tried using protocols but even that didn't work out.
I would realy appreciate any help or suggestions on how I could accomplish that. Thanks!
To access setViewControllers from your child view controllers, you will need your child view controllers to be aware of their parent PageViewController. To do so, start by making a Protocol (I know you've said you've tried Protocols, but please please see my method through). This Protocol will ensure that every child view controller has a reference to the parent PageViewController.
protocol PageObservation: class {
func getParentPageViewController(parentRef: PageViewController)
}
Ensure that your child view controllers adhere to the PageObservation Protocol.
class Child1ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
}
class Child2ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
}
In your PageViewController, as you create each child view controller, cast them to the PageObservation type and pass a reference of the parent PageViewController. I use an array called orderViewControllers to create my pages. My UIPageViewControllerDataSource delegate methods uses it to know which pages to load but that is irrelevant to this example, I just thought I'd let you know in case you have a different way of creating your pages.
class PageViewController: UIPageViewController {
var orderedViewControllers: [UIViewController] = []
//creating child 1
//i am using storyboard to create the child view controllers, I have given them the identifiers Child1ViewController and Child2ViewController respectively
let child1ViewController = UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "Child1ViewController")
let child1WithParent = child1ViewController as! PageObservation
child1WithParent.getParentPageViewController(parentRef: self)
orderedViewControllers.append(child1ViewController)
//creating child 2
let child2ViewController = UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "Child2ViewController")
let child2WithParent = child2ViewController as! PageObservation
child2WithParent.getParentPageViewController(parentRef: self)
orderedViewControllers.append(child2ViewController)
}
Now inside your child view controllers, you have access to setViewControllers. For example, if I want to call setViewControllers in the child1ViewController, I have created a func called accessSetViewControllers() where I access the setViewControllers:
class Child1ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
func accessSetViewControllers() {
parentPageViewController.setViewControllers( //do what you need )
}
}
On a side note, despite what other answers above have said, you can set dataSource to whatever you like. I sometimes set dataSource to nil to prevent the user from swiping away from a screen before doing something and then add the dataSource back to allow them to continue swiping.
Don't set dataSource. When it's nil, then gestures won't work.
https://developer.apple.com/reference/uikit/uipageviewcontroller
When defining a page view controller interface, you can provide the content view controllers one at a time (or two at a time, depending upon the spine position and double-sided state) or as-needed using a data source. When providing content view controllers one at a time, you use the setViewControllers(_:direction:animated:completion:) method to set the current content view controllers. To support gesture-based navigation, you must provide your view controllers using a data source object.
Simplistic approach... remove the inbuilt gesture recogniser in viewDidLoad of pageViewController:
for view in self.pageViewController!.view.subviews {
if let subView = view as? UIScrollView {
subView.scrollEnabled = false
}
}
Then add your own gesture below it. i just happened to be working with double tap at the moment but you could make it swipe left, swipe right easy enough:
let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap))
doubleTap.numberOfTapsRequired = 2
doubleTap.delaysTouchesBegan = true
self.addGestureRecognizer(doubleTap)
and the gesture function with your code:
func didDoubleTap(gesture: UITapGestureRecognizer) {
//... stuff
}

Load ViewController as child view in main view from other child view

I´m coding an application using swift3/xcode 8. My application have a central view controller, called MenuViewController, which is responsible for the side menu.
Inside this one, i load the other views as child views, so in this way I can use the side menu in all other views.
But I´m in doubt now: In a specific view controller I have a table view, which have a method to perform an action on selecting an item. This action calls other
view and pass a parameter.
So, how do I load this view in the BaseViewController with this parameter? Is it possible?
Please let me know if it is not clear....
Thank you!
Update 1:
I´m using a solution I found interesting:
Cocoacasts
Basically I have this code on MenuViewController:
Declare the view to be loaded in MenuViewController:
lazy var filamentoViewController: FilamentoViewController = {
let storyboard = storyboard.instantiateViewController(withIdentifier: "FilamentoViewController") as! FilamentoViewController
self.addViewControllerAsChildViewController(childViewController: viewController)
return viewController
}()
Adds the viewcontroller as child in MenuViewController:
private fun addViewControllerAsChildViewController(childViewController: UIViewController)
{
addChildViewController(childViewController)
view.addSubView(childViewController.view)
childViewController.view.frame = view.bounds
childViewController.view.autoResizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParentViewController: self)
}
Code in FilamentoViewController which calls other viewcontroller:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath)
{
let editFilamentoViewController:EditFilamentoViewController = EditFilamentoViewController()
editFilamentoViewController.cod = indexPath.row
self.presentViewController(editFilamentoViewController, animated: true, completion: nil)
}
The code
self.presentViewController(editFilamentoViewController, animated: true, completion: nil)
is the one I want do load into MenuViewController.
Update 2:
MenuViewController is a CentreViewController. I load the other views in the center and the menu comes from left.
There is a built-in controller for your kind of requirement(i.e UISplitViewController) , Have you considered this?
Apple documentation
Sample Tutorial
And there are also slide-out menu libraries.
slide out menu

Swift Search Result Controller in search results segue to another view controller

Problem:
I have a table view that the user can either scroll through to find something or use a search bar. The search bar was not created using the StoryBoard. My view has a UISearchController that handles the search bar and search result updating. The issue that I'm running into is that since my SearchResultsController is instantiated by another controller I cannot perform a segue to another view, or at least I can't find the solution. Everything works except for the segue between my Search Results Controller and the view it's destined for.
What I would like to do
Have my MyNavTableViewController delegate for MySearchResultsController. In the search results controller it will segue to another view when the user taps on a TableViewCell. I'm not using the StoryBoard to accomplish this I'm having difficulty using segues to transition to another view.
If I can't get this to work what I will probably do:
It's essential that I pass information between views, and for me I've always done it using segues. However if this doesn't work I will probably try presenting a view modally by pushing it unto the navigation controller stack and write the data to a shared database or something. I would prefer to use a segue though.
Research:
There is definitely more than this, but I'm not going to take too much space on urls.
Creating a segue programmatically
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html
My Setup
I'm going to try and keep this as concise as possible. There is more code than what I'm displaying. I'm just going to try and clean it up so that I'm only presenting the important stuff. I'm also changing a few names around because there may be sensitive information. It's probably not a big deal, but I'd rather be safe.
class MyNavTableViewController: UIViewController, UITableViewDataSource{
//this is
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
override func viewDidLoad(){
...code
tableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: tblId)
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
}
}
MySearchResultsController: UITableViewController, UISearchResultsUpdating {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
//self.performSegueWithIdentifier(imagePrepareStoryBoardId, sender: tableView.cellForRowAtIndexPath(indexPath))
/*let imagePrepare = ImagePrepareController()
customSegue = SearchResultSegue(identifier: imagePrepareId, source: self, destination: imagePrepare)*/
//neither storyboard nor navigationController can be nil.
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
//destVC.photo = photo!
//self.presentViewController(destVC, animated: false, completion: nil)
self.navigationController!.pushViewController(destVC, animated: false)
}
}
My Failed Attempts
1) Straight up segue - Doesn't work since the MySearchResultsController is not a view in the storyboard. Everything from what I've read is that segues can only be created in the SB.
2) Push view onto the navigation stack. The only problem with this is that I can't send data between views (or at least from what I've read). I'm also getting this error right at this break point:
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
fatal error: unexpectedly found nil while unwrapping an Optional value
I double checked the imagePrepareStoryBoardId. It's correct.
3) Use custom segue - As you can see from the commented out lines that this isn't working either. I've always used segues with the SB so this method is a little new to me. I might be messing it up somewhere.
First create a protocol
protocol SelectedCellProtocol {
func didSelectedCell(text: String)
}
on your UITableViewClass declare it
class MySearchResultsController: UITableViewController, UISearchResultsUpdating {
var delegate:SelectedCellProtocol?
}
and on the selected cell method call it like :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
self.delegate?.didSelectedCell(cell.textLabel?.text)
}
when you declare your results controller, set the delegate
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
resultsController.delegate = self
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
and then implement the protocol
class MyNavTableViewController: UIViewController, UITableViewDataSource, SelectedCellProtocol{
func didSelectedCell(text: String) {
print(text)
//make your custom segue
}
}

Implementation of UIPageViewController with UICollectionViewControllers (Swift)

I am trying to implement a UIPageViewController, where you can slide between two UICollectionViewControllers (much like the iOS 8.4 music App). This is also supposed to use one instance of UINavigationController.
This is what I've tried so far:
Using Storyboard, I've added a new PageViewController, in which I have conformed to the UIPageViewControllerDataSource as well as the UIPageViewControllerDelegate protocols.
Using Storyboard identifiers, I have successfully been able to get this to the point where you can swipe between each view.
Here is some sample code to be more specific:
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UICollectionViewController! {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("AlbumsController") as! UICollectionViewController
}
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SubscriptionsController") as! UICollectionViewController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
if index == identifiers.count - 1 {
return nil
}
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 index == 0 {
return nil
}
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
The problem arises in the gestures being lost/overwritten when swiped to the next view. Initially I can scroll the first collection view and tap on the status bar to scroll to the top, but as soon as the pageviewcontroller swipes over to the next collection view, this gesture stops working and now neither collection view responds to the "scroll to top" gesture.
I have also heard using a scrollview is possible, but I wasn't convinced that this implementation would be more appropriate that using a UIPageViewController.
Current research suggests that the gestures are affected by the 'data source' of the PageViewController and must be altered in some way.
I have also embedded the UIPageViewController inside of a UINavigationController to achieve a shared navigation bar for both views. Now of course the navigation bar overlaps the UICollectionViews as they are not themselves embedded within a UINavigationController. To work my way around this problem, I've used this code below:
collectionView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
Would anyone be able to give me some advice on how to properly re-create the 'swiping between two views' effect you get in the new Music App?
With my current implementation, I feel as it is not the correct way to achieve my goal, so I would greatly appreciate any help.

how to make UIPageViewController reuse controller like tableview reuse cell?

I need a way to make UIPageViewController reuse controllers to save memory, because I have a huge number of pages to show!
I did the basic implementation of the UIPageViewController but couldn't manage to make controller reusable, please advice!
To solve this problem in my current project, I cache all view controllers that are created as pages for the UIPageViewController in a Set. Whenever the UIPageViewController requests a new view controller from its data source, I filter out an unused from that cache by checking the parentViewController property; if no unused view controller is available, a new one is created.
My setup of the UIPageViewController and the cache looks similar to this:
class MyPageViewController: UIPageViewController {
private var reusableViewControllers = Set<MyViewController>()
init() {
super.init(/* ... */)
self.dataSource = self
let initialViewController = self.unusedViewController()
// Configure the initial view controller
// ...
self.setViewControllers([ initialViewController ],
direction: .Forward,
animated: false,
completion: nil)
}
// This function returns an unused view controller from the cache
// or creates and returns a new one
private func unusedViewController() -> MyViewController {
let unusedViewControllers = reusableViewControllers.filter { $0.parentViewController == nil }
if let someUnusedViewController = unusedViewControllers.last {
return someUnusedViewController
} else {
let newViewController = MyViewController()
reusableViewControllers.insert(newViewController)
return newViewController
}
}
}
The data source uses the same function to obtain an unused view controller:
extension MyPageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let nextViewController = unusedViewController()
// Configure the next view controller for display
// ...
return nextViewController
}
}
You could use ACReuseQueue to achieve what you want. It provides a queue for reusing your view controllers. You can use the UIPageViewController data source methods to dequeue a VC from the reuse queue. The tricky part is to know when to put them back in the reuse queue. UIPageViewController does not provide any method to know when this happens, but there is a way. UIPageViewController is a container VC that adds and removes its child view controllers using the VC containment APIs.
Your VC will receive didMoveToParentViewController: with nil as the argument if it is being removed from the UIPageViewController. Use this method to add yourself back to the queue.

Resources