Моя проблема в том, что при прокрутке 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 по-прежнему отстает при прокрутке.
Вот скриншот из инструментов
Любой совет, как улучшить производительность прокрутки, высоко ценится!
(возможно, я забыл включить код / информацию, поэтому не стесняйтесь спрашивать меня в комментариях.)