programmatically Design a View in objective - C

802 views Asked by At

My project is design a View with numerous label, image, textField without storyboard or Nib. Do I need manually alloc and add every single thing to the view? I think this is very overkill but I have no idea how to do it any other way. Example:

UILabel *firstLabel =[UILabel alloc] init];
firstLabel.frame  = (0,x,20,10) ;
firstLabel.text = ...;
firstLabel.font = ...;
firstLabel.textColor = ...;
.................
[self.view addSubView:firstLabel];

UILabel *secondLabel =[UIlabel alloc] init];
secondLabel.frame = (0,y,20,10);    
secondLabel.text = ...;
secondLabel.font = ...;
secondLabel.textColor = ...;
.................
[self.view addSubView:secondLabel];

UILabel *thirdLabel =[UIlabel alloc] init];
    thirdLabel.frame = (0,z,20,10);    
    thirdLabel.text = ...;
    thirdLabel.font = ...;
   thirdLabel.textColor = ...;
    .................
    [self.view addSubView:thirdLabel];

Should I put all of them in viewDidLoad or loadView or init method?

Or I just need make a method for CreatLabel and use it again and again? How to do it?

3

There are 3 answers

1
Rob On

If you're programmatically creating view from scratch, you'd do that in loadView. (See Creating a View Programmatically.) If, however, you have NIB/storyboard for the top level view and you are merely adding subviews, then you could do that in viewDidLoad.

Regarding the creation of a bunch of labels, yes, you could use subroutine. Or, assuming there is a regular pattern that dictates where these are positioned, you might even use a for loop in which you increment the y coordinate or build the constraints. (You can save references to these views in an array or use tag values to keep track of them.) But however you do it, yes, you'd want to minimize the amount of repeated code you write (simplifying life from a maintenance perspective, if nothing else).

0
vikingosegundo On

If I understand you correctly you ask how to apply DRY (Don't repeat yourself) to this code.

«Two of more — use a for» Edsger W. Dijkstra

or

«Two of more — use an enumeration» vikingosegundo

- (void)viewDidLoad { // or loadView, see Rob's answer
    [super viewDidLoad];

    NSArray *properties= @[@{@"color": [UIColor orangeColor], @"text": @"Ceterum censeo"},
                          @{@"color": [UIColor cyanColor], @"text": @"Carthaginem esse"},
                          @{@"color": [UIColor purpleColor], @"text": @"delendam"}];

    [properties enumerateObjectsUsingBlock:^(NSDictionary *properties, NSUInteger idx, BOOL *stop) {
          UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
          l.backgroundColor = properties[@"color"];
          l.text = properties[@"text"];
          l.font= [UIFont italicSystemFontOfSize:12];
          [self.view addSubview:l];
      }];
}

Ok, why do I prefer this block-based enumeration here?
Because it has an mutation guard and gives me the correct index for free that I need for the frame.

A C for-loop for (int idx = 0; idx < [properties count]; ++idx) gives me the correct index but I must include an extra statement to get the object NSDictionary *property = properties[idx]; and as it has no mutation guard: it could be changed during iteration which might lead to bad things.

A fast enumeration for(NSDictionary *property in properties) has such a mutation guard and is even slightly faster enumerating than the block enumeration. But it has the big disadvantage that if I need the index, I must call NSUIndex idx = [properties indexForObject:property]; causing a quadratic runtime performance instead of a linear: bye bye, speed advantage. And even worse: if an array contains identical objects it will only find the first one repeatedly — a good chance of creating false data.

Depending on the amount of the code, it might be useful to move this into a helper method — but this is more about taste.


As your question in the end is about readability, I want to share another matter of taste: I like to encapsulate object creation into a distinct scope:

Either by using an implicit block

UILabel *label = ^{
    UILabel *l =[[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
    l.backgroundColor = properties[@"color"];
    l.text = properties[@"text"];
    l.font= [UIFont italicSystemFontOfSize:12];
    return l;
}();
[self.view addSubview:label];

or a statement expression

UILabel *label = ({
    UILabel *l =[[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
    l.backgroundColor = properties[@"color"];
    l.text = properties[@"text"];
    l.font= [UIFont italicSystemFontOfSize:12];
    l;
});
[self.view addSubview:label];
0
Kumar On

It is okay to put in ViewDidLoad() but use custom method if there are large number of labels.