Say I have two screens. Screen A has a picker and a button, which triggers a segue to screen B, which displays some content depending on the option selected by the picker.
How do I have the information as to what picker was selected in A, passed to B? So far, I have A doing:
#IBAction func pickThing(_ value: Int) {
self.thing = value;
}
Which seems to work; I believe that it is detecting the value and storing it. However, when I try adding #IBOutlet weak var thingLabel: WKInterfaceLabel! to match the label in B, I can only set the value of it when the app first loads.
If I put self.thingLabel.setText("test") in the awake() function, it sets the label to "test", so that works. But changing it to self.thingLabel.setText("thing \(self.thing)") doesn't work - it sets it to whatever self.thing is initialized as, but doesn't change it later. So awake() is not the right method to use. I've also tried putting it in willActivate and in pickThing, but neither of them did anything.
Is there some method that gets called when a screen is switched to? If not, how can I send data from one screen to the next?
For example on ViewController A use this function
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ViewControllerB {
vc.thing = self.thing
}
}
Or you can use closures in same methods and callback change from B ViewController on A
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ViewControllerB {
vc.clouser = { [weak self] thingB in
guard let `self` = self else { return }
self.thing = thingB
}
}
}
You need to make a variable in view controller B titled something like "parentVC," which would be of view controller A's class. In view controller A's class, you need to call prepare(for segue UIStoryboardSegue, sender: Any?). In this method, you can access the segue's destination property, which would be view controller B. From here you can set view controller B's "parentVC" property to "self," i.e. view controller A. Then in view controller B's class you can access properties of view controller A by using the "parentVC" variable. The code in view controller A's class would look something like this:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ViewControllerB {
let viewControllerB = segue.destination as? ViewControllerB
viewControllerB.parentVC! = self
}
}
Related
So I experimented with these 2 different ways of declaring a ViewController variable and it seemed to offer me the same results. However, I do feel there must be a difference between setting the destinationVC variable because if not, won't people use the more straightforward way of just declaring a new object?
[using segue.destination as! ViewControllerName]
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "changeCityName" {
let destinationVC = segue.destination as! ChangeCityViewController
destinationVC.delegate = self
}
}
[using ViewControllerName()]
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "changeCityName" {
let destinationVC = ChangeCityViewController()
destinationVC.delegate = self
}
}
In the prepareForSegue method, these two methods of creating a new VC differs greatly.
If you use segue.destination, you refer to the specific VC that the segue is going to, i.e. the one in your storyboard that the segue is connected to. If you create a new VC, then the VC you created won't be the same one as the segue is going to. i.e. you are dealing with a separate VC. Setting the delegate of the newly created VC won't do anything to the VC that is actually being presented.
If you are talking about the difference between using a segue to present a VC and this:
let vc = SomeViewController()
self.present(vc, animated: true)
Then the difference is less. If you use segues, then the views in the view controller will be read from the storyboard (NIB) file. If you create the VC by calling the initializer, you will have to handle adding the views in your view controller class.
Result may be visually same but its not true.
If you don't put any code inside prepare(for segue) still you will get same result(visually)
prepare(for segue) is called when UIViewControllers are connected through storyboard.
Since UIViewControllers are already connected in storyboard, so the destination UIViewController is called on your desired event.
In your first case using (segue.destination as! ViewControllerName) which is correct way of using segue.
Before going further one more thing is to be discussed about and that is
Why we are required to write code inside prepare(for segue) if its already connect through storyboard
1.From one button action you can connect several segues depending on your requirements, but each time button is pressed same prepare(for segue) method will be called, so to differentiate which UIViewController is to be called we do something like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "FirstViewControllerIdentifier")
{
}else if(segue.identifier == "SecondViewControllerIdentifier"){
}else if(segue.identifier == "ThirdViewControllerIdentifier"){
}else{
// and so no
}
}
Now here we get object of destination controller(UIViewController) already being prepared.So we are not required to make a new object of destination controller
2.We can pass data to destination controller and also we can set delegate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "FirstViewControllerIdentifier")
{
// here we get object of first view controller and set delegate
let firstVC = segue.destination as! FirstViewController
firstVC.delegate = self
}else if(segue.identifier == "SecondViewControllerIdentifier"){
// here we get object of second view controller and pass some data to it
let secondVC = segue.destination as! SecondViewController
secondVC.someData = someData
}else if(segue.identifier == "ThirdViewControllerIdentifier"){
}else{
// and so no
}
}
Now in your second case using ViewControllerName() (the wrong code)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "FirstViewControllerIdentifier")
{
// here we create object of first view controller.This object is different from the destination view controller
//you create an object and set a delegate but after that you are not using that object and that object is totallu useless.
let firstVC = FirstViewController()
firstVC.delegate = self
// above code does not affect any thing but the contoller which is to be presented is destination view controller which is connected through storyboard
}
}
Hope you understand how to use segue and let me know if there is any problem
I'm working on a macOS project where I have a split view containing 2 other ViewControllers and I can't figure out how to access the ViewControllers from my primary window's ViewController.
this is the setup:
Basically what I'm trying to do is use the Button in my ViewController on the top-left to access the Label in my SectionController on the right, which is embedded in my split view.
Since I can't create an IBAction or IBOutlet for a control in a different ViewController, I can't figure out how to get these to be connected. My current workaround has been to have a property on my AppDelegate and then access the main shared application delegate, but that feels hacky and won't scale. I'm completely lost as to how to proceed. I'm ok with using a function to pass data or whatever to the other ViewController(s).
I'm using Swift 4 with Xcode 9 (beta).
Any ideas?
Of course you can't create IBAction or IBOutlet for a control in a different ViewController!!
But simply each view controller in the hierarchy has a reference for its child view controllers.
Method 1:
#IBAction func buttonTapped(_ sender: Any) {
let splitViewController = self.childViewControllers[0] as! YourSplitViewController
let targetViewController = splitViewController.childViewControllers[0] as! YourTargetViewController
targetViewController.label.text = "Whatever!"
}
Method 2:
It may be better if you took a reference for each child controller in your "prepare for segue" method
ContainerViewController:
var mySplitViewController: YourSplitViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "splitViewSegue" {
self.mySplitViewController = segue.destination as! YourSplitViewController
}
}
YourSplitViewController:
var aViewController: YourFirstViewController?
var bViewController: YourSecondViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "aViewSegue" {
self.aViewController = segue.destination as! YourFirstViewController
} else if segue.identifier == "bViewSegue" {
self.bViewController = segue.destination as! YourSecondViewController
}
}
So you can access it like that in your container view controller:
#IBAction func buttonTapped(_ sender: Any) {
self.mySplitViewController.firstViewController.label.text = "Whatever!"
}
I have a UITableView inside of a UIViewController (lets call that view controller B) that I fill with objects one at a time. The objects are created in a previous UIViewController (view controller A), and added to the table's data source (which is also view controller B) in the prepare( for segue:_) function of view controller A. This adds that one object to the table, at which point the user can tap an "Add to Table" cell that brings them back to controller A, and the process is repeated. The problem is that each time I return to view controller B, the Table View only has the most recently added object. I believe this is because each segue is creating a new instance of the ViewController it is moving to. How can I prevent this, or find a better place to store the tableview data source that will not be re-instantiated and overwritten as an empty list each time?
View Controller A
class ViewControllerA : UIViewController {
var object = MyObject(...)
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewB = segue.destination as! ViewControllerB
viewB.dataSource.append(object) //
}
}
View Controller B
class ViewControllerB: UIViewController, UITableViewDataSource, UITableViewDelegate {
var dataSource = [MyObject]()
//DataSource functions fill table with dataSource array objects
...
}
The reason you lost your data is because that when view controller B is recreated every time when you trying to do the segue. So to keep the data, you must pass the data back from view controller B to view controller A. Then when you click on something else on view controller A, you need to append the new data to the existing one and then pass to controller B again.
To pass data back, you need to look at unwind segue. Here is a good example of the implementation.
You can pass the entire array back to ViewController A when you tap that "Add to Table" button. Then, append the new object to the entire list and assign it to the variable in ViewController B
A:
class ViewControllerA : UIViewController {
var dataSource = [MyObject]() // Now you have a dataSource variable in both viewControllers
var object = MyObject()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewB = segue.destination as! ViewControllerB
dataSource.append(object) // Add the new object
viewB.dataSource = dataSource // Assign the newly-updated array
}
}
B:
class ViewControllerB: UIViewController, UITableViewDataSource, UITableViewDelegate {
var dataSource = [MyObject]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewA = segue.destination as! ViewControllerA
viewA.dataSource = dataSource
}
}
You are correct that this is happening because each time you segue back from ViewControllerB to ViewControllerA, ViewControllerB is deallocated from memory, along with its datasource. I would recommend passing a dataSource array from ViewControllerA when you instantiate ViewControllerB in prepareForSegue. Something like this...
class ViewControllerA : UIViewController {
var selectedItems: MyObject = []
var object = MyObject(...)
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewB = segue.destination as! ViewControllerB
viewB.dataSource = selectedItems
}
That way, ViewControllerA is keeping track of all selected items even after ViewControllerB is deallocated.
I have a view controller with a lot (36) of buttons, and I want each of these buttons to segue to a particular view controller based on a variable that was set earlier in the program. In other words, any button could potentially go to 15 different view controllers based on a variable that was sent to the viewcontroller containing the buttons...
I think I can make this work if I click and drag each button to every viewcontroller... but it seems silly and messy.
I tried doing something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if variable == "Whatever" {
let send = segue.destination as! AViewController
send.variablesent = (sender as! UIButton).title(for: .normal)!}
}
But this only works if I click and drag the button in the storyboard to the "AViewController".
Any help is appreciated, thanks!!
For that you can make segue from SourceViewController to DestinationViewController instead of from Button to Controller, After that when you call perfromSegue in your button action then pass button reference as sender in perfromSegue call.
#IBAction func buttonAction(_ sender: UIButton) {
self.performSegue(withIdentifier: "segueIdentifier", sender: sender)
}
Now in prepareForSegue cast sender to UIButton and set title according to it.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if variable == "Whatever" {
let send = segue.destination as! AViewController
if let button = sender as? UIButton {
send.variablesent = button.titleLabel?.text ?? "Default value"
}
}
}
Since i'm new and a newbie on Swift can someone tell me how I can get the label value on screen 1 to show on screen 2. Thanks
Edit: So I tried the way you told me but for some reason the Label text did not change in View 2. Any help? Thanks
I am passing textFiled data to destViewController to show how segue performs when passing data.
Note: If you want to pass string data to your destVC.You can assign your string like var someValue: String
mainStoryBoard:
MainVC:
#IBOutlet weak var textField: UITextField!
//or you can assign string like var someValue: : String
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewController = segue.destination as! destVC
viewController.dataText = textField.text // someValue.text
}
DestVC:
#IBOutlet var label: UILabel!
var dataText = String()
override func viewDidLoad()
label.text = dataText
}
Output:
You already have a segue between ViewController1 and ViewController2 it seems, so now you just need to pass some date from one to another. This can be done in the function prepare(for segue: UIStoryboardSegue, sender: AnyObject?) which is called when you transition from ViewController1 to ViewController2.
The UIStoryboardSegue class has a property called source of type UIViewController and another property called destination which is also a UIViewController.
So, if you define a property on your ViewController2, like so:
var labelValue: String
Then you can pass a value to it in your prepareForSegue defined on ViewController1 like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let viewController2 = segue.destination as? YourViewController2 else {
return
}
viewController2.labelValue = theValueFromViewController1
}
Here is tutorial telling you a bit more (in Objective C though)
And this is also a fine introduction.
Hope that helps you.
I'll consider you have ViewController class VC1 (screen 1) & ViewController class VC2 (screen 2). I see from the screenshot that, you're already using a segue to go from the VC1 to VC2 view.
Now, declare a variable in your VC2 class let's call it labelValue,
class VC2 {
var labelValue: String?
...
}
Whenever you use a storyboard segue to move from one viewcontroller's view to other viewcontroller's view a method named,
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
will get called if it's overriden in the source viewController class (scene1 in your case). In this method you'd be able to access the destination view controller (scene 2) and it's properties. So for your case you can implement it like this,
class VC1 {
//I have assumed the below label will hold the value which you want to pass it to the scene 2.
var lblResult: UILabel!
......
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// You can access the VC2 instance from the segue
if let vc2 = segue.destinationViewController as? VC2 {
vc2.labelValue = lblResult.text
}
}
Now once you implement the prepareForSegue: method correctly as shown above, you'd be able to get the label's value from scene 1 to labelValue property of scene2.
Do learn more about segues here : More About Segues and Storyboards here.
================================
Edit:
Coming to your edited question, in your View2 viewController, you've declared labelNumber1 as String and not as UILabel, so whenever you pass the value to labelNumber1, the value will be containing in this variable and it will not be shown in the screen since it is just a normal string. So what you can do is, declare the labelNumber1 as UILabel
class View2: UIViewController {
#IBOutlet var labelNumber1: UILabel!
.....
}
and in your prepare for segue in ViewController1 make the following change,
class ViewController1: UIViewController {
.....
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// You can access the VC2 instance from the segue
if let view2 = segue.destinationViewController as? VC2 {
view2.labelNumber1.text = lblResult.text
}
}
However if you still want to keep labelNumber1 as String, and to verify whether the value has been passed from ViewController1 during segue, add below line to viewDidLoad() of View2 ViewController.
class View2: UIViewController {
.....
override func viewDidLoad() {
super.viewDidLoad()
print("labelNumber1: \(labelNumber1)")
}
}
And you can see the printed value of labelNumber1 in the console.
HTH :)