Hướng dẫn lập trình ứng dụng cho MacOS: Part 2 - Làm việc với NSTableView

Table views là một trong những controls phổ biến nhất của các ứng dụng iOS lẫn Mac OS, tương đối quen thuộc như Finder, Mail's Messages, Reminder, Notes.... NSTableview sắp xếp data theo cột và hàng, mỗi hàng là một đối tượng và mỗi hàng là một thuộc tính của đối tượng đó.

1. Bắt đầu:

Bây giờ ta thử demo ứng dụng File Browser tương tự Finder hoặc các cửa số chọn file upload. Download project starter tại đây Run thì thấy được kết quá: Ta sẽ có một khung trống để chuẩn bị hiện danh sách file. Trong project đã có sẳn các hàm liên quan đến quản lý file để chúng ta tập trung vào tableView mà thôi. Bây giờ chúng ta thử mở File > Open… (hoặc phím tắt là Command + O): Đây sẽ là màng hình mà chúng ta sẽ clone lại để tìm hiểu về Table View. 2. Khởi tạo TableView Mở Main.storyboard trong project. Chọn View Controller Scene và kéo TableView từ Object Library vào trong view. Tiếp tục, chọn đối tượng Table Container. Click nút Pin ở Auto Layout toolbar. Ta sẽ canh 4 lề của Table Container với 4 lề của ViewController để tableView luôn tràn theo kích thướt window. Điểm qua cấu tạo của TableView:

  • TableView gồm có nhiều hàng và nhiều cột.
  • Mỗi hàng là đại diện của data object riêng biệt.
  • Mỗi cột là hiện thị thông tin đặc tính của object đó.
  • Mỗi cột đều có thể có tiêu đề.
  • Hàng tiêu đề mô tả thông tin của cột đó.

Nếu bạn đã làm việc quen với UITableView trên iOS thì thực sự choáng váng khi thấy số lượng object con của NSTableView ở MacOS. NSTableView thực sự phức tạp hơn UITableView nhiều về giao diện, đặc điểm và người dùng sử dụng cả chuột lẫn trackpad. Khác biệt lớn nhất vẫn là ở NSTableView mô tả nhiều cột và tiêu đề mỗi cột để người dùng có thể tùy biến sắp xếp. Sau đó còn có NSScrollViewNSClipView, đại diện cho việc sử lý cuộn và cắt thông tin nếu window ko đủ kích thướt. Còn có 2 NSScroller, cả 2 đều thuộc NSScrollView và mỗi cái lo việc cuộn lên xuống và trái phải.

Mặc định, Interface Builder tạo tableView có 2 Cột, nhưng chúng ta lại cần 3 cột để hiển thị tên, ngày và dung lượng file. Quay về file Main.Storyboard. Chọn vào tableView ở View Controller Scene. Và đảm bảo rằng bạn chọn TableView chứ không phải scroll view. Mở ** Attributes Inspector** và tăng con số Columns lên 3, chỉ như thế, bây giờ bạn đã có 3 cột. Tiếp theo, chọn thuộc tính Multiple, vì khi sử dụng bạn cần chọn nhiều file để sử lý, copy... Và củng cần check Alternating Rows để hiển thị màu background xen kẻ, tương tự Finder.

Đổi tên tiêu đề cột để mô tả chi tiết hơn. Chọn cột đầu tiên ở View Controller Scene. Mở **Attributes Inspector ** và đổi tên cột ở Title thành Name Tương tự với 2 cột còn lại: Đổi tên lại thành Modification DateSize Tiếp theo, chúng ta chỉnh sửa lại kích thướt của mỗi cột để hiển thị vừa đủ thông tin của cột đó. Run thử phát xem thế nào:

Bây giờ, tableView đã có 3 cột Name, Date và Size, thế kiểu loại file được thể hiện như thế nào? Ta sẽ hiển thị hình ảnh thể hiện loại file đó là gì ở phía trước tên của file đó trong cột Name. Nhảy vào lại Main.storyboard, chọn Table Cell View ở cột Name và xóa nó đi. Mở Object Library và kéo Image & Text Table Cell View vào dòng đầu tiên của cột Name hoặc ngay lại ví trí Cell vừa xóa khi nãy đều được.

Đặt tên định danh cho cell

Tất cả các cell đều cần identifier (định danh). Để có thể xác định và tái sử dụng được nó. Chọn cell đầu ở tiền cột Name, và trong Identity Inspector đổi identifier lại thành NameCellID Tương tư với cột DateDateCellID, và Size là: SizeCellID.

Hiển thị dữ liệu cho TableView:

Đã xong việc thiết kế giao diện cho TableView. Bây giờ làm sao thể hiển thị dữ liệu cho nó bây giờ? TableView hiện không biết làm thể nào để hiển thị dự liệu, dữ liệu gì, và mô tả sắp xếp nó ra sao, nên bây giờ, ta cần phải implement 2 protocols để phục vụ việc này:

  • NSTableViewDataSource: báo có bao nhiều dòng cần hiển thị
  • NSTableViewDelegate: cung cấp view cho cell của từng dòng trong từng cột. Trong biểu đồ mô tả:
  1. TableView gọi hàm numberOfRows(in:) của NSTableViewDataSource để lấy thông tin số dòng cần hiển thị.
  2. Sau đó gọi hàm **tableView(:viewFor:row:) ** của NSTableViewDelegate để view hiển thị, kèm theo view là thông tin của cell đó. Cả hàm hàm bắt buộc phải được thêm vào trong ViewController, nếu không tableView sẽ không chạy được.

Mở ViewController.swift với chế độ hiển thị Assistant editor và kéo TableView vào trong file ViewControllers để tạo ánh xạ giửa file viewControllers và TableView. Chắc chắn rằng mình đã kéo đúng TableViewtype trong popup hiện lên là NSTableView, và ConnectionOutlet. Ta sẽ đặt tên cho nó là tableView.

Bây giờ tập trung vào file: ViewController.swift. Bây giờ ta chỉ cần thêm đoạn code này vào cuối file:

extension ViewController: NSTableViewDataSource {
  func numberOfRows(in tableView: NSTableView) -> Int {
    return directoryItems?.count ?? 0
  }
}

Đây là hàm numberOfRows(in:) của protocol NSTableViewDataSource, tableView sẽ gọi hàm này và ta sẻ cung cấp cho tableView biết được là cần hiển thị bao nhiu là dòng. Ở đây, ta hiện thị số dòng mà biến array directoryItems có được.

Tiếp theo ta lại thêm đoạn code này ở cuối file ViewController.swift.

extension ViewController: NSTableViewDelegate {
  fileprivate enum CellIdentifiers {
    static let NameCell = "NameCellID"
    static let DateCell = "DateCellID"
    static let SizeCell = "SizeCellID"
  }
  
  func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
    var image: NSImage?
    var text: String = ""
    var cellIdentifier: String = ""
 
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .long
    dateFormatter.timeStyle = .long
 
    // 1
    guard let item = directoryItems?[row] else {
      return nil
    }
    // 2
    if tableColumn == tableView.tableColumns[0] {
      image = item.icon
      text = item.name
      cellIdentifier = CellIdentifiers.NameCell
    } else if tableColumn == tableView.tableColumns[1] {
      text = dateFormatter.string(from: item.date)
      cellIdentifier = CellIdentifiers.DateCell
    } else if tableColumn == tableView.tableColumns[2] {
      text = item.isFolder ? "--" : sizeFormatter.string(fromByteCount: item.size)
      cellIdentifier = CellIdentifiers.SizeCell
    }
    // 3
    if let cell = tableView.make(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView {
      cell.textField?.stringValue = text
      cell.imageView?.image = image ?? nil
      return cell
    }
    return nil
  }
}

Đoạn code này khai báo thõa mãn NSTableViewDelegate protocol và mô tả hàm **tableView(:viewFor:row) **, Tableview sẽ gọi hàm này cho mỗi cell của từng hàng và từng cột. Trong đoạn code trên có đoạn chính:

  1. Nếu không có data thì return là cell trống.
  2. Dựa theo cột mà mà xác định identifier sẽ sử dụng trong cell đó.
  3. Get cellView bằng hàm make(withIdentifier:owner:), sau đó truyền thông tin vào trong cell đó theo từng hàng và cột.

Tiếp theo, ta thêm 2 đoạn code này vào trong hàm viewDidLoad():, để tableView xác định được đối tượng để lấy thông tin từ đó.

tableView.delegate = self
tableView.dataSource = self

Bây giờ, ta xem biến representedObject với hàm didSet của nó, và thêm vào dòng code này: directory = Directory(folderURL: url)

Xong, bây giờ chỉ có việc sử build và chạy thử thành quả, sau khi Run xong, ta sử dụng menu: File > Open… (hoặc phím tắt Command + O), sẽ mở ra pop up chọn file như hồi nãy, ta chọn vào thử mục bất kì và open nó. Và đây là kết quả.

Kết luận

Qua đây, chúng ta sẽ biết được cách đơn giản nhất để sử dụng tableView trên MacOS. Trong phần tiếp theo, ta sẽ xử lý tiếp với việc sắp xếp, click vào hàng, cột trên tableView.