Pass data through navigation back button
Asked Answered
R

4

19

I am in this situation:

img1

I am passing 4 array from Progress Table to Detail Exercise using prepare for segue and it works fine! The problem begin when I try to pass the data back from the Detail Exercise Controller to the Progress Table Controller. I would like to use the default navigation back button to go back to the parent view. Actually I'm using this code but it doesn't work, well the data pass from the child to the parent view but i can't see the result in the Progress Table, seems like i need to refresh but i also tryed the reloadData after the viewDidLoad and it doesn't work. Any suggestion? Thank you.

override func viewWillDisappear(animated : Bool) {
    super.viewWillDisappear(animated)

    if (self.isMovingFromParentViewController()){
        print("n'drio")

        let historyView = self.storyboard!.instantiateViewControllerWithIdentifier("historyView") as! HistoryTableViewController
        historyView.isFirstTime = false
        historyView.arrayData = arrayDataDetails
        historyView.arrayRipetizioni = arrayRipetizioniDetails
        historyView.arrayPeso = arrayPesoDetails
        historyView.arrayRecupero = arrayRecuperoDetails
        historyView.tableView.reloadData()
    }
}
Reorganize answered 22/1, 2016 at 21:8 Comment(1)
Instead of passing it back, you could also consider creating a custom class having the arrays as class properties so whenever you change them in the exercise view controller, they are also changed in the progress view controller.Waverley
W
44

When you press the back button, the navigation controller will call navigationController(willShowViewController:) so you can use this to pass the data back to your initial view controller. An example is shown below:

Using UINavigationControllerDelegate:

class DetailsViewController: UIViewController, UINavigationControllerDelegate {
                                                        //     ^
    var data: [String] = []                             // Important!

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationController?.delegate = self

        data = ["data has changed!"]
    }
}

Swift 2:

extension DetailsViewController: UINavigationControllerDelegate {
    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
        if let controller = viewController as? ProgressTableViewController {
            controller.data = data    // Here you pass the data back to your original view controller
        }
    }
}

Swift 3:

extension DetailsViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        (viewController as? ProgressTableViewController)?.data = data // Here you pass the to your original view controller
    }
}

In this example, the key is using the UINavigationControllerDelegate and setting the delegate of the navigation controller (in this case it's self). Having done this, you can send the data back to your initial view controller with the back button.

Personally I prefer using a class for my data:

Using a custom class for your data:

class Data {
    var array: [String] = []
}

Progress view controller:

class ProgressTableViewController: UITableViewController {

    var data = Data()

    override func viewDidLoad() {
        super.viewDidLoad()

        data.array = ["some data"]
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        tableView.reloadData()
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.array.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()

        cell.textLabel?.text = data.array[indexPath.row]

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "exerciseSegue", sender: self)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "exerciseSegue" {
            let destination = segue.destinationViewController as! DetailsViewController
            destination.data = data
        }
    }
}

Details view controller:

class DetailsViewController: UIViewController {

    var data = Data()

    override func viewDidLoad() {
        super.viewDidLoad()

        data.array = ["data has changed!"]
    }
}

In the last example, you don't have to worry about passing around data. Whenever you change the data, the controllers using the same class, will have the changes as well.

Waverley answered 22/1, 2016 at 22:24 Comment(6)
I appreciate you answer, I chose the class 'Bridge' way and it works very well! Thank you very much!Reorganize
Why the down vote? Would like to know so I can correct it.Waverley
I like the first example!Oleomargarine
I can't wrap my head around the 2nd example. Are the data objects in each class not two separate instances?Aroma
The first example worked once I removed the redundant UINavigationControllerDelegate from the extension.Aroma
@Deco The 2nd example works like that because when you pass a class from one object to another it uses the same instance of it, this means that when one location makes a change to it the change will be viewable in all locations.Triumphal
F
4

Typically, protocols and delegates are used to pass data back and forth between screens.

// Define a delegate that is known to both view controllers
protocol DetailsExerciseDelegate {
    func detailsWillDisappear(...);
}

class DetailsExerciseViewController {
    // Accept the delegate as a property on the details view controller
    var delegate : DetailsExerciseDelegate

    override func viewWillDisappear(animated : Bool) {
        super.viewWillDisappear(animated)

        // When you want to send data back to the caller
        // call the method on the delegate
        if let delegate = self.delegate {
            delegate.detailsWillDisappear(/* your data in one or more parameters */)
        }
    }
}

// Implement the delegate by adding the required function
class ProgressTableViewController: DetailsExerciseDelegate {
    ...

    func detailsWillDisappear(...) {
        // When the child calls the function, update the screen
        historyView.isFirstTime = false
        historyView.arrayData = arrayDataDetails
        historyView.arrayRipetizioni = arrayRipetizioniDetails
        historyView.arrayPeso = arrayPesoDetails
        historyView.arrayRecupero = arrayRecuperoDetails
        historyView.tableView.reloadData()
    }

    override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
        if segue!.identifier == "DetailsExcercise" {
            // And finally, when launching the child view,
            // make sure to set the delegate.
            let viewController = segue!.destinationViewController as DetailsExerciseViewController
            viewController.delegate = self
        }
    }
}

That being said, it seems non-standard to try to save the data when clicking the back button. Are you sure you don't want to do this on "Done" instead?

Forfar answered 22/1, 2016 at 21:22 Comment(4)
thank you for answering my question, but your code is not understandable at all! in the func detailsWillDisappear how can i pass data back to the parent view?? where did you initialize historyView??Reorganize
Keep in mind, your class DetailsExerciseViewController will have a strong reference cycle because the delegate will prevents it from being deallocated. Making it weak will solve this problem.Waverley
As @Waverley said in his comment you need to implement the delegate using weak references to avoid any retain cycleSentinel
@Waverley I am sorry, I am new at protocol and delegate, can you please answer my question with some code example for resolve my problem. thank you very much!Reorganize
M
2

Simpler solution you can use self.navigationController?.viewControllers to access previous view controller as following:

let vcsCount = self.navigationController?.viewControllers.count
self.navigationController?.viewControllers[vcsCount! - 2].updateData

updateData is the data member to update at previous view controller

Mandibular answered 11/4, 2017 at 20:16 Comment(0)
B
0

Also, you can use this extension:

extension UINavigationController {
    func viewController<T: UIViewController>(class: T.Type) -> T? {

        return viewControllers.filter({$0 is T}).first as? T
    }
}

//

if let controller = navigationController?.viewController(class: MainViewController.self) {
     controller.data = data
}

navigationController?.popViewController(animated: true)
Batruk answered 13/2, 2018 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.