TableView

IndexPath: Section & Row

Section and Row

看圖應該可以明瞭Section和Row的含意,Apple把tableView劃分為section,每個section下又有row。
在IOS中有一個叫做IndexPath的類別專門儲存這兩個值,在tableView以後要override的方法會經常看到。

必定要override的method: 幾個section 幾個row

使用tableView必須指定DataSource和Delegate (若是TableViewController則已經指定好自己作為這兩個protocol的實作)
其中有些function是必定要override的

  1. 總共有幾個section
  2. 每個section裡面有幾個row

在這個範例中,我指定兩個section,section 0有2 row,section 1有3 row。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 總共幾個section
override func numberOfSections(in tableView: UITableView) -> Int {
  return 2
}

// 每個section裡面有幾個row
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
	if section == 0{
  	return 2
  }
  else{
  	return 3
	}
}

某一列的內容

但是只有這樣,顯示出來會是空白一片,所以必須要回傳顯示的內容。
在做這個項目之前,必須先到storyBoard,將tableView裏頭的cell指定一個identifier,否則會錯誤。
在此我指定cell的identifier為myCell
Identifier

然後才可以在程式碼中使用myCell

1
2
3
4
5
6
7
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)

  // 顯示目前是第幾Row
  cell.textLabel?.text = "Row " + String(indexPath.row)
  return cell
}

目前畫面

Section Title

但是這樣少了每個section的title,沒有段落感,所以用以下的code加上。

1
2
3
4
5
6
7
8
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  if section == 0{
  	return "Section 0"
  }
  else{
  	return "Section 1"
  }
}

目前畫面

點擊某列跳轉到下一個Controller,並傳值

請參閱之前的傳值方法,我們選用方法一。這邊要補充的是:可以藉由tableview中的indexPathForselectedRow屬性來得知哪一列被選中。

  1. 在prepare裡裡面進行傳值的預備動作。
  2. 轉型Controller,這樣才可以塞property
    • 在此我們假設SecondViewController中有selectedIndexPath這個屬性。
  3. 可以藉由tableView中的indexPathForselectedRow來判定哪個Row被選中。
1
2
3
4
5
6
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let indexPath = tableView.indexPathForSelectedRow{
    let controller = segue.destination as! SecondViewController
    controller.selectedIndexPath = indexPath
  }
}

滑動刪除

只要複寫方法即可,記得必須同時刪除實際的Array以及畫面上的元件,否則會造成畫面與實際資料不同步的問題。

1
2
3
4
5
6
7
8
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {
    // 刪除元素
    myArray.remove(at: indexPath.row)
    // 刪除畫面上的元素
    tableView.deleteRows(at: [indexPath], with: .fade)
  } 
}

但使用這個方法,只能滑出一個按鈕。如果想滑動時有多個按鈕可以選擇請看下節。

自訂滑動選項

如果想要自訂滑動按鈕的文字樣式,或是想指定多個滑動按鈕,那就不能使用上述的複寫方法。而必須改用下面這個。

  1. 複寫方法
  2. 自訂UITableViewRowAction
  3. 回傳UITableViewRowAction陣列

因為此方法回傳陣列,所以可以塞進多個按鈕。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let addAction = UITableViewRowAction(style: .normal, title: "insert") { 
        	(action, indexPath) in
          // 下面可寫插入元素的動作  
        }
        
        let deleteAction = UITableViewRowAction(style: .default, title: "delete") { 
        	(action, indexPath) in
          // 下面可寫刪除元素的動作  
        }
        return [deleteAction, addAction]
    }

更新畫面的method

TableView裡面有三個method可以更新畫面。列表如下

  • 刪除:需提供IndexPath的陣列,一次可以刪一串。
  • 插入:需提供IndexPath的陣列,一次可以加入一串。
  • 重新整理

程式碼如下

1
2
3
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.insertRows(at: [indexPath], with: .fade)
tableView.reloadData()

更換位置

下拉更新

常常會看到有APP在tableView下拉時畫面會開始轉圈圈,然後更新,現在試著製作這個效果。 1. 在StoryBoard中點選TableViewController。 2. 將Refreshing設定成enable 3. 此時左方會出現一個Refresh Control,將其拉到程式碼中,選擇Action,動作為value changed。 - 設定 4. 接下來就可以對剛剛拉出的action做一些事,做完後使用self.refreshControl?.endRefreshing(),就可以結束轉圈圈的畫面。

1
2
3
4
  @IBAction func reload(_ sender: AnyObject) {
      // do something
      self.refreshControl?.endRefreshing()
  }

搜尋

1
2
3
4
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
self.tableView.tableHeaderView = searchController.searchBar
searchController.dimsBackgroundDuringPresentation = false

UICollectionView

UICollectionViewDelegateFlowLayout

可以調整cell的大小