“可扩展”单元有多种方法 - 这一种可能适合您的设计需求...
获得自我调整大小的单元格的常用方法是确保您有一个干净的“自上而下链”约束:
使用这种布局,橙色视图对黑色视图(其父视图)的底部有一个 8 pt 的约束。
为了使这个单元格可展开/可折叠,我们可以添加 另一个 8-pt 约束,这次是从蓝色视图的底部到黑色视图的底部.
最初,我们会遇到约束冲突,因为黑色视图的底部不能是蓝色视图的 8 点和橙色视图的 8 点同时。
所以,我们给他们不同的优先级...
如果我们给“blue-bottom”约束一个优先级.defaultHigh (750),给“orange-bottom”一个优先级.defaultLow (250),我们告诉自动布局enforce具有较高优先级的约束并允许较低优先级的约束中断,我们得到:
橙色视图仍然存在,但它现在超出了黑色视图的范围,所以我们看不到它。
这是一个非常简单的例子...
我们为单元格配置两个底部约束 - 一个来自标题标签视图的底部,一个来自描述标签视图的底部。
我们为每个约束设置高优先级或低优先级,具体取决于我们希望单元格展开还是折叠。
点击一行将切换其展开状态。
这一切都是通过代码完成的——没有@IBOutlet或@IBAction连接——所以只需添加一个新的UITableViewController并将其类分配给TestTableViewController:
class MyExpandableCell: UITableViewCell {
let myImageView: UIImageView = {
let v = UIImageView()
v.backgroundColor = UIColor(red: 219.0 / 255.0, green: 59.0 / 255.0, blue: 38.0 / 255.0, alpha: 1.0)
v.contentMode = .scaleAspectFit
v.tintColor = .white
v.layer.cornerRadius = 16.0
return v
}()
let myTitleView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(red: 68.0 / 255.0, green: 161.0 / 255.0, blue: 247.0 / 255.0, alpha: 1.0)
v.layer.cornerRadius = 16.0
return v
}()
let myDescView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(red: 243.0 / 255.0, green: 176.0 / 255.0, blue: 61.0 / 255.0, alpha: 1.0)
v.layer.cornerRadius = 16.0
return v
}()
let myTitleLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.textColor = .white
return v
}()
let myDescLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textColor = .white
return v
}()
let myContainerView: UIView = {
let v = UIView()
v.clipsToBounds = true
v.backgroundColor = .black
return v
}()
var isExpanded: Bool = false {
didSet {
expandedConstraint.priority = isExpanded ? .defaultHigh : .defaultLow
collapsedConstraint.priority = isExpanded ? .defaultLow : .defaultHigh
}
}
var collapsedConstraint: NSLayoutConstraint!
var expandedConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
[myImageView, myTitleView, myDescView, myTitleLabel, myDescLabel, myContainerView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
myTitleView.addSubview(myTitleLabel)
myDescView.addSubview(myDescLabel)
myContainerView.addSubview(myTitleView)
myContainerView.addSubview(myDescView)
myContainerView.addSubview(myImageView)
contentView.addSubview(myContainerView)
let g = contentView.layoutMarginsGuide
expandedConstraint = myDescView.bottomAnchor.constraint(equalTo: myContainerView.bottomAnchor, constant: -8.0)
collapsedConstraint = myTitleView.bottomAnchor.constraint(equalTo: myContainerView.bottomAnchor, constant: -8.0)
expandedConstraint.priority = .defaultLow
collapsedConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
myTitleLabel.topAnchor.constraint(equalTo: myTitleView.topAnchor, constant: 12.0),
myTitleLabel.leadingAnchor.constraint(equalTo: myTitleView.leadingAnchor, constant: 8.0),
myTitleLabel.trailingAnchor.constraint(equalTo: myTitleView.trailingAnchor, constant: -8.0),
myTitleLabel.bottomAnchor.constraint(equalTo: myTitleView.bottomAnchor, constant: -12.0),
myDescLabel.topAnchor.constraint(equalTo: myDescView.topAnchor, constant: 12.0),
myDescLabel.leadingAnchor.constraint(equalTo: myDescView.leadingAnchor, constant: 8.0),
myDescLabel.trailingAnchor.constraint(equalTo: myDescView.trailingAnchor, constant: -8.0),
myDescLabel.bottomAnchor.constraint(equalTo: myDescView.bottomAnchor, constant: -12.0),
myImageView.topAnchor.constraint(equalTo: myContainerView.topAnchor, constant: 8.0),
myImageView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
myImageView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),
myImageView.heightAnchor.constraint(equalToConstant: 80),
myTitleView.topAnchor.constraint(equalTo: myImageView.bottomAnchor, constant: 8.0),
myTitleView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
myTitleView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),
myDescView.topAnchor.constraint(equalTo: myTitleView.bottomAnchor, constant: 8.0),
myDescView.leadingAnchor.constraint(equalTo: myContainerView.leadingAnchor, constant: 8.0),
myDescView.trailingAnchor.constraint(equalTo: myContainerView.trailingAnchor, constant: -8.0),
myContainerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
myContainerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
myContainerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
myContainerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
expandedConstraint, collapsedConstraint,
])
}
}
class TestTableViewController: UITableViewController {
let myData: [[String]] = [
["Label", "A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label."],
["Button", "You can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state."],
["Segmented Control", "The segments can represent single or multiple selection, or a list of commands.\n\nEach segment can display text or an image, but not both."],
["Text Field", "Displays a rounded rectangle that can contain editable text. When a user taps a text field, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text field can handle the input in an application-specific way. UITextField supports overlay views to display additional information, such as a bookmarks icon. UITextField also provides a clear text control a user taps to erase the contents of the text field."],
["Slider", "UISlider displays a horizontal bar, called a track, that represents a range of values. The current value is shown by the position of an indicator, or thumb. A user selects a value by sliding the thumb along the track. You can customize the appearance of both the track and the thumb."],
["This cell has a TItle that will wrap onto multiple lines.", "Just to demonstrate that auto-layout is handling text wrapping in the title view."],
]
var rowState: [Bool] = [Bool]()
override func viewDidLoad() {
super.viewDidLoad()
// initialize rowState array to all False (not expanded
rowState = Array(repeating: false, count: myData.count)
tableView.register(MyExpandableCell.self, forCellReuseIdentifier: "cell")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyExpandableCell
cell.myImageView.image = UIImage(systemName: "\(indexPath.row).circle")
cell.myTitleLabel.text = myData[indexPath.row][0]
cell.myDescLabel.text = myData[indexPath.row][1]
cell.isExpanded = rowState[indexPath.row]
cell.selectionStyle = .none
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let c = tableView.cellForRow(at: indexPath) as? MyExpandableCell else {
return
}
rowState[indexPath.row].toggle()
tableView.performBatchUpdates({
c.isExpanded = rowState[indexPath.row]
}, completion: nil)
}
}
结果:
并且,在点击并滚动一下之后: