UIView: how does the appearance() proxy work?

313 Views Asked by At

I have created a simple custom UIView:

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

Then I wrote this in my view controller:

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
        }
    }
}

By doing this, I get an error:

enter image description here

Could you help me understanding what's wrong here and how to implement the UIAppearance proxy for any custom UIView?

Thank you for your help

1

There are 1 best solutions below

1
DonMag On BEST ANSWER

You need to make the property @objc and dynamic:

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

Worth noting: the UIAppearance proxy does NOT affect views which are already part of the view hierarchy.

So, in your example, adding @objc dynamic to your property will get rid of the crash, but you will not see any change to the two @IBOutlet views.

If you call it as part of viewDidLoad() (instead of DispatchQueue.main.asyncAfter):

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

The two @IBOutlet views will get the red background.

Or, if you add a new view to the hierarchy, it will get the red background:

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
    }
}