I decided to drop my UIImageView and instead use an UIPageViewController so that users can swipe between images on the same page.
What I have now is a normal UIViewController, in that I added a containerView in which I have embedded a UIPageViewController.
So the UIPageViewController acts as a childview to the viewcontroller.
I have a "details" page with some info about the product X. And instead of only showing one image I want the image to be "swipeable" so that users can see different images. But I dont want the PageControllerView to act as a full-size view, the image should still be visible inside the viewcontroller at all time.
And my problem now is that I am stuck. I have no idea on how to hook up the two controllers. So if anyone has an example app/tutorial on embedded segues/child view using swift please share.
I worked out a solution thanks to the answer bellow:
In my storyboard I have 3 VC's. VC1 with and embedded container view that has a segue to a UIPageControllerView, then I added a 3rd VC "content view controller" to display images for the UIPageControllerView.
ViewController.swift
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController!
var pageTitles: NSArray!
var pageImages: NSArray!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func restartAction(sender: AnyObject)
{
var startVC = self.viewControllerAtIndex(0) as ContentViewController
var viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
}
func viewControllerAtIndex(index: Int) -> ContentViewController
{
if ((self.pageTitles.count == 0) || (index >= self.pageTitles.count)) {
return ContentViewController()
}
var vc: ContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController
vc.imageFile = self.pageImages[index] as! String
vc.titleText = self.pageTitles[index] as! String
vc.pageIndex = index
return vc
}
// MARK: - Page View Controller Data Source
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound)
{
return nil
}
index--
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if (index == NSNotFound)
{
return nil
}
index++
if (index == self.pageTitles.count)
{
return nil
}
return self.viewControllerAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "PageViewControllerFitta" {
self.pageTitles = NSArray(objects: "Explore", "Today Widget")
self.pageImages = NSArray(objects: "page1", "page2")
let toView = segue.destinationViewController as! UIPageViewController
toView.dataSource = self
var startVC = self.viewControllerAtIndex(0) as ContentViewController
var viewControllers = NSArray(object: startVC)
toView.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
toView.view.frame = CGRectMake(0, 30, self.view.frame.width, self.view.frame.size.height - 60)
self.addChildViewController(toView)
self.view.addSubview(toView.view)
toView.didMoveToParentViewController(self)
}
}
}
ContentViewController.Swift
import UIKit
class ContentViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
var pageIndex: Int!
var imageFile: String!
override func viewDidLoad()
{
super.viewDidLoad()
self.imageView.image = UIImage(named: self.imageFile)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Embedding is actually a kind of segue that takes place as soon as the containing controller is instantiated, so the way to do this is to implement
prepareForSegue
on your containing controller, and capture a reference to your child controller within that method. Here's an example adapted from a project I'm working on right now, it probably won't compile but should give you an idea of how to proceed—let me know if you need more information.In order for this to work, you have to give the segue property an
identifier
. Assuming you are using storyboards, this is just a property on the segue itself (the little round transition icon that sits on the arrow connecting the parent and child UIViewControllers).Here's another another answer that goes into some detail and documentation, though provided in Objective-C rather than Swift: iOS Nested View Controllers view inside UIViewController's view?
Now that you have a reference to your
UIPageControllerView
, it needs a way to answer some questions for display purposes: how many pages are there, what page do we open to, what page do I show when the user swipes to the next and previous pages. It does this with a common UIKit/Cocoa idiomdataSource
(lots of view controllers have this property).In the sample code you link to, the
viewDidLoad
method contains the lineself.pageViewController.dataSource = self
. This works because the author has declared that hisViewController
, in addition to extendingUIViewController
, also conforms to theUIPageViewControllerDataSource
protocol. He then implements the required methods:See: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIPageViewControllerDataSourceProtocolRef/index.html
(The
dataSource
doesn't have to be yourViewController
, you can make a separate class or struct. For simple examples it's probably fine, though.)The specific challenge for your is to implement those methods based on your app's requirements so that the embedded
UIPageViewController
can figure out what to display from whatever you set as itsdataSource
.Suppose you want it to display a
ProductImageViewController
which just takes an image and a label. You could, in theory, create storyboards for each product but that isn't really going to work unless you have a small number of products that never changes. What's more likely is, so you want to create new instances in code based on your product list.In the sample code, the author just sets up a static array of labels and images and then the
pageViewController
methods just grabs the appropriate one from the array based on current position and the direction of the swipe. You want to set up a dataSource that knows about your product list which you presumably get from some dynamic source like CoreData, CloudKit or some other web service.As a simple experiment to familiarize yourself, try modifying that demo's variables
self.pageTitles
andself.pageImages
and see how that alters the behavior of the embedded controller. Try modifying that code so that rather than using two arrays, one of text and one of image file names, you use a single array containing a struct or class that you define, which holds both the label text and the file name. Once that's working, alter so that instead of hardcoding the list of labels/images, you load it from your product catalog. And what's that's working, you're done, ship it :)