提问人:wasi 提问时间:9/1/2023 最后编辑:wasi 更新时间:9/2/2023 访问量:71
使用 autolayout 和 authoshrink 为水平堆栈视图中的两个标签设置相同的字体大小
Set the same font size for two labels within a horizontal stack view with autolayout and authoshrink
问:
我需要创建一个视图,在该视图中,我希望两个标签并排放置,间距相等。问题是这些标签将具有不同的文本,具体取决于后端响应。我想平等地调整两个标签的字体大小,因为现在当两个标签中的一个具有非常大的文本时,自动布局只会调整该标签。此外,这种方法将帮助我使用像iPhone SE这样的小屏幕,因为同样的事情也会发生。我怎样才能做到这一点?
我确实尝试设置相等的宽度、高度和纵横比约束,但我无法实现我想要的。此外,两个标签在左上角保持对齐也非常重要(我通过将水平堆栈对齐设置为顶部来实现了这一点)。
我尝试过的一件事是:
我也尝试了这里提出的解决方案,但它对我不起作用。
更多信息和真实示例:真实示例 好的,所以预期的布局如下:
1-我有一个垂直堆栈视图,带有一个标签和水平堆栈视图
2- 水平堆栈视图具有以下插图: 水平堆栈视图插图
3- 水平堆栈视图的分布 = 等间距
4-两个标签启用了自动收缩功能,最小字体大小为9pts
问题是,如果我想在两个标签之一中添加一个很长的项目名称,我需要另一个标签具有相同的 fon 大小,并且两个标签需要保持水平堆栈视图边距并保持项目在同一行中。例如: 一方面,“非常非常非常长的项目名称”应保持在同一行中,并调整两个标签的字体大小以执行此操作,另一方面,“LongItemNameWithoutSpaces”也应执行相同的操作。长名称示例
答:
没有用于“匹配”标签自动缩小字体大小的 API。
为此,您需要:
- 遍历列表中的字符串
- 找到适合最长文本的最小字体大小
- 再次遍历标签,将该字体设置为所有标签
我们可以按照这些思路使用代码......
假设我们有这些属性:
let defaultFont: UIFont = .systemFont(ofSize: 16.0)
let maxFontSize: CGFloat = 16.0
let minFontSize: CGFloat = 9.0
let rowSpacing: CGFloat = 4.0
let columnSpacing: CGFloat = 20.0
// array of strings
let items: [String] = [
"Carrots", "Tomatos",
"Bananas", "Cola",
"Salad Dressing", "Lettuce",
"Milk", "Eggo Frozen Waffles",
]
让我们这样做:
// create a label to use for sizing
let sizingLabel = UILabel()
var sz: CGSize = .zero
// max label width is one-half of main stack width
// minus column spacing (the center "gap")
let maxW: CGFloat = floor((mainStack.bounds.width - columnSpacing) * 0.5)
var curFontSize: CGFloat = maxFontSize
var curFont: UIFont = .init(descriptor: defaultFont.fontDescriptor, size: curFontSize)
// loop through all item labels...
// if the label is too wide, reduce the font size
// until the label fits, or we reach minimum font size
sizingLabel.font = curFont
for i in 0..<items.count {
sizingLabel.text = items[i]
sz = sizingLabel.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
while ceil(sz.width) > maxW && curFontSize > minFontSize {
curFontSize -= 0.5
curFont = .init(descriptor: defaultFont.fontDescriptor, size: curFontSize)
sizingLabel.font = curFont
sz = sizingLabel.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
}
}
// loop through the labels, setting font for all labels to the same calculated font size
// eachLabel.font = curFont
因此,获得所需布局的一种方法是使用垂直轴堆栈视图,每个“标签对”或“行”都是水平轴堆栈视图。
我们可以得到这个:
像这样 - 标签是黄色的,垂直堆栈视图背景是青色的,自定义视图背景是非常浅的灰色:
如果我们使用 最大字体大小 和 最小字体大小 ,所有这些标签都适合16
9
16
如果我们添加一个稍微过长的标签,我们可以找到最大的字体大小 - 小于 16 - 并将所有标签设置为该字体大小:
再添加几个“短”项目,我们仍然使用适合“鸡蛋冷冻华夫饼”的字体大小:
现在我们添加“汤米的生日蛋糕”,我们变得更小了一点:
当我们添加“Vanilla Birthday Cake for Tommy”时,我们点击了最小字体大小:9
依此类推:
我强烈建议您尝试一下,使用上面的代码片段,以便您真正了解您需要做什么。
这里有一些完整的示例代码,你可以玩...
自定义 UIView
子类:
class MyListView: UIView {
public var defaultFont: UIFont = .systemFont(ofSize: 16.0) { didSet { setNeedsLayout() } }
public var maxFontSize: CGFloat = 16.0 { didSet { setNeedsLayout() } }
public var minFontSize: CGFloat = 9.0 { didSet { setNeedsLayout() } }
public var rowSpacing: CGFloat = 4.0 { didSet { setNeedsLayout() } }
// space between "columns" of item labels
public var columnSpacing: CGFloat = 20.0 { didSet { setNeedsLayout() } }
// "padding" around main stack view
public var insets: UIEdgeInsets = .init(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) {
didSet { updateMainStack() }
}
// during development, toggle background colors so we can see the framing
public var devColors: Bool = false { didSet { setNeedsLayout() } }
public var items: [String] = [] {
didSet {
let numRows: Int = Int(ceil(Double(items.count) / 2.0))
// remove any extra rows
while mainStack.arrangedSubviews.count > numRows {
mainStack.arrangedSubviews.last?.removeFromSuperview()
}
// add any new rows
while mainStack.arrangedSubviews.count < numRows {
let rowStack = UIStackView()
rowStack.axis = .horizontal
rowStack.alignment = .top
rowStack.spacing = columnSpacing
rowStack.distribution = .fillEqually
for _ in 0..<2 {
let label = UILabel()
rowStack.addArrangedSubview(label)
}
mainStack.addArrangedSubview(rowStack)
}
// clear current label refs
labelRefs = []
// get refs to the labels for convenience
mainStack.arrangedSubviews.forEach { v in
// we know what we're doing, but we want to safely unwrap anyway
if let sv = v as? UIStackView, sv.arrangedSubviews.count == 2 {
if let vl = sv.arrangedSubviews[0] as? UILabel {
labelRefs.append(vl)
}
if let vl = sv.arrangedSubviews[1] as? UILabel {
labelRefs.append(vl)
}
}
}
for (i, label) in labelRefs.enumerated() {
// if we have an odd number of items, the
// last item-label will use " "
label.text = i < items.count ? items[i] : " "
}
setNeedsLayout()
}
}
// for convenience, so we can easily loop through all the item labels
private var labelRefs: [UILabel] = []
private let mainStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
updateMainStack()
}
// if the insets are changed, we need to "reset" the constraints
private func updateMainStack() {
mainStack.removeFromSuperview()
addSubview(mainStack)
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: topAnchor, constant: insets.top),
mainStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left),
mainStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.right),
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom),
])
mainStack.spacing = rowSpacing
}
override func layoutSubviews() {
super.layoutSubviews()
// column spacing may have changed
mainStack.arrangedSubviews.forEach { v in
if let sv = v as? UIStackView {
sv.spacing = columnSpacing
}
}
// create a label to use for sizing
let sizingLabel = UILabel()
var sz: CGSize = .zero
// max label width is one-half of main stack width
// minus column spacing (the center "gap")
let maxW: CGFloat = floor((mainStack.bounds.width - columnSpacing) * 0.5)
var curFontSize: CGFloat = maxFontSize
var curFont: UIFont = .init(descriptor: defaultFont.fontDescriptor, size: curFontSize)
// loop through all item labels...
// if the label is too wide, reduce the font size
// until the label fits, or we reach minimum font size
sizingLabel.font = curFont
for i in 0..<items.count {
sizingLabel.text = items[i]
sz = sizingLabel.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
while ceil(sz.width) > maxW && curFontSize > minFontSize {
curFontSize -= 0.5
curFont = .init(descriptor: defaultFont.fontDescriptor, size: curFontSize)
sizingLabel.font = curFont
sz = sizingLabel.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
}
}
labelRefs.forEach { v in
// set font for all labels to the same calculated font size
v.font = curFont
// during development
v.backgroundColor = devColors ? .yellow : .clear
}
// during development
mainStack.backgroundColor = devColors ? .cyan : .clear
// we probably want to adjust "row spacing" based on resulting font size
mainStack.spacing = rowSpacing * (curFontSize / maxFontSize)
}
}
使用该视图的视图控制器:
class FontMatchVC: UIViewController {
let samples: [String] = [
"Carrots", "Tomatos",
"Bananas", "Cola",
"Salad Dressing", "Lettuce",
"Milk", "Eggo Frozen Waffles",
"Soup", "Chicken",
"Birthday Cake for Tommy", "Life Cereal",
"Eggs", "Vanilla Birthday Cake for Tommy",
"Chocalate Birthday Cake for Tommy", "Oatmeal",
"Hot Dogs", "Hamburgers",
"Ketchup",
]
var itemCount: Int = 0
let myListView = MyListView()
override func viewDidLoad() {
super.viewDidLoad()
// controls to add items and toggle dev-mode colors
let controlsStack = UIStackView()
controlsStack.axis = .vertical
controlsStack.spacing = 8.0
let tmpLabel = UILabel()
tmpLabel.text = "Show Dev Mode Colors"
let tmpSwitch = UISwitch()
tmpSwitch.addTarget(self, action: #selector(toggleColors(_:)), for: .valueChanged)
let tmpStack = UIStackView(arrangedSubviews: [tmpLabel, tmpSwitch])
controlsStack.addArrangedSubview(tmpStack)
let btn = UIButton()
btn.backgroundColor = .systemRed
btn.layer.cornerRadius = 8.0
btn.setTitle("Add Item", for: [])
btn.addTarget(self, action: #selector(addItem(_:)), for: .touchUpInside)
controlsStack.addArrangedSubview(btn)
let v = UIView()
v.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
v.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
controlsStack.addArrangedSubview(v)
let titleLabel = UILabel()
titleLabel.font = .systemFont(ofSize: 20.0, weight: .bold)
titleLabel.textAlignment = .center
titleLabel.text = "Grocery List"
titleLabel.backgroundColor = .systemBlue
titleLabel.textColor = .white
controlsStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(controlsStack)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleLabel)
myListView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myListView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
controlsStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
controlsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
controlsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
titleLabel.topAnchor.constraint(equalTo: controlsStack.bottomAnchor, constant: 20.0),
titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
titleLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
myListView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0),
myListView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myListView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
])
addItem(nil)
}
@objc func addItem(_ sender: UIButton?) {
let n: Int = itemCount % samples.count
myListView.items = Array(samples[0...n])
itemCount += 1
}
@objc func toggleColors(_ sender: UISwitch) {
myListView.backgroundColor = sender.isOn ? UIColor(white: 0.95, alpha: 1.0) : .clear
myListView.devColors = sender.isOn
}
}
运行时看起来像这样:
“添加项目”按钮更改列表中的项目,以及切换“开发模式”颜色的开关,以便我们可以轻松查看框架。
评论