almost 4 years ago

Recently, I had to explain the existing design in the project to the programmers new to iOS. I can give some explanation, but, sometimes, I am not 100% sure what I am talking about. For example, in the project, there are many customized UI component for the project's special needs (well, I can't talk about this too much). These components need some attributes from the User Defined Runtime Attributes (e.g. the shadowColor, shadowOffset, and someKey in Figure 1) provided in XCode. For that, I explained why these attributes are required and how these attributes work. However, sometimes the freshmen (including me) forgot to specify the attributes, and the resulting UI displays incorrectly.

To understand more details, I searched some network resources, and I finally saw The Nib Object Life Cycle in the Resource Programming Guide and Key-Value Coding Programming Guide. Then, I found that similar design can be seen in many frameworks (usually this is called good design or pattern). First, Key-Value Coding and JavaBeans are similar designs - to provide a mechanism to access an object's properties by using the property names and don't have to call the getters and setters of the properties directly. Therefore, XCode can user Key-Value mechanism to set the User Defined Runtime Attributes into the customized UI component objects, and the access these values at runtime.

Let's see an example directly! The native UITextField does not provide the properties to specify the text shadow. Thus, the codes in Code List 1 and Code List 2 provide a customized ShadowedTextField UI component. At the runtime, the sequence to load a UI component from a nib file is to call the initWithCoder: method to initialize the object first (although the designated initializer of UIView is initWithFrame:, if the component is loaded from a nib file, initWithCoder: is called, not initWithFrame:). Then, call the setValue:forKey: method to set the User Defined Runtime Attributes into the initialized UI components. Finally, call the awakeFromNib method to notify the component that it has been awaked from the nib file. Therefore, the initWithCoder: method of the ShadowedTextField class sets the default values of shadow color and shadow offset. And then, awakeFromNib method applies the shadow on the text field based on these two properties.

Code List 1 - ShadowedTextField Header
#import <UIKit/UIKit.h>

@interface ShadowedTextField : UITextField

@property (nonatomic) CGSize shadowOffset;
@property (nonatomic) UIColor* shadowColor;

Code List 2 - ShadowedTextField Implementation
#import "ShadowedTextField.h"

@implementation ShadowedTextField

@dynamic shadowColor;
@dynamic shadowOffset;

- (instancetype)initWithCoder:(NSCoder*)aDecoder {
    if(self = [super initWithCoder:aDecoder]) {
        [self initializeDefaultValues];
    return self;

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        [self initializeDefaultValues];
    return self;

- (void)initializeDefaultValues {
    self.shadowColor = [UIColor grayColor];
    self.shadowOffset = CGSizeMake(1.0f, 1.0f);

- (void)awakeFromNib {
    [super awakeFromNib];
    [self applyShadow];

- (void)setValue:(id)value forUndefinedKey:(NSString*)key {
    NSLog(@"undefined key-value: %@-%@", key, value);

- (void)applyShadow {
    NSShadow* shadow = [[NSShadow alloc] init];
    shadow.shadowColor = self.shadowColor;
    shadow.shadowOffset = self.shadowOffset;
    id attributes = @{
        NSShadowAttributeName: shadow,
        NSFontAttributeName: self.font
    self.attributedText = [[NSAttributedString alloc] initWithString:self.text attributes:attributes];

To use the customized component, in the Interface Builder, drag and drop a UITextField into the view. Specify the Class of the component to ShadowedTextField, and then add shadowColor and shadowOffset two attributes as shown in Figure 1. Run the app, and the result will be like Figure 2 - the text has a purple shadow (in Code List 2, the initializeDefaultValues set the default value of shadowColor to gray, but the value has benn updated by the User Defined Runtime Attributes). Although these two properties are not like the native properties that has specific editors to edit their values, the customized properties still can be edit in Interface Builder.

Figure 1 - Specify the user defined runtime attributes

Figure 2 - The actual result

Okay, there is a problem. Before the runtime calling the setValue:forKey: method, it will check whether the object has the property or not. For example, while calling [person setValue:@1982 forKey:@"birthYear"];, the runtime will check whether person object has the birthYear property. If there is no corresponding property, the setValue: forUndefinedKey: method is called to handle the special case. The default implementation in NSObject is to throw a NSUndefinedKeyException exception that terminates the app. Therefore, to customize a UI component, it is recommended to override setValue: forUndefinedKey: as Code List 2. As a result, when running tha app, something like 2014-06-07 22:30:00.359 CustomAttributes[13452:60b] set undefined key-value: someKey-23 will be shown in Console.

This seems all problems are solved, but there are still some interesting phenomena found when I wrote the example. First, change the accessors generation method of the shadowColor and shadowOffset properties from @dynamic to @synthesize. If no default text is set in the nib file, the text input at the runtime will not have shadow, but change the accessor generation method back to @dynamic, the input txt will have shadow. Second, even that the accessor generation method is @dynamic, if nothing is keyed before dismissing the keyboard on the first time editing, any text keyin on the second time editing (without changing view) will not have shadow, neither. However, if a default text is set in the nib file and all the text is deleted on the first time editing, any text keyin on the second on the second time editing still have shadow. I still do not know the reason for the phenomena. If anyone knows why, please let me know.

← 關於Custom View and Runtime Attributes 使用Maven管理Android專案 →