UIView:appearance() 代理是如何工作的?

UIView: how does the appearance() proxy work?

我创建了一个简单的自定义 UIView:

final class TestView: UIView {
    var testColor: UIColor = .white {
        didSet {
            backgroundColor = testColor
        }
    }
}

然后我在我的视图控制器中写了这个:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var testView: TestView!
    @IBOutlet weak var testView2: TestView!        
    
    override func viewDidLoad() {
        super.viewDidLoad()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            TestView.appearance().testColor = .red
        }
    }
}

通过这样做,我得到一个错误:

你能帮我理解这里出了什么问题以及如何为任何自定义 UIView 实施 UIAppearance 代理吗?

感谢您的帮助

您需要使 属性 @objcdynamic:

final class TestView: UIView {
    @objc dynamic var testColor: UIColor? = .white {
        didSet {
            backgroundColor = testColor
        }
    }
}

值得注意:UIAppearance 代理 不会 影响已经属于视图层次结构的视图。

因此,在您的示例中,将 @objc dynamic 添加到 属性 将消除崩溃,但您将 看不到 两次 @IBOutlet 次浏览。

如果您将其作为 viewDidLoad() 的一部分调用(而不是 DispatchQueue.main.asyncAfter):

override func viewDidLoad() {
    super.viewDidLoad()
    TestView.appearance().testColor = .red
}

两个 @IBOutlet 视图 获得红色背景。

或者,如果您向层次结构添加新视图,它将获得红色背景:

class AppearanceTestViewController: UIViewController {
    @IBOutlet weak var testView: TestView!
    @IBOutlet weak var testView2: TestView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            TestView.appearance().testColor = .red
            self.addAnotherTestView()
        }
    }
    func addAnotherTestView() -> Void {
        let v = TestView()
        v.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(v)
        NSLayoutConstraint.activate([
            v.widthAnchor.constraint(equalToConstant: 240.0),
            v.heightAnchor.constraint(equalTo: v.widthAnchor),
            v.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])

        // this newly added view WILL have a red background
    }
}