11

I am looking to add a new IBInspectable attribute (computed property) to UILabel via a category method. Ideally I want this attribute to be set after the labels text is set (via setValue:forKey), as this IBInspectable attribute may result in the the UILabels text being updated and we don't want the text in the UILabel to later replace it. Looking at the documentation there is no mention if pre-defined attributes are always set set before user defined attributes during the nib/storyboard load for attributes configured in Interface Builder.

Are custom attributes added to an object in Interface Builder using IBInspectable or User defined runtime attributes guaranteed to be set after the standard pre-defined objects properties/attributes?

Draken
  • 3,134
  • 13
  • 34
  • 54
bencallis
  • 3,478
  • 4
  • 32
  • 58

2 Answers2

2

The following experiment concludes that the native text property is set prior to the category property, and so the value can be safely overwritten by the category setter.

A label category:

//  UILabel+Thingy.h

#import <UIKit/UIKit.h>

@interface UILabel (Thingy)

@property (nonatomic, strong) IBInspectable NSString *thingy;

@end

//  UILabel+UILabel_Thingy.m

#import "UILabel+Thingy.h"
#import <objc/runtime.h>

@implementation UILabel (Thingy)

- (NSString *)thingy {
    return objc_getAssociatedObject(self, @selector(thingy));
}

- (void)setThingy:(NSString *)thingy {
    NSLog(@"setting thingy to '%@', my text is currently '%@'", thingy, self.text);
    objc_setAssociatedObject(self, @selector(thingy), thingy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

In IB, set the inspectable category property and the text property....

enter image description here

A little instrumentation in the containing view controller:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"didLoad text is '%@' and thingy is '%@'", self.label.text, self.label.thingy);
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"willAppear text is '%@' and thingy is '%@'", self.label.text, self.label.thingy);
}

Run it, and the NSLog output indicates that during the awake from nib, the native property is set by the time the category property setter is called...

...[794:41622] setting thingy to 'thingy value', my text is currently 'text value'

...[794:41622] didload text is 'text value' and thingy is 'thingy value'

...[794:41622] willappear text is 'text value' and thingy is 'thingy value'

Setting the label's text property in the category property setter will (and does, I tested it) result in the text property being overwritten to the thingy property, since the text property is initialized first.

Further evidence can be seen in the XIB file when rendered as XML...

<label opaque="NO" (... all the native properties) text="text value" (...) id="XAM-6h-4fn">
    <rect key="frame" x="274" y="147" width="278" height="34"/>

    (... and so on)

    <userDefinedRuntimeAttributes>
        <userDefinedRuntimeAttribute type="string" keyPath="thingy" value="thingy value"/>
    </userDefinedRuntimeAttributes>
</label>

... which is consistent with the view being instantiated and initialized via a pre-order traversal, thereby setting the (parent tag) label properties before the (child tag) userDefinedRuntimeAttributes.

Community
  • 1
  • 1
danh
  • 62,181
  • 10
  • 95
  • 136
  • Thanks for the reply. During tests on different versions of iOS I noticed the same behaviour. However; I was hoping it was documented somewhere so I could be entirely safe using it in production code. – bencallis Sep 11 '16 at 20:31
  • Doesn't appear to be covered explicitly where one would expect to see it in the docs. But the demonstrated deterministic behavior is better than a doc, right? As long as it uses 100% public APIs, the behavior can be considered as permanent to to your app as any part of the SDK. – danh Sep 11 '16 at 23:05
  • I guess a concern would be in a future version of iOS for the order to change without any noticeable API change. – bencallis Sep 12 '16 at 09:49
  • I think I will look to go down the route of using objc_setAssociatedObject. Thanks – bencallis Sep 12 '16 at 09:57
  • Up to you, of course, but the app compiles statically. The frameworks it ships with can change, but not the app without the developer rebuilding with the newer stuff. – danh Sep 12 '16 at 12:39
0

If you create an extension with an IBInspectable computer variable, the value placed into that custom field in Interface Builder will override the value in the label's text field when both values are set in Interface Builder, even though it will not preview that way in Interface Builder.

extension UILabel {

    @IBInspectable var value: String? {
        get {
            return text
        }

        set {
            text = newValue
            setNeedsDisplay()
        }
    }

}

Interface builder enter image description here

picciano
  • 22,341
  • 9
  • 69
  • 82
  • Thanks for the answers. I have implemented a similar extension and tested on different versions of iOS I noticed the same behaviour. However; I was hoping it was documented somewhere so I could be entirely safe using it in production code. – bencallis Sep 11 '16 at 20:32