Sử dụng Protocols, Delegates với UITableView

Với bài trước, các bạn đã hiểu được về Protocols, Delegates (và cả Tuples nữa). Hôm nay chúng ta sẽ ứng dụng chúng ở tron UITableView - 1 thành phần cực kỳ quan trọng mà hầu như trong bất kỳ app iOS nào cũng sử dụng

UITableView có 1 property gọi là delegate - bạn sẽ set property này với class mà "conforms" UITableViewDelegate. Đây là một protocols với rất nhiều optional methods sẽ thông báo khi về các sự kiện như khi được row được chọn/chạm, khi tableview chuyển sang chế độ edit ...

UITableView cũng có 1 property khác gọi là datasource - bạn sẽ set property này với class "conforms" UITableViewDataSource. Sự khác nhau giữa 2 property này là thay vì thông báo về các sự kiện thì table view sẽ hỏi class này về mặt data như có table view sẽ có bao nhiêu row, mỗi row hiển thị những thông tin gì ...

delegate thì là tuỳ chọn (optional) nhưng datasource thì là bắt buộc (required) nên chúng ta sẽ tạo ra datasource cho TipCalculator

Tạo 1 file playground giống như class TipCalculatorModel mà bạn đã tạo ra ở bài viết Tìm hiểu Swift thông qua việc làm 1 ứng dụng tính toán tiền Tip đơn giản trên iOS. Thêm đoạn code sau vào phía dưới cùng:

import UIKit

class TestDataSource : NSObject {

  let tipCalc = TipCalculatorModel(total: 33.25, taxPct: 0.06)
  var possibleTips = Dictionary<Int, (tipAmt:Double, total:Double)>()
  var sortedKeys:[Int] = []

  override init() {
    possibleTips = tipCalc.returnPossibleTips()
    sortedKeys = sorted(Array(possibleTips.keys))
    super.init()
  }

}

Bây giờ chúng ta sẽ làm class này conforms với UITableViewDataSource bằng cách thêm data source vào dóng khai báo class:

class TestDataSource: NSObject, UITableViewDataSource {

Sau đó thêm 2 methods sau cho class:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return sortedKeys.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

  let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: nil)

  let tipPct = sortedKeys[indexPath.row]

  let tipAmt = possibleTips[tipPct]!.tipAmt
  let total = possibleTips[tipPct]!.total

  cell.textLabel?.text = "\(tipPct)%:"
  cell.detailTextLabel?.text = String(format:"Tip: $%0.2f, Total: $%0.2f", tipAmt, total)
  return cell
}

Cuối cùng thêm đoạn code sau vào cuối cùng của file để kiểm tra table view:

let testDataSource = TestDataSource()
let tableView = UITableView(frame:CGRect(x: 0, y: 0, width: 320, height: 320), style:.Plain)
tableView.dataSource = testDataSource
tableView.reloadData()

Di chuyển chuột tới góc dưới bên phải màn hình và click vào icon hình con mắt, bạn sẽ nhìn được tableview được hiển thị như thế nào Tableview1.png

Đến đây bạn đã hoàn thành xong phần prototype, đã đến lúc chúng ta ứng dụng nó vào trong project chính thức thay vì chỉ thử ở file playground

Mở project TipCalculator, tìm file TipCalculatorModel.swift và thay thế bằng đoạn code mới ở file playground vừa xong. Tất nhiên là bạn không cần copy cả nhũng dòng test hiển thị table view nữa.

Tiếp theo mở file Main.storyboard, chọn Text View (phần hiển thị kết quá) và xoá nó đi. Từ Object Library kèo 1 Table View (không phải Table View Controler) vào trong View Controller. Set X=0, Y=192, Width=600, Height=408

Chúng ta sẽ phải setup lại phần Auto Layout bằng cách click vào nút hình tam giác của Interface Builder (khi di chuột vào thì sẽ hiện lên Resolve Auto Layout Issues) và chọn Clear Constraints sau đó lại click và chọn Add Missing Constraints

Clear Constraints

Tiếp theo, click chọn Table View, bên phần Connections Inspector. Click vào dấu + bên phải dataSource và kéo sang View Controller (Tip Calculator)

Add connection data source

Thực hiện tương tự đối với delegate

Mở chế độ Assistant Editor, giữ Control và kéo từ table view vào view controller như hình dưới

Make outlet

Đặt tên là tableView và chọn Connect. Như vậy bạn có thể refer đến table view ở trong code.

Mở file ViewController.swift và mark class conforms với UITableViewDataSource:

class ViewController: UIKit.UIViewController, UITableViewDataSource {

Và thêm 2 biến bên dưới tipCalc:

var possibleTips = Dictionary<Int, (tipAmt:Double, total:Double)>()
var sortedKeys:[Int] = []

Thay calculateTapped() với đoạn sau:

@IBAction func calculateTapped(sender : AnyObject) {
  tipCalc.total = Double((totalTextField.text as NSString).doubleValue)
  possibleTips = tipCalc.returnPossibleTips()
  sortedKeys = sorted(Array(possibleTips.keys))
  tableView.reloadData()
}

Xoá dòng set resultsTextView ở trong refreshUI()

Copy 2 methods của table view ở trong file playground vừa nãy vào trong class.

Như vậy file View Controller của chúng ta sẽ có nội dung như sau

import UIKit

class ViewController: UIKit.UIViewController, UITableViewDataSource {

    @IBOutlet var totalTextField : UITextField!
    @IBOutlet var taxPctSlider : UISlider!
    @IBOutlet var taxPctLabel : UILabel!
    @IBOutlet var resultsTextView : UITextView!
    @IBOutlet weak var tableView: UITableView!
    let tipCalc = TipCalculatorModel(total: 33.25, taxPct: 0.06)
    var possibleTips = Dictionary<Int, (tipAmt:Double, total:Double)>()
    var sortedKeys:[Int] = []

    func refreshUI() {
        totalTextField.text = String(format: "%0.2f", tipCalc.total)
        taxPctSlider.value = Float(tipCalc.taxPct) * 100.0
        taxPctLabel.text = "Tax Percentage (\(Int(taxPctSlider.value))%)"
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        refreshUI()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func calculateTapped(sender : AnyObject) {
        tipCalc.total = Double((totalTextField.text as NSString).doubleValue)
        possibleTips = tipCalc.returnPossibleTips()
        sortedKeys = sorted(Array(possibleTips.keys))
        tableView.reloadData()
    }

    @IBAction func taxPercentageChanged(sender : AnyObject) {
        tipCalc.taxPct = Double(taxPctSlider.value) / 100.0
        refreshUI()
    }

    @IBAction func viewTapped(sender : AnyObject) {
        totalTextField.resignFirstResponder()
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sortedKeys.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: nil)

        let tipPct = sortedKeys[indexPath.row]

        let tipAmt = possibleTips[tipPct]!.tipAmt
        let total = possibleTips[tipPct]!.total

        cell.textLabel?.text = "\(tipPct)%:"
        cell.detailTextLabel?.text = String(format:"Tip: $%0.2f, Total: $%0.2f", tipAmt, total)
        return cell
    }
}

Build chạy thử để xem thành quả nhé. Nếu không có vấn đề gì thì các bạn sẽ đươc kết quả như sau: Final

Hi vọng sẽ nhận được các comment hỏi han cũng như góp ý từ mọi người


All Rights Reserved