I have three View Controllers embedded in Navigation Controller. I want to go from VC1 to VC3 so that in VC3 the Navigation Item's back button would direct the user to VC2 instead of VC1. I think this should be done either by adding the VC2 to the Navigation Stack between VC1 and VC3 when VC3 is created or by skipping the second View Controller.
I tried this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
if let secondvc = segue.destinationViewController as? SecondViewController {
secondvc.performSegueWithIdentifier("ThirdVCSegue", sender: self)
}
default: break
}
}
}
but I couldn't get it working. Perhaps performing a segue isn't possible when the View Controller isn't open yet?
What is the best way to skip a View Controller / add a View Controller in the middle of Navigation Stack? I hope this helps you to understand what I'm trying to do:
Something like this should work:
self.navigationController?.pushViewController(viewController2, animated: true)
self.navigationController?.pushViewController(viewController3, animated: true)
EDIT:
If you want to push the second view controller without being noticed by the user you need to add it to the navigation controller after the third view controller is pushed. This can be done by implementing UINavigationControllerDelegate. You can store your second view controller in a variable and insert it to the navigation controllers hierarchy in delegate method. Your main view controller will look like following:
class MyViewController: UIViewController, UINavigationControllerDelegate {
var viewControllerToInsertBelow : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func pushTwoViewControllers() {
if let viewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("id1"),
let viewController3 = self.storyboard?.instantiateViewControllerWithIdentifier("id2") { //change this to your identifiers
self.viewControllerToInsertBelow = viewController2
self.navigationController?.pushViewController(viewController3, animated: true)
}
}
//MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let vc = viewControllerToInsertBelow {
viewControllerToInsertBelow = nil
let index = navigationController.viewControllers.indexOf(viewController)!
navigationController.viewControllers.insert(vc, atIndex: index)
}
}
}
if var navstack = navigationController?.viewControllers{
navstack.append(contentsOf: [vc1,vc2])
navigationController?.setViewControllers(navstack, animated: true)
}
Works well
Edit: using swift and segues, this should work:
override func performSegueWithIdentifier(identifier: String?, sender: AnyObject?) {
super.performSegueWithIdentifier(identifier, sender: sender);
if identifier == "JumpToThirdVCSegue" {
// now the vc3 was already pushed on the navigationStack
var navStackArray : [AnyObject]! = self.navigationController!.viewControllers
// insert vc2 at second last position
navStackArray.insert(viewController2, atIndex: navStackArray.count - 2)
// update navigationController viewControllers
self.navigationController!.setViewControllers(navStackArray, animated:false)
}
}
So you override the performSegueWithIdentifier in the VC1 in order to change the navigation stack when the segue to VC3 has actually been performed (and is not only in preparation).
This assumes, that you want to handle this special navigation logic in VC1.
You can use method
func setViewControllers(_ viewControllers: [UIViewController],
animated: Bool)
Here's the documentation (link) that solves the UIViewController skipping problem.
Simply pass all the needed UIViewControllers to it (in navigational order) and the last one will be made as the current active UIViewController (if it already isn't the active one).
My solution would be to keep a BOOL property of when you should skip to third and when not skip, like declaring ’shouldSkip’ in VC2 so that if u set it in prepare segue like below you can act according to that in VC2
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
secondvc.shouldSkip=true
}
default: break
}
}
}
and then in viewDidload VC2 you should check this BOOL and if it is true and perform segue and if not just move on
you could also pass pass whenever that kind of skip is not necessary
Building off of MarkHim's answer, I was getting a Black screen when navigating back to the inserted view controller so I came up with the following solution.
BTW - I wanted to insert the new view controller directly under the view controller I just segued to hence the navStack.count - 1 instead of navStack - 2.
Here is my solution:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
super.performSegue(withIdentifier: identifier, sender: sender)
if identifier == "JumpToThirdViewControllerSegue",
let navController = navigationController { // unwrap optional
// initialize the view controller you want to insert
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewControllerToInsert = storyboard.instantiateViewController(
withIdentifier: "SecondVC") as! SecondViewController
// set any passed properties
viewControllerToInsert.passedProperty = propertyToPass
// create an object using the current navigation stack
var navStackArray: [UIViewController]! = navController.viewControllers
// insert the newly initialized view controller into the navStackArray
navStackArray.insert(viewControllerToInsert, at: navStackArray.count - 1)
// replace the current navigation stack with the one you just
// inserted your view controller in to
navController.setViewControllers(navStackArray, animated: false)
}
}
Related
I have a TabBarController in my Main.storyboard file.
In my Upload.storyboard I am presenting a ViewController from the Main.storyboard file, however, it doesn't contain the tab bar.
The viewProfile button should go to a tab called Sharks and within that present a view controller based on data gathered in the Upload.storyboard (modal view).
Can I add the tab bar programmatically or am I not properly presenting the correct VC?
// MARK: - Actions
#IBAction func viewProfileButtonPressed(_ sender: UIButton) {
let stb = UIStoryboard(name: "Main", bundle: nil)
let sharkProfile = stb.instantiateViewController(withIdentifier: "sharkProfile") as! SharkProfileTableViewController
self.present(sharkProfile, animated: true) {
// add tab bar here?
}
}
What you need to do is present the tab bar view controller, not the view controller that is embedded in it
One method is to create a Delegate Protocol to allow a tap on View Profile button to "call back" to the View Controller that presented it. When that callback is received, the VC sets the "current" tab.
It will look something like this:
// define "call back" delegate protocol
protocol EncounterUploadedDelegate : class {
func didTapSharkProfileButton()
}
The Encounter view controller will need to conform to that protocol:
class EncounterViewController: UIViewController, EncounterUploadedDelegate {
// the normal stuff for this VC and all the other code for it
// ...
// conform to the protocol
func didTapSharkProfileButton() -> Void {
// when we get this call-back, switch to the Shark Profile tab
// tabs are zero-based, so assuming "SharkProfile" is
// is the 4th tab...
self.tabBarController?.selectedIndex = 3
}
// assuming a "Show Modal" segue named "ShowEncounterUploadSegue" is used
// to present the Modal View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEncounterUploadSegue" {
let vc = segue.destination as! TabModalVC
vc.encounterDelegate = self
}
}
}
The view controller to be presented as modal:
class TabModalVC: UIViewController {
weak var encounterDelegate: EncounterUploadedDelegate?
#IBAction func profileButtonTapped(_ sender: Any) {
// dismiss self (the modal view)
self.dismiss(animated: true, completion: nil)
// this will call back to the delegate, if one has been assigned
encounterDelegate?.didTapSharkProfileButton()
}
}
Hope that all makes sense :)
Suppose I have three view controllers in a Main.storyboard. Two of the three, vc_login and vc_studyDesc load the other view controller using a UIButton with 'present modally' option.
The other one vc_signup has a UIButton, which may go back to the previous controller. To implement this, I used the following methods:
vc_studyDesc has an identifier of studyDesc; I let it pass its identifier to vc_signup. In the same way, vc_login has login as an identifier.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if sender as! UIButton == qnaSignUp {
let signup = segue.destinationViewController as! vc_signup
signup.latestVC = "studyDesc"}}
This one is in the UIViewController class for vc_signup. By referencing a string latestVC, the method determines which VC to move on.
#IBAction func backBtnClick(sender: UIButton) {
print("latestVS: \(latestVC)")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(latestVC)
vc.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
print("check check")
self.presentViewController(vc, animated: true, completion: nil)}
The problem I have is that the app gets terminated when vc_studyDesc is called by vc_signup. I found that this is because I missed a significant variable which must be loaded in vc_signup.
vc_studyDesc has some data to be referenced from Firebase when it is loaded. I did this by loading a variable postID from the prior vc to vc_studyDesc; which is vc_list.
So I just saved postID using NSUserdefaults.standardUserDefaults(). It is solved but I'm wondering if there's any way to pass data using the way I used in vc_signup.
As far as I see, I cannot find any way to pass the data into vc_studyDesc.swift; for the vc is chosen by its identifier..
Can I pass the variable I want in the way I want?? And adding tags would be appreciated!
So there are a couple problems with this design.
When you instantiate a viewController you are creating a new instance of that class, and presenting it adds it to the stack. Think of the stack like a deck of cards, you start with one card and then add or remove them, the top card being the visible vc. When you are going back to studyDesc you are instantiating and presenting it so you will have 3 VCs in your stack, of which two are studyDesc (the one you started with and the one you add when you try to go back)
To remove a VC from the stack you can use
dismissViewController(animated: true, completion: nil)
or if you have the VCs in a navigation controller you can use
popViewControllerController(animated: true, completion: nil)
in terms of passing information between viewControllers, if the info is in the VC you use to present your new controller you can use prepareForSegue like you already have. To pass information back you should use a delegate pattern. So to implement a delegate pattern in this case you would do the following:
Declare a protocol (not inside your classes, above there but below your import's)
protocol SignUpDelegate {
signInCompleted(infoToPass: AnyObject)
}
Then have your studyDesc class conform to this protocol and implement the function signInCompleted
StudyDescVC: UIViewController, SignUpDelegate {
func signInCompleted(infoToPass: AnyObject) {
// do what you want with the info here
}
}
Then in your signUpVc add a var delegate (which will be used to call the signInCompeleted function)
class SignInVC: UIViewController {
var delegate: SignUpDelegate!
func finishedSigningIn() {
delegate.signInCompleted(infoToPass: //yourinfo)
self.dismissViewControllerAnimated(true, completion: nil)
}
And then in your prepareForSegue set the delegate
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if sender as! UIButton == qnaSignUp {
let signup = segue.destinationViewController as! vc_signup
signup.delegate = self
}
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(latestVC) as! YOUR_VIEW_CONTROLLER_NAME
vc.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
vc.name = "Andrew"
print("check check")
self.presentViewController(vc, animated: true, completion: nil)
//set a variale or property to your viewController
class YOUR_VIEW_CONTROLLER_NAME: UIViewController {
var name: String?
}
I have an app setup to have a Master root viewController with a Navigation bar that has a "Settings button". When the user taps the settings button, it brings up a 'settingsTableViewController' that is not dynamic, but rather static so i can format the tableviews with settings like style.
in this settings view, i have a UITextField that takes a Person's name.
When the user clicks "Done" in the navigation bar, i want to pass that name back to the Master Root ViewController so i can use it in the title to display the Person's name. (I want to pass the name upon dismissing the view)
I have tried to use segue's but no luck. Here is a bit of what i tried.
SettingsViewController (pop's over the MasterVC)
class SettingsTableViewController: UITableViewController {
#IBOutlet weak var nameTextfield: UITextField!
#IBAction func done(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override viewDidLoad(){
self.title = "Settings"
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Trying to pass what was typed in the textfield to the Root ViewController and store this name in a variable of type String called 'name'
if segue.identifier == "masterView"{
let masterViewController = segue.destinationViewController as! MasterTableViewController
masterViewController.name = nameTextField.text
print("Segue was used")
}
}
}
MasterTableViewController.swift
class MasterTableViewController: UITableViewController {
var name: String!
// I am trying to then display the name entered in the pop-over view in this root view controller's title
override viewDidLoad(){
self.title = name!
}
}
My question is, what did i do wrong? I have tried to use delegation but i get nil and the data doesn't seem to get passed either way so any leads will greatly help. Thanks
There is a different kind of segue called Unwind Segue to do that. It doesn't create a new mvc and its used to pass data back to the vc that presented the current one. here is how to do it
First, go to your storyboard and control drag from settingVc to the exit button on top of it (to itself). it will give you 'Selection Segue' and choose IBAction goBack. This means any vc that presented the current one will get to prepare if they implement this method. In other words, you're putting out a protocol and the presenter vc will conform by implementing goBack IBAction. here is how to implement that. In your Mater vc
//You must have this function before you do the segue in storyboard
#IBAction func goBack(segue: UIStoryboardSegue){
/* this is optional
if let stv = segue.sourceViewController as? SettingsTableViewController{
self.name = stv.nameTextfield?.text
}
*/
}
In your setting vc (current vc)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//make sure you set the id of your segue to "Your Go back Unwind segue". ofc u can name it whatever
if segue.identifier == "Your Go back Unwind segue"{
if let mtvc = segue.destinationViewController as? MasterTableViewController{
mtvc.name = nameTextField.text
print("Segue was used")
}
}
}
and done method should be something like this
#IBAction func doneEditing(sender: UIButton) {
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
I know this is Duplicate Question, but i didn't find the solution for my problem. I have two controllers. HomeViewcontroller's variable of "myValue"'s value can't be set by using ViewController class.I don't know the issue here but code is working without error.But when i print "myValue" it shows nil.Destination view controller is not the controller that i want to set variable value. Destination view controller is a tab bar controller, so i want to set the variable value of first tab bar and second tab bar view controllers. This is my code :-
import UIKit
class ViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var viewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
viewController.myValue = 8888
}
}
}
this is the other view controller
import UIKit
class HomeViewController: UIViewController {
var myValue: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
println(myValue)
}
}
You don't need to instantiate the viewcontroller, as it is already been done, you just need to fetch the viewcontroller for the current segue operation, so your code should look like below
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
let controller = segue.destinationViewController as! UITabBarController
let homeController = controller.selectedViewController as !HomeViewController
homeController.myValue = 8888
}
}
Hope it helps.
Change to :
let viewController = segue.destinationViewController as! HomeViewController
You're printing the value of myValue in viewDidLoad which is called when you're instantiating it from your storyboard. You're setting myValue after creating your instance. Try to print myValue in viewWillAppear for example, then it should be set.
I'm really having trouble instantiating a custom view controller.
This is my storyboard set up. The third view controller is the one I'm trying to present.
I've tried both of these methods.
1: This results in black screen.
var searchController: SearchController = SearchController()
self.presentViewController(searchController, animated: true, completion: nil)
2: This results in a white, empty view controller popping up.
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let searchController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("searchController") as! UIViewController
self.presentViewController(searchController, animated: true, completion: nil)
Here is the code for the actual view controller:
class SearchController: UIViewController {
lazy var searchBar: UISearchBar = UISearchBar(frame: CGRectMake(0, 0, 200, 20))
override func viewDidLoad() {
super.viewDidLoad()
var leftItem = (UIBarButtonItem)(customView: searchBar)
self.title = "Search"
self.navigationItem.leftBarButtonItem = leftItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
It's very confusing because another custom view controller I am is presented properly while using method #1 above. Why wouldn't it work for this controller?
Thanks a lot everyone.
When using Storyboards you don't need to use presentViewController or instantiate your view controllers manually. Instead it's best to use a segue to move from one UIViewController to the next.
1. In your case, you want a show segue, which it looks like you've already done judging by your storyboard.
2. You need to give your segue an Identifier which you can do by selecting your segue and going to the Attributes Editor.
3. To perform your segue just call the following from your first view controller (instead of presentViewController).
self.performSegueWithIdentifier("YourIdHere", sender: self)
This will cause the storyboard to instantiate your SearchViewController and present it in the way that you've defined for that segue.
4. If you want to pass any values to your SearchViewController, you can override prepareForSegue in UIViewController. In your first view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let searchViewController = segue.destinationViewController where segue.identifier == "YourIdHere") {
// Now you can set variables you have access to in `searchViewController`.
}
}
And that should be it!