【问题标题】:.equalSpacing in UIStackView not working properly with CustomViewUIStackView 中的 .equalSpacing 无法与 CustomView 一起正常工作
【发布时间】:2021-11-08 17:19:19
【问题描述】:

我创建了一个复选框/单选按钮的自定义视图,在选中和取消选中时会显示动画。问题是当我将自定义视图嵌入到 .equalSpacing 的 stackView 时,按钮无法再被点击,我的猜测是borderView(具有点击手势)收缩得太小,因此无法点击。

奇怪的是,即使它真的缩小了,仍然可以看到borderView。

如果是 .fillEqually.fillProportionally 则可以正常工作。

我正在使用 SnapKit 来安排约束。

让我知道我是否应该在此处粘贴整个代码。

自定义视图

public final class CustomView: UIView {
    
    // MARK: - Properties
    
    private let borderedView: UIView = {
        let view = UIView()
        view.layer.borderWidth = 1
        return view
    }()
    
    private var errorStackView: UIStackView = {
        let sv = UIStackView()
        sv.isHidden = true
        return sv
    }()
    
    private var borderStackView: UIStackView = {
        let sv = UIStackView()
        return sv
    }()
    
    private var parentStackView: UIStackView = {
        let sv = UIStackView()
        return sv
    }()

.
.
.
.
.


// MARK: - Lifecycle
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupUI()
    }
    
    public init(button: ButtonType) {
        super.init(frame: CGRect.zero)
        setupUI()
    }
    
    public override func draw(_ rect: CGRect) {
        setNeedsDisplay()
    }

.
.
.
.
.
.

     private func setupUI() {
        backgroundColor = .clear
        borderedView.addSubview(disabledImageView)
        
        borderedView.snp.makeConstraints { make in
            make.width.height.equalTo(borderSize)
        }
        
        borderStackView = UIStackView(arrangedSubviews: [borderedView, label])
        borderStackView.spacing = 12
        borderStackView.distribution = .fillProportionally
        
        borderStackView.snp.makeConstraints { make in
            make.height.width.equalTo(borderSize)
        }

        
        errorStackView = UIStackView(arrangedSubviews: [errorIconImageView, inlineErrorLabel])
        errorStackView.spacing = 7
        errorStackView.distribution = .fillProportionally
        
        parentStackView = UIStackView(arrangedSubviews: [borderStackView, errorStackView])
        parentStackView.axis = .vertical
        parentStackView.distribution = .fillProportionally
        addSubview(parentStackView)

        parentStackView.snp.makeConstraints { make in
            make.centerY.equalTo(self.snp.centerY)
            make.width.equalToSuperview()
        }
        
        disabledImageView.snp.makeConstraints { make in
            make
                .leading
                .trailing
                .top
                .bottom
                .height
                .width
                .equalToSuperview()
        }
        
        parentStackView.sizeToFit()
        layoutIfNeeded()
    }

.
.
.
.
.
.
    // MARK: - Action
    
    public func addAction(action: @escaping ((Bool?) -> Void)) {
        borderedView.addGestureRecognizerOnView(target: self, #selector(handleTapGesture(sender:)))
        status = action
    }
}

视图控制器


class ViewController: UIViewController {
    
    // MARK: - Properties
    
    private lazy var checkboxDefault: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleUnchecked()
        }
        return checkbox
    }()
    
    private lazy var checkboxEnabledAndChecked: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleChecked()
        }
        return checkbox
    }()
    
    private lazy var checkboxDisableAndUnchecked: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleDisabledAndUnchecked()
        }
        return checkbox
    }()
    
    private lazy var checkboxDisableAndChecked: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleDisabledAndChecked()
        }
        return checkbox
    }()
    
    private lazy var checkboxError: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleError()
        }
        return checkbox
    }()
    
    private lazy var checkboxMultilineError: CustomView = {
        let checkbox = CustomView()
        checkbox.addAction { [weak self] checkboxStatus in
            self?.handleMultiError()
        }
        return checkbox
    }()

.
.
.
.
.
.
 override func viewDidLoad() {

        let stackview = UIStackView(
            arrangedSubviews: [
                checkboxDefault,
                checkboxEnabledAndChecked,
                checkboxDisableAndUnchecked,
                checkboxDisableAndChecked,
                checkboxError,
                checkboxMultilineError
            ])
        
        stackview.distribution = .equalSpacing
        stackview.axis = .vertical

        view.addSubview(stackview)

        stackview.snp.makeConstraints { make in
            make.center.equalTo(view.snp.center)
            make.width.equalTo(300)
            make.height.equalTo(300)
        }
}

【问题讨论】:

  • 即使没有将CustomView 放在堆栈视图中(只需设置frametranslatesAutoresizingMaskIntoConstraints = true),我也会遇到冲突的约束。你也明白吗?
  • 如果您要问的只是一个带有标签的复选框,您应该删除代码中与 errorStackViewinlineErrorLabel 或类似内容有关的部分。制作minimal reproducible example。 “最小”是关键字。
  • @Sweeper 我包含了这些,因为它是我的 stackView 的内容,因此其他人可以看到 parentStackView 和其他 stackView 中的内容。
  • 如果视图对问题很重要,请将它们包含在复选框的图片中,以便清楚您要实现哪种布局。现在还不清楚为什么要使用这些嵌套的堆栈视图来获得一个简单的复选框 + 标签。如果它与问题无关,请将其从代码 sn-p 中删除。请记住,有责任提出minimal reproducible example

标签: swift uikit uistackview custom-view snapkit


【解决方案1】:

我设法通过在自定义视图中添加intrinsicContentSize 来解决这个问题

并在每次更新视图时添加 invalidateIntrinsicContentSize() 。就我而言,它在 handleTapGesture

// MARK: - Lifecycle

    public override var intrinsicContentSize: CGSize {
       return CGSize(width: 300, height: 50)
    }
.
.
.
.
.
// MARK: - Action
    
    @objc private func handleTapGesture(sender: UITapGestureRecognizer) {
        invalidateIntrinsicContentSize() // Call this when custom view needs to be updated
    }

【讨论】: