How to pass data from child to parent view controller? in swift
Asked Answered
R

5

12

I created an activity where when one of the text fields clicked it will pop up a child(alert dialog) with list of product but when i click one item on the list I can't display it on the text filed once the alert dismissed.

this is the parent view

import Foundation
import UIKit

class ViewAward: UIViewController{

@IBOutlet var tfMCN: UITextField!
@IBOutlet var tfAmount: UITextField!
@IBOutlet var tfProduct: UITextField!
@IBOutlet var tfTotal: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()

    let rightAddBarButtonItem:UIBarButtonItem = UIBarButtonItem(title: "Send", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ViewAward.searchTapped))

    self.navigationItem.setRightBarButtonItems([rightAddBarButtonItem], animated: true)

    let state = String(ViewPopUpProduct.Product.ProductDescription)
    print("My view state:"+state)

    self.tfProduct.text = state
    tfProduct.addTarget(self, action:  #selector(ViewAward.productTapped), forControlEvents: UIControlEvents.TouchDown)

}

func searchTapped(sender:UIButton) {

    let alertController = UIAlertController(
        title: "Award",
        message:"Award successfully posted!",
        preferredStyle: UIAlertControllerStyle.Alert)
    alertController.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,handler: nil))

    self.presentViewController(alertController, animated: true, completion: nil)
}

func productTapped(textfield: UITextField){

    //tfProduct.endEditing(true)
    tfProduct.resignFirstResponder()

    let popOverVC = UIStoryboard(name:"Main",bundle:nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! ViewPopUpProduct

    self.addChildViewController(popOverVC)

    popOverVC.view.frame = self.view.frame

    self.view.addSubview(popOverVC.view)

    popOverVC.didMoveToParentViewController(self)

}
}

and this when the user clicked on of the items

import UIKit

class ViewPopUpProduct: UIViewController {

@IBOutlet var tableView: UITableView!

var productDescription = ["Product 1","Product 2","Product 3"]
var productID = ["prdct1","prdct2","prdct3"]


// Global Variables 
struct Product {
    static var ProductID = String()
    static var ProductDescription = String()
}

override func viewDidLoad() {
    super.viewDidLoad()
    self.showAnimate()
    self.view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.4)

    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

@IBAction func cancelPopUp(sender: AnyObject) {
    self.removeAnimate()
}


func showAnimate()
{
    self.view.transform = CGAffineTransformMakeScale(1.3, 1.3)
    self.view.alpha = 0.0;
    UIView.animateWithDuration(0.25, animations: {
        self.view.alpha = 1.0
        self.view.transform = CGAffineTransformMakeScale(1.0, 1.0)
    });
}

func removeAnimate()
{
    UIView.animateWithDuration(0.25, animations: {
        self.view.transform = CGAffineTransformMakeScale(1.3, 1.3)
        self.view.alpha = 0.0;
        }, completion:{(finished : Bool)  in
            if (finished)
            {
                self.view.removeFromSuperview()
            }
    });
}

//Mark - Table View

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.productID.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("cell",forIndexPath: indexPath) as! ProductViewCell

    cell.productLabel.text = productDescription[indexPath.row]

    return cell
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    tableView.deselectRowAtIndexPath(indexPath, animated: true)

    Product.ProductID = String(productID[indexPath.row])
    Product.ProductDescription = String(productDescription[indexPath.row])

    self.removeAnimate()

}

}
Regin answered 2/9, 2016 at 6:24 Comment(7)
can you add your code & type of alert you have displayed.Betelgeuse
Try to update your question with some code that you have implemented which will give us proper idea about what you are doing. Based on that will suggest you the best answer.Boa
unexpectedNil i already update the questionRegin
Easiest way: When you're going to present the child controller, pass a reference to self or a closure as callback.Bottoms
vadian i used self presenting the child but i cant update the value of textfield whe the child is dismissedRegin
@unexpectedNil thank you looking forward to it.Regin
what is product here?Cornie
C
12

You can use protocols/delegate

Here is a very very straightforward explanation, no bs: https://www.youtube.com/watch?v=guSYMPaXLaw

Or in your situation you can also use NSNotificationCenter

You can do something like this:

The "sender" view controller would do

let nc = NSNotificationCenter.defaultCenter()
nc.postNotificationName("printValue", object: nil, userInfo: ["value" : "Pass Me this string"])

The receiver view controller then can listen to the notification.

let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: #selector(printValue), name: "printValue", object: nil)

func printValue(notification:NSNotification) {
    let userInfo:Dictionary<String,String> = notification.userInfo as! Dictionary<String,String>
    let item = userInfo["value"]! as String

    print(item,self)
}
Correggio answered 2/9, 2016 at 6:37 Comment(3)
thanks but my problem is that i did not use a segue i load a child view and in that child view there are tables of items when i clicked on of the item i will grab the seleted item and dismissed the child view and to display the grabbed data on the text fieldRegin
NSNotification is the worst and most expensive way for related view controllers. Protocol/Delegate is much better (and closures as callbacks are state of the art).Bottoms
@Bottoms then give us your solution :|Vargueno
N
29

I usually use closures for this purpose. Much simpler and less verbose than delegates:

class MainViewController: UIViewController {

    func showChildViewController() {
        guard let vc = storyboard?.instantiateViewControllerWithIdentifier("ChildViewController") as? ChildViewController else {
            return
        }
        vc.didSelectItem = { [weak self](item) in
            if let vc = self {
                // Do something with the item.
            }
        }
        presentViewController(vc, animated: true, completion: nil)
    }

}

class ChildViewController: UIViewController {

    var didSelectItem: ((item: Item) -> Void)?

    @IBAction func buttonPressed() {
        didSelectItem?(item: <#your item goes here#>)
    }

}
Nephoscope answered 2/9, 2016 at 6:38 Comment(4)
i used this to display the child 'code' func productTapped(textfield: UITextField){ //tfProduct.endEditing(true) tfProduct.resignFirstResponder() let popOverVC = UIStoryboard(name:"Main",bundle:nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! ViewPopUpProduct self.addChildViewController(popOverVC) popOverVC.view.frame = self.view.frame self.view.addSubview(popOverVC.view) popOverVC.didMoveToParentViewController(self) } 'code'Regin
@JosephVincentMagtalas I gave you a general idea how to achieve that. Please adapt my answer to your needs by yourself.Nephoscope
where is the Item from? in ` var didSelectItem: ((item: Item) -> Void)? ` im new to developing apps thanksRegin
@JosephVincentMagtalas check out buttonPressed method. There you call the closure with an appropriate parameterNephoscope
C
12

You can use protocols/delegate

Here is a very very straightforward explanation, no bs: https://www.youtube.com/watch?v=guSYMPaXLaw

Or in your situation you can also use NSNotificationCenter

You can do something like this:

The "sender" view controller would do

let nc = NSNotificationCenter.defaultCenter()
nc.postNotificationName("printValue", object: nil, userInfo: ["value" : "Pass Me this string"])

The receiver view controller then can listen to the notification.

let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: #selector(printValue), name: "printValue", object: nil)

func printValue(notification:NSNotification) {
    let userInfo:Dictionary<String,String> = notification.userInfo as! Dictionary<String,String>
    let item = userInfo["value"]! as String

    print(item,self)
}
Correggio answered 2/9, 2016 at 6:37 Comment(3)
thanks but my problem is that i did not use a segue i load a child view and in that child view there are tables of items when i clicked on of the item i will grab the seleted item and dismissed the child view and to display the grabbed data on the text fieldRegin
NSNotification is the worst and most expensive way for related view controllers. Protocol/Delegate is much better (and closures as callbacks are state of the art).Bottoms
@Bottoms then give us your solution :|Vargueno
F
8

There are few ways by which you can implement the callback functionality to pass data.

  • Delegate
  • Using Block CallBack
  • Post Notification

But I would suggest to use delegate which is best way, Post Notification is also a way but I do not want to prefer.

Floridafloridia answered 2/9, 2016 at 6:29 Comment(1)
can you please help me with the code? i already assign a global variable but it updates the textfield value when the view is reload not when the child is dismissedRegin
B
7
  1. My first preference must be Custom Delegate which is faster and perfect. (If you can use closures as callbacks then that can also be a good option. Choosing delegate to explain using your code a bit.)

  2. Please avoid using NSNotificationCenter as much as you can because there are so many thing you have to deal with when using this and it is little slower than delegates. You may easily get into bugs with that too.

Now here is my code.

1. Child ViewController Setup.

   //  TrendingProductPageViewController.swift
   //  buddyiOSApp
   //
   //  Created by Tuhin Samui on 5/21/16.
   //  Copyright © 2016 Buddy. All rights reserved.
   //

    import UIKit

    protocol TrendingProductsCustomDelegate: class { //Setting up a Custom delegate for this class. I am using `class` here to make it weak.
        func sendDataBackToHomePageViewController(categoryToRefresh: String?) //This function will send the data back to origin viewcontroller.
    }


    class TrendingProductPageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NetworkReachabilityDelegate {

        @IBOutlet weak var productListTableView: UITableView! //Having a tableview outlet here from storyboard itself. BTW not going to explain with tableView delegate and datasource, Sorry..:(

        weak var customDelegateForDataReturn: TrendingProductsCustomDelegate? //Please use weak reference for this to manage the memory issue.


func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
            let rowNumberFromTable: Int = indexPath.row //Getting the index of the selected cell.
            let dataToSendBack = moreMenuTableData[rowNumberFromTable] as! String //This is an array which contains the data for the tableview. Getting the exact data which is selected on the table.
            customDelegateForDataReturn?.sendDataBackToHomePageViewController?(dataToSendBack) //Now sending the selected data back to parent viewController using the custom delegate which I made before.                 presentingViewController?.dismissViewControllerAnimated(true, completion: nil) //Dismissing the viewController here.
    }

2. Parent ViewController code here.

class HomePageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, TrendingProductsCustomDelegate, UINavigationControllerDelegate{ //Adding the protocol here as `TrendingProductsCustomDelegate`

@IBAction func topTrendingProductsBtnAction(sender: UIButton) { //Normal IBAction of UIButton as you are using.
        let trendingProductsPageForAnimation = storyboard!.instantiateViewControllerWithIdentifier("showTrendingProductpage") as! TrendingProductPageViewController //You can understand this right. Same as yours.
        trendingProductsPageForAnimation.customDelegateForDataReturn = self //Setting up the custom delegate for this class which I have written on the presenting class.
        trendingProductsPageForAnimation.modalPresentationStyle = UIModalPresentationStyle.FullScreen
        presentViewController(trendingProductsPageForAnimation, animated: true, completion: nil) //Same as yours.
    }


func sendDataBackToHomePageViewController(categoryToRefresh: String?) { //Custom delegate function which was defined inside child class to get the data and do the other stuffs.
        if categoryToRefresh != nil {
            print("Got the data is \(categoryToRefresh)")
        }
    }

}

Hope this helped. Sorry for any mistake.

Boa answered 2/9, 2016 at 8:53 Comment(2)
Hi I've used the same approach but the customDelegate returns NIL when calling from the childViewController. Is there anything that I'm missing?Residential
@Residential Are you sure you are passing the proper value? Have you conformed the delegate properly? If all you think are correct on your side then you may share the code you did so far so that I can look into it. Thanks!Boa
D
3

In ChildVC(ViewPopUpProduct) add instance of ParentVC

class ViewPopUpProduct: UIViewController {
  var parentVC = ViewAward? //ParentView
  var someValueToSend : String? 
  .
  .
  .
  fun sendData(){
    // After fetching some value call this function from child
    parentVC.result = someValueToSend
    self.view.removeFromSuperview()
  }
}

In Parent View While you invoke child(subview) share the instance

class ViewAward: UIViewController{

   var result = String?//Variable to store result from child

   func productTapped(textfield: UITextField){

    //tfProduct.endEditing(true)
    tfProduct.resignFirstResponder()

    let popOverVC = UIStoryboard(name:"Main",bundle:nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! ViewPopUpProduct

    popOverVC.parentVC = self//Sharing Parent Views Instance

    self.addChildViewController(popOverVC)

    popOverVC.view.frame = self.view.frame

    self.view.addSubview(popOverVC.view)

    popOverVC.didMoveToParentViewController(self)
 }
}

now after removing the child view Access the Result variable and enjoy!

Disregard answered 14/7, 2018 at 4:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.