I have the following setup:
I have a UIViewController embedded in a UINavigationController. I have a second UIViewController embedded in a second UINavigationController. I need to call the second UIViewController in two ways. I have two buttons set up on the first UIViewController. The first button has a segue created by control-click-drag between the button and the second UINavigationController. When I click the button, the second view controller displays. The second button calls the created segue using performSegueWithIdentifier like this:
#IBAction func segueTwoButton(sender: AnyObject) {
performSegueWithIdentifier("ShowSecondView", sender: self)
}
In both cases the second view controller displays correctly.
But, I have some information that I want to pass. So in the prepareForSegue function, I have this:
let vc = segue.destinationViewController as! SecondViewController
This fails, because the segue is going through the second UINavigationController to get to the second UIViewController.
I've come up with two ways of handling this.
I can use the following in the prepareForSegue
let vc = segue.destinationViewController.childViewControllers[0] as! SecondViewController
I can add a UINavigationController class and then pipe it through there, but that seems like a lot of work to do this.
Both of these ways work, but is this the best way, or is there some other way that I should consider handling it?
Second question - Assuming that I do it this way and put a UINavigationController class in so that I can pass the information along, what is the proper way to unwind? I can create an unwind segue that will go straight from the second view controller, back to the first, but is it acceptable to do it this way? Or would I need to unwind to the UINavigationController class and then to the first view controller?
I'm not sure why you decided to embed another navigation controller within another. Generally, if you embed a navigation controller one time that will suffice for the rest after you've set up your other segues. So first thing's first delete that second nav controller.
Secondly, from experience, I would always avoid control dragging from anything other the view controller itself. Otherwise, you get duplicate calls to perform segues. So go ahead and delete all remnants of the prior segues you created with the buttons. Instead, Control drag from the top yellow view controller icon to the next view controller. make sure you name the segue arrow so that you can properly performsegueWithIdentifier
Now you should have First View directly segued to the Second View. This will be easier to pipe or funnel information through the segue function exactly like you had coded above
let vc = segue.destinationViewController as! SecondViewController
Global Variable ( will replace segue funneling information )
Lets take this as an example. Say I wanted to recored the user's name on the first view controller.
// First View Controller
private let userName: String
// After that we can create a new object called NameSingleton
class NameSingleton {
// NameSingleton.swift
var UserName:String = " " // Since its global this value will change
static let sharedinstance = NameSingleton()
}
// Now hop back over to your first ViewController
// before you trigger your segue assign whatever data you need to your
// singleton. In this case, a name.
// First View Controller.swift
#IBAction func segueTwoButton(sender: AnyObject) {
NameSingleton.sharedInstance.UserName = "Hi"
performSegueWithIdentifier("ShowSecondView", sender: self)
}
// Now if you go to the second view controller and access it, the name
// will be stored in the singleton. (Great for moving huge pieces info
// (i.e. structs, objects, lists)
Related
i have 3 buttons, 3 Classes and 3 Viewcontrollers same UI, only different values.
so i want to do 3 buttons showed 1 Viewcontrollers but used different Classes.
My logic:
when pressing the button > set class > present vc
#IBAction func openTest(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ExSim")
// i want to change class here
present(vc, animated: true)
}
You have a basic misunderstanding. When you add a view controller to a storyboard, you tell the storyboard what subclass of view controller you are adding, and give it a unique identifier. Any time you instantiate a view controller from your storyboard using a specific identifier, you will always get a view controller of the subclass that’s defined in the storyboard. You can’t fetch a view controller from a storyboard and then transform it to a different class.
If you have 3 different screens that have have the same UI but different contents, you should use a model object to describe the contents that view controller displays. Always use the same identifier to instantiate it, and install a different model object into it depending on which button is pressed. Have the view controller load its contents into its views from the model object.
If you don’t know what a model object is, you need to read up on design patterns like MVC and MVVM, and maybe try a few tutorials that create apps using the MVC and MVVM design patterns
In my macOS application I have menu items, which are replicated also in main UI. Application is consisted of main window with its delegate and single view along with its view controller. In app delegate I capture menu item click action, then I need to send this event to my view controller in order to take appropriate actions and also update main UI.
Question is how to access my view controller (NSViewController) from app delegate?
If you have window as an IBOutlet you can do
var rootViewController: MyViewController? {
return window!.contentViewController as? MyViewController
}
func sendPressed(_ sender: Any?) {
rootViewController?.sendPressed()
}
If you don‘t have a window variable you can get it through
NSApplication.shared.orderedWindows.first!
Actually you don't need a reference to the controller. There is First Responder
Declare the IBActions
In Interface Builder connect the menu items to the First Responder (red cube) of the target object and choose the appropriate action.
The action will be executed in the first object of the responder chain which implements the action.
I have a ViewController, which holds a ContainerView.
So therefore I added a class for the ViewController() and a class for the ContainerViews ViewController(). To Illustrate it in better, I have attaced a photo of my storboard scene:
When I click the button inside the ContainerView I want to update the Label with a different text, how do I achieve this? IMPORTANT: The ContainerViews button is an IBAction in a seperate ViewController and I want to update the IBOutlet in the seperate ViewController() class. Thanks very much for help!!!
Getting the parent view controller in the container view and casting it to the type of your parent controller (as mentioned in
thefredelement's comment) will work, however it has some drawbacks.
Your container view should really be a view that just happens to be
a MVC itself. As a view, it should not know who is controlling it,
It might however know that it has a delegate that it can send
messages to. For example does a UITextView know that it is a child
of a particular class of view controller? No, but it knows it might
have a delegate that it can send messages to.
You can't reuse this view controller as a child of any other view controller since it is tightly coupled to the parent class
By simply casting the received parent to type of your parent you are bypassing the type checking.
So the "quick fix" might be to do as suggested in the comment, however, I am providing this answer as a suggestion as how I think in the long run this should be handled. Hopefully others find it useful. I know for me going through the thought process was helpful.
Ideally I think you should declare a variable in the ContainerViewController that either is the Class of your parent view controller, or even better a protocol that your parent controller will conform to. Then in the prepare for segue of the parent view controller you would set this variable.
At the same time your parent should (or at least can) have a variable declared as being of the type of your container view or another protocol which the container will conform to. Then also in the prepare for segue of the parent, get a handle for the Container view controller and store it in the variable you declared.
So something like this:
In your ContainerViewController
var myContainerViewDelegate: ParentViewControllerOrProtocol
In your parent view controller
weak var myChildViewOutlet: ChildViewControllerOrProtocol
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embededSegueToChild" {
if let dvc = segue.destinationViewController.contentViewController as? ChildViewControllerOrProtocol {
dvc.myContainerViewDelegate = self
myChildViewOutlet = dvc
}
}
This way your ContainerViewController looks like view object (which I think is what it is supposed consider to be), and the myChildViewOutlet variable is your outlet to that view (which probably should be weak).
The myContainerViewDelegate reference in the Container essentially sets up your parent as the delegate for the container view controller.
now both view controllers have a handle for each other
So I have an app that where you choose a button in the first screen it will bring you to another screen depending on which one you chose on the first one. Because I have these possible different layouts I can't just use a segue from one button to the next view controller because there is a middle screen involved. SO it is like this:
1 ---> 2 ---> 3a || 3b || 3c || 3d etc...
So your choice on the first screen dictates what your third screen will look like.
So instead I was thinking to have a value associated with each button in the first view, have it sent to the second view via a segue, and then sent again to the third view and tell it to use the proper segue to the proper third view Controller based on the value associated with the first view. But I can't do that because I can't set the second screen to segue to all the possible layouts for the third screen...At least i think I can't.
Bottom line question: is there anyway to go to another view controller after clicking a button without a segue while also sending data to that next view controller, again, without a segue to store it in?
Please help mehhh
Follow these steps...
1: Create a Separate file called Manager.swift and place this code in it...
//manager.swift
import Foundation
struct Manager {
static var messageText = String()
}
2: Clean your project by pressing Shift+Command+K.
3: In the first view controller set the messageText to the data you want to pass...
Manager.messageText = self.dataToPass
4: In the second view controller retrieve the data and set the content to the message text...
self.dataToReceive = Manager.messageText
5: Your Finished!!
So instead I was thinking to have a value associated with each button
in the first view, have it sent to the second view via a segue, and
then sent again to the third view and tell it to use the proper segue
to the proper third view Controller based on the value associated with
the first view. But I can't do that because I can't set the second
screen to segue to all the possible layouts for the third screen...At
least i think I can't.
You can do this with segues; you just have to wire them up properly. Instead of hooking the segues up to buttons, you can create them by control-dragging from the ViewController icon at the top to the next ViewController.
Once you have created such a segue, click on the resulting segue arrow and give it an identifier in the Attributes Inspector (something like "segueToVC3a").
So, you would do this multiple times for each segue from VC2 to VC3a, VC2 to VC3b, etc.
Assuming in your first viewController you decide the value of whereToGo and you pass that to your second viewController with a segue. Then in your second viewController, you could then do something like this:
var identifier: String = ""
switch whereToGo {
case "VC3a": identifier = "segueToVC3a"
case "VC3b": identifier = "segueToVC3b"
default: break
}
if identifier != "" {
performSegueWithIdentifer(identifier, sender: self)
}
I'm trying to come up with something simple, but i can't figure it out.
I have a UITabBarController and at one point I have to display another tab's UIViewController, and call a method in that new UIViewController, using data from the original UIViewController.
So basically I want to pass data to another UIViewController (that may not be initialized yet), and show the right tab.
If I use NSNotificationCenter, I'm not sure if the tab's UIViewController is initialized yet, and also it's a bit ugly to use delegation here.
What is a clean way to send and show data in the new tab?
A better idea would be to have a protocol implemented in the tab view controller the view controller can call to send data and set up any other view controller
You should have a data model of some sort (singleton?) that is accessible by both view controllers. When the second view controller is about to display its view, it should reference the data model to determine the data it should display.
int x = 1; //this is the view controller you want to go to.
MySecondViewController *secondViewController = (MySecondViewController *)self.tabBarController.viewControllers[x];
[secondViewController setDataObject:dataObject];
[self.tabBarController setSelectedIndex:x];
So what's happening is you're calling the controller from the tabBarController, it will initiate there if its not. Then you can set whatever you want on the controller, then you just switch to the selected tab.