Add mock item to todo list

เพิ่ม item กลับไปที่หน้าแรก

เราทำอย่างไรได้บ้าง ถ้าต้องการเพิ่มของใน Todo

ViewController หน้าแรก ส่ง Todo ไปให้หน้า Add เลย แล้วหน้า Add ไปจัดการเอง วิธีนี้จะมีปัญหาคือ หน้าแรกจะรู้ได้ยังไงว่า item เพิ่มหรือไม่เพิ่ม ตารางจะอัพเดตยังไงหรืออัพเดตทั้งตารางเลย

หรือว่าหน้า Add ประกาศตัวแปรชี้มาที่หน้าแรกเลย แล้วเวลาทำอะไรก็สั่งกลับมาที่ Controller หน้าแรกเลย อันนี้ก็จะกลายเป็นว่า อ่าวทำไมหน้า Add ต้องรู้จักหน้าแรกนั่นด้วย แล้วถ้าหน้า Add ถูกสั่งเปิดจากที่อื่นอีก ไม่ต้องกลายเป็นเก็บอีกตัวแปรหรอ

หรือว่าทำแบบ TableView ได้มะ มี delegate ประกาศว่า delegate นี้ทำอะไรได้ 1 2 3 ใครก็ทำตัวเองเป็น delegate ได้แค่ประกาศและทำ 1 2 3 แล้วหน้า add เก็บ delegate ไว้ เวลาจะใช้ก็เรียกเลย ไม่รู้ด้วยว่าเป็นใคร รู้แต่ว่า delegate ทำได้ ส่วนหน้าแรก ก็ทำตัวเป็น delegate แล้วตอนเปิดหน้า add ก็ส่งว่าฉันคือ delegate นี่คือสิ่งที่เรียกว่า Delegate Pattern ใน iOS

เราจะเลือกใช้ Delegate Pattern กับงานนี้

เริ่มจากการสร้าง Protocol

เราจะสร้าง protocol ที่ระบุว่า delegate ของเราต้องทำอะไรได้บ้าง

  1. Delegate ของเราสามารถจัดการ การ AddItem แทนเราได้

  2. Delegate ของเราสามารถจัดการ การ Cancel แทนเราได้

protocol AddNewItemViewControllerDelegate: class {
    func addNewItemViewController(controller: AddNewItemViewController, didAdd item: TodoItem)
    func addNewItemViewControllerDidCancel(controller: AddNewItemViewController)
}

เพิ่มตัวแปรที่จะเก็บว่า Delegate คือใครใน AddNewItemViewController

weak var delegate: AddNewItemViewControllerDelegate?

เนื่องจาก Delegate ของเราจัดการได้ เราก็เรียกให้มันจัดการแทนได้เลย

@IBAction func doneButtonDidTap(_ sender: UIBarButtonItem) {
    delegate?.addNewItemViewController(controller: self, didAdd: TodoItem(title: "Test"))
}

@IBAction func cancelButtonDidTap(_ sender: UIBarButtonItem) {
    delegate?.addNewItemViewControllerDidCancel(controller: self)
}

จบในส่วน AddNewItemViewController ที่เรียกให้ Delegate ทำงานแทน

ทำ ViewController ให้เป็น delegate ของหน้า Add

ViewController ต้องประกาศว่าตัวฉันเป็น AddNewItemViewControllerDelegate ได้นะ โดยประกาศไว้ที่หัวคลาสเพิ่มเข้าไปแบบ UITableViewDelegate

AddNewItemViewControllerDelegate

พอบอกว่าเป็น AddNewItemViewControllerDelegate ได้ ก็จะโดนบังคับให้เขียนฟังก์ชั่นของ Delegate (แน่นอนสิ ก็บอกว่าตัวเองทำได้)

เราก็ทำแบบนี้ กรณี Add มาเราก็ Add ลง Todo แล้วก็ปิดหน้า Modal ไป กรณี Cancel เราก็ไม่ทำอะไรปิดหน้า Modal ไปเฉย ๆ

func addNewItemViewController(controller: AddNewItemViewController, didAdd item: TodoItem) {
    todo.add(item: item)
    controller.dismiss(animated: true, completion: nil)
}

func addNewItemViewControllerDidCancel(controller: AddNewItemViewController) {
    controller.dismiss(animated: true, completion: nil)
}

ลองรันดูจะพบว่าใช้งานไม่ได้ เพราะเราเอาแต่ประกาศว่า ฉันเรียก Delegate นะ แล้วก็ฉันเป็น Delegate ได้นะ แต่ยังไม่ได้ผูกกันว่า ViewController เป็น delegate ของหน้า Add

ให้เราเติมโค้ดนี้ลงไป โค้ดนี้ทำให้เวลาเปิดหน้า Add ด้วย openAddItemSegue จะระบุหน้า Add ว่า delegate คือหน้า ViewController นี้

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "openAddItemSegue" {
        if let nav = segue.destination as? UINavigationController,
            let controller = nav.topViewController as? AddNewItemViewController {
            controller.delegate = self
        }
    }
}

รันอีกที ก็ยังไม่ได้ เพราะอะไร

เพราะอะไร

เพราะเราเอาของเข้า Todo แต่เรายังไม่ได้สั่ง TableView ให้อัพเดตข้อมูลเลย

เริ่มที่การสร้าง Outlet เพื่อเข้าถึง TableView ใน storyboard จาก Controller

@IBOutlet weak var tableView: UITableView?

จากนั้นสั่งให้ TableView insertRows ที่เพิ่มเข้ามา

func addNewItemViewController(controller: AddNewItemViewController, didAdd item: TodoItem) {
    todo.add(item: item)
    if let index = todo.index(of: item) {
        tableView?.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
    }
    controller.dismiss(animated: true, completion: nil)
}

อย่าลืมว่าเราจะต้องผูก Outlet ไปที่ storyboard ด้วย โดยเปิด Main.storyboard แล้วคลิ๊กขวาที่ ViewController ใน Document Outline จากนั้นลาก tableView ใน dialog ไปที่ TableView ใน document outline

เมื่อเรารันและกด Done เราจะได้ item Test โผล่ขึ้นมา

โค้ดสุดท้ายของบทนี้

ViewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddNewItemViewControllerDelegate {

    @IBOutlet weak var tableView: UITableView?
    var todo = Todo()

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todo.totalItems
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "todoItemCell", for: indexPath)
        let item = todo.item(at: indexPath.row)
        cell.textLabel?.text = item.title
        cell.accessoryType = item.isDone ? .checkmark : .none
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        todo.add(item: TodoItem(title: "Download XCode", isDone: true))
        todo.add(item: TodoItem(title: "Buy milk"))
        todo.add(item: TodoItem(title: "Learning Swift"))
    }

    func addNewItemViewController(controller: AddNewItemViewController, didAdd item: TodoItem) {
        todo.add(item: item)
        if let index = todo.index(of: item) {
            tableView?.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
        }
        controller.dismiss(animated: true, completion: nil)
    }

    func addNewItemViewControllerDidCancel(controller: AddNewItemViewController) {
        controller.dismiss(animated: true, completion: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "openAddItemSegue" {
            if let nav = segue.destination as? UINavigationController,
                let controller = nav.topViewController as? AddNewItemViewController {
                controller.delegate = self
            }
        }
    }
}

Last updated