Есть ли способ добиться такого выравнивания чисел в нескольких строках, желательно в построителе интерфейса? Пожалуйста, смотрите прикрепленный скриншот.
Вы можете заставить его работать со шрифтом нефиксированной ширины, разделив каждое число на две метки — одну для целой части числа и одну для десятичной части.
SwiftUI или UIKit?
@JoakimDanielson Привет! UIKit.
@HangarRash Я думал о чем-то подобном. Но затем мне нужно будет использовать эти цифры для дальнейших вычислений, и я подумал, что разделение строк и их обратное соединение, а также многократное преобразование Int<->String обернется для меня кошмаром.
@CalebGates Ничего этого не было бы. Ваши данные будут храниться как Int. Эти значения Int будут использоваться для всех расчетов. Только при отображении выровненных меток вам нужно будет преобразовать каждое Int в два значения String для размещения меток. Строки никогда не будут использоваться в качестве хранилища данных. Никогда не возникнет необходимости собирать их вместе. Вам уже нужно преобразовать каждый Int в один String для отображения в метке. Я просто предлагаю использовать две строки и две метки на Int. Небольшой изменение.
@HangarRash Интересно. Так должен ли я использовать горизонтальное представление стека с тремя элементами с десятичной точкой посередине? Спасибо за совет, сейчас попробую
@HangarRash, но как мне быть с целыми числами? Я вообще не хочу видеть десятичную дробь в этом случае
Я думал об использовании двух вертикальных представлений стека бок о бок. Левый стек для целых частей и правый стек для десятичных частей. Для целых чисел вы просто использовали бы пустую строку для второй строки значения.
Какой тип(ы) являются базовыми значениями? В чем разница между первым и третьим значением на скриншоте, если первое число с плавающей запятой, то как узнать, что оно имеет два десятичных знака?





Один из способов добиться этого — использовать табличное представление и программно добавить ограничения для выравнивания символа-разделителя. Этот метод предлагает масштабируемость, поскольку он включает только элементы, видимые на экране. Интересным аспектом этого подхода является то, что положение разделителя может меняться в зависимости от наибольшего смещения, отображаемого в данный момент на экране. Желательно ли такое поведение или нет, зависит от ваших конкретных требований.
В этом решении я предлагаю разбить строку на три компонента и поместить их в три отдельные метки: одну для содержимого до разделителя, одну для самого разделителя и еще одну для содержимого после разделителя. Дополнительно создайте невидимый эталонный вид на верхнем уровне, который будет использоваться для подключения метки-разделителя.
Хотя следующий код реализован программно для ясности понимания связей ограничений, рекомендуется переместить большую часть кода в раскадровку для лучшей организации и удобства сопровождения.
Я надеюсь, что этот фрагмент кода поможет вам решить вашу проблему.
class OffsetNumberViewController: UIViewController {
var values: [NSDecimalNumber] = [] {
didSet {
tableView.reloadData()
}
}
private lazy var tableView: UITableView = {
let view = UITableView()
view.delegate = self
view.dataSource = self
return view
}()
private lazy var numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.decimalSeparator = "."
formatter.usesGroupingSeparator = true
formatter.groupingSeparator = ","
formatter.groupingSize = 3
formatter.maximumFractionDigits = 5
return formatter
}()
private lazy var referenceView = {
let view = UIView()
view.isHidden = true
self.view.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints([
.init(item: view, attribute: .trailing, relatedBy: .lessThanOrEqual, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0.0),
.init(item: view, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0.0),
.init(item: view, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0.0)
])
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(NumberTableViewCell.self, forCellReuseIdentifier: "amountCell")
tableView.frame = view.bounds
view.addSubview(tableView)
values = generateRandomValues(count: 1000)
}
private func generateRandomValues(count: Int) -> [NSDecimalNumber] {
(0..<count).map { index in
let startValue: Int = 1234567890
let maximumDivision = 5
let randomDivision: Int = 1<<Int.random(in: 0...maximumDivision)
return .init(integerLiteral: startValue).dividing(by: .init(integerLiteral: randomDivision))
}
}
private func formatValue(_ value: NSDecimalNumber) -> String {
numberFormatter.string(for: value) ?? "NaN"
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension OffsetNumberViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "amountCell", for: indexPath) as? NumberTableViewCell {
cell.setup(withNumberAsString: formatValue(values[indexPath.row]), decimalSeparator: numberFormatter.decimalSeparator)
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as? NumberTableViewCell)?.attachCenterTo(referenceView, parent: self.view)
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as? NumberTableViewCell)?.detachExternalConstraints()
}
}
// MARK: - NumberTableViewCell
extension OffsetNumberViewController {
class NumberTableViewCell: UITableViewCell {
lazy private var leftSideLabel: UILabel = UILabel()
lazy private var rightSideLabel: UILabel = UILabel()
lazy private var separatorLabel: UILabel = UILabel()
private var currentExternalConstraints: [NSLayoutConstraint] = []
lazy private var stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .fill
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.addArrangedSubview(leftSideLabel)
stackView.addArrangedSubview(separatorLabel)
stackView.addArrangedSubview(rightSideLabel)
addSubview(stackView)
addConstraints([
.init(item: stackView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: -12.0),
.init(item: stackView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0),
.init(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0),
])
return stackView
}()
func setup(withNumberAsString numberString: String, decimalSeparator: String) {
let components = numberString.components(separatedBy: decimalSeparator)
let _ = stackView
separatorLabel.text = decimalSeparator
if components.count == 1 {
leftSideLabel.text = components[0]
rightSideLabel.text = ""
separatorLabel.alpha = 0
} else if components.count == 2 {
leftSideLabel.text = components[0]
rightSideLabel.text = components[1]
separatorLabel.alpha = 1
} else {
// Something went wrong
leftSideLabel.text = ""
rightSideLabel.text = "error"
separatorLabel.alpha = 0
}
}
func detachExternalConstraints() {
currentExternalConstraints.forEach { constrain in
constrain.isActive = false
}
currentExternalConstraints = []
}
func attachCenterTo(_ referenceView: UIView, parent: UIView) {
currentExternalConstraints = [
.init(item: separatorLabel,
attribute: .centerX,
relatedBy: .greaterThanOrEqual,
toItem: referenceView,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0),
.init(item: referenceView,
attribute: .centerX,
relatedBy: .greaterThanOrEqual,
toItem: separatorLabel,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0)
]
parent.addConstraints(currentExternalConstraints)
}
}
}
Я предлагаю не жестко кодировать большинство настроек форматирования чисел. Код должен обрабатывать любую локаль.
Вероятно, самым простым способом было бы использовать шрифт с фиксированной шириной и при необходимости дополнять числа пробелами. Не уверен, что есть какие-то лучшие или более правильные способы.