IB Hack - Setting the runtime variables

23 Jul 2012

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. <!-- more -->

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:

1
2
3
@property(strong, nonatomic) IBOutlet UITextField *nameField;
@property(strong, nonatomic) IBOutlet UITextField *surnameField;
@property(strong, nonatomic) IBOutlet UITextField *middleNameField;

and then in your code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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...
}

Logically, if you had 20 of this inputs, your controller would get bloated. A lot. 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 , surname ,... then your fields identifiers should be name , surname ,... If you can manage that, than the above methods could become very short.

1
2
3
4
5
6
7
8
9
- (void)textFieldDidEndEditing:(UITextField *)textField {
    [self.form setValue:textField.text forKey:textField.myAwesomeIdentifier];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    for (id input in self.view.subviews) // this is more a pseudo. Your view can be differently structured.
      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.

1
2
3
4
- (void)textFieldDidEndEditing:(UITextField *)textField {
    NSLog("Label: %@ , Hint: %@", textField.accessibilityLabel, textField.accessibilityHint);
}
// Label: (null) , Hint: (null)

The weird part is, if you set them in the code, it works like a charm.

The solution:

1
2
3
4
- (void)textFieldDidEndEditing:(UITextField *)textField {
    NSLog("Label: %@ , Hint: %@", textField.accessibilityLabel, textField.accessibilityHint);
}
//LABEL: working label, HINT: working hint, IDENTIFIER: working identifier 

I'm not advocating the usage of accessibility-stuff. You should probably subclass the UI*Component and that way document your code better, open some new possibilities, etc.
Let's say I've made a FormTextField, with 2 properties: required , and databaseField. In that case the IB would look like:

To sum up:

  • You don't need the IBOutlets anymore!
  • You don't need the big methods for identifying / saving / loading in your controller.