文本对齐方式按小数点 (Swift)

huangapple go评论89阅读模式
英文:

String text alignment by decimal point (Swift)

问题

有没有一种方法可以在多个字符串中实现这种数字对齐,最好是在界面构建器中?请查看附带的屏幕截图。

英文:

Is there a way to achieve such kind of alignment of numbers in multiple strings, preferably in interface builder? Please see the attached screenshot.

文本对齐方式按小数点 (Swift)

答案1

得分: 0

以下是翻译好的代码部分:

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)
        }
        
    }
    
}
英文:

One approach to achieve this is by using a table view and programmatically adding constraints to align the separator symbol. This method offers scalability as it only includes elements visible on the screen. An interesting aspect of this approach is that the separator's position may change based on the largest offset currently on the screen. Whether this behavior is desired or not depends on your specific requirements.

In this solution, I suggest splitting the string into three components and placing them into three separate labels: one for the content before the separator, one for the separator itself, and another for the content after the separator. Additionally, create an invisible reference view at the top level, which will be used to connect the separator label.

Although the following code is implemented programmatically for clarity in understanding constraint connections, it is recommended to move most of the code into the storyboard for better organization and maintainability.

I hope this code snippet helps you solve your problem.

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 = &quot;.&quot;
formatter.usesGroupingSeparator = true
formatter.groupingSeparator = &quot;,&quot;
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: &quot;amountCell&quot;)
tableView.frame = view.bounds
view.addSubview(tableView)
values = generateRandomValues(count: 1000)
}
private func generateRandomValues(count: Int) -&gt; [NSDecimalNumber] {
(0..&lt;count).map { index in
let startValue: Int = 1234567890
let maximumDivision = 5
let randomDivision: Int = 1&lt;&lt;Int.random(in: 0...maximumDivision)
return .init(integerLiteral: startValue).dividing(by: .init(integerLiteral: randomDivision))
}
}
private func formatValue(_ value: NSDecimalNumber) -&gt; String {
numberFormatter.string(for: value) ?? &quot;NaN&quot;
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension OffsetNumberViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: &quot;amountCell&quot;, 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 = &quot;&quot;
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 = &quot;&quot;
rightSideLabel.text = &quot;error&quot;
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)
}
}
}

huangapple
  • 本文由 发表于 2023年6月2日 01:15:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76384259.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定