50

The Apple documentation states:

To participate in the appearance proxy API, tag your appearance property selectors in your header with UI_APPEARANCE_SELECTOR.

In Objective-C one can annotate properties with UI_APPEARANCE_SELECTOR like this:

@property (nonatomic, strong) UIColor *foregroundColor UI_APPEARANCE_SELECTOR;

How can I do the same in Swift?

Klaas
  • 22,394
  • 11
  • 96
  • 107

4 Answers4

51

Mark your custom view property as dynamic.

For example:

class YourCustomView: UIView {
    @objc dynamic var subviewColor: UIColor? {
        get { return self.yourSubview.backgroundColor }
        set { self.yourSubview.backgroundColor = newValue }
    }
    ...
}

Then:

YourCustomView.appearance().subviewColor = UIColor.greenColor()
Yoichi Tagaya
  • 4,547
  • 2
  • 27
  • 38
  • I need to try that out. Is it documented somewhere? – Mazyod Feb 26 '15 at 16:08
  • As far as I know, it is not documented, but I tried and it worked. Basically Swift properties dynamically accessed from Objective-C frameworks should be marked as dynamic. UI_APPEARANCE_SELECTOR is just a compiler annotation for code completion, and it has no effect on runtime. Check /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer‌​/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIAppeara‌​nce.h – Yoichi Tagaya Feb 27 '15 at 12:41
  • Unfortunately it does not work with UIBarButton subclass in Xcode 8.3.2 iOS 9.1 and makes crash of UIKit after I set appearance and instantiate new Custom UIBarButtonSubclass – Nikolay Shubenkov Jun 01 '17 at 06:57
  • Also need to add the `@objc` attribute to the property to make it work. – maxkonovalov Mar 20 '19 at 20:16
3

I did not find the solution but a workaround. Instead of annotating the properties I made them as a class variable.

private struct StarFillColor { static var _color = UIColor.blackColor() }
internal class var starFillColor: UIColor {
    get { return StarFillColor._color }
    set { StarFillColor._color = newValue }
}

And in the file where I setup all my appearances:

MyClass.starFillColor = UIColor.r(181, g: 60, b: 109) 

I hope it will help somebody!

Kevin Delord
  • 2,498
  • 24
  • 22
1

In Swift, you don't need (actually, you cannot) to annotate properties with UI_APPEARANCE_SELECTOR.

Just make sure your appearance property accessor methods be of the form:

func propertyForAxis1(axis1: IntegerType, axis2: IntegerType, axisN: IntegerType) -> PropertyType
func setProperty(property: PropertyType, forAxis1 axis1: IntegerType, axis2: IntegerType)

Source: Apple Documentation

For example:

func setStarViewColor(color: UIColor) {
    self.backgroundColor = color
}

Then you can set your appearance property like this:

MyView.appearance().setStarViewColor(someColor)

I currently using this solution in my Swift project and it works, hope it's helpful to you too.

Koen.
  • 25,449
  • 7
  • 83
  • 78
guojiubo
  • 544
  • 1
  • 7
  • 15
  • Hi, can you post your class you're using it in please! I can't seem to call my setter correctly, I get a `BAD_ACCESS` when try to call it. I'm wondering if I'm declaring my class correctly, its a sub class of `UIButton` and I'm conforming to `UIAppearanceContainer` like it says in the docs! Cheers! – Rich Dec 02 '14 at 22:14
  • @Rich Checkout my [gist](https://gist.github.com/guojiubo/adff619fdba8f8ba698c), including the whole class, hope it helps. – guojiubo Dec 04 '14 at 02:27
  • Unfortunately this doesn't allow me to change the color for specific instance. Any idea how this can be achieved? Thanks! – Reinhold Oct 13 '15 at 14:25
  • 2
    Sadly a very late response to this, but I found that marking the methods as dynamic resolved the BAD_ACCESS issue for UIButton. – Keab42 Apr 10 '17 at 12:30
1

Based on previous answers, here is the extension to UIView I implemented to manage UIView appearance with view borders, I hope it can help someone :

extension UIView {
    @objc dynamic  var borderColor: UIColor? {
        get {
            if let color = self.layer.borderColor {
                return UIColor(cgColor: color)
            } else {
                return nil
            }
        }
        set(color) {
            self.layer.borderColor = color?.cgColor
        }
    }

    @objc dynamic  var borderWidth: NSNumber? {
        get { return NSNumber(value: Float(self.layer.borderWidth))}
        set(width) {
            self.layer.borderWidth = CGFloat(width?.floatValue ?? 0)
        }
    }

    @objc dynamic  var cornerRadius: NSNumber? {
        get { return NSNumber(value: Float(self.layer.cornerRadius))}
        set(radius) {
            self.layer.cornerRadius = CGFloat(radius?.floatValue ?? 0)
        }
    }
}
Climbatize
  • 1,103
  • 19
  • 34