【问题标题】:How to get frame from UIView subclass with AutoLayout如何使用 AutoLayout 从 UIView 子类获取框架
【发布时间】:2018-12-20 11:49:06
【问题描述】:

我有一个自定义 UIView 子类,我在其中以编程方式添加了几个子视图。我正在使用 AutoLayout 设置所有布局代码。

当我重写 UIView 的 layoutSubviews() 方法以尝试获取我的子视图框架时,问题就出现了,因为它们总是返回 .zero 作为它们的框架。

但是,如果我转到 XCode 中的 View Hiarchy Debugger,所有帧都已计算并正确显示。

这是我在layoutSubviews() 方法中记录的控制台输出:

layoutSubviews(): <PrologueTextView: 0x7fa50961abc0; frame = (19.75 -19.5; 335.5 168); clipsToBounds = YES; autoresize = RM+BM; layer = <CAShapeLayer: 0x60000022be20>>
layoutSubviews(): <Label: 0x7fa509424c60; baseClass = UILabel; frame = (0 0; 0 0); text = 'This is'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d160>>
layoutSubviews(): <Label: 0x7fa509424f60; baseClass = UILabel; frame = (0 0; 0 0); text = 'some sample'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d2a0>>
layoutSubviews(): <Label: 0x7fa509425260; baseClass = UILabel; frame = (0 0; 0 0); text = 'text for you'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d3e0>>

这是我的 UIView 子类相关代码:

internal class PrologueTextView: UIView {
    internal var labels: [UILabel] = []
    internal let container: UIVisualEffectView = UIVisualEffectView()

    // region #Properties
    internal var shapeLayer: CAShapeLayer? {
        return self.layer as? CAShapeLayer
    }

    internal override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    // endregion

    // region #Initializers
    internal override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }

    internal required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.setup()
    }
    // endregion

    // region #UIView lifecycle
    internal override func layoutSubviews() {
        super.layoutSubviews()

        let mask: UIBezierPath = UIBezierPath()

        for label in self.labels {
            let roundedCorners = self.roundedCorners(for: label)
            let maskBezierPath = UIBezierPath(roundedRect: label.frame, byRoundingCorners: roundedCorners, cornerRadius: 4.0)
            mask.append(maskBezierPath)
        }

        self.shapeLayer?.path = mask.cgPath

        print("layoutSubviews(): \(self)")
        print("layoutSubviews(): \(labels[0])")
        print("layoutSubviews(): \(labels[1])")
        print("layoutSubviews(): \(labels[2])")
    }
    // endregion

    // region #Helper methods
    private func setup() {
        self.setupSubviews()
        self.setupSubviewsAnchors()
    }

    private func setupSubviews() {
        self.container.effect = UIBlurEffect(style: .light)
        self.container.translatesAutoresizingMaskIntoConstraints = false

        self.addSubview(self.container)

        let someSampleText = "This is\nsome sample\ntext for you"

        for paragraph in someSampleText.components(separatedBy: "\n") {
            let label = UILabel()
                label.text = paragraph
                label.translatesAutoresizingMaskIntoConstraints = false

            self.labels.append(label)

            self.container.contentView.addSubview(label)
        }
    }

    private func setupSubviewsAnchors() {
        NSLayoutConstraint.activate([
            self.container.topAnchor.constraint(equalTo: self.topAnchor),
            self.container.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            self.container.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.container.trailingAnchor.constraint(equalTo: self.trailingAnchor)
        ])

        for (index, label) in self.labels.enumerated() {
            let offset = 16.0 * CGFloat(index)

            if index == 0 {
                label.topAnchor.constraint(equalTo: self.container.contentView.topAnchor).isActive = true
            } else {
                let prev = self.labels[index - 1]
                label.topAnchor.constraint(equalTo: prev.bottomAnchor).isActive = true

                if index == self.labels.count - 1 {
                    label.bottomAnchor.constraint(equalTo: self.container.contentView.bottomAnchor).isActive = true
                }
            }

            NSLayoutConstraint.activate([
                label.leadingAnchor.constraint(equalTo: self.container.leadingAnchor, constant: offset),
                label.trailingAnchor.constraint(lessThanOrEqualTo: self.container.trailingAnchor)])
        }
    }

    private func roundedCorners(for label: Label) -> UIRectCorner {
        switch label {
        case self.labels.first:
            return [.topLeft, .topRight, .bottomRight]
        case self.labels.last:
            return [.topRight, .bottomLeft, .bottomRight]
        default:
            return [.topRight, .bottomLeft]
        }
    }
    // endregion
}

那么,在 AutoLayout 计算并设置视图及其子视图的框架之后,是否有任何 UIView 方法被调用?

【问题讨论】:

  • 请显示您创建和添加标签的代码。
  • 你从未为标签添加约束
  • 从发布的代码中,我看不到 labels 在您的视图中填充的位置,因此我不确定它们的组成或最初的配置方式(帧明智)。

标签: ios swift autolayout uikit


【解决方案1】:

您需要在打印行之前调用self.container.layoutIfNeeded()

internal class PrologueTextView: UIView {
    internal var labels: [UILabel] = []
    internal let container: UIVisualEffectView = UIVisualEffectView()

    // region #Properties
    internal var shapeLayer: CAShapeLayer? {
        return self.layer as? CAShapeLayer
    }

    internal override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    // endregion

    // region #Initializers
    internal override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }

    internal required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.setup()
    }
    // endregion

    // region #UIView lifecycle
    internal override func layoutSubviews() {
        super.layoutSubviews()

        let mask: UIBezierPath = UIBezierPath()

        for label in self.labels {
            let roundedCorners = self.roundedCorners(for: label)
            let maskBezierPath = UIBezierPath(roundedRect: label.frame, byRoundingCorners: roundedCorners, cornerRadii: CGSize(width: 20, height: 20))
            mask.append(maskBezierPath)
        }

        self.shapeLayer?.path = mask.cgPath

        self.container.layoutIfNeeded()  // here

        print("layoutSubviews(): \(self)")
        print("layoutSubviews(): \(labels[0])")
        print("layoutSubviews(): \(labels[1])")
        print("layoutSubviews(): \(labels[2])")
    }
    // endregion

    // region #Helper methods
    private func setup() {
        self.setupSubviews()
        self.setupSubviewsAnchors()
    }

    private func setupSubviews() {
        self.container.effect = UIBlurEffect(style: .light)
        self.container.translatesAutoresizingMaskIntoConstraints = false

        self.addSubview(self.container)

        let someSampleText = "This is\nsome sample\ntext for you"

        for paragraph in someSampleText.components(separatedBy: "\n") {
            let label = UILabel()
            label.text = paragraph
            label.translatesAutoresizingMaskIntoConstraints = false

            self.labels.append(label)

            self.container.contentView.addSubview(label)
        }
    }

    private func setupSubviewsAnchors() {
        NSLayoutConstraint.activate([
            self.container.topAnchor.constraint(equalTo: self.topAnchor),
            self.container.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            self.container.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.container.trailingAnchor.constraint(equalTo: self.trailingAnchor)
            ])

        for (index, label) in self.labels.enumerated() {
            let offset = 16.0 * CGFloat(index)

            if index == 0 {
                label.topAnchor.constraint(equalTo: self.container.contentView.topAnchor).isActive = true
            } else {
                let prev = self.labels[index - 1]
                label.topAnchor.constraint(equalTo: prev.bottomAnchor).isActive = true

                if index == self.labels.count - 1 {
                    label.bottomAnchor.constraint(equalTo: self.container.contentView.bottomAnchor).isActive = true
                }
            }

            NSLayoutConstraint.activate([
                label.leadingAnchor.constraint(equalTo: self.container.leadingAnchor, constant: offset),
                label.trailingAnchor.constraint(lessThanOrEqualTo: self.container.trailingAnchor)])
        }
    }

    private func roundedCorners(for label: UILabel) -> UIRectCorner {
        switch label {
        case self.labels.first:
            return [.topLeft, .topRight, .bottomRight]
        case self.labels.last:
            return [.topRight, .bottomLeft, .bottomRight]
        default:
            return [.topRight, .bottomLeft]
        }
    }
    // endregion
}

【讨论】:

    【解决方案2】:

    在与它斗争之后,我已经弄清楚发生了什么。

    layoutSubviews() 确实是要走的路; 它将计算视图的框架和它的直接子视图。

    这里的问题是UILabels不是主UIView子类的子视图,而是主UIView的子视图(效果视图)的子视图。

    您的视图层次结构示例:

    --> TestView
        --> EffectView
            --> UILabel
            --> UILabel
            --> UILabel
    

    如您所见,layoutSubviews() 将为您提供 TestViewEffectView 的正确帧,因为它是其直接子视图,但不会为您提供来自 UILabels 的计算帧,因为它们不是直接的TestView 的子视图。

    【讨论】:

    • 那么,从 TestView 中,不可能得到其中一个 UILabel 的框架?
    • @Anthony 是的,据我所知,您只能获得 view 的直接子项。在我的视图层次结构中,UILabelEffectView 的孩子,而不是TestView。这就是为什么我无法计算它们的尺寸。那是 18 年,不知道今天情况是否有所改善。
    【解决方案3】:

    @available(iOS 6.0, *) open func updateConstraints() // 在约束更新过程中覆盖它来调整你的特殊约束

    在 super.updateConstraints() 之后帧应该有合适的尺寸

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 2013-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多