Swift(二十一)UITableView

更新:2018.05.24

整理了一下demo:SwiftDemo


最近比较忙,没什么时间写,断断续续写一点。

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)
    }  
}

  • 添加了代理协议UITableViewCell,主要用来给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里面截图出来的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容