Getting Started
Consider the following two monitors:
On the first screen, displays a list of items. When the button “Add item” is pressed, the second screen will be displayed, allowing new items to be added. The traditional approach to doing this is as follows:
ItemsViewController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import UIKit class ItemsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } @IBAction func addButtonTapped(_ sender: UIButton) { let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) let addItemViewController = storyBoard.instantiateViewController(withIdentifier: "addItemViewController") as! AddItemViewController addItemViewController.delegate = self self.present(addItemViewController, animated: true, completion: nil) } var items: [String] = [] @IBOutlet weak var tableView: UITableView! @IBOutlet weak var addButton: UIButton! } extension ItemsViewController: AddItemViewControllerDelegate { func didAddItem(_ item: String) { self.items.append(item) self.tableView.beginUpdates() self.tableView.insertRows( at: [ .init(row: items.count - 1, section: 0) ], with: .automatic ) self.tableView.endUpdates() } } extension ItemsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! cell.textLabel?.text = items[indexPath.row] return cell } } |
As we have seen, inside the addButtonTapped (sender 🙂 function, we create the AddItemViewController and assign self to its delegate property.
Through AddItemViewControllerDelegate to get information of newly added item and update to tableView .
AddItemViewController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import UIKit protocol AddItemViewControllerDelegate: class { func didAddItem(_ item: String) } class AddItemViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } weak var delegate: AddItemViewControllerDelegate? @IBAction func doneButtonTapped(_ sender: UIButton) { delegate?.didAddItem(textField.text!) self.dismiss(animated: true, completion: nil) } @IBOutlet weak var textField: UITextField! @IBOutlet weak var doneButton: UIButton! } |
Here, in the doneButtonTapped (sender 🙂 function, we’ll run the delegate didAddItem (item 🙂 and dismiss viewController.
How can it be simpler? What if we don’t need any delegate protocol and can still track the update to add new item inside the ItemsViewController?
Combine’s publishers and observers will help us to do this
Using Combine
First delete the AddItemViewControllerDelegate and create a PassthroughSubject of type String:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import UIKit import Combine class AddItemViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } @IBAction func doneButtonTapped(_ sender: UIButton) { newItem.send(textField.text!) self.dismiss(animated: true, completion: nil) } let newItem = PassthroughSubject<String, Never>() @IBOutlet weak var textField: UITextField! @IBOutlet weak var doneButton: UIButton! } |
As can be seen here, we have sent a new value to the newItem inside the doneButtonTapped (sender 🙂 function .
Now that AddItemViewController has a valid publisher, we can subcribe it inside ItemViewController.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | import UIKit import Combine class ItemsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } @IBAction func addButtonTapped(_ sender: UIButton) { let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) let addItemViewController = storyBoard.instantiateViewController(withIdentifier: "addItemViewController") as! AddItemViewController addItemViewController.newItem .handleEvents(receiveOutput: { [unowned self] newItem in self.updateTableView(withItem: newItem) }) .sink { _ in } .store(in: &subscriptions) self.present(addItemViewController, animated: true, completion: nil) } func updateTableView(withItem item: String) { self.items.append(item) self.tableView.beginUpdates() self.tableView.insertRows( at: [ .init(row: self.items.count - 1, section: 0) ], with: .automatic ) self.tableView.endUpdates() } var subscriptions = Set<AnyCancellable>() var items: [String] = [] @IBOutlet weak var tableView: UITableView! @IBOutlet weak var addButton: UIButton! } extension ItemsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! cell.textLabel?.text = items[indexPath.row] return cell } } |
When a new value is sent to the newItem object inside the AddItemViewController , we update the tableView inside the .handleEvents operator.
Note that to prevent the subcription from being released immediately, let’s create a subcription property to store our subscription.
Finally we can replace the traditional delegate approach with Combine.
See more about Combine: https://developer.apple.com/documentation/combine