当前位置: > > > Swift - 实现点击UITableView单元格时自动展开单元格

Swift - 实现点击UITableView单元格时自动展开单元格

(本文代码已升级至Swift4)

下面是一个列表单元格cell的折叠展开效果的demo。
当点击单元格时会展开该单元格,便于显示一些详情什么的。点击其他单元格原来的会关闭,同时有动画效果。

效果如如下:
 
代码如下:
import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    var tableView:UITableView?
    
    var ctrlnames:[String] = ["UILabel 标签","UIButton 按钮","UIDatePiker 日期选择器",
                              "UITableView 表格视图"]
    
    var selectedCellIndexPath:IndexPath!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        self.tableView = UITableView(frame: UIScreen.main.bounds,
                                     style:UITableViewStyle.plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                      forCellReuseIdentifier: "SwiftCell")
        self.view.addSubview(self.tableView!)
    }
    
    //在本例中,只有一个分区
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    //返回表格行数(也就是返回控件数)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.ctrlnames.count
    }
    
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        let label =  UILabel(frame: CGRect.zero)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = self.ctrlnames[indexPath.row]
        
        let textview=UITextView(frame: CGRect.zero)
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.textColor = UIColor.gray
        //演示效果,暂时写死
        textview.text = "UIDatePicker是一个控制器类,封装了UIPickerView,但是他是UIControl的子类"
        
        let identify:String = "SwiftCell"
        let cell = UITableViewCell(style: .default, reuseIdentifier:identify)
        //自动遮罩不可见区域,超出的不显示
        cell.layer.masksToBounds = true
        cell.contentView.addSubview(label)
        cell.contentView.addSubview(textview)
        
        //创建一个控件数组
        let views = ["label":label, "textview":textview]
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-15-[label]-15-|", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-15-[textview]-15-|", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|[label(40)]", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|-40-[textview(80)]", options: [], metrics: nil,
            views: views))
        
        return cell
    }
    
    // UITableViewDelegate 方法,处理列表项的选中事件
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.tableView!.deselectRow(at: indexPath, animated: false)
        selectedCellIndexPath = indexPath
        // Forces the table view to call heightForRowAtIndexPath
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
    
    //点击单元格会引起cell高度的变化,所以要重新设置
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)
        -> CGFloat {
        if(selectedCellIndexPath != nil && selectedCellIndexPath == indexPath){
            return 120
        }
        return 40
    }
}
关于 tableView.reloadRows(at: [indexPath], with: .automatic) 的说明:
1,上面方法只会重新刷新 indexPath 这个单元格内容(即调用其 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)方法)。在实际项目中,我们就可以在单元格打开的时候再加载详细内容,节约资源。
2,上面方法调用后会重新设置所有的单元格高度(即调用 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)),就算传个空数组进去也一样(tableView.reloadRows(at: [], with: .automatic)


功能修改:单元格展开收缩单独控制,不影响其他单元格
上面样例是每次只能打开一个单元格,新的单元格展开后、原来展开的单元格就会自动关闭。我们可以稍作修改,让每个单元格的展开收缩互不影响。

实现原理就是原来只记录最近一次选中单元格的 IndexPath,现在我们使用数组记录所有展开的单元格的 IndexPath(下面高亮处表示修改的地方)
import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    var tableView:UITableView?
    
    var ctrlnames:[String] = ["UILabel 标签","UIButton 按钮","UIDatePiker 日期选择器",
                              "UITableView 表格视图"]
    
    var selectedCellIndexPaths:[IndexPath] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        self.tableView = UITableView(frame: UIScreen.main.bounds,
                                     style:UITableViewStyle.plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                      forCellReuseIdentifier: "SwiftCell")
        self.view.addSubview(self.tableView!)
    }
    
    //在本例中,只有一个分区
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    //返回表格行数(也就是返回控件数)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.ctrlnames.count
    }
    
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        let label =  UILabel(frame: CGRect.zero)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = self.ctrlnames[indexPath.row]
        
        let textview=UITextView(frame: CGRect.zero)
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.textColor = UIColor.gray
        //演示效果,暂时写死
        textview.text = "UIDatePicker是一个控制器类,封装了UIPickerView,但是他是UIControl的子类"
        
        let identify:String = "SwiftCell"
        let cell = UITableViewCell(style: .default, reuseIdentifier:identify)
        //自动遮罩不可见区域,超出的不显示
        cell.layer.masksToBounds = true
        cell.contentView.addSubview(label)
        cell.contentView.addSubview(textview)
        
        //创建一个控件数组
        let views = ["label":label, "textview":textview]
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-15-[label]-15-|", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-15-[textview]-15-|", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|[label(40)]", options: [], metrics: nil,
            views: views))
        cell.contentView.addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|-40-[textview(80)]", options: [], metrics: nil,
            views: views))
        
        return cell
    }
    
    // UITableViewDelegate 方法,处理列表项的选中事件
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.tableView!.deselectRow(at: indexPath, animated: false)
        if let index = selectedCellIndexPaths.index(of: indexPath) {
            selectedCellIndexPaths.remove(at: index)
        }else{
            selectedCellIndexPaths.append(indexPath)
        }
        //强制改变高度
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
    
    //点击单元格会引起cell高度的变化,所以要重新设置
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)
        -> CGFloat {
        if selectedCellIndexPaths.contains(indexPath) {
            return 120
        }
        return 40
    }
}
评论10
  • 10楼
    2017-12-21 16:46
    peng

    你试试把高度设置高度设置成 100和 cell(180)

    站长回复

    我把文章代码中heightForRowAt方法里的120、40分别改成180、100,数据量为200,结果还是正常的,

  • 9楼
    2017-11-25 09:03
    peng

    您好 ,我把高度设置成了100和180。当数据多了的时候(超过屏幕的两倍)第一页点击展开正常, 但是当滑动到第二页的时候点击展开的效果很差 有卡顿的现象。

    站长回复

    我刚把文章样例里的代码数据量改成200条,又测试了下。没有发现卡顿的现象。你运行的是我文章代码吗?还是单元格里放置了其他东西、或者其他操作。

  • 8楼
    2017-08-31 14:25
    瑾年

    你好 我按照你的写了 ,点击第一个展开,再次点击收缩,当我再点第二个的时候,第二个正常,第一个的高度没变,但是第一个的底部内容显示出来了...

    站长回复

    我又测试了下我的代码,没有发现你说的问题啊。确定代码是和我文章里的一样没有修改吗?

  • 7楼
    2017-08-04 15:26
    LB

    大神,请问在边上加上展开和收缩的箭头怎么加啊?

    站长回复

    在单元格里添加一个UIImageView,当该单元格的展开收缩状态改变时,显示对应的图标即可。

  • 6楼
    2016-12-19 15:19
    求知

    点击cell展开收缩view要怎么实现呢

    站长回复

    点击cell就是展开收缩这个cell啊,不知你说的view是指什么。

  • 5楼
    2016-06-07 19:49
    dreamrooms

    你好 一次只展开一个单元格 在iphone5以下的的设备上不能展开 iphone5s以及以上就可以 经过输出发现hieghtforrow 里面的electedCellIndexPath == indexPath 不相等

    站长回复

    你的代码确定是和我一样的吗?我刚测试了下,不管是5还是4s都是没问题的。

  • 4楼
    2016-03-14 16:09
    park

    很棒的教程,非常感谢!变化后的高度能不能改成自适应的呢?

    站长回复

    可以,现在展开时高度返回固定140。如果想要自适应的话,可以先使用boundingRectWithSize方法算出内容文字的实际高度,再返回计算后的单元格总高度。

  • 3楼
    2016-03-04 19:06
    onetwo

    很不错,请问如果要实现点击另外一个单元格但是不影响其他单元格应该怎么办?试了很多种方法,就是不行。谢谢

    站长回复

    我在文章末尾更新样例了,你可以看下。

  • 2楼
    2015-12-29 19:05
    dreamrooms

    你好 cellForRowAtIndexPath方法里面有问题 望解决

    站长回复

    已修正,你可以再看下。

  • 1楼
    2015-07-29 14:40
    Pandastudio

    果然复杂的效果都不能用Storyboard去实现……

    站长回复

    是的,而且Storyboard争议也很大,有人就认为所有的东西都写在代码里更好。我觉得Storyboard负责做一些简单布局、样式什么的还是很方便的。