如何在 iOS 中创建具有适当值对齐的简单 grid-like 布局?

How to create a simple grid-like layout with proper value alignment in iOS?

我基本上需要创建以下 grid-like 布局:

我目前的尝试是使它成为一个带有自定义单元格的 UITableView:第一个是红色圆角矩形以及 3 个静态列(名称、余额、货币)以及之后的每一行将由具有每个值的另一个自定义单元格表示。

列(在第一个 table 视图单元格中)由具有相等间距的水平堆栈表示,值行将由另一个具有与水平列完全相同的约束和间距的水平堆栈表示堆栈(这是我尝试确保值实际上与列标题对齐)。

然而,虽然布局最终相似,但值与标题并不完全匹配,我需要将值以标题为中心,但这并没有发生。我尝试使用堆栈的多种分布类型,但似乎无法获得所需的结果。

ViewController 典型代码为:

extension ViewController: UITableViewDataSource, UITableViewDelegate {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    10
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    switch indexPath.row {
    case 0:
        let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderTableViewCell", for: indexPath) as! HeaderTableViewCell
        return cell
    default:
        let cell = tableView.dequeueReusableCell(withIdentifier: "RowTableViewCell", for: indexPath) as! RowTableViewCell
        return cell
    }
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    switch indexPath.row {
        case 0:
            return 100
    default:
        return 50
    }
}

}

当前的布局是(最终是相似的,只是没有完全对齐,也不确定如果我需要使用大数字,UI 会如何反应):

https://i.imgur.com/3zRtunB.png

欢迎任何意见。

我可能会做的只是构造一个带有 3 个标签的单元格,并使用乘数将它们相互约束,以便它们的宽度由视图的宽度决定(最终 table ) 而不是它的内容。将其插入 Playground 并随意摆弄它。

import PlaygroundSupport
import UIKit

class Cell: UITableViewCell {
    static let reuseId = "cell"
    let nameLabel = UILabel()
    let balanceLabel = UILabel()
    let currencyLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        backgroundColor = .darkGray
        addLabels()
    }
    
    required init?(coder: NSCoder) {
        return nil
    }
    
    private func addLabels() {
        let firstColumn = UIView()
        firstColumn.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(firstColumn)
        firstColumn.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        firstColumn.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        firstColumn.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        firstColumn.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.4).isActive = true
        
        let secondColumn = UIView()
        secondColumn.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(secondColumn)
        secondColumn.leadingAnchor.constraint(equalTo: firstColumn.trailingAnchor).isActive = true
        secondColumn.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        secondColumn.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.3).isActive = true
        
        let thirdColumn = UIView()
        thirdColumn.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(thirdColumn)
        thirdColumn.leadingAnchor.constraint(equalTo: secondColumn.trailingAnchor).isActive = true
        thirdColumn.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        thirdColumn.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.3).isActive = true
        thirdColumn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        
        nameLabel.backgroundColor = .yellow
        nameLabel.numberOfLines = 1
        nameLabel.lineBreakMode = .byTruncatingTail
        nameLabel.translatesAutoresizingMaskIntoConstraints = false
        firstColumn.addSubview(nameLabel)
        nameLabel.leadingAnchor.constraint(equalTo: firstColumn.leadingAnchor, constant: 8).isActive = true
        nameLabel.topAnchor.constraint(equalTo: firstColumn.topAnchor, constant: 4).isActive = true
        nameLabel.bottomAnchor.constraint(equalTo: firstColumn.bottomAnchor, constant: -4).isActive = true
        nameLabel.trailingAnchor.constraint(equalTo: firstColumn.trailingAnchor, constant: -8).isActive = true
        
        balanceLabel.backgroundColor = .green
        balanceLabel.textAlignment = .center
        balanceLabel.numberOfLines = 1
        balanceLabel.lineBreakMode = .byTruncatingTail
        balanceLabel.translatesAutoresizingMaskIntoConstraints = false
        secondColumn.addSubview(balanceLabel)
        balanceLabel.leadingAnchor.constraint(equalTo: secondColumn.leadingAnchor, constant: 8).isActive = true
        balanceLabel.topAnchor.constraint(equalTo: secondColumn.topAnchor, constant: 4).isActive = true
        balanceLabel.bottomAnchor.constraint(equalTo: secondColumn.bottomAnchor, constant: -4).isActive = true
        balanceLabel.trailingAnchor.constraint(equalTo: secondColumn.trailingAnchor, constant: -8).isActive = true
        
        currencyLabel.backgroundColor = .red
        currencyLabel.textAlignment = .center
        currencyLabel.numberOfLines = 1
        currencyLabel.lineBreakMode = .byTruncatingTail
        currencyLabel.translatesAutoresizingMaskIntoConstraints = false
        thirdColumn.addSubview(currencyLabel)
        currencyLabel.leadingAnchor.constraint(equalTo: thirdColumn.leadingAnchor, constant: 8).isActive = true
        currencyLabel.topAnchor.constraint(equalTo: thirdColumn.topAnchor, constant: 4).isActive = true
        currencyLabel.bottomAnchor.constraint(equalTo: thirdColumn.bottomAnchor, constant: -4).isActive = true
        currencyLabel.trailingAnchor.constraint(equalTo: thirdColumn.trailingAnchor, constant: -8).isActive = true
    }
}

class VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
    override func loadView() {
        view = UIView()
        
        let tableView = UITableView()
        tableView.backgroundColor = .blue
        tableView.dataSource = self
        tableView.delegate = self
        tableView.separatorStyle = .none
        tableView.register(Cell.self, forCellReuseIdentifier: Cell.reuseId)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        tableView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
    }

    // MARK: TABLE VIEW DATA SOURCE
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: Cell.reuseId, for: indexPath) as! Cell
        cell.nameLabel.text = "John Smith"
        cell.balanceLabel.text = ",890,223,000,000"
        cell.currencyLabel.text = "EUR"
        return cell
    }
    
    // MARK: TABLE VIEW DELEGATE
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return UIView()
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        return UIView()
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }
}

PlaygroundPage.current.liveView = VC()