如何在 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()
我基本上需要创建以下 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()