7

Accoding to NSHipster, "having custom UI classes conform to UIAppearance is not only a best-practice, but it demonstrates a certain level of care being put into its implementation."

Therefore, I'm trying to set text attributes, which are later used to create a NSAttributedString, to a property var titleTextAttributes: [String : AnyObject]? in a UIView subclass like this:

func applyAppearance() {

    UINavigationBar.appearance().translucent = true
    UINavigationBar.appearance().tintColor = UIColor(named: .NavBarTextColor)
    UINavigationBar.appearance().barTintColor = UIColor(named: .NavBarBlue)
    UINavigationBar.appearance().titleTextAttributes = [
        NSForegroundColorAttributeName: UIColor(named: .NavBarTextColor),
        NSFontAttributeName: UIFont.navBarTitleFont()!
    ]

    ActionBarView.appearance().backgroundColor = UIColor(white: 1, alpha: 0.15)
    ActionBarView.appearance().titleTextAttributes = [
        NSKernAttributeName: 1.29,
        NSFontAttributeName: UIFont.buttonFont()!,
        NSForegroundColorAttributeName: UIColor.whiteColor(),
    ]
}

This is a snipped from my AppDelegate.

Now, when trying to set ActionBarView.appearance().titleTextAttributes = [ ... ], I'm getting the following runtime error:

error when trying to set text attirbutes on a custom uiview subclass

What's worth mentioning is that setting the attributes on UINavigationBar works without any problems.

Going to UINavigationBar's header file reveals this:

/* You may specify the font, text color, and shadow properties for the title in the text attributes dictionary, using the keys found in NSAttributedString.h.
 */
@available(iOS 5.0, *)
public var titleTextAttributes: [String : AnyObject]?

Which is exactly the same definition of a property as in my ActionBarView class:

class ActionBarView: UIView {

    var titleTextAttributes: [String : AnyObject]?

    // ...
}

So my question is: Is there anything I can do to implement a property with a dictionary of text attributes in my own UIView subclass that can be set via UIAppearance proxy? Since using UI_APPEARANCE_SELECTOR is not possible in Swift? And why is it working out-of-the-box for UIKit classes like UINavigationBar? Is there some kind of black magic involved?

Tom Kraina
  • 3,569
  • 1
  • 38
  • 58
  • Seems like marking my property `titleTextAttributes` as `dynamic` and backing it with another one as a storage solves this issue: http://stackoverflow.com/a/28734970/1161723 – Tom Kraina Jul 21 '16 at 16:15
  • Well, I guess you told me! Sorry about that. So this feature must work by some sort of swizzling (like KVO, which also requires `dynamic`). – matt Jul 21 '16 at 16:21

2 Answers2

5

For appearance property just add dynamic keyword:

class ActionBarView: UIView {

    dynamic var titleTextAttributes: [String : AnyObject] = [:]

    // use ActionBarView.appearance().titleTextAttributes 
}

and For appearance property accessor methods must be of the form:

func propertyForAxis1(axis1: IntegerType, axis2: IntegerType, axisN: IntegerType) -> PropertyType
func setProperty(property: PropertyType, forAxis1 axis1: IntegerType, axis2: IntegerType)
c0ming
  • 3,407
  • 1
  • 21
  • 26
  • It's possible to implement styling insensitive to `dynamic` and able to work with properties of nested objects (like `view.layer.cornerRadius`). Take a look at [StyleSheet](https://github.com/werediver/StyleSheet) if you're interested. – werediver Nov 02 '16 at 22:05
3

Based on this answer, marking the property titleTextAttributes as dynamic and backing it with another one as a storage solves the issue:

class ActionBarView: UIView {

    /// UIAppearance compatible property
    dynamic var titleTextAttributes: [String : AnyObject]? { // UI_APPEARANCE_SELECTOR
        get { return self._titleTextAttributes }
        set { self._titleTextAttributes = newValue }
    }

    private var _titleTextAttributes: [String : AnyObject]?

    // ... use `self.titleTextAttributes` in the implementation
}
Community
  • 1
  • 1
Tom Kraina
  • 3,569
  • 1
  • 38
  • 58