Search code examples
iosswiftparsinguikit

How to transfer data between ViewControllers?


I'm currently working on the currency rates app. I have a main ViewController which has a UILabel which presents parsed rate for selected currencies. Also, there is a button which is used for changing one of the currency rates in order to get a new rate (second ViewController is presented with N tableView cells).
I have some troubles with transferring data of new selected value to the main ViewController. When I select a new rate, I get this error for UILabel:

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

I guess I know how to fix it - create a variable inside main and set its value inside second viewcontroller, but i don't know how do it properly. Here is my code. Thanks in advance.

class MainVC: UITableViewController {

    var convertedValue: String = ""

    // some stuff

    func parseCurrency(top: String) {
        let URL_Exchange = URL(string: "MY URL\(top)")!
        let task = URLSession.shared.dataTask(with: URL_Exchange) {(data, response, error) in
            guard let data = data else { return }
            let queue = DispatchQueue(label: "result")
            queue.async {
                let JSON = String(data: data, encoding: .utf8)
                let jsonData = JSON!.data(using: .utf8)!
                let result: Rates = try! JSONDecoder().decode(Rates.self, from: jsonData)
            
                let currencies = result.conversion_rates?.dictionary
                var doubleVar:Double = 0
                for item in currencies! {
                    if (item.key == self.BottomCurrency.sublabel) {
                        doubleVar = item.value as! Double
                    }
                }
            
                // this value is the text to print inside "Result" Label
                let mult = Double(round(10000*doubleVar * self.InputText)/10000)
             

                self.convertedValue = String(mult)
                DispatchQueue.main.async {
                    if self.convertedValue == "0" {
                        self.Result.text = String(0)
                    } else {
                        // Here I get an error
                        self.Result.text = self.formatNumber(num: Double(self.convertedValue))
                    }
                }
            }
    
        }
        task.resume()
}

Code for second VC:

class SecondVC: UITableViewController {
    private var temp: Int = 0

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if isBeingDismissed {
            // updating temp value...
            let vc = storyboard!.instantiateViewController(withIdentifier: "main") as! MainVC
            vc.parseCurrency(top: Currencies[temp].short)
            // or to call vc.convertedValue = something
            // vc.parseCurrency(...)
        }
    }

Solution

  • If I had to guess, the reason that your code is crashing is that the property Result you are calling on a freshly created instance of main view controller, is actually an IBOutlet pointing to a UILabel! which is not yet created.

    Important!

    There is a great amount of resources available on the internet on passing data between view controllers so I am pointing you towards already existing post with huge amount of answers: Passing data between view controllers

    However, I will describe one of the available ways to solve it below

    Delegation

    One way to solve it would be to use a "traditional" UIKit pattern which is Delegation.

    You simply create a protocol, that will enable a way for the secondVC communicate with mainVC

    protocol SecondVCDelegate: AnyObject {
        func secondVCDidSelect(currency: String)
    }
    

    Then you add a weak property on the secondVC

    class SecondVC: UITableViewController {
    
        weak var delegate: SecondVCDelegate?
    
        // ...
    
    }
    

    And now you should be able to pass any value you want to the delegate. For the sake of the example, you could do it on the didSet observer of the temp property

    class SecondVC: UITableViewController {
    
        private var temp: Int = 0 {
            didSet {
                delegate?.secondVCDidSelect(currency: Currencies[temp].short)
            }
        }
    
        weak var delegate: SecondVCDelegate?
    
    }
    

    Personally I would call the delegate the moment user is selecting the value, and not store it in the temp property

    the only thing missing is setting a mainVC as the delegate of the secondVC and conforming to the protocol. Perfect place to do it is at the moment you are presenting the secondVC from mainVC

    let viewController: SecondVC = // init SecondVC or load from storyboard
    viewController.delegate = self
    // present secondVC
    
    extension MainVC: SecondVCDelegate {
        func secondVCDidSelect(currency: String) {
            parseCurrency(top: currency)
        }
    }
    

    I encourage you to read more about Delegation since this is a very popular pattern in the iOS development

    Delegation in Swift by John Sundell