Автоматическое измерение табличного представления, заставляющее дублировать ограничения - PullRequest
0 голосов
/ 24 октября 2019

Чтобы получить автоматическое измерение, работающее для UITableView.rowHeight, в моем классе UITableViewCell требуется дублирующее ограничение.

Я создаю UITableView программно (SwiftUI, без раскадровки) и имею ячейки разной высоты. Я установил для таблицы rowHeight значение UITableView.automaticDimension, но не могу найти правильную комбинацию ограничений, чтобы таблица фактически вычисляла правильную высоту для ячеек без добавления дублирующего ограничения.

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

2019-10-23 18:06:53.515025-0700 Test[15858:7764405] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600001651c20 V:|-(0)-[UIView:0x7f8fc5a08890]   (active, names: '|':Aries.TestTableViewCell:0x7f8fc5a084e0'TestTableViewCell' )>",
    "<NSLayoutConstraint:0x600001651e50 UIView:0x7f8fc5a08890.bottom == Aries.TestTableViewCell:0x7f8fc5a084e0'TestTableViewCell'.bottom   (active)>",
    "<NSLayoutConstraint:0x600001651ea0 UIView:0x7f8fc5a08890.height == 40   (active)>",
    "<NSLayoutConstraint:0x600001652120 'UIView-Encapsulated-Layout-Height' Aries.TestTableViewCell:0x7f8fc5a084e0'TestTableViewCell'.height == 40.3333   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600001651ea0 UIView:0x7f8fc5a08890.height == 40   (active)>

Если я удаляю ограничение высоты или ограничение нижнего якоря, дублирующее ограничение исчезает, а предупреждение исчезает. Однако тогда таблица не будет правильно определять размер строк.

Представление:

import SwiftUI

struct TableViewTest: View {
    var body: some View {
        TestTableView().frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
    }
}

Представление таблицы:

import SwiftUI

struct TestTableView: UIViewRepresentable {
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    func makeUIView(context: UIViewRepresentableContext<TestTableView>) -> UITableView {

        let tableView = UITableView(frame: .zero)
        tableView.register(TestTableViewCell.self, forCellReuseIdentifier: "TestTableViewCell")
        tableView.rowHeight = UITableView.automaticDimension

        let dataSource = UITableViewDiffableDataSource<Section, TestData>(tableView: tableView) { tableView, indexPath, data in

            let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell

            cell.data = data

            return cell
        }

        populate(dataSource: dataSource)
        context.coordinator.dataSource = dataSource

        return tableView
    }

    func populate(dataSource: UITableViewDiffableDataSource<Section, TestData>) {

        let items = [
            TestData(color: .red, size: CGSize(width: 40, height: 20)),
            TestData(color: .blue, size: CGSize(width: 40, height: 40)),
            TestData(color: .green, size: CGSize(width: 40, height: 80))
        ]

        var snapshot = NSDiffableDataSourceSnapshot<Section, TestData>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items)
        dataSource.apply(snapshot)
    }

    func updateUIView(_ tableView: UITableView, context: UIViewRepresentableContext<TestTableView>) {

        guard let dataSource = context.coordinator.dataSource else {
            return
        }

        populate(dataSource: dataSource)
    }

    class Coordinator {
        var dataSource: UITableViewDiffableDataSource<Section, TestData>?
    }

    enum Section {
        case main
    }

    struct TestData: Hashable {
        var id = UUID()
        var color: UIColor
        var size: CGSize

        func hash(into hasher: inout Hasher) {
            hasher.combine(id)
        }
    }
}

Ячейка представления таблицы:

import UIKit

class TestTableViewCell: UITableViewCell {

    var data: TestTableView.TestData? {
        didSet {
            if let data = data {
                let view = UIView()
                view.backgroundColor = data.color

                addSubview(view)
                // !!! SETTING THE BOTTOM ANCHOR TO NIL OR HEIGHT TO 0 PREVENTS THE TABLE FROM SIZING THE ROWS CORRECTLY
                view.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: data.size.width, height: data.size.height, enableInsets: false)
            }
        }
    }

    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")
    }
}

Расширение привязки UIView:

func anchor (top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat, enableInsets: Bool) {

        var topInset = CGFloat(0)
        var bottomInset = CGFloat(0)

        if #available(iOS 11, *), enableInsets {
            let insets = self.safeAreaInsets
            topInset = insets.top
            bottomInset = insets.bottom
        }

        translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            self.topAnchor.constraint(equalTo: top, constant: paddingTop+topInset).isActive = true
        }
        if let left = left {
            self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
        }
        if let right = right {
            rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
        }
        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom-bottomInset).isActive = true
        }
        if height != 0 {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
        if width != 0 {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }
    }

Если я закреплю только верхний левый угол и укажу ширину и высоту в ячейке табличного представления, табличное представление не будет правильно вычислять высоту,Однако, если я также укажу высоту, строки будут иметь правильный размер, но будет сгенерировано предупреждение о двойном ограничении. Существует ли волшебная комбинация, которая позволяет правильное расположение, но не выдает предупреждение?

Плохо (но без дублирующих ограничений):

Bad (but no duplicate constraints):

Хорошо (с дублирующими ограничениями)):

Good (with duplicate constraints):

1 Ответ

1 голос
/ 24 октября 2019

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

 if let bottom = bottom {
    let bottomConstraint = bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom-bottomInset)
    bottomConstraint.priority = UILayoutPriority(750)
    bottomConstraint.isActive = true
  }
...