Passing data to between view controllers is crucial in your application flows.
There are 2 types of passing data:
- Passing Data to Another ViewController
- when creating new view controller
- 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)") } } 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) } } 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)") } } 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) } } 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() } } 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) } 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() } } 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) } } 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
Top comments (0)