仅在首次初始化自定义 UIView 时如何在 LayoutSubviews 中执行代码

How to execute code in LayoutSubviews only when first initialising a custom UIView

我们有一个在 Interface Builder 中使用约束的自定义 UIView,我们已修改此视图以具有我们需要绘制的自定义形状。

形状大小取决于在 Interface Builder 中应用于此自定义视图的约束,因此为了在自定义 UIView 中获得正确大小的形状,我们需要在 LayoutSubviews 中调用它。

问题是在屏幕方向期间调用 LayoutSubviews 时,另一个形状被添加到屏幕,因为它是在自定义 UIView 的设置函数中调用的。

可以通过添加一个布尔值来解决此行为,该布尔值确定这是否是第一次调用 LayoutSubviews,只有在调用时,才将形状图层添加到视图。

我在这里和网上做了一些研究,找到了一篇关于如何使用不同方法实现所描述行为的文章。

https://thomasclowes.com/ios-when-to-layout-your-views/

作者提到了一个自定义 class BaseView 和一个可以解决此问题的协议。

解决方案写在"UIView and layoutSubviews"下。

我很难理解该解决方案,想知道是否有人可以澄清并展示有关如何实现本文中提到的解决方案的代码示例。

提前致谢。

class CustomView: UIView {

    private var firstTimeInit = true

    override func layoutSubviews() {
        super.layoutSubviews()

        setup()
    }

    private func setup() {

        // Check if the the method was already executed
        if firstTimeInit == false { return }

        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
        label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
        label.text = "Daily"
        label.textAlignment = .center
        label.sizeToFit()
        addSubview(label)
        label.center.x = bounds.midX

        let shapePath = UIBezierPath()
        shapePath.move(to: CGPoint(x: 0, y: 0))
        shapePath.addLine(to: CGPoint(x: frame.width, y: 0))

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = shapePath.cgPath
        shapeLayer.strokeColor = UIColor(hex: 0xD5E5EF).cgColor
        shapeLayer.lineWidth = 3
        shapeLayer.lineCap = .round
        shapeLayer.position.y = frame.height / 2
        shapeLayer.lineDashPattern = [frame.width / 2.5, frame.width / 5, frame.width / 2.5] as [NSNumber]
        layer.addSublayer(shapeLayer)

        firstTimeInit = false
    }
}

为什么不创建 UILabelCAShapeLayer 一次,将其添加到构造函数中的子视图,然后仅使用 layoutSubviews 重新计算它们的框架或图案?

像这样:

class CustomView: UIView {

    private let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
    private let shapeLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
        label.text = "Daily"
        label.textAlignment = .center
        addSubview(label)

        shapeLayer.strokeColor = UIColor(hex: 0xD5E5EF).cgColor
        shapeLayer.lineWidth = 3
        shapeLayer.lineCap = .round
        layer.addSublayer(shapeLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        label.sizeToFit()
        label.center.x = bounds.midX

        let shapePath = UIBezierPath()
        shapePath.move(to: CGPoint(x: 0, y: 0))
        shapePath.addLine(to: CGPoint(x: frame.width, y: 0))

        shapeLayer.path = shapePath.cgPath
        shapeLayer.position.y = frame.height / 2
        shapeLayer.lineDashPattern = [frame.width / 2.5, frame.width / 5, frame.width / 2.5] as [NSNumber]
    }
}

这样您的自定义形状将适应 frame 变化,而不会自我复制。

显然,如果使用它,您可能需要实际实现 required init?(coder aDecoder: NSCoder)

一种方法是在自定义视图中使用 didSet 方法。例如

class CustomView: UIView {

    var firstTimeInit : Bool = false {
        didSet {
            layoutSubviews()
        }
    }


    override func layoutSubviews() {
        super.layoutSubviews()
        // i am only changing the color to check if the layoutSubview is called. 
        // i have a viewController in storyboard with a UIView Background Color set to gray. 
        backgroundColor = UIColor.red
    }

}

要确保您的 didSet 方法在自定义视图中被调用,您可以在将加载自定义视图的视图控制器的 viewdidLoad 函数中使用它。

class ViewController: UIViewController {

    let customView = CustomView()

    override func viewDidLoad() {
        super.viewDidLoad()
        customView.viewDidSet = true
    }
}