Pass data back to previous viewcontroller - iphone

I am trying to pass data BACK TO previous viewController.
Does anyone know how to pass data back from ViewController B to ViewController A? So I want a string to go 'from' BIDAddTypeOfDealViewController to BIDDCCreateViewController. A user edits viewController B and I want that edited data back in ViewController A where I then use it.
I am using the 'passing data back' section of this answer. How mine differs: Point 3 and 6 just mentions when views are popped so I have put that code in viewWillDisappear. I think that is correct?
Also on Point 6 I did not initialise with nib as that is old. I'm using storyboards. And I did not add that last line as I do not believe I would have to push it. Pressing a button on my storyboard already takes me forward.
I think the problem may arise in BIDDCCreateViewController, I have the method but I cannot run it. To run a method it should go [self method]. I am unable to do that. Well that is just what I am guessing.
It compiles and runs fine just nothing is logged, so I don't know if it works.
UPDATE: I am unable to get the 'sendDataToA' method to execute.
#import <UIKit/UIKit.h>
#import "BIDAddTypeOfDealViewController.h"
#interface BIDDCCreateViewController : UIViewController
#property (strong, nonatomic) NSString *placeId;
- (IBAction)gotoBViewController:(id)sender;
#end
#import "BIDDCCreateViewController.h"
#import "BIDAddTypeOfDealViewController.h"
#implementation BIDDCCreateViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"SUCCESSFULLY PASSED PLACE ID: %#", self.placeId);
}
-(void)sendDataToA:(NSString *)myStringData
{
NSLog(#"Inside sendDataToA");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Your string Data Showing" message:myStringData delegate:self cancelButtonTitle:#"Ok " otherButtonTitles:nil];
[alert show];
}
- (IBAction)gotoBViewController:(id)sender {
NSLog(#"pressed");
BIDAddTypeOfDealViewController *bidAddType = [[BIDAddTypeOfDealViewController alloc]init];
bidAddType.delegate = self;
}
#end
#protocol senddataProtocol <NSObject>
-(void)sendDataToA:(NSString *)myStringData;
#end
#import <UIKit/UIKit.h>
#interface BIDAddTypeOfDealViewController : UIViewController <UITextFieldDelegate>//Using this delegate for data a user inputs
#property(nonatomic,assign)id delegate;
//other textfield outlets not relevant
- (IBAction)chooseDiscountDeal:(id)sender;
#end
#import "BIDAddTypeOfDealViewController.h"
#interface BIDAddTypeOfDealViewController ()
#end
#implementation BIDAddTypeOfDealViewController
#synthesize delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillDisappear:(BOOL)animated
{
[delegate sendDataToA:#"Apple"];
}
#end

You can use a delegate. So in your ViewController B you need to create a protocol that sends data back to your ViewController A. Your ViewController A would become a delegate of ViewController B.
If you are new to objective C, please look at What is Delegate.
Create protocol in ViewControllerB.h :
#import <UIKit/UIKit.h>
#protocol senddataProtocol <NSObject>
-(void)sendDataToA:(NSArray *)array; //I am thinking my data is NSArray, you can use another object for store your information.
#end
#interface ViewControllerB : UIViewController
#property(nonatomic,assign)id delegate;
ViewControllerB.m
#synthesize delegate;
-(void)viewWillDisappear:(BOOL)animated
{
[delegate sendDataToA:yourdata];
}
in your ViewControllerA : when you go to ViewControllerB
ViewControllerA *acontollerobject=[[ViewControllerA alloc] initWithNibName:#"ViewControllerA" bundle:nil];
acontollerobject.delegate=self; // protocol listener
[self.navigationController pushViewController:acontollerobject animated:YES];
and define your function:
-(void)sendDataToA:(NSArray *)array
{
// data will come here inside of ViewControllerA
}
Edited :
You can See this example : How you can Pass data back to previous viewcontroller: Tutorial link

A shorter and simpler method than protocol/delegate is to create a closure:
For sending a String back in my case.
In ViewControllerA:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewControllerB = segue.destination as? ViewControllerB {
viewControllerB.callback = { message in
//Do what you want in here!
}
}
}
In ViewControllerB:
var callback : ((String) -> Void)?
#IBAction func done(sender: AnyObject) {
callback?("Hi")
self.dismiss(animated: true, completion: nil)
}

Swift: Sending data back using the delegate pattern
My full answer that covers passing data both ways is here. My answer explaining the delegate pattern is here.
To pass data back from the second view controller to the first view controller, you use a protocol and a delegate. This video is a very clear walk though of that process:
YouTube tutorial: iOS Swift Basics Tutorial: Protocols and Delegates. But also read this post to make sure you don't get into a strong reference cycle.
The following is an example based on the video (with a few modifications).
Create the storyboard layout in the Interface Builder. Again, to make the segue, you just Control drag from the button to the Second View Controller. Set the segue identifier to showSecondViewController. Also, don't forget to hook up the outlets and actions using the names in the following code.
First View Controller
The code for the First View Controller is
import UIKit
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destinationViewController as! SecondViewController
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
Note the use of our custom DataEnteredDelegate protocol.
Second View Controller and Protocol
The code for the second view controller is
import UIKit
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondViewController: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var textField: UITextField!
#IBAction func sendTextBackButton(sender: UIButton) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(textField.text!)
// go back to the previous view controller
self.navigationController?.popViewControllerAnimated(true)
}
}
Note that the protocol is outside of the View Controller class.
That's it. Running the app now you should be able to send data back from the second view controller to the first.

As Erhan Demirci answered, you can use delegates. Delegates are helpful when you want to pass data to a single view controller.
NSNotificationCenter is another convenient way to transfer data between viewcontrollers/objects. This is very helpful in broadcasting data within the application.
read documentation here.

Edit:
Use #Erhan's solution above. Not this one. This is not a good solution.
This will help. Write this in your ViewControllerB.
// Get array of current navigation stack
NSArray *arrayViewControllers = [self.navigationController viewControllers];
// Get previous viewController object from it
YOUR_VIEW_CONTROLLER_NAME *objViewController = (YOUR_VIEW_CONTROLLER_NAME *)[arrayViewControllers objectAtIndex:arrayViewControllers.count-2];
// For safety this check is needed. whether it the class that you want or not.
if ([objViewController isKindOfClass:[YOUR_VIEW_CONTROLLER_NAME class]])
{
// Access properties of YOUR_VIEW_CONTROLLER_NAME here
objViewController.yourProperty = YOUR_VALUE;
}

Custom delegate is the best option to move data but you can try this also.
You can use NSUserDefaults for Moving the data any where you want.
Swift 3 Code
UserDefaults.standard.set(<Value>, forKey: <Key>)
// To set data
UserDefaults.standard.object(forKey: <Key>)
// To get data
You can also use NSNotification for move data.
NotificationCenter.default.post(name: Notification.Name(rawValue: "refresh"), object: myDict)
NotificationCenter.default.addObserver(self, selector: #selector(refreshList(_:)), name: NSNotification.Name(rawValue: "refresh"), object: nil)

There is protocol there is closure. With closure, we need to avoid memory leaks by using weak self (or unowned self). With protocol, there would be one per viewController that you want to "monitor", end up with dozens of delegate to implement. Here I've another simple solutions in Swift:
Inside a new file or existing one (for example: UIViewController+Extensions.swift), create this protocol:
protocol ViewControllerBackDelegate: class {
func back(from viewController: UIViewController)
}
Inside LEVEL-2 viewController, where you want a callback when Back is pressed from:
class LevelTwoViewController: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: ViewControllerBackDelegate? = nil
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
if (parent == nil) {
delegate?.back(from: self)
}
}
}
Since delegate is optional, you may add this code to a base class of your view controllers. I would add to where it needs to be.
In your LEVEL-1 viewController, assume you calling LEVEL-2 via a segue in Storyboard:
class LevelOneViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "Go to Level 2") {
if let vc = segue.destination as? LevelTwoViewController {
vc.selectedItems = self.selectedItems // passing data-in
vc.delegate = self
}
}
// repeat `if` for another sub-level view controller
}
}
extension LevelOneViewController: ViewControllerBackDelegate {
func back(from viewController: UIViewController) {
if let vc = viewController as? LevelTwoViewController {
self.selectedItems = vc.selectedItems
// call update if necessary
}
// repeat `if` for another sub-level view controller
}
}
only one protocol is required.
only one extension per first-level viewController.
no modifications to sub-level viewController if more/less data need to return
handle data-out just like data-in in prepare(for:sender:)

//FirstViewController
import UIKit
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
}
#IBAction func pushToSecond(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "SecondViewController")as? SecondViewController {
vc.callBack = { (id: String,name: String,age: Int) in
print(id,name,age)
}
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
// //SecondViewController
import UIKit
class SecondViewController: UIViewController {
var callBack: ((_ id: String, _ name: String, _ age: Int)-> Void)?
#IBAction func BackToFirstWitData(_ sender: Any) {
callBack?("1","Test",22)
self.navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

Here is how I would do it.
#interface ViewControllerA:UIViewController
#property(strong, nonatomic) ViewControllerB * recieverB;
#end
#implementation ViewControllerA
//implement class
- (void)prepareForSegue:(UIStoryboardSegue *) sender:(id)sender
{
segue.destinationViewController.recieverA = self;
}
-(void)viewDidLoad
{
//stop strong refrence cycle
self.viewControllerB = nil;
}
#end
Class B
#interface ViewControllerB:UIViewController
#property(strong, nonatomic, getter = parentClass) ViewControllerB * recieverA;
#end
#implementation ViewControllerB
//implement class
- (void)viewWillDisappear:(BOOL)animated
{
parentClass.recieverB = self;
//now class A will have an instance on class b
}
#end
I didn't put the #import

Related

Swift - How to pass data from VC A to B after VC B is presented?

I have 2 ViewControllers, A and B. ViewController A uses sockets and updates its data whenever changes occur.
How can I pass those updated data to ViewController B when it's already presented (programmatically) by A?
I'm thinking to pass ViewController A's update handler class to ViewController B and take advantage of the fact that classes are reference-type, so any change would happen to A's handler, it would also happen to B's. Is it a valid architectural choice?
You just need to maintain a reference to ViewControllerB in ViewControllerA when you're presenting ViewControllerB in ViewControllerA, here's how:
class ViewControllerA: UIViewController {
var viewControllerB: ViewControllerB?
func presentViewControllerB() {
if let viewControllerB = viewControllerB {
present(viewControllerB, animated: true)
} else {
viewControllerB = ViewControllerB()
presentViewControllerB()
}
}
func passDataToViewControllerB() {
viewControllerB?.someData = "Data from ViewControllerA."
}
}
class ViewControllerB: UIViewController {
var someData = ""
}

How can I check popularProduct.xib file loaded into HomeViewController

In my HomeViewController have four section:
Banner
Popular Product
New Product
Old Product
All are .xib files and they are working fine.
Now I want to add ActivityIndicator in my HomeViewController
So now I want to show ActivityIndicator on HomeViewController until all the .xib's file not fully loaded in HomeViewController as .xib's ActivityIndicator should hide.
Now, the problem for me is that how can I get the confirmation about .xib's loaded to HomeViewController or not?
As a pretty simple direct solution, you could follow the delegation approach. So, when "Popular Product" View complete the needed process, you should fire a delegate method which will be handled by the view controller.
Example:
consider that in PopularProduct class you are implementing a method called doSomething() which need to get called and finish its work to hide the activity indicator from the view controller and it should send Data instance to the view controller. You could do it like this:
protocol PopularProductDelegate: class {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data)
}
class PopularProduct: UIView {
// ...
weak var delegate: PopularProductDelegate?
func doSomething() {
// consider that there is much work to be done here
// which generates a data instance:
let data = Data(base64Encoded: "...")!
// therefore:
delegate?.popularProduct(popularProduct: self, doSomethingDidPerformWith: data)
}
// ...
}
Therefore, in ViewController:
class ViewController: UIViewController {
// ...
var popularProduct: PopularProduct!
override func viewDidLoad() {
super.viewDidLoad()
// show indicator
popularProduct.doSomething()
}
// ...
}
extension ViewController: PopularProductDelegate {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data) {
print(data)
// hide indicator
}
}
Furthermore, you could check my answer for better understanding for delegates.

How to access a referencing outlet from a different view controller?

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.

Swift – Using popViewController and passing data to the ViewController you're returning to

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()
}
}

Pass variables from one ViewController to another in Swift

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.