StackView - 在 ArrangedSubviews 中交换按钮

StackView -Swap Buttons in ArrangedSubviews

我有一个 UIStackView 初始设置有 4 个按钮。如果我以后需要用新按钮换掉最后一个按钮或返回到初始按钮,我该怎么做?

lazy var stackView: UIStackView = {
    let sv = UIStackView()
    sv.axis = .horizontal
    sv.distribution = .fillEqually
    sv.alignment = .fill
    return sv
}()

// ...
var bt4: UIButton!
var bt5: UIButton!

// viewDidLoad
func configureStackView() {

    view.addSubview(stackView)

    stackView.addArrangedSubview(bt1)
    stackView.addArrangedSubview(bt2)
    stackView.addArrangedSubview(bt3)
    stackView.addArrangedSubview(bt4)

    // place stackView at bottom of scene
}

func swapLastButtonInStackViewWithNewButton(_ val: Bool) {

    if val {

        // if true replace bt4 in stackView with bt5

    } else {

        // if false replace bt5 in stackView with bt4
    }
}

您可以像这样存储堆栈子视图的排列:

    lazy var stackViewArrangedSubviews = stackView.arrangedSubviews {
        didSet {
            setStackViewSubviews(with: stackViewArrangedSubviews)
        }
    }

然后

    func setStackViewSubviews(with subviews: [UIView]) {
        stackView.arrangedSubviews.forEach { [=11=].removeFromSuperview() }
        subviews.forEach { stackView.addArrangedSubview([=11=]) }
    }

最后像这样实现交换功能:

    func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
        
        if val {
            stackViewArrangedSubviews[3] = bt5
            // if true replace bt4 in stackView with bt5
            
        } else {
            stackViewArrangedSubviews[3] = bt4
            // if false replace bt5 in stackView with bt4
        }
    }

这并不完美,您可以根据需要改进代码。

UIStackView 在视图隐藏时自动删除该视图。所以基本上你所要做的就是正确设置按钮 4 和按钮 5 的 isHidden 布尔值。

class ViewController: UIViewController {
    @IBOutlet private weak var button4: UIButton!
    @IBOutlet private weak var button5: UIButton!
    private var showButton5 = false {
        didSet {
            button5.isHidden = !showButton5
            button4.isHidden = showButton5
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        showButton5 = false
    }

    @IBAction private func toggle() {
        showButton5.toggle()
    }
}

您可以很容易地做到这一点,无需保留对 bt4bt5:

的引用
func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
    
    // if true replace bt4 in stackView with bt5
    
    // must have 5 buttons in the stack view
    guard stackView.arrangedSubviews.count == 5 else { return }
    
    stackView.arrangedSubviews[3].isHidden = val
    stackView.arrangedSubviews[4].isHidden = !val

}

如果您真的想保留对按钮和add-to/remove-from堆栈视图的单独引用,您可以这样做:

func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
    
    // if true replace bt4 in stackView with bt5
    
    let btnToShow: UIButton = val ? bt5 : bt4

    // we only want to replace the button if it's not already there
    guard let lastButton = stackView.arrangedSubviews.last as? UIButton,
          lastButton != btnToShow
    else { return }
    
    lastButton.removeFromSuperview()
    stackView.addArrangedSubview(btnToShow)
    
}

这里有完整的例子...

首先,使用.isHidden方法:

class StackViewController: UIViewController {
    
    lazy var stackView: UIStackView = {
        let sv = UIStackView()
        sv.axis = .horizontal
        sv.distribution = .fillEqually
        sv.alignment = .fill
        sv.spacing = 12
        return sv
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureStackView()
    }
    
    func configureStackView() {
        
        for i in 1...5 {
            let b = UIButton()
            b.setTitle("\(i)", for: [])
            b.backgroundColor = .red
            stackView.addArrangedSubview(b)
        }
        
        // place stackView at bottom of scene
        stackView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
            stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
        ])
        
        // add a couple "set val" buttons
        let btnSV: UIStackView = {
            let sv = UIStackView()
            sv.axis = .horizontal
            sv.distribution = .fillEqually
            sv.alignment = .fill
            sv.spacing = 12
            return sv
        }()
        ["True", "False"].forEach { t in
            let b = UIButton()
            b.setTitle(t, for: [])
            b.backgroundColor = .blue
            b.addTarget(self, action: #selector(setTrueFalse(_:)), for: .touchUpInside)
            btnSV.addArrangedSubview(b)
        }
        
        btnSV.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(btnSV)
        
        NSLayoutConstraint.activate([
            btnSV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
            btnSV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
            btnSV.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])

    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // start with button "5" hidden
        swapLastButtonInStackViewWithNewButton(false)
    }

    @objc func setTrueFalse(_ sender: UIButton) {
        guard let t = sender.currentTitle else { return }
        swapLastButtonInStackViewWithNewButton(t == "True")
    }
    
    func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
        
        // if true replace bt4 in stackView with bt5
        
        // must have 5 buttons in the stack view
        guard stackView.arrangedSubviews.count == 5 else { return }
        
        stackView.arrangedSubviews[3].isHidden = val
        stackView.arrangedSubviews[4].isHidden = !val

    }

}

或者,使用对 bt4bt5 以及 adding/removing 的引用:

class StackViewController: UIViewController {
    
    lazy var stackView: UIStackView = {
        let sv = UIStackView()
        sv.axis = .horizontal
        sv.distribution = .fillEqually
        sv.alignment = .fill
        sv.spacing = 12
        return sv
    }()
    
    var bt4: UIButton!
    var bt5: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureStackView()
    }
    
    func configureStackView() {
        
        for i in 1...5 {
            let b = UIButton()
            b.setTitle("\(i)", for: [])
            b.backgroundColor = .red
            stackView.addArrangedSubview(b)
        }
        
        // place stackView at bottom of scene
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
            stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
        ])
        
        // add a couple "set val" buttons
        let btnSV: UIStackView = {
            let sv = UIStackView()
            sv.axis = .horizontal
            sv.distribution = .fillEqually
            sv.alignment = .fill
            sv.spacing = 12
            return sv
        }()
        ["True", "False"].forEach { t in
            let b = UIButton()
            b.setTitle(t, for: [])
            b.backgroundColor = .blue
            b.addTarget(self, action: #selector(setTrueFalse(_:)), for: .touchUpInside)
            btnSV.addArrangedSubview(b)
        }
        
        btnSV.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(btnSV)
        
        NSLayoutConstraint.activate([
            btnSV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
            btnSV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
            btnSV.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        ])
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // this would go at the end of configureStackView(), but
        //  we'll put it here to keep the changes obvious
        // references to btn4 and btn5
        guard stackView.arrangedSubviews.count == 5,
              let b4 = stackView.arrangedSubviews[3] as? UIButton,
              let b5 = stackView.arrangedSubviews[4] as? UIButton
        else {
            fatalError("Bad setup - stackView does not have 5 buttons!")
        }
        bt4 = b4
        bt5 = b5
        
        // start with button "5" hidden
        swapLastButtonInStackViewWithNewButton(false)
    }
    
    @objc func setTrueFalse(_ sender: UIButton) {
        guard let t = sender.currentTitle else { return }
        swapLastButtonInStackViewWithNewButton(t == "True")
    }
    
    func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
        
        // if true replace bt4 in stackView with bt5
        
        let btnToShow: UIButton = val ? bt5 : bt4

        // we only want to replace the button if it's not already there
        guard let lastButton = stackView.arrangedSubviews.last as? UIButton,
              lastButton != btnToShow
        else { return }
        
        lastButton.removeFromSuperview()
        stackView.addArrangedSubview(btnToShow)
        
    }
    
}

编辑

上面的代码可能看起来有点过于复杂——但我认为这与所有设置和“额外”检查更相关。

作为更 straight-forward 的回答...

只要您设置了堆栈视图并有效引用了 bt4bt5,您需要做的就是:

func swapLastButtonInStackViewWithNewButton(_ val: Bool) {
    
    // if true replace bt4 in stackView with bt5
    if val {
        bt4.removeFromSuperview()
        stackView.addArrangedSubview(bt5)
    } else {
        bt5.removeFromSuperview()
        stackView.addArrangedSubview(bt4)
    }

}

这将避免动画问题。