将自定义视图添加到容器视图的子视图时,tableHeaderView 重叠单元格

tableHeaderView is overlapping cells when adding custom view to subview of container view

我目前正在使用 UIViewController 并向视图添加 UITableView。有了这个 tableView,我在它的 tableHeaderView 中添加了一个名为 containerViewUIView。我设置了容器视图的高度,然后将第二个 UIView 添加到它的子视图,它固定在 containerView.

的底部

当我将它添加到 header 视图时,单元格重叠。奇怪的是,如果我不将子视图添加到容器视图,headerView 不会被单元格重叠,只有当我将第二个视图作为子视图添加到容器视图时才会发生。

class ViewController: UIViewController {

    private var containerView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.alpha = 0.7
        view.backgroundColor = .red
        return view
    }()

    private var bottomView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .blue
        return view
    }()

    private(set) lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)

        containerView.addSubview(bottomView)
        tableView.tableHeaderView = containerView

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            containerView.topAnchor.constraint(equalTo: tableView.topAnchor),
            containerView.heightAnchor.constraint(equalToConstant: 214),
            containerView.widthAnchor.constraint(equalToConstant: view.frame.size.width),

            bottomView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
            bottomView.heightAnchor.constraint(equalToConstant: 114),
            bottomView.widthAnchor.constraint(equalToConstant: view.frame.size.width),
        ])
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        tableView.contentInset = UIEdgeInsets(top: -view.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
        tableView.tableHeaderView?.autoresizingMask = []
        tableView.tableHeaderView?.layoutIfNeeded()
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
    }
} 

您的“蓝色视图”与单元格重叠的原因是您将其顶部限制在红色视图的底部,但您没有更新 header 视图大小。

一个好的方法是创建一个 UIView subclass 用作您的 header 视图。使用适当的 auto-layout 约束设置其所有内容。

然后,在控制器的 viewDidLayoutSubviews() 中,我们使用 .systemLayoutSizeFitting(...) 确定 header 视图的高度并更新其框架:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    // update table header size

    guard let headerView = tableView.tableHeaderView else { return }
    
    let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
    
    var frame = headerView.frame
    
    // avoids infinite loop!
    if height != frame.height {
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
    }
}

这是一个完整的例子...

首先是我们的自定义视图class:

class SampleHeaderView: UIView {

    let redView: UIView = {
        let v = UIView()
        v.backgroundColor = .systemRed
        return v
    }()
    let blueView: UIView = {
        let v = UIView()
        v.backgroundColor = .systemBlue
        return v
    }()
    let redTopLabel: UILabel = {
        let v = UILabel()
        v.backgroundColor = .yellow
        v.numberOfLines = 0
        return v
    }()
    let redBottomLabel: UILabel = {
        let v = UILabel()
        v.backgroundColor = .green
        v.numberOfLines = 0
        return v
    }()
    let multiLineLabel: UILabel = {
        let v = UILabel()
        v.backgroundColor = .cyan
        v.numberOfLines = 0
        return v
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        // all views will use auto-layout
        [redView, blueView, redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // prevent label vertical compression
        [redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
            v.setContentCompressionResistancePriority(.required, for: .vertical)
        }
        
        // add top and bottom labels to red view
        redView.addSubview(redTopLabel)
        redView.addSubview(redBottomLabel)

        // add multi-line label to blue view
        blueView.addSubview(multiLineLabel)
        
        // add red and blue views to self
        addSubview(redView)
        addSubview(blueView)

        // the following constraints need to have less-than required to avoid
        //  auto-layout warnings
        
        // blue view bottom to self
        let c1 = blueView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
        
        // labels trailing contraints
        let c2 = redTopLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
        let c3 = redBottomLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
        let c4 = multiLineLabel.trailingAnchor.constraint(equalTo: blueView.trailingAnchor, constant: -8.0)
        
        [c1, c2, c3, c4].forEach { c in
            c.priority = .required - 1
        }
        
        NSLayoutConstraint.activate([
            
            // red view top to self
            redView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),

            // leading / trailing to self
            redView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
            redView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),

            // blue view top to red view bottom
            blueView.topAnchor.constraint(equalTo: redView.bottomAnchor, constant: 0.0),

            //  leading / trailing to self
            blueView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
            blueView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
            
            // top and bottom labels, constrained in red view
            //  with a little "padding"
            redTopLabel.topAnchor.constraint(equalTo: redView.topAnchor, constant: 8.0),
            redTopLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),

            redBottomLabel.topAnchor.constraint(equalTo: redTopLabel.bottomAnchor, constant: 8.0),
            redBottomLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),

            redBottomLabel.bottomAnchor.constraint(equalTo: redView.bottomAnchor, constant: -8.0),

            // multi-line label, constrained in blue view
            //  with a little "padding"
            multiLineLabel.topAnchor.constraint(equalTo: blueView.topAnchor, constant: 8.0),
            multiLineLabel.leadingAnchor.constraint(equalTo: blueView.leadingAnchor, constant: 8.0),
            multiLineLabel.bottomAnchor.constraint(equalTo: blueView.bottomAnchor, constant: -8.0),

            // the less-than-required priority constraints
            c1, c2, c3, c4,

        ])
        
    }
}

和示例控制器:

class TableHeaderViewController: UIViewController {
    
    var sampleHeaderView = SampleHeaderView()
    
    private(set) lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])

        sampleHeaderView.redTopLabel.text = "The Red Top Label"
        sampleHeaderView.redBottomLabel.text = "The Red Bottom Label, with enough text that is should wrap."
        sampleHeaderView.multiLineLabel.text = "This text is for the Label in the Blue View. It is also long enough that it will require word-wrapping. Note that the header updates itself when the frame changes, such as on device rotation."
        tableView.tableHeaderView = sampleHeaderView

    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // update table header size

        guard let headerView = tableView.tableHeaderView else { return }
        
        let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
        
        var frame = headerView.frame
        
        // avoids infinite loop!
        if height != frame.height {
            frame.size.height = height
            headerView.frame = frame
            tableView.tableHeaderView = headerView
        }
    }
    
}

extension TableHeaderViewController: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 20
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        c.textLabel?.text = "\(indexPath)"
        return c
    }
    
}

输出:

并旋转: