UITableView с пользовательскими ячейками запаздывает при прокрутке - PullRequest
0 голосов
/ 01 мая 2018

Моя проблема в том, что при прокрутке UITableView сильно отстает.

Это то, чего я пытаюсь достичь

Начиная сверху, у меня есть простой заголовок раздела только с одним флажком и одним UILabel. Под этим заголовком вы можете видеть пользовательскую ячейку с одним UILabel, выровненным по центру. Эта пользовательская ячейка работает как другой заголовок для данных, которые будут показаны ниже (в основном, трехмерный массив). Под этими «заголовками» находятся пользовательские ячейки, которые содержат одну многострочную UILabel, а под этой меткой - контейнер для переменного количества строк, содержащий флажок и UILabel. На правой стороне ячейки также расположена кнопка (сине-белая стрелка).

Так что это означает, что контент показывается так:

  • Заголовок раздела (с указанием дня и даты)
  • Пользовательский UITableViewCell = заголовок (содержащий некоторую информацию заголовка)
  • Пользовательский UITableViewCell (содержащий данные для отображения)

Вот мой код:

cellForRowAt:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let (isHeader, headerNumber, semiResult) = checkIfIsHeader(section: indexPath.section, row: indexPath.row)

        let row = indexPath.row

        if isHeader {
            let chod = objednavkaDny[indexPath.section].chody[headerNumber+1]
            let cell = tableView.dequeueReusableCell(withIdentifier: cellHeaderReuseIdentifier, for: indexPath) as! ObjednavkyHeaderTableViewCell
            cell.titleLabel.text = chod.popisPoradiJidla
            cell.selectionStyle = .none
            return cell
        }else{
            let chod = objednavkaDny[indexPath.section].chody[headerNumber]

            let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ObjednavkyTableViewCell
            cell.updateData(objednavka: chod.objednavky[row-semiResult], canSetAmount: self.typDialogu == 3)
            return cell
        }
    }

checkIfIsHeader:

func checkIfIsHeader(section: Int, row: Int) -> (Bool, Int, Int){
        if let cachedResult = checkIfHeaderCache[section]?[row] {
            return (cachedResult[0] == 1, cachedResult[1], cachedResult[2])
        }

        var isHeader = false

        var semiResult = 0
        var headerNumber = -1

        for (index, chod) in objednavkaDny[section].chody.enumerated() {
            let sum = chod.objednavky.count
            if row == semiResult {
                isHeader = true
                break
            }else if row < semiResult {
                semiResult -= objednavkaDny[section].chody[index-1].objednavky.count
                break
            }else {
                headerNumber += 1
                semiResult += 1
                if index != objednavkaDny[section].chody.count - 1 {
                    semiResult += sum
                }
            }
        }
        checkIfHeaderCache[section] = [Int:[Int]]()
        checkIfHeaderCache[section]![row] = [isHeader ? 1 : 0, headerNumber, semiResult]
        return (isHeader, headerNumber, semiResult)
    }

и основная ячейка, в которой отображаются данные:

class ObjednavkyTableViewCell: UITableViewCell {

    lazy var numberTextField: ObjednavkyTextField = {
        let textField = ObjednavkyTextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    let mealLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        label.textAlignment = .left
        label.font = UIFont(name: ".SFUIText", size: 15)
        label.numberOfLines = 0
        label.backgroundColor = .white
        label.isOpaque = true
        return label
    }()


    lazy var detailsButton: UIButton = {
        let button = UIButton(type: .custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setImage(UIImage(named: "arrow-right")?.withRenderingMode(.alwaysTemplate), for: .normal)
        button.imageView?.tintColor = UIColor.custom.blue.classicBlue
        button.imageView?.contentMode = .scaleAspectFit
        button.contentHorizontalAlignment = .right
        button.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0)
        button.addTarget(self, action: #selector(detailsButtonPressed), for: .touchUpInside)
        button.backgroundColor = .white
        button.isOpaque = true
        return button
    }()

    let pricesContainerView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .white
        view.isOpaque = true
        return view
    }()


    var canSetAmount = false {
        didSet {
            canSetAmount ? showNumberTextField() : hideNumberTextField()
        }
    }


    var shouldShowPrices = false {
        didSet {
            shouldShowPrices ? showPricesContainerView() : hidePricesContainerView()
        }
    }

    var pricesContainerHeight: CGFloat = 0

    private let priceViewHeight: CGFloat = 30

    var mealLabelLeadingConstraint: NSLayoutConstraint?
    var mealLabelBottomConstraint: NSLayoutConstraint?
    var pricesContainerViewHeightConstraint: NSLayoutConstraint?
    var pricesContainerViewBottomConstraint: NSLayoutConstraint?

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.selectionStyle = .none
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    @objc func detailsButtonPressed() {

    }


    func updateData(objednavka: Objednavka, canSetAmount: Bool) {
        self.canSetAmount = canSetAmount
        if let popisJidla = objednavka.popisJidla, popisJidla != "", popisJidla != " " {
            self.mealLabel.text = popisJidla
        }else{
            self.mealLabel.text = objednavka.nazevJidelnicku
        }


        if objednavka.objects.count > 1 {
            shouldShowPrices = true
            setPricesStackView(with: objednavka.objects)
            checkIfSelected(objects: objednavka.objects)
        }else{
            shouldShowPrices = false
            self.numberTextField.text = String(objednavka.objects[0].pocet)
            //setSelected(objednavka.objects[0].pocet > 0, animated: false)
            objednavka.objects[0].pocet > 0 ? setSelectedStyle() : setDeselectedStyle()
        }
    }

    //---------------

    func checkIfSelected(objects: [ObjednavkaObject]) {
        var didChangeSelection = false
        for object in objects {          // Checks wether cell should be selected or not
            if object.pocet > 0 {
                setSelected(true, animated: false)
                setSelectedStyle()
                didChangeSelection = true
                break
            }
        }
        if !didChangeSelection {
            setSelected(false, animated: false)
            setDeselectedStyle()
        }
    }


    //--------------

    func showNumberTextField() {
        numberTextField.isHidden = false

        mealLabelLeadingConstraint?.isActive = false
        mealLabelLeadingConstraint = mealLabel.leadingAnchor.constraint(equalTo: numberTextField.trailingAnchor, constant: 10)
        mealLabelLeadingConstraint?.isActive = true
    }

    func hideNumberTextField() {
        numberTextField.isHidden = true

        mealLabelLeadingConstraint?.isActive = false
        mealLabelLeadingConstraint = mealLabel.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor, constant: 0)
        mealLabelLeadingConstraint?.isActive = true
    }

    func showPricesContainerView() {
        hideNumberTextField()

        pricesContainerView.isHidden = false

        mealLabelBottomConstraint?.isActive = false
        pricesContainerViewBottomConstraint?.isActive = true
    }

    func hidePricesContainerView() {
        pricesContainerView.isHidden = true

        pricesContainerViewBottomConstraint?.isActive = false
        mealLabelBottomConstraint?.isActive = true
    }

    //--------------

    func setSelectedStyle() {
        self.backgroundColor = UIColor.custom.blue.classicBlue
        mealLabel.textColor = .white
        mealLabel.backgroundColor = UIColor.custom.blue.classicBlue

        for subview in pricesContainerView.subviews where subview is ObjednavkyPriceView {
            let priceView = (subview as! ObjednavkyPriceView)
            priceView.titleLabel.textColor = .white
            priceView.checkBox.backgroundColor = UIColor.custom.blue.classicBlue
            priceView.titleLabel.backgroundColor = UIColor.custom.blue.classicBlue
            priceView.backgroundColor = UIColor.custom.blue.classicBlue
        }

        pricesContainerView.backgroundColor = UIColor.custom.blue.classicBlue

        detailsButton.imageView?.tintColor = .white
        detailsButton.backgroundColor = UIColor.custom.blue.classicBlue

    }

    func setDeselectedStyle() {
        self.backgroundColor = .white
        mealLabel.textColor = .black
        mealLabel.backgroundColor = .white

        for subview in pricesContainerView.subviews where subview is ObjednavkyPriceView {
            let priceView = (subview as! ObjednavkyPriceView)
            priceView.titleLabel.textColor = .black
            priceView.checkBox.backgroundColor = .white
            priceView.titleLabel.backgroundColor = .white
            priceView.backgroundColor = .white
        }

        pricesContainerView.backgroundColor = .white

        detailsButton.imageView?.tintColor = UIColor.custom.blue.classicBlue
        detailsButton.backgroundColor = .white
    }

    //-----------------

    func setPricesStackView(with objects: [ObjednavkaObject]) {
        let subviews = pricesContainerView.subviews
        var subviewsToDelete = subviews.count

        for (index, object) in objects.enumerated() {
            subviewsToDelete -= 1
            if subviews.count - 1 >= index {
                let priceView = subviews[index] as! ObjednavkyPriceView
                priceView.titleLabel.text = object.popisProduktu  // + " " + NSNumber(value: object.cena).getFormattedString(currencySymbol: "Kč") // TODO: currencySymbol
                priceView.canSetAmount = canSetAmount
                priceView.count = object.pocet
                priceView.canOrder = (object.nelzeObj == nil || object.nelzeObj == "")
            }else {
                let priceView = ObjednavkyPriceView(frame: CGRect(x: 0, y: CGFloat(index) * priceViewHeight + CGFloat(index * 5), width: pricesContainerView.frame.width, height: priceViewHeight))
                pricesContainerView.addSubview(priceView)
                priceView.titleLabel.text = object.popisProduktu  // + " " + NSNumber(value: object.cena).getFormattedString(currencySymbol: "Kč") // TODO: currencySymbol
                priceView.numberTextField.delegate = self
                priceView.canSetAmount = canSetAmount
                priceView.canOrder = (object.nelzeObj == nil || object.nelzeObj == "")
                priceView.count = object.pocet
                pricesContainerHeight += ((index == 0) ? 30 : 35)
            }
        }

        if subviewsToDelete > 0 {  // Deletes unwanted subviews
            for _ in 0..<subviewsToDelete {
                pricesContainerView.subviews.last?.removeFromSuperview()
                pricesContainerHeight -= pricesContainerHeight + 5
            }
        }

        if pricesContainerHeight < 0 {
            pricesContainerHeight = 0
        }

        pricesContainerViewHeightConstraint?.constant = pricesContainerHeight
    }

    func setupView() {
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = UIScreen.main.scale

        self.backgroundColor = .white

        contentView.addSubview(numberTextField)
        contentView.addSubview(mealLabel)
        contentView.addSubview(detailsButton)
        contentView.addSubview(pricesContainerView)

        setupConstraints()
    }

    func setupConstraints() {
        numberTextField.anchor(leading: readableContentGuide.leadingAnchor, size: CGSize(width: 30, height: 30))
        numberTextField.centerYAnchor.constraint(equalTo: mealLabel.centerYAnchor).isActive = true

        detailsButton.anchor(trailing: readableContentGuide.trailingAnchor, size: CGSize(width: 30, height: 30))
        detailsButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true

        mealLabel.anchor(top: contentView.topAnchor, trailing: detailsButton.leadingAnchor, padding: .init(top: 10, left: 0, bottom: 0, right: -10))
        mealLabelBottomConstraint = mealLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
        mealLabelBottomConstraint?.priority = UILayoutPriority(rawValue: 999)

        pricesContainerView.anchor(top: mealLabel.bottomAnchor, leading: readableContentGuide.leadingAnchor, trailing: detailsButton.leadingAnchor, padding: .init(top: 10, left: 0, bottom: 0, right: -10))
        pricesContainerViewBottomConstraint = pricesContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
        pricesContainerViewBottomConstraint?.priority = UILayoutPriority(rawValue: 999)

        pricesContainerViewHeightConstraint = pricesContainerView.heightAnchor.constraint(equalToConstant: 0)
        pricesContainerViewHeightConstraint?.priority = UILayoutPriority(rawValue: 999)
        pricesContainerViewHeightConstraint?.isActive = true
    }
}

Чтобы сделать вывод, как это делается:

  • tableView.rowHeight установлено на UITableViewAutomaticDymension
  • внутри cellForRowAt Я получаю данные из массива и передаю их клетка
  • все ячейки заданы в коде с использованием ограничений
  • все просмотры установлены isOpaque = true
  • высоты ячеек кэшируются
  • ячейки настроены на растеризацию

Я также заметил, что он отстает на определенных уровнях прокрутки и что иногда он работает просто отлично, а иногда сильно отстает.

Несмотря на всю проведенную оптимизацию, tableView по-прежнему отстает при прокрутке.

Вот скриншот из инструментов

Любой совет, как улучшить производительность прокрутки, высоко ценится!

(возможно, я забыл включить код / ​​информацию, поэтому не стесняйтесь спрашивать меня в комментариях.)

1 Ответ

0 голосов
/ 01 мая 2018

Я не могу сказать вам, где именно происходит задержка, но когда мы говорим об отставании во время прокрутки, это относится к вашему cellForRowAt методу делегата. Что происходит, так это то, что в этом методе происходит слишком много всего, и он вызывается для каждой ячейки, которая отображается и будет отображаться. Я вижу, что вы пытаетесь кэшировать результат с помощью checkIfHeaderCache, но, тем не менее, в самом начале есть цикл for для определения ячейки заголовка.

Предложения: Я не знаю, откуда вы берете данные (objednavkaDny), но сразу после того, как вы их получили, выполните полный цикл и определите тип ячейки один за другим и сохраните результат где-нибудь в соответствии с вашим дизайном. В течение этого времени загрузки вы можете показать некоторые сообщения о загрузке на экране. Затем в методе cellForRow вы должны просто использовать такие вещи, как

if (isHeader) {
  render header cell
} else {
  render other cell
}

Итог: cellForRow метод не предназначен для обработки сложных вычислений, и при этом он замедлит прокрутку. Этот метод предназначен для назначения значений только ячейке кэшированного табличного представления, и это единственное, в чем он хорош.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...