patternMinor
Setting a common background image on a dozen screens
Viewed 0 times
imagebackgroundscreenssettingcommondozen
Problem
Is there something as "they have too little in common to use a superclass"?
In my case, doing iOS programming, in a project, every screen has the same background. So I created a superclass which only has:
.h
.m
Is this too little to use a superclass for?
If I didn't add it here, I'd have about 10 view controllers where I would have to do this. (Obviously code duplication).
In my case, doing iOS programming, in a project, every screen has the same background. So I created a superclass which only has:
.h
@property (nonatomic) IBOutlet UIImageView *backgroundImageView;.m
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *backgroundImage = ...;
[self.backgroundImageView setImage:backgroundImage];
}Is this too little to use a superclass for?
If I didn't add it here, I'd have about 10 view controllers where I would have to do this. (Obviously code duplication).
Solution
The short answer is, no, this is not too little code to "extract" into a super class.
If you intend for this bit of code to always be the same throughout every use of this class and its subclasses, then it's absolutely appropriate to put the code in the superclass. The primary point is, here, if we ever decide to change something about this code, we can change it in once place and have this change applied across all instances.
With that said, however, I want to point out some problems.
As a start, we have no guarantee that this code will actually run. If our subclass implements
As this StackOverflow answer outlines, the
Now, a subclass of this class which implements
There's problems still however. For starters, as a rule, I don't like
First, not only do we have to remember to call to
I'm a huge proponent of using interface builder. It does a lot to vastly simplify our written code, but in this case, it doesn't suffice for producing simplified results.
We should remove this outlet altogether and not worry in the slightest about relying on the subclass setting up the image view in interface builder and hooking it up properly. The whole point of subclassing is minimizing redundancy, and so far, we're only half way there.
(Moreover, what guarantee do we have that every subclass of this class will even use interface builder at all? View Controllers don't require a corresponding interface builder representation--the view can be set up entirely programmatically.)
We need to change our
The number one problem we'll run into in manually creating and adding the image view to the view controller is getting it appropriately as the background. We can't be sure whether the subclass will do it's set up first then call
So, we need to add the view as the bottom-most view in the view controller's view's subviews.
This might be a good implementation:
I'm not particularly a fan of setting up UI in code (especially auto
If you intend for this bit of code to always be the same throughout every use of this class and its subclasses, then it's absolutely appropriate to put the code in the superclass. The primary point is, here, if we ever decide to change something about this code, we can change it in once place and have this change applied across all instances.
With that said, however, I want to point out some problems.
As a start, we have no guarantee that this code will actually run. If our subclass implements
viewDidLoad and fails to call to super, now our code doesn't run. There's still no way to make this guarantee, but there is a way to make a reminder for ourselves.As this StackOverflow answer outlines, the
NS_REQUIRES_SUPER directive will throw a warning when subclass implement the method without calling the super method. So we should add the following to our .h file:- (void)viewDidLoad NS_REQUIRES_SUPER;Now, a subclass of this class which implements
viewDidLoad will throw a compiler warning until [super viewDidLoad]; is added to its viewDidLoad implementation.There's problems still however. For starters, as a rule, I don't like
IBOutlets in my header file. There's absolutely no reason for these to be public. But in this particular case, we shouldn't have an IBOutlet at all. Using an image view set up in interface builder and requiring it be hooked up to this outlet will supremely complicate things.First, not only do we have to remember to call to
super, which I already address. But we also have to remember to add the image view. And we have to remember to hook the image view up as an outlet. Trouble is though, we can't (or shouldn't) hook it up to our current class. We should open this, the super class, in assistant editor and hook it up to that. Problem is, assistant editor won't automatically load super class's in the assistant window... so that's kind of a pain.I'm a huge proponent of using interface builder. It does a lot to vastly simplify our written code, but in this case, it doesn't suffice for producing simplified results.
We should remove this outlet altogether and not worry in the slightest about relying on the subclass setting up the image view in interface builder and hooking it up properly. The whole point of subclassing is minimizing redundancy, and so far, we're only half way there.
(Moreover, what guarantee do we have that every subclass of this class will even use interface builder at all? View Controllers don't require a corresponding interface builder representation--the view can be set up entirely programmatically.)
We need to change our
viewDidLoad code to manually create an image view with an image and load it as the background.The number one problem we'll run into in manually creating and adding the image view to the view controller is getting it appropriately as the background. We can't be sure whether the subclass will do it's set up first then call
super, or call super then do its set up (in case you're wondering, in viewDidLoad, it's appropriate to call to super FIRST, then add your implementation). Moreover, the view controller will almost certainly have views added on it in interface builder if the developer is using interface builder.So, we need to add the view as the bottom-most view in the view controller's view's subviews.
This might be a good implementation:
- (void)viewDidLoad {
[super viewDidLoad];
// Set up image view
UIImage *backgroundImage = [UIImage imageNamed:@"background"];
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
[backgroundView setTranslatesAutoresizingMaskIntoConstraints:NO];
// aspect fill may be preferred
backgroundView.contentMode = UIViewContentModeScaleAspectFit;
// Add background view as back-most view
[self.view insertSubview:backgroundView atIndex:0];
// Set up auto layout constraints so this view controller is applicable
// on any device in any rotation
NSDictionary *views = NSDictionaryOfVariableBindings(backgroundView);
NSArray *verticalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[backgroundView]-0-|"
options:0
metrics:nil
views:views];
NSArray *horizontalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[backgroundView]-0-|"
options:0
metrics:nil
views:views];
[self.view addConstraints:verticalConstraints];
[self.view addConstraints:horizontalConstraints];
}I'm not particularly a fan of setting up UI in code (especially auto
Code Snippets
- (void)viewDidLoad NS_REQUIRES_SUPER;- (void)viewDidLoad {
[super viewDidLoad];
// Set up image view
UIImage *backgroundImage = [UIImage imageNamed:@"background"];
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
[backgroundView setTranslatesAutoresizingMaskIntoConstraints:NO];
// aspect fill may be preferred
backgroundView.contentMode = UIViewContentModeScaleAspectFit;
// Add background view as back-most view
[self.view insertSubview:backgroundView atIndex:0];
// Set up auto layout constraints so this view controller is applicable
// on any device in any rotation
NSDictionary *views = NSDictionaryOfVariableBindings(backgroundView);
NSArray *verticalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[backgroundView]-0-|"
options:0
metrics:nil
views:views];
NSArray *horizontalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[backgroundView]-0-|"
options:0
metrics:nil
views:views];
[self.view addConstraints:verticalConstraints];
[self.view addConstraints:horizontalConstraints];
}Context
StackExchange Code Review Q#68861, answer score: 8
Revisions (0)
No revisions yet.