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)