简体   繁体   English

如何使用AutoLayout从UIView子类获取框架

[英]How to get frame from UIView subclass with AutoLayout

I've got a custom UIView subclass where I'm adding a couple of subviews programmatically. 我有一个自定义的UIView子类,在其中以编程方式添加了几个子视图。 I'm setting up all the layout code with AutoLayout. 我正在使用AutoLayout设置所有布局代码。

The problem comes when I override my UIView's layoutSubviews() method to try to get my subview frames, as they always return .zero as their frame. 当我重写UIView的layoutSubviews()方法以尝试获取子视图框架时,就会出现问题,因为它们总是返回.zero作为框架。

However, If I go to the View Hiearchy Debugger in XCode, all the frames are calculated and shown correctly. 但是,如果我转到XCode中的View Hiearchy Debugger,则会计算并正确显示所有帧。

Here is the console output I log inside the layoutSubviews() method: 这是我在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>>

And here is my UIView subclass relevant code: 这是我的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
}

So, is there any UIView method that gets called after AutoLayout has computed and set the frames for the view and it's subviews? 那么,在AutoLayout计算并设置视图及其子视图的框架之后,是否有任何UIView方法会被调用?

You need to call self.container.layoutIfNeeded() before the print lines 您需要在打印行之前调用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
}

@available(iOS 6.0, *) open func updateConstraints() // Override this to adjust your special constraints during a constraints update pass @available(iOS 6.0,*)open func updateConstraints()//重写它,以在约束更新过程中调整您的特殊约束

after super.updateConstraints() frames should have right sizes 在super.updateConstraints()框架之后应具有正确的大小

After battling around with it I've figured out what was happening. 在与之抗争之后,我弄清楚了发生了什么。

layoutSubviews() is indeed the way to go; layoutSubviews()确实是必经之路。 It will calculate the frames of the view and it's direct subviews. 它将计算视图及其直接子视图的框架。

The problem here is that the UILabels are not subviews of the main UIView subclass, but rather are subviews of the main UIView's subview (effect view). 这里的问题是UILabels不是主UIView子类的子视图,而是主UIView子视图(效果视图)的子视图。

Your view hierarchy example: 您的视图层次结构示例:

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

As you can see, layoutSubviews() will give you the correct frames for TestView and EffectView since it's its direct subview, but won't give you the calculated frames from UILabels because they aren't direct subviews of TestView . 如您所见,由于layoutSubviews()是直接子视图,因此layoutSubviews()将为您提供正确的TestViewEffectView框架,但由于它们不是TestView的直接子视图,因此不会为您提供UILabels的计算框架。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM