Home > Software design >  UIView: how does the appearance() proxy work?
UIView: how does the appearance() proxy work?

Time:10-13

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

CodePudding user response:

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