In Swift, I currently have the following code in my first view controller, yet when I swipe nothing happens? What is wrong with my code? Do I have to implement something else? Thanks alot SO users, appreciate it.
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var myViewControllers = Array(count: 4, repeatedValue:UIViewController())
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let pvc = segue.destinationViewController as UIPageViewController
pvc.dataSource = self
let storyboard = UIStoryboard(name: "Main", bundle: nil);
var vc0 = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as UIViewController
var vc1 = storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as UIViewController
var vc2 = storyboard.instantiateViewControllerWithIdentifier("ThirdViewController") as UIViewController
var vc3 = storyboard.instantiateViewControllerWithIdentifier("FourthViewController") as UIViewController
self.myViewControllers = [vc0, vc1, vc2, vc3]
pvc.setViewControllers([myViewControllers[1]], direction:.Forward, animated:false, completion:nil)
println("Loaded")
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var currentIndex = find(self.myViewControllers, viewController)!+1
if currentIndex >= self.myViewControllers.count {
return nil
}
return self.myViewControllers[currentIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var currentIndex = find(self.myViewControllers, viewController)!-1
if currentIndex < 0 {
return nil
}
return self.myViewControllers[currentIndex]
}
}
Sorry, your code is working perfectly for me. I made a trivial test (never having used a UIPageViewController before) with a button to segue from the initial VC to the Page VC, and two disconnected VCs in the storyboard to be constructed programatically with your code (I pasted it direct from above) and given to the Page VC. Each of the 3 visible pages was identified with a label. And they all work fine.
Does your UIPageViewController get instantiated? Do your two delegate routines above get called?
I suspect that you have not set up your storyboard appropriately. Here is mine:
When iOS runs your app, it transfers control to the initial VC. That's the one that you're writing your code in, called ViewController in the above source. That is not a UIPageViewController; it's an ordinary UIViewController, it says so here:
class ViewController: UIViewController, UIPageViewControllerDataSource {
The UIPageViewControllerDataSource bit just says it also provides the routines that tell a UIPageViewController what to display. When you do a segue out of ViewController, the prepareForSegue code runs then, and programs the destination of the segue, which is expected to be a UIPageViewController, with the other 4 VCs. To make the segue work, I dragged a UIButton to the first ViewController in storyboard, and dragged a UIPageViewController onto the right. Then control-drag from the button to the UIPageViewController. You should get a popup asking what kind of segue you want. I chose modal. When you run the app, you should see the button. When you press the button, you should see VC1, and swiping left or right will show the others.
Related
I have what seems to be a very common setup in my universal application, with a root UISplitViewController, using a UITabBarController as a masterViewController, and then I want to:
either push the detail view controller onto the stack if I'm on a vertical iPhone
show the detail controller in the detailViewController of the UISplitViewController on lanscape iPhone 6+ and other larger screens like iPads and such
To that effect, I have exactly the same setup as the ones described in all those discussions that mention a similar issue:
UINavigationController inside a UITabBarController inside a UISplitViewController presented modally on iPhone
iOS8 TabbarController inside a UISplitviewController Master
Adaptive show detail segue transformed to modal instead of push on iPhone when master view controller is a UITabBarController
But none of the solutions mentioned in those questions works. Some of them create an infinite recursive loop and an EXC_BAD_ACCESS. And the latest one I tried simply keeps presenting the detail view controller modally instead of pushing it onto the stack on iPhones. What I did is create a custom UISplitViewController subclass as such:
class RootSplitViewController: UISplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
}
extension RootSplitViewController: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
tabController.selectedViewController?.show(vc, sender: sender)
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
return navController.popViewController(animated: false)
} else {
return nil
}
} else {
return nil
}
}
}
And here is the code in the master view controller to show the detail view controller:
self.performSegue(withIdentifier: "showReference", sender: ["tags": tags, "reference": reference])
Where tags and reference where loaded from Firebase. And of course the "showReference" segue is of the "Show Detail (e.g. Replace)" kind.
The first delegate method is called correctly, as evidenced by the breakpoint that gets hit there when I click an item in the list inside the UITabBarController. And yet the detail view controller still presents modally on iPhone. No problem on iPad though: the detail view controller appears on the right, as expected.
Most of the answers mentioned above are pretty old and some of the solutions are implemented in Objective-C so maybe I did something wrong in the conversion, or something changed in the UISplitViewController implementation since then.
Does anyone have any suggestion?
I figured it out. In fact, it was related to the target view controller I was trying to show. Of the 2 methods I was overriding in UISplitViewControllerDelegate, only the first one was called:
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
tabController.selectedViewController?.show(vc, sender: sender)
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
But the view controller I was showing in the first branch of the test was already embedded into a UINavigationController, so I was essentially showing a UINavigationController into another one, and in that case the modal made more sense. So in that case I needed to show the top view controller of the UINavigationController, which I assume was the purpose of the second method I'm overriding in the delegate, but it was never called. So I did it right there with the following implementation:
extension RootSplitViewController: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if let tabController = splitViewController.viewControllers[0] as? UITabBarController {
if(splitViewController.traitCollection.horizontalSizeClass == .compact) {
if let navController = vc as? UINavigationController, let actualVc = navController.topViewController {
tabController.selectedViewController?.show(actualVc, sender: sender)
navController.popViewController(animated: false)
} else {
tabController.selectedViewController?.show(vc, sender: sender)
}
} else {
splitViewController.viewControllers = [tabController, vc]
}
}
return true
}
}
And that seems to work perfectly, both on iPhones and iPads
Having you tried ShowDetailViewController Method to change the detail view controller in split view controller.
splitViewController.showDetailViewController(vc, sender: self)
If in case your view controller does not contain navigation controller you can also embed it in a navigation controller.
let nav = UINavigationController.init(rootViewController: vc)
splitViewController.showDetailViewController(nav, sender: self)
I have a viewcontroller (FirstVC.swift) with a container view, in the upper part, that has an embedded pageviewcontroller (SecondPageVC.swift), and a view that has 3 buttons in the lower part.
At first, the only button visible is the middle button and other two is hidden. If the user reaches the last page, the other two buttons should appear. How do I pass the bool value that will make the buttons appear?
In my SecondPageVC.swift
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if index == myViewControllers.count-1{ //if last page
hide = true
}
Define a protocol called PageDelegate like this :
protocol PageDelegate { func hasReachLastPage(hasReached: Bool) }
Create a delegate var in your SecondPageVC like this : weak var delegate: PageDelegate?
On your pageViewController func called your func hasReachLastPage(hasReached: Bool) like this :
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if index == myViewControllers.count-1{ //if last page
delegate?.hasReachLastPage(true)
}
On your FirstVC makes it adopt your protocol that you just create like this : FirstVC : PageDelegate
Then, on your FirstVC when you have an instance of your SecondPageVC you are going to set your delegate variable to that FirstVC like this : SecondPageVC.delegate = self
You are saying that FirstVC is going to handle SecondPageVC delegate methods
Finally always on your FirstVC you have to implement the body of func hasReachLastPage(hasReached: Bool) by hidding or not your two buttons like this :
func hasReachLastPage(hasReached: Bool) {
if hasReached {
// Unhide two buttons
} else {
// Hide buttons or whatever
}
}
This is how you implement the delegate pattern. It's something very used and very useful to pass data between view controllers.
So, if I understand correctly, you just want to pass this hide property to the instance of FirstVC? If so, I would advise you to define a protocol SecondPageVCDelegate with a method secondPageVCDidReachLastPage:. Assigning the delegate can be done in prepare for segue method of the FirstVC.
I'm using the following code to goto another view programmatically in swift 3. There is no error while running. But don't know why it is not going to that view
Code I used:
let images = self.storyboard?.instantiateViewController(withIdentifier:"Collection") as! UICollectionViewController
self.navigationController?.pushViewController(images, animated: true)
I want to goto CollectionView.swift
In order to navigate to between view controllers you use UINavigationController.
I will provide you a basic example of navigation, hopefully it will help you to make navigation work in your project.
Result
ViewController passes an image to DetailViewController between navigation:
Setting up your Views
First ensure that your root controller is embedded with a navigation controller control so that you can navigate using segues:
Connect your views that are being used to navigate.
Code
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// showDetail Segue
if segue.identifier == "showDetail" {
// Sending the image to DetailViewController
// Before appears in the screen.
let detailViewController = segue.destination as! DetailViewController
detailViewController.image = sender as? UIImage
}
}
#IBAction func loginButton(_ sender: AnyObject) {
// Go to another view controller
performSegue(withIdentifier: "showDetail", sender: imageView.image)
}
}
class DetailViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
if let imageSent = image {
imageView.image = imageSent
}
}
}
I think it would be best instead of programmatically calling the Storyboard ID of the controller, to use Segues instead. You can find out how to use them here.
But if you have to use the SBID, here is an example snippet from one of my projects...
let vc = self.storyboard?.instantiateViewController(withIdentifier: "UpdateFilesViewController")
self.navigationController?.present(vc!, animated: true, completion: nil)
Notice that presenting might be the solution you are after instead of pushing. Additionally, you seem to be casting the UIViewController to a UICollectionViewController, which may cause problems as well. Keep note that a UICollectionViewController is a subclass of a UIViewController so the casting is unnecessary.
As we found out in the comments, your current viewController does not have a navigationController.
This means that
self.navigationController?.pushViewController(images, animated: true)
does nothing. You need to set up the initial viewController with a navigationController for this to have any effect.
Change your storyboard Identifier
You just need to change your storyboard identifier as "CollectionOfImages" in identity inspector as Storybroad identity so it will move on there.
Note : Please make sure that your current view controller is embed with navigation controller otherwise it will not push new controller during movement.
If you don't want to use navigation controller, you can simply present your controller using:
let images = self.storyboard?.instantiateViewController(withIdentifier:"Collection") as! UICollectionViewController
self.present(images, animated: true, completion: nil)
I have the following setup:
StartViewController has a ContainerView that contains ContainerViewController
I try to find a way to hidden an element in StartViewController after a task is performed in ContainerViewController.
For this I try to use delegation method like this:
StartViewController
class StartViewController: UIViewController, showBannerAdDelegate {
#IBOutlet weak var bannerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
bannerView.hidden = false
}
func bannerAdHidden(status: Bool) {
bannerView.hidden = status
}
}
ContainerViewController
protocol showBannerAdDelegate: class {
func bannerAdHidden(status: Bool)
}
class ContainerViewController: UIViewController {
weak var delegate: showBannerAdDelegate! = nil
#IBAction func buttonPressed(sender: UIButton) {
delegate.bannerAdHidden(true)
}
}
If I presented the ContainerViewController I could do in prepareForSegue
let destination = segue.destinationViewController as! ContainerViewController
destination.delegate = self
But in this case both View Controller are always present.
What code should I add to the View Controller to make it work?
Thank you,
If one of the view controllers is inside a container view then it is loaded with an embed segue, which fires when the containing view controller is first loaded. The prepareForSegue method still gets called, so you can set up a delegate exactly as you've described. I always thought embed segues were a little odd (it's not really a segue, more like loading a child view controller) but that's how it works.
I am looking to create a PageViewController very similar to SnapChat's one, whereby you can swipe from the UIImagePickerController to another VC. To do this, I have my initial VC which displays the imagepickercontroller, and a second VC (a caption VC) which I want to come after this initial VC. To encapsulate my PageViewController, I have created another VC class (shown below) which I have now set as my initial VC, and I am trying to handle the PageVC data source.
For whatever reason, it is not working and the error - 'fatal error: unexpectedly found nil while unwrapping an Optional value' occurs. Is this because you can't contain an imagePickerController in a PageVC (doubtful as SnapChat do). I created a simpler template which contained two simple VCs perfectly - why can I not do this here? The other one I did, I contained all the below code in the initial VC that the project starts with, whereas here I created an additional VC and manually changed it to make it the 'initial view controller'.
NB. the project compiles fine without the pageVC so it is nothing to do with any bad code in the other VCs.
I am very stuck and would hugely appreciate some help to this tricky issue. Thanks!
class PageViewController: UIViewController, UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController?
private let VCarray = [ViewController(), CaptionViewController()]
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
}
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as! UIPageViewController
pageController.dataSource = self
if VCarray.count > 0 {
pageController.setViewControllers([ViewController()], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController!.didMoveToParentViewController(self)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if viewController.isKindOfClass(CaptionViewController) {
let pvc = self.storyboard?.instantiateViewControllerWithIdentifier("CameraVC")
return pvc
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if viewController.isKindOfClass(ViewController) {
let pvc = self.storyboard?.instantiateViewControllerWithIdentifier("CaptionVC")
return pvc
}
return nil
}
The only optional unwrapping I see is on the first line. Is that where the exception is being thrown? Are you sure your main controller has a storyboard associated with it? If so, are you sure that the storyboard contains a controller named "PageController"?