UIStackView:基于所有子视图的一致高度,即使它们丢失

Posted

技术标签:

【中文标题】UIStackView:基于所有子视图的一致高度,即使它们丢失【英文标题】:UIStackView: consistent height based on all subviews even if they are missing 【发布时间】:2021-03-31 12:29:29 【问题描述】:

我有一个带有两个标签的 StackView 作为排列的子视图。我希望 StackView 始终是这两个标签的高度(包括 DynamicType 更改),即使其中一个标签的文本为 nil 或为空(通常将堆栈视图高度更改为该单个标签的高度)。

我试图勾勒出这个问题,希望我能找到一个很好的解决方案。 (我不能简单地对堆栈视图进行高度约束,因为 dynamicType 会改变标签的高度)。

【问题讨论】:

您可以添加高度限制并根据标签的数量激活/停用。 如果 BOTH 标签为空/nil 会怎样? 而且...两个标签使用相同的字体吗?一个或两个标签可能不止一行? 哦...如果一个标签为空,您希望另一个标签垂直居中,同时保持两个标签都存在的高度? @DonMag:情况并非如此,因为标签 A 要么有文本,要么有默认文本。两个标签都有不同的字体粗细和可能的高度。他们只有一行。 【参考方案1】:

我认为您不想为此尝试使用堆栈视图。

相反,创建一个自定义UIView,带有三个标签:

顶部标签 顶部限制在视图顶部 底部标签 顶部限制在顶部标签的底部(具有所需的空间) 底部限制在视图底部 中心标签 将 centerY 约束到视图

然后:

如果两个标签都有文本 显示它们并隐藏中心标签 如果只有一个标签有文字 给他们两个“”作为文本 把它们都隐藏起来 显示中心标签 将中心标签的文本和字体设置为相应的顶部或底部标签

这是一个例子:

自定义视图

class WalterView: UIView 

    let labelA = UILabel()
    let labelB = UILabel()
    let labelC = UILabel()

    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    
    func commonInit() -> Void 
        
        for (v, c) in zip([labelA, labelB, labelC], [UIColor.cyan, UIColor.green, UIColor.yellow]) 
            addSubview(v)
            v.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                v.leadingAnchor.constraint(equalTo: leadingAnchor),
                v.trailingAnchor.constraint(equalTo: trailingAnchor),
            ])
            v.backgroundColor = c
            v.textAlignment = .center
        

        NSLayoutConstraint.activate([
            
            labelA.topAnchor.constraint(equalTo: topAnchor),
            labelB.topAnchor.constraint(equalTo: labelA.bottomAnchor, constant: 8.0),
            labelB.bottomAnchor.constraint(equalTo: bottomAnchor),
            
            labelC.centerYAnchor.constraint(equalTo: centerYAnchor),
            
        ])
        
        labelA.text = " "
        labelB.text = " "
        labelC.isHidden = true
        
        // set fonts as desired
        labelA.font = .preferredFont(forTextStyle: .headline)
        labelB.font = .preferredFont(forTextStyle: .subheadline)
        labelA.adjustsFontForContentSizeCategory = true
        labelB.adjustsFontForContentSizeCategory = true
        
    
    
    func setLabels(topLabel strA: String, botLabel strB: String) -> Void 

        if !strA.isEmpty && !strB.isEmpty 
            
            // if neither string is empty
            //  show A & B
            //  set text for A & B
            //  hide C

            labelA.text = strA
            labelB.text = strB
            labelC.text = ""
            labelA.isHidden = false
            labelB.isHidden = false
            labelC.isHidden = true
            
         else 
            
            // if either top or bottom string is empty
            //  hide A & B
            //  show C
            //  set A & B text to " " (so they're not empty)
            //  set text for C to the non-empty string
            //  set C's font & background color to respective top or bottom label

            labelA.isHidden = true
            labelB.isHidden = true
            labelC.isHidden = false

            labelA.text = " "
            labelB.text = " "

            if strA.isEmpty 
                labelC.text = strB
                labelC.backgroundColor = labelB.backgroundColor
                guard let f = labelB.font else 
                    return
                
                labelC.font = f
            
            
            if strB.isEmpty 
                labelC.text = strA
                labelC.backgroundColor = labelA.backgroundColor
                guard let f = labelA.font else 
                    return
                
                labelC.font = f
            

        
        
    


示例控制器 - 每次点击都会在“两者”、“仅顶部”和“仅底部”之间循环:

class WalterViewController: UIViewController 
    
    var wView = WalterView()
    
    var idx: Int = 0
    
    var testStrings: [[String]] = [
        ["Top Label",   "Bottom Label"],
        ["Top Only",    ""],
        ["",            "Bottom Only"],
    ]
    
    override func viewDidLoad() 
        super.viewDidLoad()

        wView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(wView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            wView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            wView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            wView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            // NO Height constraint...
            // height will be determined by wView's labels
        ])
        
        // so we can see the frame of wView
        wView.layer.borderWidth = 1
        wView.layer.borderColor = UIColor.red.cgColor
        
        updateLabels()
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        updateLabels()
    
    
    func updateLabels() -> Void 
        let strs = testStrings[idx % testStrings.count]
        wView.setLabels(topLabel: strs[0], botLabel: strs[1])
        idx += 1
    
    

结果:

【讨论】:

以上是关于UIStackView:基于所有子视图的一致高度,即使它们丢失的主要内容,如果未能解决你的问题,请参考以下文章

相对于堆栈视图高度约束 UIStackView 的子视图

相对于堆栈视图高度,限制UIStackView的子视图

如何在 UIStackView 内排列的子视图上设置自定义高度?

UISlider 在水平 UIStackView 中的高度

将视图添加到 UIStackView 后 UITableViewCell 不更新高度

当轴垂直时在 UIStackView 的子视图上设置恒定宽度