【问题标题】:UIStackView, 2 labels: Change Axis based on sizeUIStackView,2 个标签:根据大小更改轴
【发布时间】:2018-10-13 03:08:59
【问题描述】:

我在 horizo​​ntal UIStackView 中嵌入了两个标签。其中一个标签可能会变得太大,因此,其中一个会被截断。

或者,它们的大小被按一定比例分割并垂直增长。

如果标签不合适,我想要实现的是让 UIStackViewaxis 更改为 vertical

请参考下附两张图:

图 1:标签排成一行,使用水平轴。
图 2:其中一个标签是多行。使用垂直轴。

使用UICollectionViewFlowLayout时的行为类似。

【问题讨论】:

  • 您是否以编程方式或在情节提要中创建了 stackview?
  • 我以编程方式创建了 UIStackView。不过,对于这个问题的范围来说,这应该无关紧要。
  • 如果您想知道内容大小何时更改,这可能会对您有所帮助:stackoverflow.com/questions/18951332/…

标签: ios autolayout uilabel uistackview intrinsic-content-size


【解决方案1】:

试一试。您只需要测量文本,并且必须将一个堆栈视图与另一个堆栈视图包装在一起。

import UIKit

class ViewController: UIViewController {

    var stackView : UIStackView?
    var outerStackView : UIStackView?
    var label1 = UILabel()
    var label2 = UILabel()

    var heightAnchor : NSLayoutConstraint?
    var widthAnchor : NSLayoutConstraint?
    var button : UIButton?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        createStackView()
        createLabelsAndAddToStack()

        button = UIButton(frame: CGRect(x: 20, y: self.view.frame.height - 60, width: self.view.frame.width - 40, height: 40))
        button?.backgroundColor = .green
        button?.setTitleColor(.black, for: .normal)
        button?.setTitle("Toggle Text", for: .normal)
        button?.addTarget(self, action: #selector(toggleText), for: .touchUpInside)
        button?.setTitleColor(.gray, for: .highlighted)
        self.view.addSubview(button!)
    }

    func createStackView(){

        outerStackView = UIStackView(frame: CGRect(x: 20, y: 20, width: self.view.bounds.width - 40, height: 100))
        outerStackView?.axis = .vertical
        outerStackView?.distribution = .fill
        outerStackView?.alignment = .center
        outerStackView?.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(outerStackView!)

        outerStackView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 40).isActive = true
        outerStackView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -40).isActive = true
        outerStackView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50).isActive = true


        stackView = UIStackView(frame: CGRect(x: 20, y: 20, width: self.view.bounds.width - 40, height: 100))
        stackView?.alignment = .center
        stackView?.spacing = 20
        stackView?.distribution = .fillEqually
        stackView?.axis = .horizontal
        outerStackView?.addArrangedSubview(stackView!)

        heightAnchor = stackView?.heightAnchor.constraint(equalTo: outerStackView!.heightAnchor)
        widthAnchor = stackView?.widthAnchor.constraint(equalTo: outerStackView!.widthAnchor)

    }

    func createLabelsAndAddToStack(){
        label1 = UILabel(frame: .zero)
        label1.textColor = .black
        label1.font = UIFont.systemFont(ofSize: 17)
        label1.text = "Label 1"
        label1.numberOfLines = 0
        stackView?.addArrangedSubview(label1)

        label2 = UILabel(frame: .zero)
        label2.font = UIFont.systemFont(ofSize: 17)
        label2.textColor = .green
        label2.text = "Label 2"
        label2.numberOfLines = 0
        stackView?.addArrangedSubview(label2)

    }

    func adaptStackView(){

        self.outerStackView?.layoutIfNeeded()
        self.stackView?.layoutIfNeeded()
        //let maxWidth = label2.frame.width
        //if you want it to be the max it could be
        let maxWidth = (self.outerStackView!.frame.width - stackView!.spacing)/2
        let height = label2.font.lineHeight

        if let label1Text = label1.text{
            //lets measure
            if label1Text.width(withConstraintedHeight: height, font: label1.font) > maxWidth{
                outerStackView?.axis = .horizontal
                outerStackView?.distribution = .fill

                stackView?.axis = .vertical
                stackView?.distribution = .fill
                stackView?.alignment = .leading

                widthAnchor?.isActive = true
                heightAnchor?.isActive = true

            }else{
                outerStackView?.axis = .vertical
                outerStackView?.distribution = .fill

                stackView?.alignment = .center
                stackView?.axis = .horizontal
                stackView?.distribution = .fillEqually

                stackView?.removeConstraints(self.stackView!.constraints)
                widthAnchor?.isActive = false
                widthAnchor?.isActive = false
            }

            UIView.animate(withDuration: 0.5) {
                self.view.layoutIfNeeded()
            }
        }
    }

    @objc func toggleText(){
        if label1.text == "Label 1"{
            label1.text = "This is some longer text to test everything out to see how we are doing"

        }else{
            label1.text = "Label 1"
        }
        adaptStackView()
    }

}

extension String{
    func width(withConstraintedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin], attributes: [.font: font], context: nil)

        return ceil(boundingBox.width)
    }
}

【讨论】:

  • 这绝对是这个问题最有创意的答案。这两个 StackView 都可以封装到具有上述行为的单个类中,瞧!谢谢!
  • @RichardTopchiy 在回答问题时我不喜欢将其放在子类中以便其他人可以理解。很高兴它有帮助。
  • 清除,但在我的应用程序中有很多这样的视图,所以子类应该适合我。
【解决方案2】:

如果您正在寻找更通用的解决方案,这对我有用:

//
//  UIStackView+.swift
//

import UIKit

extension UIStackView {

    // Check if a given set of views and subviews fit into their desired frame
    private func checkSubviews(views: [UIView], includeSubviews: Bool = false) -> Bool {

        for view in views {
            let idealSize = view.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: view.bounds.height))
            if idealSize.width > view.bounds.width {
                axis = .vertical
                return false
            }
            if includeSubviews {
                if checkSubviews(views: view.subviews, includeSubviews: true) == false {
                    return false
                }
            }
        }
        return true
    }

    @discardableResult
    func updateAxisToBestFit(views: [UIView], includeSubviews: Bool = false) -> UILayoutConstraintAxis {
        self.axis = .horizontal
        setNeedsLayout()
        layoutIfNeeded()
        let subviewsFit = checkSubviews(views: views, includeSubviews: includeSubviews)
        guard subviewsFit == false else { return .horizontal }
        self.axis = .vertical
        setNeedsLayout()
        layoutIfNeeded()
        return .vertical
    }

    @discardableResult
    func updateAxisToBestFit() -> UILayoutConstraintAxis {
        return updateAxisToBestFit(views: self.arrangedSubviews, includeSubviews: true)
    }

}

那时的用法是:

class MyCellView: UITableCellView {
    @IBOutlet weak var myStackView: UIStackView!

    public func configure(with model: MyCellViewModel) {
        // update cell content according to model
        myStackView?.updateAxisToBestFit()
    }
}

【讨论】:

  • 它是否适用于 UITableViewHeaderFooterView?我面临的问题不会增加节标题的高度
  • 我的解决方案不是设置单元格的高度,而是更改堆栈视图的轴(因此不是将单元格放在 x 轴上,而是将其更改为 y 轴。应该设置高度自动。
猜你喜欢
  • 1970-01-01
  • 2011-07-18
  • 2022-09-28
  • 1970-01-01
  • 2016-04-25
  • 1970-01-01
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多