What is the simplest way to modify variable by using the Button for later use of that variable in another View Controller? - swift

I have two view controllers. One VC with a button and a label and one VC only with a label. I created button action that can modify variable (declared in the class of that first viewcontroller) if the button is pressed and I can show that MODIFIED variable as the label in that same VC but my intent is to show that MODIFIED variable on another VC.
I want to use that button to go to the second VC where the modified variable should be displayed on the label when that VC is displayed. In first VC I declared:
var myVariable: String = "default"
In the button action:
{myVariable = "700"}
In second VC I declared:
let vc1: ViewController1 = ViewController1()
I assigned an action to the button in first VC (dragging to second VC to show the second VC) and in viewdidload of the second VC I try to acces the variable from first VC with:
label.text = vc1.myVariable and it accesses it but ...
...but the result is DECLARED/notMODIFIED state of variable. So if I show myVariable on same VC it is OK but on the second VC myVariable was not changed by the button action on the first VC.
I am trying to avoid delegation as it seems complicated..
What is the easiest way to accomplish task? Thank you guys.

To get updated value of first VC variable to second VC you can use NSNotificationCenter.
In first VC when you change value at that time you have to post notification with dictionary like following.
NotificationCenter.default.post(name: NSNotification.Name("ValueUpdate"), object: nil, userInfo: ["variable": myVariable])
For that you have to observe Notification in second VC
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(notification:)), name: Notification.Name("ValueUpdate"), object: nil)
And then you have to implement following method to get update in your second VC
#objc func handleNotification(notification: Notification) {
label.text = notification.userInfo?["variable"]
}

The SIMPLEST way to do this is to define a global variable. So you can access it from anywhere. It should be define outside of anything to be global.
#import UIKit
var myGlobalVariable = "Initial value"
class MyClass { ... }
then you can change or read it from anywhere like print(myGlobalVariable).
Note that although it is the simplest solution, it is also THE WORST solution too. Global functions and variables use cases are vary rare.

Inject your data via initializer while showing controller.
show(SecondViewController(name: name), sender: nil)
Check out this code.

#Eljer...
Although Delegation seems VERY hard at first its very easy.
You need to remember 3 steps only as a beginner ! I'll try to make it easy for you to use here.
Step 1 : Create tempVariable (of same type) on second View controller also (you can make with same name also doesnt matter)
Step 2 : Write this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let xyz = segue.destination as! abc
}
The xyz is the name you want to call it with.
The abc is the name of your second view controller where you made the other same type of variable.
What this will do is, it will make an object of EVERYTHING (that includes all variables and functions) that you have on your Second View controller available to you on your First view controller also.
To use / assign value to the label on second view controller what you need to do is...
just add :
xyz.yourVariableNameOnSecondViewController = variableNameOnFirstViewController
this will assign the value of your variable on First VC to the variable on Second VC also
Finally, to get the value updated. You need to get this updated value using viewDidLoad on your second view controller.
I hope this helps, if its still confusing ask me anything.
Gluck

Related

Saving Changes from an Unwind. Working in conjunction with <Back

I have a problem understanding what I can do with the default Back BarButtonItem and how I can instigate a rewind.
The following shows a simplified layout. I embed a ViewController in a Navigation controller and add a BarButtonItem ("Show") and connect to a second VC. This adds the "Back" button to the second controller as shown below.
In the first VC I will show the user some details, in the second VC greater details will be shown. With the show button and the default back button the user can easily navigate back and forth with expected behavior.
My problem comes in that the user will be allowed to modify the "More Details Here" and I will need to pass that to the first VC after the user unwinds back.
I cannot control-drag from the Back to the Exit icon but I have determined I can do this with a "Save" button on the navigation bar as shown. (First dragging an Navigation Item to the top, then the BarButtonItem) then control-drag to Exit icon.
With the Save button, I can initiate a segue unwind and capture the changes back in my first VC with code like the following.
#IBAction func unwindFromSecondVC(_ sender: UIStoryboardSegue) {
if sender.source is AddCharacterViewController {
if let senderVC = sender.source as? SecondViewController {
details = senderVC.newDetails
}
}
}
This creates a problem when the user makes a change and then clicks the back button thereby loosing the changes.
My preferred solution would be to have the back button initiate a segue and transfer the changes. But this does not appear to be possible. My second solution would be to have the back button notice a transfer has not been made and stop the unwind seque. Neither approach appears possible based on my research.
How should I best handle this problem while giving the user the common Back navigation?
If you want to save any changes the user has made in the second VC, you could create a SecondVCDelegate:
protocol SecondVCDelegate : class {
func detailsDidChange(newDetails: String)
}
In SecondVC, declare a delegate property:
weak var delegate: SecondVCDelegate?
And whenever the details change (the text field's value changed, or whatever event happened), you call
delegate?.detailsDidChange(newDetails: newDetails)
In FirstVC.prepareForSegue, you should set self as the delegate of SecondVC:
if let vc = segue.destination as? SecondVC {
vc.delegate = self
}
// ...
extension FirstVC : SecondVCDelegate {
func detailsDidChange(newDetails: String) {
details = newDetails
}
}
This way, whenever the newDetails change, FirstVC will be notified.
If you just want to notify FirstVC when the user leaves SecondVC, you could instead call the delegate in SecondVC.viewWillDisappear or a method like that.

Programmatically press back button for UIViewController with UITableView iOS swift

I have a UIViewController that implements UITableViewDelegate, UITableViewDataSource and that contains a UITableView as a member variable. When a user click on one of the rows of that table, the app performs a storyboard segue to open the detail view controller. That detail view controller of course has a button in the top left of the screen that is the "back" button to go back up to the UIViewController with the UIViewTable.
So, suppose that I want to programmatically "click" that back button. How exactly would I do that in swift? This is the most recent version of swift (swift 4?) in XCode 10.1.
UPDATE:
So here is how I solved this. As the answers below show, it is possible to use self.navigationController?.popViewController(animated: true) to just return to the previous view controller. What I discovered I also wanted to do, however, was to call a specific method in that view controller so that it executed a certain behavior once it got shown. It turns out that is also possible, but in my case it was a bit tricky, since that prior view controller was actually a UITabBarController. Therefore I had to get the ViewController that I was interested in from the UITabBarController. I did it like this:
let numvc = navigationController!.viewControllers.count
let tvc:UITabBarController = navigationController!.viewControllers[numvc-2] as! UITabBarController
let my_vc: MyCustomVC = tvc.viewControllers![0] as! MyCustomVC
my_vc.some_function()
Here of course MyCustomV is my custom view controller class and some_function() is the method I want to call on that class. Hope this helps someone.
When You run a segue you perform a "pushViewController" method to the next view, so if you want to go back to the previous view programmatically you just have to do is pop the last view like so:
self.navigationController?.popViewController(animated: true)
UPDATE
You just need the if statement if you have multiple segues from that viewController, if not, you can delete and just cast the next view as you wish and set the properties, let the autocomplete write the *prepare(for segue... * method for you, so You don't run into any problems
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueName" {
let destinationVC = segue.destination as! CustomViewController
destinationVC.labelExample.text = "Some text I'm sending"
}
}
Are you sure you need to "click" the button?
If all you need is to dismiss details view controller, you can just call navigationController?.popViewController(animated: true)
Or if you want to deal directly with button, you can tell it to send its actions: backButton.sendActions(for: .touchUpInside)
Or if you absolutely need to show button clicking animation, then you will need something like this (you should play and choose suitable delay):
backButton.isHighlighted = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
backButton.isHighlighted = false
backButton.sendActions(for: .touchUpInside)
}

Segue and Unwind segue without UI element

I have been looking for an answer for this on SO without success.
I have UIViewController A and B. They are NOT linked in Storyboard, and I want to perform a Segue from A to B, and upon clicking a button in B, to activate an unwindSegue to A.
So far I have :
1) The #IBAction in A to unwind back to
2) The function to prepare the Segue from A to B (In A)
3) The button to call the unwindSegue from B to A
What I'm missing is the logic of the function in 2.
More info about B:
Title: FilterView
Class: FilterViewController
Storyboard ID: FilterView
I tried creating a segue:
let segueToFilter = UIStoryboardSegue(identifier: "SegueToFilterView", source: self, destination: FilterViewController)
Thinking this might just get me what I need.
but I get this error:
Cannot convert value of type 'FilterViewController.Type' to expected argument type UIViewController
Help would be appreciated :)
I still do not understand how segues are causing problems to you, but if you really really don't want to use segues, you can try presenting and dismissing VCs programmatically.
The idea goes like this:
Create an instance of your VC
Pass data to it by setting some properties
Present!
In the VC, dismiss.
The actual code will be different if you embedded your VCs in a navigation controller. Instead of calling present and dismiss, you would call pushVC and popVC on the navigation controller.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "FilterView") as FilterViewController
vc.someProperty = someData // this is where you pass data
vc.present(vc, animated: true, completion: nil)
To pass data back, you should use the delegate pattern by creating a FilterViewDelegate that the first VC conforms to. Look up on Google for more info.

in swift, ViewController wont let me access its textfield from another class

here is what is happening https://imgur.com/a/fblot
yes i've read the article about optional
but that textfield isn't nil or without a value as it does it
when i press enter when theres a value in the text-bar
i also tried ! and ? with same results
i tried many many things and it wont work
always the same crash
can someone explain what is the reason that textfield is hard to access
i tried
var = ViewControler()
If you want to call the class function from the viewcontroller that contains the textfield change the class function to have one argument :
func changetextfieldincontroller(_ vc: NSViewController){
if let vc = vc as? ViewController{
vc.textField.stringValue = "New text"
}
}
when you call it from the controller make sure to pass self as an argument.
You need to instantiate the viewController since it looks like it's from a storyboard/xib. If that's the case, you can check this other question: How can I load storyboard programmatically from class?

How to pass map annotation on segue to mapView on another vc?

I currently have a map with annotation and would like to pass this to another map on another VC (replicate the map). How can I do this in my prepare for Segue function?
In my current map VC
if segue.identifier == "addEntrySegue",
let addEntryVC = segue.destination as? AddEntryViewController
{
addEntryVC.mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05,0.05)
let region = MKCoordinateRegionMake(annotation.coordinate, span)
addEntryVC.mapView.setRegion(region, animated: true)
}
The basic idea of passing data in prepare(for:sender:) is correct.
Obviously, you will want to make sure that the identifier and the cast of the view controller were successful, perhaps by adding a breakpoint inside that if statement. I would have thought that if this if statement was successful, that you would have crashed when you tried to interact with mapView at this point, so I wonder if this if statement is succeeding at all. If it isn't succeeding, you'll want to double check (a) the spelling and capitalization of the storyboard identifier that you specified for the segue in IB; and (b) that the base class of the destination scene is really AddEntryViewController.
But, even if you confirm that the if statement is working properly, you cannot just update UIKit controls of the destination view controller in prepare(for:sender:), because they haven't been created yet. Thus, you can't reference the map view in prepare(for:sender:).
You should have an annotation property in your AddEntryViewController, and the prepare should simply set that. Then, in viewDidLoad of AddEntryViewController, by which point the views have been created, add that annotation to the map view and set the region/camera.