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?
2 Answers 2
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;
-
\$\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– benhowdle892014年09月02日 13:19:27 +00:00Commented Sep 2, 2014 at 13:19
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.
-
1\$\begingroup\$ This is an interesting approach. I usually write a
UIColor
category myself. \$\endgroup\$nhgrif– nhgrif2014年09月07日 17:08:58 +00:00Commented Sep 7, 2014 at 17:08 -
\$\begingroup\$ Not sure how can you achieve same with UIColor category. Can you elaborate? \$\endgroup\$sha– sha2014年09月07日 17:14:05 +00:00Commented Sep 7, 2014 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– nhgrif2014年09月07日 17:18:27 +00:00Commented Sep 7, 2014 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– sha2014年09月07日 17:23:44 +00:00Commented Sep 7, 2014 at 17:23
-
\$\begingroup\$ If you're going to define constants, it might be better to use
static NSString * const kMyConstant = ...
, instead. Thestatic
limits the scope to the current compilation unit, andconst
enforces the constant nature of the variable. \$\endgroup\$Rob– Rob2014年10月06日 06:25:37 +00:00Commented Oct 6, 2014 at 6:25
initWithFrame:
method you gave us, right? \$\endgroup\$