Swift 自定義UITableView

github demo:https://github.com/LINGLemon/LXFSwiftApp

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

1. UITableViewCell

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

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

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

2. 創建一個UITableView

創建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後執行此方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }  
}

  • 添加了代理協議UITableViewDataSource,主要用來給UITableView提供數據來源,並用來處理數據源的變化。
    它的主要帶你方法:

    • tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
      初始化和複用指定索引位置的UITableViewCell必須實現
    • tableView(_ tableView: UITableView, numberOfRowsInSection section: Int):
      設置某一章節(section)中的單元格數量,必須實現
    • numberOfSections(in tableView: UITableView):
      設置表格中的章節(section)個數。
    • tableView(_ tableView: UITableView, titleForHeaderInSection section: Int):
      設置指定章節的標題文字,如果不設置或代理返回值爲nil,不顯示。
    • tableView(_ tableView: UITableView, titleForFooterInSection section: Int):
      設置章節腳部標題文字,如果不設置或代理返回值爲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

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

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

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

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

3. UITableView 複用機制

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

UITableView也採用複用機制,一個UITableView可能需要顯示100條數據,但屏幕尺寸有限,假設一次只能顯示9條,如果滑動一下的話,會顯示出第10條的一部分,所以當前屏幕在這種情況下最多隻能顯示出10條。
所以系統只需要創建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

一般對於相對複雜一些的顯示內容,我們會創建一個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的創建,再到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後執行此方法
    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. 添加索引和章節(Section)

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

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

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

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

代碼搞完了,上代碼:

import UIKit

class IndexsViewController: UIViewController,UITableViewDataSource {

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

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

  • .plain:如果傳的是這個參數,向上滑動,當章節頭部滑動到UITableVeiw的上方邊界時,章節頭部會停在邊界位置,知道下一個章節頭部到達它的位置,它纔會繼續向上滑動,下一個章節頭部會佔據它的位置。
  • . grouped:就正常滑動,沒啥影響。

哦,除了這個還是有別的區別的,當設置的是plain,如果cell的個數不夠撲滿屏幕,系統會一直創建空的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是代表是否選中個狀態的string,如果選中,就把數據源裏這個位置的value變成select,但是隻能單選,所以還需要把其他的都變成no

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

  • tableView(_:, didSelectRowAt:)方法中,可以看到,用For循環來修改元數據中選中狀態的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:)
    當執行編輯操作時,調用此方法

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

  • setEditing(_ editing:, animated:)
    • editing: 是否開啓編輯狀態
    • 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: 執行編輯操作時,調用此方法
    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做刪除操作,所以數據源裏放的是兩個數組。
  • 在導航條右側添加了一個按鈕,用來確定編輯狀態是否開啓。
  • setEditing方法上面說過了,就不說了。看下面兩個代理方法。
  • tableView(_: ,editingStyleForRowAt:)方法返回參數是UITableViewCellEditingStyle:
    • insert: 添加操作
    • delete: 刪除操作
    • none: 沒有任何操作
  • tableView(_:, commit editingStyle:, forRowAt:),當執行了編輯操作,就會調起這個方法,你可以通過編輯狀態對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裏面截圖出來的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章