IB Hack - Setting the runtime variables
This post is about reducing your controller lines of code, and moving some stuff in the Interface Builder. If you already know why this is for
and why setting this attributes can’t be read in code
you can skip this post.
Intro
Let’s say you have a form with 20 text fields, and various other inputs. When the text field looses focus, you want to save it’s value to the database. When you open that screen later again, you want all the values preloaded. A common way of doing it if you had 2–3 inputs, would be:
@property(strong, nonatomic) IBOutlet UITextField *nameField;
@property(strong, nonatomic) IBOutlet UITextField *surnameField;
@property(strong, nonatomic) IBOutlet UITextField *middleNameField;
and then in your code
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == nameField)
//store the name in the database, e.g.
self.form.name = textField.text;
else if (textField == surnameField)
self.form.surname = textField.text;
else if (textField == middleNameField)
self.form.middleName = textField.text;
// and so on
}
- (void)viewDidLoad {
[super viewDidLoad];
self.nameField.text = self.form.name;
self.surnameField.text = self.form.surname;
self.middleNameField.text = self.form.middleName;
// and going...
}
If you had 20 of this inputs, your controller would become a bloated mess. The second problem is, if you change the database field, you need to change the controller (and probably the view). That’s violating the SRP principle.
Motivation
Wouldn’t it be great if you could give those fields the same identifier as the
database fields are called? For instance, if a Person has name and surname
field then your identifiers should be name and surname. If you can
manage that, than the above methods could become very short.
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.form setValue:textField.text forKey:textField.myAwesomeIdentifier];
}
- (void)viewDidLoad {
[super viewDidLoad];
for (id input in self.view.subviews)
input.text = [self.form valueForKey:input.myAwesomeIdentifier];
}
Y U NO Interface Builder?
Let’s say we want to make a small hack, and use the Accessibility (label, hint,
identifier) for myAwesomeIdentifier. You can set these values in the IB, but
you can’t read them in the code.
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSLog("Label: %@ , Hint: %@", textField.accessibilityLabel, textField.accessibilityHint);
}
// Prints
// Label: (null) , Hint: (null)
The weird part is, if you set them in the code, it works like charm.
The solution:
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSLog("Label: %@ , Hint: %@", textField.accessibilityLabel, textField.accessibilityHint);
}
// Prints
//LABEL: working label, HINT: working hint, IDENTIFIER: working identifier
I’m not advocating the usage of accessibility-stuff, but showing that assigning
values to existing class fields does indeed work. You should subclass the
UI*Component and add the properties yourself. Let’s say I’ve made a
FormTextField with 2 properties: required, and databaseField. In that case
the IB would look like:
Conclusion
I’ve built a whole app that had multiple huge forms to fill on an iPad. I had to find a dynamic way of mapping view components to database fields, otherwise the development would be almost impossible. This approach has worked well for the use case, and I haven’t found any major drawbacks.