Swift(二十一)UITableView

更新:2018.05.24

整理了一下demo:SwiftDemo


最近比較忙,沒什么時間寫,斷斷續(xù)續(xù)寫一點。

UITableView是我們開發(fā)過程中比較常用的,用于顯示一系列對象,UITableView繼承自UIScrollView,UIScrollView可以在任意方向滑動,而UITableView只在垂直方向上滑動。UITableView中的內容是由UITableViewCell負責顯示的。

1. UITableViewCell

  • UITableViewUITableViewCell組成,UITabelViewCell負責顯示數據。
  • UITableView的每一行,即每一個UITableViewCell顯示一條項目。
  • UITableViewCell對象的數量不受限制,僅由設備內存決定。
  • UITableViewCell類定義了單元格在UITableView中的屬性和行為。

創(chuàng)建 UITableViewCell 的時候,你可以自定義一個 cell ,或者使用系統(tǒng)預定義的幾種格式。系統(tǒng)預定義的 cell 提供了 textLabeldetailTextLabel屬性和imageView屬性用來設置cell的內容和圖片。樣式由UITableViewCellStyle枚舉來控制:

枚舉類型 描述
.default 包含一個左側的可選圖像視圖,和一個左對齊的標簽對象。
.value1 包含一個左側的可選視圖和一個左對齊的標簽對象,在單元格右側還有一個灰色、右對齊的標簽對象。
.value2 包含一個左側、右對齊的藍色文字標簽對象和一個右側的左對齊的標簽對象。
.subtitle 包含一個左側的可選圖像視圖,和一個左對齊的標簽對象,在這個標簽對象下方,還有一個字體較小的標簽對象。

2. 創(chuàng)建一個UITableView

創(chuàng)建UITableView,首先是實例化一個UITableView對象,還要涉及到它的代理UITabelViewDataSource、UITableViewDelegate,在UITableViewDataSource代理方法中定義UITableViewCell的樣式。

import UIKit

class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white;
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
    }

//MARK: UITableViewDataSource
    // cell的個數
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    // UITableViewCell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellid = "testCellID"
        var cell = tableView.dequeueReusableCell(withIdentifier: cellid)
        if cell==nil {
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }
        
        cell?.textLabel?.text = "這個是標題~"
        cell?.detailTextLabel?.text = "這里是內容了油~"
        cell?.imageView?.image = UIImage(named:"Expense_success")
        return cell!
    }
  
//MARK: UITableViewDelegate
    // 設置cell高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 44.0
    }
    // 選中cell后執(zhí)行此方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }  
}

  • 添加了代理協(xié)議UITableViewCell,主要用來給UITableView提供數據來源,并用來處理數據源的變化。
    它的主要帶你方法:

    • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
      初始化和復用指定索引位置的UITableViewCell,必須實現。
    • tableView(_ tableView: UITableView, numberOfRowsInSection section: Int):
      設置某一章節(jié)(section)中的單元格數量,必須實現。
    • numberOfSections(in tableView: UITableView):
      設置表格中的章節(jié)(section)個數。
    • tableView(_ tableView: UITableView, titleForHeaderInSection section: Int):
      設置指定章節(jié)的標題文字,如果不設置或代理返回值為nil,不顯示。
    • tableView(_ tableView: UITableView, titleForFooterInSection section: Int):
      設置章節(jié)腳部標題文字,如果不設置或代理返回值為nil,不顯示。
    • tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath):
      設置表格中指定索引位置的cell是否可編輯,可編輯的cell會顯示插入和刪除的圖標。
    • tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath):
      當完成插入或刪除操作時會調用此方法。
    • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath):
      設置指定索引位置的cell是否可以通過拖動的方式,改變它的位置。
    • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath):
      cell從一個位置拖動到另一個位置時調用此方法。
  • 然后我們進行了實例化,設置位置和尺寸,然后設置UITableView的數據源為當前視圖控制器對象,即設置代理UITableViewDataSource。

  • 實現數據源協(xié)議定義中的方法,從而設置章節(jié)中cell的個數,以及對cell進行初始化和復用設置。

  • indexPathNSIndexPath類用來描述在嵌套數列的樹種指定節(jié)點的路徑,即索引路徑。索引路徑中的每一個索引,都用來表示一個節(jié)點的子數組中的指定索引位置。事實上,NSIndexPath描述了一整個數列,表示在表格視圖中指定的章節(jié)中的指定行。
    UITableView中的索引路徑包括兩個元素,第一個元素section是表格的章節(jié)序號,第二個元素row表示章節(jié)中的行序號。

  • 還添加了UITableViewDelegate代理協(xié)議,它的主要作用是提供一些可選的方法,用來控制表格的選擇、指定章節(jié)的頭和尾的顯示、單元格內容的復制和粘貼以及協(xié)助完成單元格的排序等功能。
    主要代理方法有:

    • tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath):
      設置單元格高度,每當表格需要顯示時,都會調用此方法。
    • tableView(_ tableView: UITableView, heightForHeaderInSection section: Int)
      設置某一索引下的章節(jié)頭部的高度。
    • tableView(_ tableView: UITableView, heightForFooterInSection section: Int):
      設置某一索引下的章節(jié)尾部的高度。
    • tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath):
      當指定索引位置上的單元格即將顯示時,調用此方法。此方法是委托對象有機會在單元格顯示之前重寫其狀態(tài)屬性,如背景顏色等。
    • tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath):
      當用戶點擊選擇指定索引位置的單元格時,調用此方法。
    • tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath):
      當用戶點擊一個已經被選中的單元格時,調用此方法。

3. UITableView 復用機制

復用機制在很多地方都有應用,比如去飯店吃飯,盤子、碗都不是一次性的,當客人使用過之后,會經過消毒、清洗。然后再給下一批客人使用。如果每個客人使用過之后都換一批新的話,那成本太高了。

UITableView也采用復用機制,一個UITableView可能需要顯示100條數據,但屏幕尺寸有限,假設一次只能顯示9條,如果滑動一下的話,會顯示出第10條的一部分,所以當前屏幕在這種情況下最多只能顯示出10條。
所以系統(tǒng)只需要創(chuàng)建10個UITableViewCell對象就好,當手指從下往上滑動時,回收處于屏幕之外的最上方單元格,并放置到表格最下方,作為將要顯示的11個單元格。當UITableView對象從上往下滑動時,也是同樣的服用機制。

在上面的代碼中:

        let cellid = "testCellID"
        var cell = tableView.dequeueReusableCell(withIdentifier: cellid)

dequeueReusableCell方法的作用是從單元格對象池中獲取指定類型并可復用的單元格對象。

        if cell==nil {
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }

如果從對象池中沒有獲得可復用的單元格,就調用實例化方法實例一個某一類型的、可復用的單元格。

  • style參數: 枚舉常量,用于表示單元格的樣式。
  • reuseIdentifier: 作為一個字符串類型的參數,它用來標識具有相同類型的、可復用的單元格。對于相同類型的單元格,需要使用相同的reuseIdentifier參數。

4. 自定義UITableViewCell

一般對于相對復雜一些的顯示內容,我們會創(chuàng)建一個UITableViewCell的類文件。


Subclass of 寫UITableViewCell

上代碼:


import UIKit

class NewTableViewCell: UITableViewCell {

    let width:CGFloat = UIScreen.main.bounds.width
    var userLabel:UILabel!      // 名字
    var birthdayLabel:UILabel!  // 出生日期
    var sexLabel:UILabel!       // 性別
    var iconImv:UIImageView!    // 頭像
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // 頭像
        iconImv = UIImageView(frame: CGRect(x: 20, y: 15, width: 44, height: 44))
        iconImv.layer.masksToBounds = true
        iconImv.layer.cornerRadius = 22.0
        
        // 名字
        userLabel = UILabel(frame: CGRect(x: 74, y: 18, width: 70, height: 15))
        userLabel.textColor = UIColor.black
        userLabel.font = UIFont.boldSystemFont(ofSize: 15)
        userLabel.textAlignment = .left
        
        // 性別
        sexLabel = UILabel(frame: CGRect(x: 150, y: 20, width: 50, height: 13))
        sexLabel.textColor = UIColor.black
        sexLabel.font = UIFont.systemFont(ofSize: 13)
        sexLabel.textAlignment = .left
        
        // 出生日期
        birthdayLabel = UILabel(frame: CGRect(x: 74, y: 49, width: width-94, height: 13))
        birthdayLabel.textColor = UIColor.gray
        birthdayLabel.font = UIFont.systemFont(ofSize: 13)
        birthdayLabel.textAlignment = .left
        
        contentView.addSubview(iconImv)
        contentView.addSubview(userLabel)
        contentView.addSubview(sexLabel)
        contentView.addSubview(birthdayLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    } 
}
  • 上面代碼中,首先給NewTableViewCell添加了五個屬性:width屏幕寬度、iconImv頭像、userLabel用戶名、sexLabel性別和birthdayLabel生日。
  • 然后添加實例化方法:init(style: UITableViewCellStyle, reuseIdentifier: String?)并在方法中實例化定義的4個屬性,將他們添加到屏幕上。
  • 最后實現繼承自UITableViewCell類所必須的init?(coder aDecoder: NSCoder)構造函數。

現在我們完成了NewTableViewCell的創(chuàng)建,再到ViewController.swift類文件中,調用這個自定義單元格類。

import UIKit

class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {

    var dataSource = [[String:String]()]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white;
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
        
        dataSource = [
          ["name":"王小明","sex":"男","icon":"my_def_photo","birthday":"2017-10-11"],
          ["name":"李磊","sex":"男","icon":"my_def_photo","birthday":"2011-12-30"],
          ["name":"韓梅","sex":"女","icon":"my_def_photo","birthday":"2014-9-10"],
          ["name":"JIM","sex":"男","icon":"my_def_photo","birthday":"2008-10-1"]]
        tableView.reloadData()
    }

//MARK: UITableViewDataSource
    // cell的個數
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    // UITableViewCell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellid = "testCellID"
        var cell:NewTableViewCell? = tableView.dequeueReusableCell(withIdentifier: cellid) as? NewTableViewCell
        if cell==nil {
            cell = NewTableViewCell(style: .subtitle, reuseIdentifier: cellid)
        }
        let dict:Dictionary = dataSource[indexPath.row]
        cell?.iconImv.image = UIImage(named: dict["icon"]!)
        cell?.userLabel.text = dict["name"]
        cell?.sexLabel.text = dict["sex"]
        cell?.birthdayLabel.text = dict["birthday"]
        return cell!
    }
    
//MARK: UITableViewDelegate
    // 設置cell高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 74.0
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 20
    }
    // 選中cell后執(zhí)行此方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }
}
  • 這里實例了一個數組,數組內的元素是字典,用來存放需要展示的數據。
  • 然后注意tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)代理方法,返回參數是dataSource.count,意思是數組中有幾條數據就展示幾個Cell。
  • 接下來就是修改tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)方法中的Cell了。并根據dataSource數組中的數據對cell的元素進行賦值。
  • 后面我們還修改了cell的高度,和header的高度。跑一下項目:

5. 添加索引和章節(jié)(Section)

最常見的帶有索引的TableView就是通訊錄了吧,在TableView的右側有一個垂直的索引序列,點擊索引序列的元素可在表格中迅速定位到指定的位置,尤其是擁有大量數據的時候。

先來看一下索引需要用到的代理方法:

  • numberOfSections(in tableView: UITableView)
    設置TableView中章節(jié)(Section的數量)不設置默認為1。
  • tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
    在指定章節(jié)中,cell的個數。
    -tableView(_ tableView: UITableView, titleForHeaderInSection section: Int)
    設置章節(jié)標題文字,返回結果為字符串,如果返回為nil,則不顯示標題。
  • sectionIndexTitles(for tableView: UITableView)
    設置在表格右側顯示的索引序列的內容,返回結果為一個字符串數組
  • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
    TableViewCell初始化和復用

開始之前,需要先創(chuàng)建索引表格所需的數據源,剛才的例子中是添加了一個 數組 作為數據源,這里索引的話需要一個 字典 來作為數據源。
開發(fā)中需要數據源通常各種各樣,不如加載本地文本文件和plist文件,或者從服務器請求書院,通過返回的JSON或XML作為數據源。這里我們僅創(chuàng)建一個字典作為數據源。

代碼搞完了,上代碼:

import UIKit

class IndexsViewController: UIViewController,UITableViewDataSource {

    let contents:Dictionary<String,[String]> =
        ["A":["安其拉"],
         "B":["步驚云","不知火舞","白起","扁鵲"],
         "C":["程咬金","成吉思汗","蔡文姬","曹操"],
         "D":["妲己","狄仁杰","典韋","貂蟬","達摩","大喬","東皇太一"],
         "G":["高漸離","關羽","宮本武藏","干將莫邪","鬼谷子"],
         "H":["韓信","后羿","花木蘭","黃忠"],
         "J":["荊軻","姜子牙"],
         "L":["老夫子","劉邦","劉嬋","魯班七號","蘭陵王","露娜","廉頗","李元芳","劉備","李白","呂布"],
         "M":["墨子","羋月"],
         "N":["牛魔","娜可露露","哪吒","女媧"],
         "P":["龐統(tǒng)",""],
         "S":["孫臏","孫尚香","孫悟空"],
         "W":["王昭君","武則天"],
         "X":["項羽","小喬"],
         "Y":["亞瑟","虞姬","嬴政"],
         "Z":["周瑜","莊周","甄姬","鐘無艷","張飛","張良","鐘馗","趙云","諸葛亮"]]
    var keys:[String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.isNavigationBarHidden = false
        // 把字典里的key拿出來放到一個數組中,備用,作為章節(jié)的標題
        keys = contents.keys.sorted()
        
        let tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.dataSource = self
        view.addSubview(tableView)
    }
//MARK: UITableViewDataSource
    //MARK: 章節(jié)的個數
    func numberOfSections(in tableView: UITableView) -> Int {
        return keys.count
    }
    //MARK: 某一章節(jié)cell個數
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let arr = contents[keys[section]]
        return (arr?.count)!
    }
    //MARK: 初始化cell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "indexsCellId")
        if cell==nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "indexsCellId")
        }
        let arr = contents[keys[indexPath.section]]
        cell?.textLabel?.text = arr?[indexPath.row]
        return cell!
    }
    //MARK: 每一個章節(jié)的標題
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return keys[section]
    }
    //MARK: 設置索引序列內容
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        return keys
    }  
}
  • 首先我們來確定一下數據源,本來是想用人名的,不過想想萬一暴露了什么被發(fā)現~~,然后就用了農藥里的英雄,勞逸結合,勞逸結合。這不是重點
  • 然后我們定義了一個數組keys,用來存放數據源里面的key。
  • 實例化TableView,設置代理,實現需要用到的代理方法。這還不是重點。
  • 實現代理方法,都有注釋,就不細說了,設置章節(jié)個數、設置每個章節(jié)的cell個數,初始化cell、設置每一個章節(jié)的頭部標題。這也不是重點
  • 然后實現代理,設置索引內容:sectionIndexTitles(for tableView: UITableView),這才是重點,添加了這個方法,右側才會出現索引序列。
    點擊索引條目,會迅速的到達點擊索引內容的部分。

需要注意的是: 實例化的時候,init方法第二個參數有兩個值:.plain.grouped。
如果不添加章節(jié)頭部的話,基本看不出這兩個值給tableView帶來的變化。
但在這里,是有區(qū)別的:

  • .plain:如果傳的是這個參數,向上滑動,當章節(jié)頭部滑動到UITableVeiw的上方邊界時,章節(jié)頭部會停在邊界位置,知道下一個章節(jié)頭部到達它的位置,它才會繼續(xù)向上滑動,下一個章節(jié)頭部會占據它的位置。
  • . grouped:就正?;瑒樱瑳]啥影響。

哦,除了這個還是有別的區(qū)別的,當設置的是plain,如果cell的個數不夠撲滿屏幕,系統(tǒng)會一直創(chuàng)建空的cell來撲滿,可以試一下,能看到一條一條的橫線,cell的分割線,如果是設置的grouped就不會有這種情況。

上張圖:

6. cell的選擇和取消選擇

本來想等著看WWDC的,結果睡著了,早上看了新聞,又特么要做適配了,還特么這么貴。

這個有很多場景會遇到的,比如說,我們之前項目里有支付功能,需要設置一下默認支付方式,默認微信還是支付寶,產品給的UI就是一表格。

需求:三種支付方式,只能單選。

import UIKit

class SelectViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
    
    // 數據源,
    var dataSource = [["微信支付":"select"],["支付寶支付":"on"],["銀聯支付":"no"]]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.backgroundColor = UIColor.white
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0.01
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "selectCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "selectCell")
            cell?.selectionStyle = .none
        }
        let dic = dataSource[indexPath.row] as Dictionary
        cell?.textLabel?.text = dic.keys.first
        if dic.values.first == "select" {
            cell?.accessoryType = .checkmark
        } else {
            cell?.accessoryType = .none
        }
        return cell!
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
       
        var i = 0
        for var dict in dataSource {
            
            if i == indexPath.row {
                dict[dict.keys.first!] = "select"
                dataSource[i] = dict
            } else {
                dict[dict.keys.first!] = "no"
                dataSource[i] = dict
            }
            i = i+1
        }
        tableView.reloadData()
    }
    
}
  • 先說一下思路:首先定義了一個數據源,因為是單選,所以用的是數組嵌套字典,key就是支付方式,value是代表是否選中個狀態(tài)的string,如果選中,就把數據源里這個位置的value變成select,但是只能單選,所以還需要把其他的都變成no。

  • 直接說重點:cell的實例化和復用代理方法中,可以看到,如果數據源里的valueselecrt,就把 accessoryType屬性設置成checkmark。

  • tableView(_:, didSelectRowAt:)方法中,可以看到,用For循環(huán)來修改元數據中選中狀態(tài)的value,然后調用reloadData()方法刷新cell。

  • cellaccessoryType屬性的值是枚舉UITableViewCellAccessoryType:

枚舉類型 說明
none 沒有任何的樣式
detailButton 右側藍色的圓圈,中間帶嘆號
detailDisclosureButton 右側藍色圓圈帶嘆號,它的右側還有一個灰色向右的箭頭
disclosureIndicator 右側一個灰色的向右箭頭
checkmark 右側藍色對號

7. cell的插入和刪除

插入和刪除設計到的兩個代理方法:

  • tableView(_ tableView:, editingStyleForRowAt indexPath:)
    確定編輯模式,Add or Delete
  • tableView(_ tableView:, commit editingStyle:, forRowAt indexPath:)
    當執(zhí)行編輯操作時,調用此方法

和一個開啟TableView編輯模式的方法:

  • setEditing(_ editing:, animated:)
    • editing: 是否開啟編輯狀態(tài)
    • animated: 是否有動畫效果
import UIKit

class AddViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    var dataSource = [["微信","支付寶","銀聯"],["微信","支付寶","銀聯"]]
    var tableView:UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "支付方式"
        
        let rightBar = UIBarButtonItem.init(barButtonSystemItem: .add, target: self, action: #selector(addButtonClick))
        navigationItem.rightBarButtonItem = rightBar
        
        tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
        
    }
    
    //MARK: 導航欄右側按鈕,點擊開啟或關閉編輯模式
    func addButtonClick() {
        tableView.setEditing(!tableView.isEditing, animated: true)
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource[section].count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "addCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "addCell")
            cell?.selectionStyle = .none
        }
        let arr = dataSource[indexPath.section] as Array
        cell?.textLabel?.text = arr[indexPath.row] as String
        return cell!
    }
    
    //MARK: 編輯模式,增加還是刪除
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
        if indexPath.section == 1 {
            return .delete
        }
        return .insert
    }
    //MARK: 執(zhí)行編輯操作時,調用此方法
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        var arr = dataSource[indexPath.section] as Array
        if editingStyle == .insert {
            arr.insert("Apple Pay", at: indexPath.row)
            dataSource[indexPath.section] = arr
            tableView.insertRows(at: [indexPath], with: .right)
        } else {
            arr.remove(at: indexPath.row)
            dataSource[indexPath.section] = arr
            tableView.deleteRows(at: [indexPath], with: .left)
        }
    }
    
}
  • 說下思路,為了方便舉例,我設置了兩個Section,第一個Section做增加操作,第二個Section做刪除操作,所以數據源里放的是兩個數組。
  • 在導航條右側添加了一個按鈕,用來確定編輯狀態(tài)是否開啟。
  • setEditing方法上面說過了,就不說了。看下面兩個代理方法。
  • tableView(_: ,editingStyleForRowAt:)方法返回參數是UITableViewCellEditingStyle:
    • insert: 添加操作
    • delete: 刪除操作
    • none: 沒有任何操作
  • tableView(_:, commit editingStyle:, forRowAt:),當執(zhí)行了編輯操作,就會調起這個方法,你可以通過編輯狀態(tài)對TableView和數據源進行操作。注意一定要把數據源和視圖顯示操作保持一致,不然很容易數組越界導致崩潰。
  • insertRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)deleteRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)方法的作用都是對TableViewCell的條數進行操作,一個增加一個刪除
  • UITableViewRowAnimation枚舉的作用是控制操作的動畫:
屬性 說明
fade 以淡入淡出的方式顯示或移除
right 添加或刪除時,從右側滑入或劃出
left 添加或刪除時,從左側滑入或劃出
top 添加或刪除時,從上方滑入或劃出
bottom 添加或刪除時,從底部滑入或劃出
middle 表格視圖將盡量使新舊cell居中顯示在曾經或將要顯示的位置
automatic 自動選擇適合自身的動畫方式
none 采用默認動畫方式

8. cell位置移動功能

支持重新排序(Reordering)功能的TableView,允許用戶拖動位于單元格右側的排序圖標,來重新排序TableView中的單元格。
排序功能一般用到的還是比較多的,我曾經做過一個類似PPT的功能,要求可以更換演示版頁的排列方式,就是用的這個功能。

移動功能同樣設計到了兩個代理方法:

  • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath)
    設置cell是否可移動
  • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
    每次移動結束后會調用此方法

移動功能同樣需要開啟編輯模式setEditing(_: ,animated:)

import UIKit

class ReorderViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    var dataSource = ["微信","支付寶","銀聯","易寶"]
    var tableView:UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let rightBar = UIBarButtonItem.init(barButtonSystemItem: .add, target: self, action: #selector(ReorderButtonClick))
        navigationItem.rightBarButtonItem = rightBar
        
        tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.delegate = self
        tableView.dataSource = self
        view.addSubview(tableView)
    }
    
    func ReorderButtonClick() {
        tableView.setEditing(!tableView.isEditing, animated: true)
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0.01
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "addCell")
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: "addCell")
            cell?.selectionStyle = .none
        }
        cell?.textLabel?.text = dataSource[indexPath.row] as String
        return cell!
    }
    //MARK: 選擇編輯模式,不刪除也不添加就設置為none
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
        return .none
    }
    //MARK: 設置cell是否可移動
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    //MARK: 移動結束后調用此方法
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        
        let data = dataSource[sourceIndexPath.row]
        dataSource.remove(at: sourceIndexPath.row)
        dataSource.insert(data, at: destinationIndexPath.row)
    }
}

  • 說下思路,先開啟TableView的編輯模式,但我們現在不需要添加或刪除,只需要移動功能。 直接看后面三個代理方法:
  • tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath):
    因為我們不需要添加或刪除操作,所以調用此方法,并設置返回none
  • tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath):
    移動返回true
  • tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath):
    主要說這個,這個方法的作用是移動結束后調用,一般我們用它來同步數據源中的數據保持與視圖同步,從代碼里可以看出,三個參數的作用:
    • 第一個就是移動的tableView了,如果當前視圖只有一個tableView不用管他。
    • 第二個參數是移動的cell曾經的位置。
    • 第三個參數是移動的最后位置。

沒有PS工具,我是把圖片放到word里面截圖出來的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容