4
\$\begingroup\$

I've subclassed UI button and used it in my View Controller's viewDidLoad method, like so:

PLOTDefaultButton *createAccountBtn = [[PLOTDefaultButton alloc] init];
CGRect createAccountBtnFrame = createAccountBtn.frame;
createAccountBtnFrame.origin.x = 20;
createAccountBtnFrame.origin.y = self.view.bounds.size.height - 135;
createAccountBtnFrame.size.width = self.view.bounds.size.width - 40;
createAccountBtn.frame = createAccountBtnFrame;
[createAccountBtn setTitle:@"Create an account" forState:UIControlStateNormal];

createAccountBtn.backgroundColor = [UIColor plotYellow];
createAccountBtn.titleLabel.textColor = [UIColor colorWithRed:0.165 green:0.212 blue:0.267 alpha:1];
createAccountBtn.layer.shadowColor = [UIColor colorWithRed:0.831 green:0.106 blue:0.082 alpha:1].CGColor;

[createAccountBtn addTarget:self action:@selector(showCreateAccount:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:createAccountBtn];

PLOTDefaultButton *loginBtn = [[PLOTDefaultButton alloc] init];
CGRect loginBtnFrame = loginBtn.frame;
loginBtnFrame.origin.x = 20;
loginBtnFrame.origin.y = self.view.bounds.size.height - 67.5;
loginBtnFrame.size.width = self.view.bounds.size.width - 40;
loginBtn.frame = loginBtnFrame;
[loginBtn setTitle:@"Log in" forState:UIControlStateNormal];
[loginBtn addTarget:self action:@selector(showLogin:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:loginBtn];

And the UIButton subclass:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor plotDarkBlue];
        self.titleLabel.textColor = [UIColor whiteColor];
        self.layer.cornerRadius = 5;
        self.layer.masksToBounds = NO;
        self.layer.borderWidth = 0;

        self.layer.shadowColor = [UIColor colorWithRed:0.086 green:0.110 blue:0.141 alpha:1].CGColor;
        self.layer.shadowOpacity = 1;
        self.layer.shadowRadius = 0;
        self.layer.shadowOffset = CGSizeMake(0, 3);

        self.titleLabel.font = [UIFont fontWithName:@"Avenir-Medium" size:17];

        CGRect frame = self.frame;
        frame.size.height = 47.5;
        self.frame = frame;
    }
    return self;
}

With quite a few different button styles going on, ie.

enter image description here enter image description here

With most of the differences coming from positioning, dimensions, text colour, background colour and shadow colour. Now am I trying to fit too much into one subclass, what would be people's approach to a sane amount of code in the viewDidLoad methods in their View Controllers, in relation to styling the UI?

\$\endgroup\$
  • \$\begingroup\$ You know that neither of the examples you provided are actually calling the initWithFrame: method you gave us, right? \$\endgroup\$ – nhgrif Sep 2 '14 at 11:39
4
\$\begingroup\$

It doesn't necessarily bother me that so much of our styling code is outside of the init method (though I would change some of this up--I'll get back to that). What bothers me is that it seems it's all directly in viewDidLoad.

I don't know about you, but for me, there's regularly lots of things to set up in viewDidLoad. Here, you've committed 21 lines of viewDidLoad code to setting up these buttons. Once we add code for a few other UI elements, plus code for anything else we want to set up in viewDidLoad, the method very quickly becomes a mega-do-too-much-method.

What I'd rather see is a method to contain all the code that sets up the buttons, and let viewDidLoad call that method. I'd much rather see:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupButtons];
    [self setupLabels];
    [self checkWebForSomething];
}

We've got who knows how many very distinct tasks that all want to happen in viewDidLoad (or any other view controller life-cycle event).

If we separate these tasks out into distinct methods, it makes maintenance easier in the future. I don't want to spend time skimming through the UI code when I really just need to fix something in the code that does the checkWebForSomething stuff (and vice versa).


Now, with all that said, let's talk about the specifics of your initWithFrame: method, shall we?

I don't mind that we set things like our corner radius and shadow off set in init. I don't even necessarily mind that we set our font and have a hard coded height for the button. The most bothersome of these 4 things to me would be the hard coded height, but there's plenty of Apple precedent for UI elements with fixed height or width.

What bothers me are the preset colors.

Honestly, I think rather than initWithFrame:, we should be seeing a method that looks more like:

- (UIButton *)initWithFrame:(CGRect)frame
                      title:(NSString *)title
            foregroundColor:(UIColor *)foregroundColor
            backgroundColor:(UIColor *)backgroundColor
                shadowColor:(UIColor *)shadowColor;
\$\endgroup\$
  • \$\begingroup\$ Thanks a bunch for your suggestions. I've got my viewDidLoad method looking pretty trim now, and I'll move on to passing in the colours. Thanks again. \$\endgroup\$ – benhowdle89 Sep 2 '14 at 13:19
2
\$\begingroup\$

I think it's good idea to separate framing and styling for your custom subclasses. And for styling I found useful a clean approach to send custom colors/fonts etc through dictionaries. Something like that:

In your button subclass define few constants for dictionary keys:

static NSString *kTitleFontKey = @"TitleFont";
static NSString *kBackgroundColorKey = @"BackgroundColor";
...

and method to set style:

- (void)setStyle: (NSDictionary *)style
{
    self.backgroundColor = (UIColor *)style[kBackgroundColorKey];
    self.textLabel.font = (UIFont *)style[kTitleFontKey];
    ...
}

and then in your view controller you will do something like that:

...
[self.button setStyle: @{
    kTitleFontKey: [UIFont systemFontOfSize: 11.0],
    kBackgroundColorKey: [UIColor redColor];
}];
...

And when you have multiple elements with same style you just define style dictionary beforehand and reuse it for multiple elements.

I found this approach to be very useful and easy to extend.

\$\endgroup\$
  • 1
    \$\begingroup\$ This is an interesting approach. I usually write a UIColor category myself. \$\endgroup\$ – nhgrif Sep 7 '14 at 17:08
  • \$\begingroup\$ Not sure how can you achieve same with UIColor category. Can you elaborate? \$\endgroup\$ – sha Sep 7 '14 at 17:14
  • \$\begingroup\$ Well typically, my colors are consistent throughout the entirety of the app and not specific to a single view controller. So I just add methods similar to [UIColor redColor] but for the various specific colors I want to use. \$\endgroup\$ – nhgrif Sep 7 '14 at 17:18
  • \$\begingroup\$ That makes sense. I was trying to solve the issue of configuring like 10 different things at the same time \$\endgroup\$ – sha Sep 7 '14 at 17:23
  • \$\begingroup\$ If you're going to define constants, it might be better to use static NSString * const kMyConstant = ..., instead. The static limits the scope to the current compilation unit, and const enforces the constant nature of the variable. \$\endgroup\$ – Rob Oct 6 '14 at 6:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.