Swift - 实现点击UITableView单元格时自动展开单元格
(本文代码已升级至Swift4)
当点击单元格时会展开该单元格,便于显示一些详情什么的。点击其他单元格原来的会关闭,同时有动画效果。

代码如下:
效果如如下:

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

你试试把高度设置高度设置成 100和 cell(180)
您好 ,我把高度设置成了100和180。当数据多了的时候(超过屏幕的两倍)第一页点击展开正常, 但是当滑动到第二页的时候点击展开的效果很差 有卡顿的现象。
你好 我按照你的写了 ,点击第一个展开,再次点击收缩,当我再点第二个的时候,第二个正常,第一个的高度没变,但是第一个的底部内容显示出来了...
大神,请问在边上加上展开和收缩的箭头怎么加啊?
点击cell展开收缩view要怎么实现呢
你好 一次只展开一个单元格 在iphone5以下的的设备上不能展开 iphone5s以及以上就可以 经过输出发现hieghtforrow 里面的electedCellIndexPath == indexPath 不相等
很棒的教程,非常感谢!变化后的高度能不能改成自适应的呢?
很不错,请问如果要实现点击另外一个单元格但是不影响其他单元格应该怎么办?试了很多种方法,就是不行。谢谢
你好 cellForRowAtIndexPath方法里面有问题 望解决
果然复杂的效果都不能用Storyboard去实现……