Выравнивание текста строки по десятичной точке (Swift)

Есть ли способ добиться такого выравнивания чисел в нескольких строках, желательно в построителе интерфейса? Пожалуйста, смотрите прикрепленный скриншот.

Вероятно, самым простым способом было бы использовать шрифт с фиксированной шириной и при необходимости дополнять числа пробелами. Не уверен, что есть какие-то лучшие или более правильные способы.

Alexander 01.06.2023 20:12

Вы можете заставить его работать со шрифтом нефиксированной ширины, разделив каждое число на две метки — одну для целой части числа и одну для десятичной части.

HangarRash 01.06.2023 20:40

SwiftUI или UIKit?

Joakim Danielson 01.06.2023 21:58

@JoakimDanielson Привет! UIKit.

CalebGates 01.06.2023 22:19

@HangarRash Я думал о чем-то подобном. Но затем мне нужно будет использовать эти цифры для дальнейших вычислений, и я подумал, что разделение строк и их обратное соединение, а также многократное преобразование Int<->String обернется для меня кошмаром.

CalebGates 01.06.2023 22:22

@CalebGates Ничего этого не было бы. Ваши данные будут храниться как Int. Эти значения Int будут использоваться для всех расчетов. Только при отображении выровненных меток вам нужно будет преобразовать каждое Int в два значения String для размещения меток. Строки никогда не будут использоваться в качестве хранилища данных. Никогда не возникнет необходимости собирать их вместе. Вам уже нужно преобразовать каждый Int в один String для отображения в метке. Я просто предлагаю использовать две строки и две метки на Int. Небольшой изменение.

HangarRash 01.06.2023 22:26

@HangarRash Интересно. Так должен ли я использовать горизонтальное представление стека с тремя элементами с десятичной точкой посередине? Спасибо за совет, сейчас попробую

CalebGates 01.06.2023 22:31

@HangarRash, но как мне быть с целыми числами? Я вообще не хочу видеть десятичную дробь в этом случае

CalebGates 01.06.2023 22:34

Я думал об использовании двух вертикальных представлений стека бок о бок. Левый стек для целых частей и правый стек для десятичных частей. Для целых чисел вы просто использовали бы пустую строку для второй строки значения.

HangarRash 01.06.2023 22:34

Какой тип(ы) являются базовыми значениями? В чем разница между первым и третьим значением на скриншоте, если первое число с плавающей запятой, то как узнать, что оно имеет два десятичных знака?

Joakim Danielson 01.06.2023 22:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Один из способов добиться этого — использовать табличное представление и программно добавить ограничения для выравнивания символа-разделителя. Этот метод предлагает масштабируемость, поскольку он включает только элементы, видимые на экране. Интересным аспектом этого подхода является то, что положение разделителя может меняться в зависимости от наибольшего смещения, отображаемого в данный момент на экране. Желательно ли такое поведение или нет, зависит от ваших конкретных требований.

В этом решении я предлагаю разбить строку на три компонента и поместить их в три отдельные метки: одну для содержимого до разделителя, одну для самого разделителя и еще одну для содержимого после разделителя. Дополнительно создайте невидимый эталонный вид на верхнем уровне, который будет использоваться для подключения метки-разделителя.

Хотя следующий код реализован программно для ясности понимания связей ограничений, рекомендуется переместить большую часть кода в раскадровку для лучшей организации и удобства сопровождения.

Я надеюсь, что этот фрагмент кода поможет вам решить вашу проблему.

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)
        }
        
    }
    
}

Я предлагаю не жестко кодировать большинство настроек форматирования чисел. Код должен обрабатывать любую локаль.

HangarRash 02.06.2023 01:58

Другие вопросы по теме