DEV Community

Theo Millard
Theo Millard

Posted on

Data Passing in iOS Apps - UI Kit

Passing data to between view controllers is crucial in your application flows.
There are 2 types of passing data:

  1. Passing Data to Another ViewController
    • when creating new view controller
  2. Returning Data to Previous Activity
    • when closing current view controller

There will be more detailed explanation with example below.


Passing Data to Another ViewController


Property Access

Detail View Controller

class DetailViewController: UIViewController { var gameTitle: String? override func viewDidLoad() { super.viewDidLoad() print("Selected Game: \(gameTitle)") } } 
Enter fullscreen mode Exit fullscreen mode

Main View Controller

class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() print("Game List: ") let Button = UIButton() // Add Event Listener - FOR EXAMPLE Button.addTarget(self, action: #selector(navigateToDetail),for: .touchUpInside) } @objc func navigateToDetail() { // Create view controler page object  let otherViewController = DetailViewController() otherViewController.gameTitle = "Black Myth Wukong" // push new detail page navigationController?.pushViewController(otherViewController, animated: true) } } 
Enter fullscreen mode Exit fullscreen mode

Even though it seems much simpler, it also can introduce new bugs. Here, using variable provide no documentation.
Let's say we want to call the detail in another view controller, we simply can forgot to modify the variables.

Or we have many variables, we can easily skip one of them and only can caught it when testing or applications are running.

Benefits

  • Easy to Implement

Drawbacks

  • No Documentation on what's variable need to be filled
  • No compile-time safety: Any forgotten passes only be caught after testing or running the app

init Function

Detail View Controller

class DetailViewController: UIViewController { private let gameTitle: String init(gameTitle: String) { self.gameTitle = gameTitle // Need to implement if creating init super.init(nibName: nil, bundle: nil) } // Need to implement if creating init on storyboard  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() print("Selected Game: \(gameTitle)") } } 
Enter fullscreen mode Exit fullscreen mode

Main View Controller

class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() print("Game List: ") let Button = UIButton() // Add Event Listener - FOR EXAMPLE Button.addTarget(self, action: #selector(navigateToDetail),for: .touchUpInside) } @objc func navigateToDetail() { // Create view controler page object  let otherViewController = DetailViewController(gameTitle: "Black Myth Wukong") // push new detail page navigationController?.pushViewController(otherViewController, animated: true) } } 
Enter fullscreen mode Exit fullscreen mode

Here, using init can provide more clear and documentations. If we want to call the detail view controller in other places, we can just init it with arguments.

Also, if we add more variables, other class that init this will have compile error and must adapt to new init function.

Benefits

  • Extra safety on compile-time: If you add new params, all the functions call need to adapt
  • Extra documentation on what arguments needed
  • More clean
  • More scalable

Drawbacks

  • Custom initializer

Returning Data to Previous Activity

Closure

Detail View Controller

class DetailViewController: UIViewController { private let gameTitle: String private let onSubmitHandler: (String) -> Void init( gameTitle: String, onSubmitHandler: @escaping (String) -> Void ) { self.gameTitle = gameTitle self.onSubmitHandler = onSubmitHandler super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func submitTapped() { onSubmitHandler("Game: \(gameTitle)") } override func viewDidLoad() { super.viewDidLoad() print("Selected Game: \(gameTitle)") // EXAMPLE - Directly calling the onSubmitHandler submitTapped() } } 
Enter fullscreen mode Exit fullscreen mode

Main View Controller

func navigateToDetail() { /// Create demo `ClosureViewController` page object let viewController = DetailViewController( gameTitle: "Black Myth Wukong", // weak self explanation below onSubmitHandler: { [weak self] result in // got result and can do anything print(result) } } /// Push detail page into navigation controller navigationController?.pushViewController(viewController, animated: true) } 
Enter fullscreen mode Exit fullscreen mode

Here, passing the closure function can use both of the method that is given on the first part. In the example we use the init one.

Passing weak self in closure, making sure there is no memory leaks. If Parent got cleaned or get deinitialized, if the Child doesn't have other reference count,
memory will be freed.

Benefits

  • No boilerplate (Protocol)
  • Easy to implement, just pass the closure

Drawbacks

  • Harder to digest (Closure)
  • Not common practices
  • Hard to test

Delegate

Detail View Controller

protocol PrinterDelegate: AnyObject { func printInfo(gameTitle: String) -> Void } class DetailViewController: UIViewController { private let gameTitle: String // weak var same explanation with above weak var printerDelegate: PrinterDelegate init( gameTitle: String, printerDelegate: PrinterDelegate ) { self.gameTitle = gameTitle self.printerDelegate = printerDelegate super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func submitTapped() { // calling delegate's function printerDelegate.printInfo(self.gameTitle) } override func viewDidLoad() { super.viewDidLoad() print("Selected Game: \(gameTitle)") // EXAMPLE - Directly calling the onSubmitHandler submitTapped() } } 
Enter fullscreen mode Exit fullscreen mode

Main View Controller

class MainViewController: UIViewController, SelectionDelegate { func navigateToDetail() { // Conforms to delegate func printInfo(gameTitle: String) -> Void { // Can do anything we want } /// Create demo `ClosureViewController` page object let viewController = DetailViewController( gameTitle: "Black Myth Wukong", // Pass self printerDelegate: self } /// Push detail page into navigation controller navigationController?.pushViewController(viewController, animated: true) } } 
Enter fullscreen mode Exit fullscreen mode

Here, by using protocol, each class that want's to create detail view controller needs to conform to the protocol. creating more separation.

Benefits

  • Scalable and Reusable
  • Easier only need to create normal function not closure
  • More common and easy to test

Drawbacks

  • Boilerplate

References

Top comments (0)