ios 如何通过编程创建包含两个子stackview的水平stackview?

niknxzdl  于 2022-12-20  发布在  iOS
关注(0)|答案(2)|浏览(132)

前一段时间我问如何在this question中绘制UI块。感谢@HangarRash我得到了答案,并了解如何做到这一点。但现在我想创建StackView,这是基于其他两个stackview。我有这样的代码:

class ViewController: UIViewController {
    @IBOutlet weak var mainContainer: UIView!
    
    let colorDictionary = [
        "Red":UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0),
        "Green":UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0),
        "Blue":UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0),
        //        "Green2":UIColor(red: 1.0, green: 0.7, blue: 0.0, alpha: 1.0),
    ]
    
    //MARK: Instance methods
    func colorButton(withColor color:UIColor, title:String) -> UILabel{
        let newButton = UILabel()
        newButton.backgroundColor = .gray
        newButton.text = title
        newButton.textAlignment = .center
        newButton.textColor = UIColor.white
        return newButton
    }
    
    
    
    
    func displayKeyboard(){
        var buttonArray = [UILabel]()
        for (myKey,myValue) in colorDictionary{
            buttonArray += [colorButton(withColor: myValue, title: myKey)]
        }
        
        
        let horizontalStack = UIStackView(arrangedSubviews: buttonArray)
        horizontalStack.axis = .horizontal
        horizontalStack.distribution = .fillEqually
        horizontalStack.alignment = .fill
        horizontalStack.translatesAutoresizingMaskIntoConstraints = false

        let label2 = UILabel()
        label2.text = "Label"
        label2.backgroundColor = .red
        label2.textColor = .white
        label2.textAlignment = .center
        label2.lineBreakMode = .byCharWrapping
        label2.numberOfLines = 0
        label2.translatesAutoresizingMaskIntoConstraints = false

        let leftStack = UIStackView()
        leftStack.backgroundColor = .blue
        leftStack.axis = .vertical
        leftStack.distribution = .equalSpacing
        leftStack.translatesAutoresizingMaskIntoConstraints = false
        leftStack.addArrangedSubview(label2)
        leftStack.addArrangedSubview(horizontalStack)
        leftStack.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
        
        
        
        
        let bottomStackView = UIStackView(arrangedSubviews: buttonArray)
        bottomStackView.axis = .horizontal
        bottomStackView.distribution = .fillEqually
        bottomStackView.alignment = .fill
        
        
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        
        let url = URL(string: "https://picsum.photos/270")!
        let image = UIImageView()
        
        
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, _, error in
            if let data = data {
                DispatchQueue.main.async {
                    image.image =  UIImage(data: data)
                    image.contentMode = .scaleToFill
                    image.translatesAutoresizingMaskIntoConstraints = false
                }
            }
        }.resume()
        
        stackView.addArrangedSubview(image)
        stackView.addArrangedSubview(bottomStackView)
        
        
        
        
        let mainStackView = UIStackView()
        mainStackView.axis = .horizontal
        

        mainStackView.addArrangedSubview(leftStack)
        mainStackView.addArrangedSubview(stackView)
        
        mainContainer.addSubview(mainStackView)
        mainStackView.translatesAutoresizingMaskIntoConstraints = false
        
        
        NSLayoutConstraint.activate([
            mainStackView.topAnchor.constraint(equalTo: mainContainer.topAnchor, constant: 5),
            mainStackView.leftAnchor.constraint(equalTo: mainContainer.leftAnchor),
            mainStackView.rightAnchor.constraint(equalTo: mainContainer.rightAnchor),
            mainStackView.heightAnchor.constraint(equalToConstant: 270),
            leftStack.widthAnchor.constraint(equalToConstant: 270),
            
        ])
        
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        displayKeyboard()
    }
}

它给我画了这样一幅画面:

但是我不明白我的左UI块在哪里,有4个不同的标签,以及如何使stackview UI按比例填充。我的意思是,我不需要巨大的左视图,我需要它的10%左右的主stackview。我试图使它在这样的方式:

leftStack.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true

但这并没有帮助我。我需要像10%的左块和90%的主块与图像。我认为这是可能的设置比例为视图内的堆栈视图

a8jjtwal

a8jjtwal1#

您遇到的问题与转换和自动布局如何交互有关--或者,也许更好地说,交互。
来自Apple的docs
在iOS 8.0及更高版本中,transform属性不影响自动布局。自动布局基于视图的未变换框架计算视图的对齐矩形。
因此,当旋转堆栈视图时,将使用其未变换帧
举个简单的例子...让我们把三个标签放在一个水平堆栈视图中,并对中间的标签应用旋转变换:

class Step1VC: UIViewController {
    
    let leftLabel = UILabel()
    let centerLabel = UILabel()
    let rightLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        let mainStackView = UIStackView()
        mainStackView.axis = .horizontal
        
        mainStackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(mainStackView)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            mainStackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            mainStackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])
        
        // add three labels to the stack view
        
        leftLabel.textAlignment = .center
        leftLabel.text = "Left"
        leftLabel.backgroundColor = .yellow
        
        centerLabel.textAlignment = .center
        centerLabel.text = "Let's rotate this label"
        centerLabel.backgroundColor = .green

        rightLabel.textAlignment = .center
        rightLabel.text = "Right"
        rightLabel.backgroundColor = .cyan

        mainStackView.addArrangedSubview(leftLabel)
        mainStackView.addArrangedSubview(centerLabel)
        mainStackView.addArrangedSubview(rightLabel)
        
        // outline the stack view so we can see its frame
        mainStackView.layer.borderColor = UIColor.red.cgColor
        mainStackView.layer.borderWidth = 1
    
        // info label
        let iLabel = UILabel()
        iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        iLabel.numberOfLines = 0
        iLabel.textAlignment = .center
        iLabel.text = "\nStep 1\n\nTap anywhere to rotate center label\n"
        iLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(iLabel)
        NSLayoutConstraint.activate([
            iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
            iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
        ])
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if centerLabel.transform == .identity {
            centerLabel.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
        } else {
            centerLabel.transform = .identity
        }
    }
}

我们得到的结果是:
第一节第一节第一节第一节第一次
有很多方法可以解决这个问题......考虑一下您的最终目标,让我们创建一个UIView子类,其中包含一个标签,当根据标签的框架进行转换时,它会自动调整自身。
因此,自定义类:

class MyCustomLabelView: UIView {
    
    // public properties to replicate UILabel
    //  add any additional if needed
    
    public var text: String = "" {
        didSet { theLabel.text = text }
    }
    public var textColor: UIColor = .black {
        didSet { theLabel.textColor = textColor }
    }
    public var font: UIFont = .systemFont(ofSize: 17.0) {
        didSet { theLabel.font = font }
    }
    public var textAlignment: NSTextAlignment = .left {
        didSet { theLabel.textAlignment = textAlignment }
    }
    override var backgroundColor: UIColor? {
        didSet {
            theLabel.backgroundColor = backgroundColor
            super.backgroundColor = .clear
        }
    }
    
    private let theLabel = UILabel()
    
    public func rotateTo(_ d: Double) {
        if let v = subviews.first {
            // set the rotation transform
            if d == 0 {
                self.transform = .identity
            } else {
                self.transform = CGAffineTransform(rotationAngle: d)
            }
            
            // remove the label
            v.removeFromSuperview()
            
            // tell it to layout itself
            v.setNeedsLayout()
            v.layoutIfNeeded()
            
            // get the frame of the label
            //  apply the same transform
            let r = v.frame.applying(self.transform)
            
            wC.isActive = false
            hC.isActive = false

            // add the label back
            addSubview(v)
            
            // set self's width and height anchors
            //  to the width and height of the label

            wC = self.widthAnchor.constraint(equalToConstant: r.width)
            hC = self.heightAnchor.constraint(equalToConstant: r.height)

            // apply the new constraints
            NSLayoutConstraint.activate([
                
                v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
                v.centerYAnchor.constraint(equalTo: self.centerYAnchor),

                wC, hC
                
            ])
        }
    }

    private var wC: NSLayoutConstraint!
    private var hC: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        backgroundColor = .clear
        theLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(theLabel)
        
        wC = self.widthAnchor.constraint(equalTo: theLabel.widthAnchor)
        hC = self.heightAnchor.constraint(equalTo: theLabel.heightAnchor)
        
        NSLayoutConstraint.activate([
            
            theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
            
            wC, hC,
            
        ])
    }
}

和与Step1相同的控制器,但我们将使用三个MyCustomLabelView,而不是三个UILabel

class Step2VC: UIViewController {
    
    let leftLabel = MyCustomLabelView()
    let centerLabel = MyCustomLabelView()
    let rightLabel = MyCustomLabelView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        let mainStackView = UIStackView()
        mainStackView.axis = .horizontal
        
        mainStackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(mainStackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            mainStackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            mainStackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])
        
        // add three labels to the stack view
        
        leftLabel.textAlignment = .center
        leftLabel.text = "Left"
        leftLabel.backgroundColor = .yellow
        
        centerLabel.textAlignment = .center
        centerLabel.text = "Let's rotate this label"
        centerLabel.backgroundColor = .green
        
        rightLabel.textAlignment = .center
        rightLabel.text = "Right"
        rightLabel.backgroundColor = .cyan
        
        mainStackView.addArrangedSubview(leftLabel)
        mainStackView.addArrangedSubview(centerLabel)
        mainStackView.addArrangedSubview(rightLabel)
        
        // outline the stack view so we can see its frame
        mainStackView.layer.borderColor = UIColor.red.cgColor
        mainStackView.layer.borderWidth = 1
        
        // info label
        let iLabel = UILabel()
        iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        iLabel.numberOfLines = 0
        iLabel.textAlignment = .center
        iLabel.text = "\nStep 2\n\nTap anywhere to rotate center label\n"
        iLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(iLabel)
        NSLayoutConstraint.activate([
            iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
            iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
        ])
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if centerLabel.transform == .identity {
            centerLabel.rotateTo(-.pi * 0.5)
        } else {
            centerLabel.rotateTo(0)
        }
    }
}

现在,当我们旋转中心标签(视图)时,我们会得到:
第一节第二节第一节第三节第一节
因此,为了获得您想要的完整布局,我们将创建一个自定义视图,其中包含"左侧"标签(在两个堆栈视图中)、一个图像视图、一个底部标签的堆栈视图和一个"外部"堆栈视图,以将所有内容放在一起。
自定义"左侧"类:

class MyCustomView: UIView {
    
    public var titleText: String = "" {
        didSet { titleLabel.text = titleText }
    }
    
    public func addLabel(_ v: UIView) {
        labelsStack.addArrangedSubview(v)
    }
    
    public func rotateTo(_ d: Double) {
        
        // get the container view (in this case, it's the outer stack view)
        if let v = subviews.first {
            // set the rotation transform
            if d == 0 {
                self.transform = .identity
            } else {
                self.transform = CGAffineTransform(rotationAngle: d)
            }
            
            // remove the container view
            v.removeFromSuperview()
            
            // tell it to layout itself
            v.setNeedsLayout()
            v.layoutIfNeeded()
            
            // get the frame of the container view
            //  apply the same transform as self
            let r = v.frame.applying(self.transform)
            
            wC.isActive = false
            hC.isActive = false
            
            // add it back
            addSubview(v)
            
            // set self's width and height anchors
            //  to the width and height of the container
            wC = self.widthAnchor.constraint(equalToConstant: r.width)
            hC = self.heightAnchor.constraint(equalToConstant: r.height)
            
            // apply the new constraints
            NSLayoutConstraint.activate([

                v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
                v.centerYAnchor.constraint(equalTo: self.centerYAnchor),
                wC, hC

            ])
        }
    }
    
    // our subviews
    private let outerStack = UIStackView()
    private let titleLabel = UILabel()
    private let labelsStack = UIStackView()
    
    private var wC: NSLayoutConstraint!
    private var hC: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        
        // stack views and label properties
        
        outerStack.axis = .vertical
        outerStack.distribution = .fillEqually
        
        labelsStack.axis = .horizontal
        labelsStack.distribution = .fillEqually
        
        titleLabel.textAlignment = .center
        titleLabel.backgroundColor = .lightGray
        titleLabel.textColor = .white
        
        // add title label and labels stack to outer stack
        outerStack.addArrangedSubview(titleLabel)
        outerStack.addArrangedSubview(labelsStack)
        
        outerStack.translatesAutoresizingMaskIntoConstraints = false
        addSubview(outerStack)
        
        wC = self.widthAnchor.constraint(equalTo: outerStack.widthAnchor)
        hC = self.heightAnchor.constraint(equalTo: outerStack.heightAnchor)

        NSLayoutConstraint.activate([
            
            outerStack.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            outerStack.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            wC, hC,
            
        ])
        
    }
    
}

以及示例控制器:

class Step3VC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        guard let img = UIImage(named: "testPic") else {
            fatalError("Need an image!")
        }

        // create the image view
        let imgView = UIImageView()
        imgView.contentMode = .scaleToFill
        imgView.backgroundColor = .systemBlue
        imgView.image = img
        
        // create the "main" stack view
        let mainStackView = UIStackView()
        mainStackView.axis = .horizontal

        // create the "right-side" stack view
        let rightSideStack = UIStackView()
        rightSideStack.axis = .vertical
        
        // create the "bottom labels" stack view
        let bottomLabelsStack = UIStackView()
        bottomLabelsStack.distribution = .fillEqually
        
        // add the image view and bottom labels stack view
        //  to the right-side stack view
        rightSideStack.addArrangedSubview(imgView)
        rightSideStack.addArrangedSubview(bottomLabelsStack)
        
        // create the custom "left-side" view
        let myView = MyCustomView()
        
        // add views to the main stack view
        mainStackView.addArrangedSubview(myView)
        mainStackView.addArrangedSubview(rightSideStack)

        // add main stack view to view
        mainStackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(mainStackView)

        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain Top/Leading/Trailing
            mainStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            mainStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            mainStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
            // main stack view height will be determined by its subviews
            
        ])

        // setup the left-side custom view
        myView.titleText = "Gefährdung"
        
        let titles: [String] = [
            "keine / gering", "mittlere", "erhöhte", "hohe",
        ]
        let colors: [UIColor] = [
            UIColor(red: 0.863, green: 0.894, blue: 0.527, alpha: 1.0),
            UIColor(red: 0.942, green: 0.956, blue: 0.767, alpha: 1.0),
            UIColor(red: 0.728, green: 0.828, blue: 0.838, alpha: 1.0),
            UIColor(red: 0.499, green: 0.706, blue: 0.739, alpha: 1.0),
        ]
        
        for (c, t) in zip(colors, titles) {
            myView.addLabel(colorLabel(withColor: c, title: t, titleColor: .black))
        }
        
        // rotate the left-side custom view 90-degrees counter-clockwise
        myView.rotateTo(-.pi * 0.5)
        
        // setup the bottom labels
        let colorDictionary = [
            "Red":UIColor.systemRed,
            "Green":UIColor.systemGreen,
            "Blue":UIColor.systemBlue,
        ]
        
        for (myKey,myValue) in colorDictionary {
            bottomLabelsStack.addArrangedSubview(colorLabel(withColor: myValue, title: myKey, titleColor: .white))
        }

        // info label
        let iLabel = UILabel()
        iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        iLabel.numberOfLines = 0
        iLabel.textAlignment = .center
        iLabel.text = "\nStep 3\n"
        iLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(iLabel)
        NSLayoutConstraint.activate([
            iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
            iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
        ])

    }
    
    func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
        let newLabel = UILabel()
        newLabel.backgroundColor = color
        newLabel.text = title
        newLabel.textAlignment = .center
        newLabel.textColor = titleColor
        return newLabel
    }

}

结果是:

为了稍微改善视觉效果,我想在标签上添加一些"填充"......因此,我使用了这个简单的标签子类:

class PaddedLabel: UILabel {
    var padding: UIEdgeInsets = .zero
    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: padding))
    }
    override var intrinsicContentSize : CGSize {
        let sz = super.intrinsicContentSize
        return CGSize(width: sz.width + padding.left + padding.right, height: sz.height + padding.top + padding.bottom)
    }
}

colorLabel(...)函数替换为:

func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
    let newLabel = PaddedLabel()
    newLabel.padding = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
    newLabel.backgroundColor = color
    newLabel.text = title
    newLabel.textAlignment = .center
    newLabel.textColor = titleColor
    return newLabel
}

得到最终结果:

vdgimpew

vdgimpew2#

首先,创建两个子堆栈视图,并根据需要配置它们。

let stackView1 = UIStackView()
stackView1.axis = .vertical
stackView1.alignment = .fill
stackView1.distribution = .fillEqually

let stackView2 = UIStackView()
stackView2.axis = .vertical
stackView2.alignment = .fill
stackView2.distribution = .fillEqually

接下来,创建水平堆栈视图,并将两个子堆栈视图添加为已排列的子视图:

let horizontalStackView = UIStackView()
horizontalStackView.axis = .horizontal
horizontalStackView.alignment = .fill
horizontalStackView.distribution = .fillEqually

horizontalStackView.addArrangedSubview(stackView1)
horizontalStackView.addArrangedSubview(stackView2)

最后,将水平堆栈视图添加到视图层次结构中,并根据需要配置其约束。例如:

view.addSubview(horizontalStackView)
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
horizontalStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
horizontalStackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
horizontalStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

这将创建一个水平堆栈视图,其中两个子堆栈视图并排排列,填充父视图的整个宽度。然后,您可以根据需要向子堆栈视图添加视图以创建布局。

相关问题