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
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.
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
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
shadowOffset properties from
@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.