Пользовательская ячейка UITableView дублирует UIButton при прокрутке - PullRequest
0 голосов
/ 03 января 2019

У меня есть UITableViewController, который содержит несколько пользовательских UITableViewCells типов.

Одним из этих типов просто является ячейка, содержащая UIStackView, которая сама содержит один или несколько UIButton..

При прокрутке экрана и включении снова добавляются кнопки.Это происходит при каждом событии прокрутки.

Изображение до прокрутки Изображение после прокрутки

Я понимаю, что, поскольку ячейка повторно используется для производительности, потенциально то, чтоЭто мой установочный код в 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
    }
}

Ответы [ 2 ]

0 голосов
/ 03 января 2019

Сделайте ваши свойства button и buttonGroupStackView необязательными и weak. Метод addSubview будет сохранять сильную ссылку на свое подпредставление. Так что это никогда не удаляется. И переопределите prepareForReuse() для выполнения необходимой очистки и убедитесь, что представление стека удалено из ячейки. Вот как это можно сделать:

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 свойства, если не требуется иное.

0 голосов
/ 03 января 2019

Поскольку ячейки можно использовать повторно, содержимое остается.Таким образом, ваши старые кнопки все еще находятся в виде стека, и вы каждый раз добавляете следующие кнопки.

Чтобы это исправить, перед добавлением новых кнопок в 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)
        }    
        ...
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...