为什么我的列分隔符在我的 Swift Xcode 项目的 table 视图中没有对齐?

Why don't my column separators line up in my table view in my Swift Xcode project?

我已经为我的自定义 table 视图创建了自定义 header 和自定义 table 视图单元格 classes,但是列分隔符没有完全对齐,即使我在 header 和单元格中的字段宽度相同。我注意到单元格宽度为 320,而 header 宽度和 table 视图宽度均为 311.

这是我的自定义 table 视图单元格 class:

class LOMCell: UITableViewCell {

    // MARK: - Properties
    
    lazy var nameLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .center
        label.font = UIFont(name: "AvenirNext-Regular", size: 12.0)
        label.backgroundColor = .white
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 4.0 * self.frame.width / 16.0)
        return label
    }()

    private lazy var containerView: UIView = {
        let cv = UIView()
        cv.addSubview(ratingImageView)
        ratingImageView.anchor(top: cv.topAnchor, left: cv.leftAnchor, bottom: cv.bottomAnchor, right: cv.rightAnchor, paddingTop: 0.0, paddingLeft: 0.0, paddingBottom: 0.0, paddingRight: 0.0)
        cv.layer.borderWidth = 0.25
        cv.layer.borderColor = UIColor.black.cgColor
        cv.setWidth(width: 5.0 * self.frame.width / 16.0 )
        return cv
    }()
    
    lazy var ratingImageView: UIImageView = {
        let iv = UIImageView()
        iv.backgroundColor = .white
        iv.contentMode = .scaleAspectFit
        iv.clipsToBounds = true
        return iv
    }()
    
    lazy var footprintLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .center
        label.font = UIFont(name: "AvenirNext-Regular", size: 12.0)
        label.backgroundColor = .white
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 3.5 * self.frame.width / 16.0)
        return label
    }()
    
    lazy var feedbackLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.font = UIFont(name: "AvenirNext-Regular", size: 12.0)
        label.textColor = .black
        label.backgroundColor = .white
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 3.5 * self.frame.width / 16.0)
        return label
    }()
    
    // MARK: - Lifecycle
    
    override init(style: UITableViewCell.CellStyle , reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        configureUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Helper Functions
    
    private func configureUI() {
        
        let stackView = UIStackView(arrangedSubviews: [nameLabel,
                                                       containerView,
                                                       footprintLabel,
                                                       feedbackLabel])
        stackView.axis = .horizontal
        stackView.distribution = .fill
        stackView.spacing = 0
        
        self.addSubview(stackView)
        stackView.anchor(top: self.topAnchor,
                         left: self.leftAnchor,
                         bottom: self.bottomAnchor,
                         right: self.rightAnchor,
                         paddingTop: 0.0,
                         paddingLeft: 0.0,
                         paddingBottom: 0.0,
                         paddingRight: 0.0)
        
        print("DEBUG: cell width = \(self.frame.width)")
    }
}

这是我的自定义 table 视图 header class:

class LOMHeader: UIView {
    
    // MARK: - Properties
    
    private lazy var nameLabel: UILabel = {
        let label = UILabel()
        label.text = "Name"
        label.textColor = .white
        label.textAlignment = .center
        label.backgroundColor = .systemGreen
        label.font = UIFont(name: "AvenirNext-Bold", size: 12.0)
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 4.0 * self.frame.width / 16.0)
        return label
    }()

    private lazy var ratingLabel: UILabel = {
        let label = UILabel()
        label.text = "Rating"
        label.textColor = .white
        label.textAlignment = .center
        label.backgroundColor = .systemGreen
        label.font = UIFont(name: "AvenirNext-Bold", size: 12.0)
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 5.0 * self.frame.width / 16.0)
        return label
    }()

    private lazy var footprintLabel: UILabel = {
        let label = UILabel()
        label.text = "Footprint"
        label.textColor = .white
        label.textAlignment = .center
        label.backgroundColor = .systemGreen
        label.font = UIFont(name: "AvenirNext-Bold", size: 12.0)
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 3.5 * self.frame.width / 16.0)
        return label
    }()

    private lazy var feedbackLabel: UILabel = {
        let label = UILabel()
        label.text = "Feedback"
        label.textColor = .white
        label.textAlignment = .center
        label.backgroundColor = .systemGreen
        label.font = UIFont(name: "AvenirNext-Bold", size: 12.0)
        label.layer.borderWidth = 0.25
        label.layer.borderColor = UIColor.black.cgColor
        label.setWidth(width: 3.5 * self.frame.width / 16.0)
        return label
    }()

    // MARK: - Lifecycle
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        let stackView = UIStackView(arrangedSubviews: [nameLabel,
                                                       ratingLabel,
                                                       footprintLabel,
                                                       feedbackLabel])
        stackView.axis = .horizontal
        stackView.distribution = .fill
        stackView.spacing = 0
        
        self.addSubview(stackView)
        stackView.anchor(top: topAnchor,
                           left: leftAnchor,
                           bottom: bottomAnchor,
                           right: rightAnchor,
                           paddingTop: 0.0,
                           paddingLeft: 0.0,
                           paddingBottom: 0.0,
                           paddingRight: 0.0)
        
        print("DEBUG: header width = \(self.frame.width)")
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

这是我的带有自定义 table 视图的自定义视图控制器:

class ListOfMembersVC: UIViewController {

    // MARK: - Properties
    
    private let cellID = "ListOfMembersCellID"
    
    private lazy var tableView: UITableView = {
        let tv = UITableView()
        tv.rowHeight = 40.0
        tv.register(LOMCell.self, forCellReuseIdentifier: cellID)
        tv.delegate = self
        tv.dataSource = self
        return tv
    }()
    
    private let maxNumberOfRows = 6
    
    private let listOfMembers: [[String : Any]] = [["Name":"Louise", "Rating":UIImage(imageLiteralResourceName: "Rating Stars 2 out of 5"), "Footprint": 2, "Feedback": "??"]]
    
    // MARK: - Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureUI()
        
    }
    
    // MARK: - Helper Functions
    
    private func configureUI() {
        
        configureGradientLayer()
        
        title = "List of Members"

        navigationController?.navigationBar.barTintColor = .systemBlue
        navigationController?.navigationBar.tintColor = .white
        navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 30)]
        navigationController?.navigationBar.barStyle = .black
        
        view.addSubview(tableView)
        tableView.anchor(top: view.safeAreaLayoutGuide.topAnchor,
                         left: view.leftAnchor,
                         right: view.rightAnchor,
                         paddingTop: 40.0,
                         paddingLeft: 32.0,
                         paddingRight: 32.0,
                         height: 60.0 + 40.8 * CGFloat(maxNumberOfRows))
        
    }

}

extension ListOfMembersVC: UITableViewDelegate {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 60.0
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = LOMHeader(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 60.0))
        print("DEBUG: table width = \(self.tableView.frame.width)")
        return header
    }
}

extension ListOfMembersVC: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return listOfMembers.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! LOMCell
        cell.nameLabel.text = listOfMembers[indexPath.row]["Name"] as? String
        cell.ratingImageView.image = (listOfMembers[indexPath.row]["Rating"] as? UIImage)?.withAlignmentRectInsets(UIEdgeInsets(top: -5, left: -10, bottom: -5, right: -10))
        cell.footprintLabel.text = String((listOfMembers[indexPath.row]["Footprint"] as? Int)!)
        cell.feedbackLabel.text = listOfMembers[indexPath.row]["Feedback"] as? String
        print("DEBUG: cell width 2 = \(cell.frame.width)")
        return cell
    }
    

}

这里是问题的截图:

控制器加载时和显示时的帧值会有所不同。在您的 viewDidAppear(_ animated: Bool) 中调用它们以更新框架值或在单元格和 header 子视图上使用自动布局。

self.view.setNeedsLayout()
self.view.layoutIfNeeded()
self.tableView.reloadData()

首先,在布局视图之前,您不会获得有效的框架尺寸。您将在 viewDidAppear:

中获得更准确的帧尺寸
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print("INFO: tableView.width: \(tableView.frame.size.width)")
    tableView.visibleCells.compactMap({ [=10=] as? LOMCell }).forEach {
        print("INFO: tableView.cell.width: \([=10=].frame.size.width)")
    }
    tableView.subviews.compactMap { [=10=] as? LOMHeader }.forEach {
        print("INFO: tableView.header.width: \([=10=].frame.size.width)")
    }
}

当您尝试像这样限制子视图宽度时,您会遇到同样的问题。你需要摆脱这些。

label.setWidth(width: 4.0 * self.frame.width / 16.0)

单元格的 frame 属性 在布局之前无效。

我要做的是使宽度与其父视图成比例,以便在单元格或 header 大小发生变化时(例如在旋转期间)为您提供更大的灵活性:

extension UIView {
    func setWidthProportionalToSuperview(by multipler: CGFloat) {
        guard let superview = superview else { fatalError("Missing superview") }
        widthAnchor.constraint(equalTo: superview.widthAnchor, multiplier: multipler).isActive = true
    }
}

您可以在将子视图添加到 stackView 后立即设置比例:

private func configureUI() {
    
    let stackView = UIStackView(arrangedSubviews: [nameLabel,
                                                   containerView,
                                                   footprintLabel,
                                                   feedbackLabel])
    stackView.axis = .horizontal
    stackView.distribution = .fill
    stackView.spacing = 0
    
    self.addSubview(stackView)
    stackView.anchor(top: self.topAnchor,
                     left: self.leftAnchor,
                     bottom: self.bottomAnchor,
                     right: self.rightAnchor,
                     paddingTop: 0.0,
                     paddingLeft: 0.0,
                     paddingBottom: 0.0,
                     paddingRight: 0.0)

    nameLabel.setWidthProportionalToSuperview(by: 4.0 / 16.0)
    containerView.setWidthProportionalToSuperview(by: 5.0 / 16.0)
    footprintLabel.setWidthProportionalToSuperview(by: 3.5 / 16.0)
    feedbackLabel.setWidthProportionalToSuperview(by: 3.5 / 16.0)

}

最后,您不应将子视图直接添加到您的单元格。您应该添加然后作为您的 contentView 的子视图:

private func configureUI() {
    
    let stackView = UIStackView(arrangedSubviews: [nameLabel,
                                                   containerView,
                                                   footprintLabel,
                                                   feedbackLabel])
    // ...

    contentView.addSubview(stackView)
    stackView.anchor(top: contentView.topAnchor,
                     left: contentView.leftAnchor,
                     bottom: contentView.bottomAnchor,
                     right: contentView.rightAnchor,
                     paddingTop: 0.0,
                     paddingLeft: 0.0,
                     paddingBottom: 0.0,
                     paddingRight: 0.0)
}