实用工具首页投稿(暂停使用,暂停投稿)iOS Developer

Swift(二十一)UITableView

2017-09-13  本文已影响426人  YvanLiu

更新:2018.05.24

整理了一下demo:SwiftDemo


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

UITableView是我们开发过程中比较常用的,用于显示一系列对象,UITableView继承自UIScrollViewUIScrollView可以在任意方向滑动,而UITableView只在垂直方向上滑动。UITableView中的内容是由UITableViewCell负责显示的。

1. UITableViewCell

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

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

如果从对象池中没有获得可复用的单元格,就调用实例化方法实例一个某一类型的、可复用的单元格。

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的创建,再到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)
    }
}

5. 添加索引和章节(Section)

最常见的带有索引的TableView就是通讯录了吧,在TableView的右侧有一个垂直的索引序列,点击索引序列的元素可在表格中迅速定位到指定的位置,尤其是拥有大量数据的时候。

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

开始之前,需要先创建索引表格所需的数据源,刚才的例子中是添加了一个 数组 作为数据源,这里索引的话需要一个 字典 来作为数据源。
开发中需要数据源通常各种各样,不如加载本地文本文件和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
    }  
}

需要注意的是: 实例化的时候,init方法第二个参数有两个值:.plain.grouped
如果不添加章节头部的话,基本看不出这两个值给tableView带来的变化。
但在这里,是有区别的:

哦,除了这个还是有别的区别的,当设置的是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()
    }
    
}
枚举类型 说明
none 没有任何的样式
detailButton 右侧蓝色的圆圈,中间带叹号
detailDisclosureButton 右侧蓝色圆圈带叹号,它的右侧还有一个灰色向右的箭头
disclosureIndicator 右侧一个灰色的向右箭头
checkmark 右侧蓝色对号

7. cell的插入和删除

插入和删除设计到的两个代理方法:

和一个开启TableView编辑模式的方法:

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)
        }
    }
    
}
属性 说明
fade 以淡入淡出的方式显示或移除
right 添加或删除时,从右侧滑入或划出
left 添加或删除时,从左侧滑入或划出
top 添加或删除时,从上方滑入或划出
bottom 添加或删除时,从底部滑入或划出
middle 表格视图将尽量使新旧cell居中显示在曾经或将要显示的位置
automatic 自动选择适合自身的动画方式
none 采用默认动画方式

8. cell位置移动功能

支持重新排序(Reordering)功能的TableView,允许用户拖动位于单元格右侧的排序图标,来重新排序TableView中的单元格。
排序功能一般用到的还是比较多的,我曾经做过一个类似PPT的功能,要求可以更换演示版页的排列方式,就是用的这个功能。

移动功能同样设计到了两个代理方法:

移动功能同样需要开启编辑模式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)
    }
}

没有PS工具,我是把图片放到word里面截图出来的。

上一篇 下一篇

猜你喜欢

热点阅读