I am new to Swift and Xcode. I am building an Financial Expense ios app.
In my first view controller, I created a referencing outlet for a label called expenseNum.
In my second view controller, I have a function for a button called Add Expense. When it is clicked, I need it to update the expenseNum variable with the amount of the expense.
What is the best way to go about this? I had created an object of the first view controller class and accessed it like "firstviewcontroller.expenseNum" but this will create a new instance of the class and I need it to be all the same instance so it can continuously add to the same variable. Thanks for the help!
You need a delegate
protocol SendManager {
func send(str:String)
}
In first
class FirstVc:UIViewcontroller , SendManager {
func send(str:string) {
self.expenseNum.text = str
}
}
when you present SecondVc
let sec = SecondVc()
sec.delegate = self
// present
In second
class SecondVc:UIViewcontroller {
var delegate:SendManager?
#IBAction func btnClicked(_ sender:UIButton) {
delegate?.send(str:"value")
}
}
// setting delegate
in viewDidLoad of SecondVc
if let first = self.tabBarController.viewControllers[0] as? FirstVc {
self.delegate = first
}
There are several ways you can pass data from ViewController2 to another ViewController1
The best way here is Protocol Delegates
Please follow below steps to pass data
In Your SecondViewController from where you want to send data back declare a protocol at the top of class declaration
protocol SendDataBack: class {
func sendDataFromSecondVCtoFirstVC(myValue: String)
}
Now in the class , declare a object of your protocol in same ViewController
weak var myDelegateObj: SendDataBack?
And now in your Add Expense button action just call the delegate method
myDelegateObj?.sendDataFromSecondVCtoFirstVC(myValue: yourValue)
Now go to your first ViewController
the place from where you have pushed/present to SecondViewController you must have taken the object of SecondVC to push to push from first
if let secondVC = (UIStoryboard.init(name: "Main", bundle: nil)).instantiateViewController(withIdentifier: "secondVCID") as? SecondViewController {
vc?.myDelegateObj = self
self.navigationController?.pushViewController(secondVC, animated: true)
**OR**
self.present(secondVC, animated: true, completion: nil)
}
now in your FirstViewController make an extension of FirstViewVC
extension FirstViewController: SendDataBack {
func sendDataFromSecondVCtoFirstVC(myValue: String) {
}
}
I think you can make a variable in your properties in second ViewController (before viewDidLoad method)
var delegate: FirstViewController? = nil
and use from the properties of the first view controller anywhere of the second view controller.
delegate!.mainTableView.alpha=1.0
//for example access to a tableView in first view controller
The simplest way to achieve this is to use a public var. Add a new Swift file to your project, call it Globals. Declare the public variable in Globals.swift like so:
public var theValue: Int = 0
Set its required value in the first ViewController, and you'll find you can read it in the second with ease.
Related
I'm trying to pass data from a SecondViewController to my FirstViewController when I click on my back button (UINaviagtionController).
For pass my data from FirstViewController to the SecondViewController I do this:
if segue.identifier == "showSecondVC" {
let vc = segue.destination as! SecondViewController
vc.rows = rows[pathForAVC]
vc.lap = lapTime[pathForAVC]
vc.indexPath = pathForAVC
}
But I have no idea how to pass data from SecondViewController to the FirstViewController and I really don't understand topics about it on Stack Overflow.
I want to transfer it when I click here:
Thanks.
You can use delegate pattern for that. You can grab the back button press event like this and update the data
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController {
self.delegate.updateData( data)
}
}
For more information on delegates you can go through this.
Actually things depend on your requirement, if you want data to be updated in first view controller as soon as it is updated in second view controller, you would need to call delegate as soon as the data is updated. But as in the question you have mentioned that you want it to be updated on back button only, above is the place to do it.
Another way would be to have Datasource as singleton so that it is available to all the view controllers and the changes are reflected in all view controllers. But create singleton if absolutely necessary, because these nasty guys hang around for entire time your application is running.
You should have a custom protocol such as:
public protocol SendDataDelegate: class {
func sendData(_ dataArray:[String])
}
Here I suppose you want to send a single array back to FirstViewController
Then make your first view controller to conform to the custom protocol, such as:
class FirstViewController: UIViewController, SendDataDelegate
In the second view controller, create a delegate a variable for that protocol, such as:
weak var delegate: SendDataDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.sendData(arrayToSend)
In the first viewController, in the prepare for segue function just set the delegate like
vc.delegate = self
Hello I tried using delegate to connect my Master and Detail view controllers.
In the master view controller I got this above the class:
protocol CategorySelectionDelegate: class {
func categorySelected(id: Int)
func progress(percentage: Float)
}
and within the class I declare it as:
weak var delegate: CategorySelectionDelegate?
In the detail view controller I do
extension DetailViewController: CategorySelectionDelegate {
func categorySelected(id: Int) {
print("set row \(id)")
selectedCategoryID = id
}
func progress(percentage: Float) {
dispatch_async(dispatch_get_main_queue(), {
self.progressView.setProgress(percentage, animated: true)
})
}
}
So far so good. Everything works. But when I try to connect them both by doing
self.delegate = DetailViewController
Inside the viewDidLoad in my MasterViewController it says "cannot assign value of type 'DetailViewController.Type' to 'CategorySelectionDelegate?'"
What am I Doing wrong here?
First of all you are assigning delegate in wrong class if you have created delegate in master view controller class then you want to respond to delegate in detail view controller then you need to assign like this in Detail view controller...
Let masterVC : MasterlViewController = MasterViewController()
masterVC.delegate = self
Now in above line you will get same error as you told above so you must include delegate name in class as we do for uitableview datasource ...
Class DetailViewController :UIViewController ,CategorySelectionDelegate
I have an optional bool variable called showSettings on my first view controller which is called ViewController, and I'm popping from SecondViewController back to ViewController.
Before I pop, I want to set the bool to true. Seems wrong to instantiate another view controller since ViewController is in memory.
What's the best way to do this? I'm not using storyboards, if that's important for your answer.
Thanks for your help
So I figured it out, based mostly from this post – http://makeapppie.com/2014/09/15/swift-swift-programmatic-navigation-view-controllers-in-swift/
In SecondViewController, above the class declaration, add this code:
protocol SecondVCDelegate {
func didFinishSecondVC(controller: SecondViewController)
}
Then inside of SecondViewContoller add a class variable:
var delegate: MeditationVCDelegate! = nil
Then inside of your function that your button targets, add this:
self.navigationController?.popViewControllerAnimated(true)
delegate.didFinishSecondVC(self)
What we're doing here is doing the pop in SecondViewController, and not passing any data, but since we've defined a protocol, we're going to use that in ViewController to handle the data.
So next, in ViewController, add the protocol you defined in SecondViewController to the list of classes ViewController inherits from:
class ViewController: UIViewController, SecondVCDelegate { ... your code... }
You'll need to add the function we defined in the new protocol in order to make the compiler happy. Inside of ViewController's class, add this:
func didFinishSecondVC(controller: SecondViewController) {
self.myBoolVar = true
controller.navigationController?.popViewControllerAnimated(true)
}
In SecondViewController where we're calling didFinishSecondVC, we're calling this method inside of the ViewController class, the controller we're popping to. It's similar to if we wrote this code inside of SecondViewController but we've written it inside of ViewController and we're using a delegate to manage the messaging between the two.
Finally, in ViewController, in the function we're targeting to push to SecondViewController, add this code:
let secondVC = secondViewController()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
That's it! You should be all set to pass code between two view controllers without using storyboards!
_ = self.navigationController?.popViewController(animated: true)
let previousViewController = self.navigationController?.viewControllers.last as! PreviousViewController
previousViewController.PropertyOrMethod
I came across this while looking for a way to do it. Since I use Storyboards more often, I found that I can get the array of controllers in the navigation stack, get the one just before the current one that's on top, check to see if it's my delegate, and if so, cast it as the delegate, set my methods, then pop myself from the stack. Although the code is in ObjC, it should be easily translatable to swift:
// we need to get the previous view controller
NSArray *array = self.navigationController.viewControllers;
if ( array.count > 1) {
UIViewController *controller = [array objectAtIndex:(array.count - 2)];
if ( [controller conformsToProtocol:#protocol(GenreSelectionDelegate)]) {
id<GenreSelectionDelegate> genreDelegate = (id<GenreSelectionDelegate>)controller;
[genreDelegate setGenre:_selectedGenre];
}
[self.navigationController popViewControllerAnimated:YES];
}
Expanding upon the answer by Abdul Baseer Khan:
For cases where the current view controller may have been loaded by different types of previous view controller, we can use the safer as? call instead of as!, which will return nil if the controller is not what we were looking for:
let previousVC = self.navigationController?.viewControllers.last as? AnExampleController
previousVC?.doSomething()
Although, you would need to repeat that for each different view controller that could load the current view controller.
So, you may want to, instead, implement a protocol to be assigned to all the possible previous view controllers:
protocol PreviousController: UIViewController {
func doSomething()
}
class AnExampleController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class AnotherController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class CurrentController: UIViewController {
// ...
func goBack() {
let previousVC = self.navigationController?.viewControllers.last as? PreviousController
previousVC?.doSomething()
}
}
I have sorted the original array in popover controller. Now I want to send that array back to the original view controller for tableview and map view.
Below is my code
If propertyNameSrt == false
{
if ascSorting == false
{
properties.sort(sorterForbuildingAsc)
}
else
{
properties.sort(sorterForbuildingDesc)
}
}
My array is properties which includes custom object.
How can pass this to my original view controller?
Thanks in advance,
Dhaval.
You can use delegate(protocol) methods to send back data to previous view controller.
IN CURRENT VC:
protocol MyProtocol: class
{
func sendArrayToPreviousVC(myArray:[AnyObject])
}
Make a var in your class.
weak var mDelegate:MyProtocol?
Now call the protocol method when you pop the view controller, with your "properties" array as parameter.
mDelegate?.sendArrayToPreviousVC(properties)
IN PREVIOUS VC:
In your previous VC, set the mDelegate property to self, when you push the current VC.
currentVC.mDelegate = self
//PUSH VC
Now implement the protocol method in your previous VC.
func sendArrayToPreviousVC(myArray:[AnyObject]) {
//DO YOUR THING
}
I have a calculator class, a first ViewController to insert the values and a second ViewController to show the result of the calculation. Unfortunately I get a error called "Can't unwrap Optional.None" if I click the button. I know it's something wrong with the syntax, but I don't know how to improve it.
The button in the first Viewcontroller is set to "Segue: Show (e.g. Push)" in the storyboard to switch to the secondViewController if he gets tapped.
the calculator class is something like:
class Calculator: NSObject {
func calculate (a:Int,b:Int) -> (Int) {
var result = a * b
return (result)
}
}
The Viewcontroller calls the function, inserts a/b and wants to change the label which is located in the secondviewcontroller:
class ViewController: UIViewController {
#IBAction func myButtonPressed(sender : AnyObject) {
showResult()
}
var numberOne = 4
var numberTwo = 7
var myCalc = Calculator()
func showResult () {
var myResult = myCalc.calculate(numberOne, b: numberTwo)
println("myResult is \(String(myResult))")
var myVC = secondViewController()
myVC.setResultLabel(myResult)
}
And here is the code of the secondViewController
class secondViewController: UIViewController {
#IBOutlet var myResultLabel : UILabel = nil
func setResultLabel (resultValue:Int) {
myResultLabel.text = String(resultValue)
}
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
In Swift, everything is public by default.
Define your variables outside the classes:
import UIKit
var placesArray: NSMutableArray!
class FirstViewController: UIViewController {
//
..
//
}
and then access it
import UIKit
class TableViewController: UITableViewController {
//
placesArray = [1, 2, 3]
//
}
The problem here is that the FirstViewController has no reference to the instance of SecondViewController. Because of this, this line:
secondViewController.setResultLabel(myResult)
does nothing (except probably causing the Can't unwrap Optional.None error). There are a few ways to solve this problem. If you are using storyboard segues you can use the -prepareForSegue method of UIViewController. Here is an example:
In FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue!,sender: AnyObject!){
//make sure that the segue is going to secondViewController
if segue.destinationViewController is secondViewController{
// now set a var that points to that new viewcontroller so you can call the method correctly
let nextController = (segue.destinationViewController as! secondViewController)
nextController.setResultLabel((String(myResult)))
}
}
Note: this code will not run as is because the function has no access to the result variable. you'll have to figure that out yourself :)
I think the issue here is, you are trying to set the UI component (here, its the label : myResultLabel)
When segue is fired from first view controller, the second view has not yet been initialized. In other words, the UI object "myResultLabel" is still nil.
To solve this, you will need to create a local string variable in second controller. Now, set that string to what you are trying to display, and finally, set the actual label in "viewDidLoad()" of the second controller.
Best Regards,
Gopal Nair.