我有一个UITableViewController
,其中包含许多自定义的UITableViewCells
类型。
这些类型之一就是一个包含UIStackView
的单元格,该单元格本身包含一个或多个UIButton
。
在屏幕上滚动并重新打开时,将再次添加按钮。每次滚动事件都会发生这种情况。
Pre Scroll Image Post Scroll Image
我了解到,当单元被重用以提高性能时,可能发生的是我在cellForRowAt
中配置单元的设置代码再次被执行。
因此,它将数据源中的3个按钮添加到该单元格中,该单元格已经包含上次渲染的按钮。
我不知道如何清除并防止这种行为,并且非常感谢有人在我迷路时提供见解。
我已经能够准备一个小型应用程序来重新创建此应用程序,因为由于它是封闭源,所以我无法共享当前项目。
我为下面的大量代码表示歉意,但这只是简单地放入项目并重新创建的最低要求。
class TableViewController: UITableViewController {
let textCellId = "textCellId"
let buttonCellId = "buttonCellId"
// MARK: - Mock Data Source
let cellContent = [
Message(type: .buttonGroup, buttonGroup: [
MessageButton(label: "Button #1"),
MessageButton(label: "Button #2"),
MessageButton(label: "Button #3")
]),
Message(type: .text, text: "A"),
Message(type: .text, text: "B"),
Message(type: .text, text: "C"),
Message(type: .text, text: "D"),
Message(type: .text, text: "E"),
Message(type: .text, text: "F"),
Message(type: .text, text: "G"),
Message(type: .text, text: "H"),
Message(type: .text, text: "I"),
Message(type: .text, text: "J"),
Message(type: .text, text: "K"),
Message(type: .text, text: "L"),
Message(type: .text, text: "M"),
Message(type: .text, text: "N"),
Message(type: .text, text: "O"),
Message(type: .text, text: "P"),
Message(type: .text, text: "Q"),
Message(type: .text, text: "R"),
Message(type: .text, text: "S"),
]
override func viewDidLoad() {
super.viewDidLoad()
registerCells()
configureTableView()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellContent.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = cellContent[indexPath.row]
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: buttonCellId, for: indexPath) as! ButtonCell
cell.buttonGroupContent = item.buttonGroup
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: textCellId, for: indexPath) as! TextCell
cell.textLabel?.text = item.text
return cell
}
}
}
// MARK: - Misc TableView Setup
extension TableViewController {
fileprivate func registerCells() {
tableView.register(TextCell.self, forCellReuseIdentifier: textCellId)
tableView.register(ButtonCell.self, forCellReuseIdentifier: buttonCellId)
}
fileprivate func configureTableView() {
tableView.allowsSelection = false
tableView.alwaysBounceVertical = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor.lightGray
tableView.contentInset = UIEdgeInsets(top: 24, left: 0, bottom: 50, right: 0)
tableView.tableFooterView = UIView()
}
}
// MARK: - Cell Types
class TextCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ButtonCell: UITableViewCell {
var buttonGroupContent: [MessageButton]? {
didSet {
anchorSubViews()
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate var button: UIButton {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.darkGray
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.layer.cornerRadius = 5
button.layer.masksToBounds = true
return button
}
fileprivate let buttonGroupStackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.spacing = UIStackView.spacingUseSystem
return stackView
}()
}
extension ButtonCell {
fileprivate func anchorSubViews() {
guard let buttons = buttonGroupContent?.enumerated() else { return }
for (index, b) in buttons {
let btn = button
btn.setTitle(b.label, for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 200, height: 40)
btn.tag = index
buttonGroupStackView.addArrangedSubview(btn)
}
addSubview(buttonGroupStackView)
NSLayoutConstraint.activate([
buttonGroupStackView.topAnchor.constraint(equalTo: topAnchor),
buttonGroupStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
buttonGroupStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
buttonGroupStackView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
}
// MARK: - Misc for setup
struct MessageButton {
let label: String
}
enum MessageType {
case text, buttonGroup
}
struct Message {
let type: MessageType
let text: String?
let buttonGroup: [MessageButton]?
init(type: MessageType, text: String? = nil, buttonGroup: [MessageButton]? = nil) {
self.type = type
self.text = text
self.buttonGroup = buttonGroup
}
}
答案 0 :(得分:1)
由于单元格是可重复使用的,因此内容保持不变。因此,您以前的按钮仍在堆栈视图中,并且每次都添加下一个按钮。
要解决此问题,请在向UIStackView
添加新按钮之前,先删除旧按钮
extension ButtonCell {
fileprivate func anchorSubViews() {
...
for case let button as UIButton in buttonGroupStackView.subviews {
button.removeFromSuperview()
}
for (index, b) in buttons {
...
buttonGroupStackView.addArrangedSubview(btn)
}
...
}
}
答案 1 :(得分:0)
将button
和buttonGroupStackView
属性设为可选,并将weak
设为可选。 addSubview
方法的启用将保留对其子视图的强烈引用。因此它永远不会被删除。并覆盖prepareForReuse()
进行必要的清理,并确保已从单元格中删除了stackview。这是您的操作方法:
class ButtonCell: UITableViewCell {
var buttonGroupContent: [MessageButton]? {
didSet {
anchorSubViews()
}
}
fileprivate weak var buttonGroupStackView: UIStackView?
// MARK: - Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initialSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialSetup()
}
private func initialSetup() -> Void {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.spacing = UIStackView.spacingUseSystem
self.addSubview(stackView)
self.buttonGroupStackView = stackView
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
// MARK: - Subclass Overrides
override func prepareForReuse() {
super.prepareForReuse()
self.buttonGroupStackView?.subviews.forEach({ $0.removeFromSuperview()} )
}
// MARK: - Private
fileprivate func createButton() -> UIButton {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.darkGray
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.layer.cornerRadius = 5
button.layer.masksToBounds = true
return button
}
}
extension ButtonCell {
fileprivate func anchorSubViews() {
guard let buttons = buttonGroupContent?.enumerated() else { return }
for (index, b) in buttons {
let btn = self.createButton()
btn.setTitle(b.label, for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 200, height: 40)
btn.tag = index
self.buttonGroupStackView?.addArrangedSubview(btn)
}
}
}
除非有其他要求,否则始终建议使用weak
引用子视图或IBOutlet
属性。