The problem here is that my UIView
(master) does not layout it's subviews automatically when the device rotates and the "springs & struts" layout method used to position the image and interior UIView
was inefficient. I solved the problem by doing two things.
- I got rid of the internal
UIView
(child) instance, leaving only theUIView
(master) and inside of that aUILabel
andUIImageView
. - I then created a
UIView
subclass calledStatusView
and in it I implement thelayoutSubviews
method. In its constructor I add aUIImageView
andUILabel
and position them dynamically. TheUILabel
is positioned first based on the size of the text and then theUIImageView
is placed just to the left of it and vertically centered. That's it. InlayoutSubviews
I ensure that the positions of the elements are adjusted for the new frame.
Additionally, since I need to swap the background, message and possibly the image in some circumstances, it made sense to go with a custom class. There may be memory issues here/there but I'll iron them out when I run through this with the profiling tool.
Finally, I'm not totally certain if this code is rock solid but it does work. I don't know if I need the layout code in my init method, either. Layout subviews seems to be called shortly after the view is added as a subview.
Here's my class header:
#import <UIKit/UIKit.h>
typedef enum {
StatusViewRecordCountType = 0,
StatusViewReachedMaxRecordCountType = 1,
StatusViewZoomInType = 2,
StatusViewConnectionLostType = 3,
StatusViewConnectionFoundType = 4,
StatusViewNoDataFoundType = 5,
StatusViewGeographyIntersectionsType = 6,
StatusViewRetreivingRecordsType = 7
} StatusViewType;
@interface StatusView : UIView
@property (strong, nonatomic) NSString *statusMessage;
@property (nonatomic) StatusViewType statusViewType;
- (id)initWithFrame:(CGRect)frame message:(NSString*)message type:(StatusViewType)type;
@end
... and implementation:
#import "StatusView.h"
#define kConstrainSizeWidthOffset 10
#define kImageBufferWidth 15
@interface StatusView ()
@property (nonatomic, strong) UILabel *statusMessageLabel;
@property (nonatomic, strong) UIFont *statusMessageFont;
@property (nonatomic, strong) UIImage *statusImage;
@property (nonatomic, strong) UIImageView *statusImageView;
@end
@implementation StatusView
@synthesize statusMessageLabel = _statusMessageLabel;
@synthesize statusMessageFont = _statusMessageFont;
@synthesize statusImageView = _statusImageView;
@synthesize statusMessage = _statusMessage;
@synthesize statusViewType = _statusViewType;
@synthesize statusImage = _statusImage;
- (id)initWithFrame:(CGRect)frame message:(NSString *)message type:(StatusViewType)type {
self = [super initWithFrame:frame];
if (self) {
if (message != nil) {
_statusMessage = message;
_statusMessageFont = [UIFont fontWithName:@"Avenir-Roman" size:15.0];
CGSize constrainSize = CGSizeMake(self.frame.size.width - kImageBufferWidth - kConstrainSizeWidthOffset, self.frame.size.height);
// Find the size appropriate for this message
CGSize messageSize = [_statusMessage sizeWithFont:_statusMessageFont constrainedToSize:constrainSize];
// Create label and position at center of status view
CGRect labelFrame = CGRectMake(self.frame.origin.x,
self.frame.origin.y,
messageSize.width,
messageSize.height);
_statusMessageLabel = [[UILabel alloc] initWithFrame:labelFrame];
_statusMessageLabel.backgroundColor = [UIColor clearColor];
_statusMessageLabel.textColor = [UIColor whiteColor];
_statusMessageLabel.font = _statusMessageFont;
// Set shadow and color
_statusMessageLabel.shadowOffset = CGSizeMake(0, 1);
_statusMessageLabel.shadowColor = [UIColor blackColor];
// Center the label
CGPoint centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
_statusMessageLabel.center = centerPoint;
// Gets rid of fuzziness
_statusMessageLabel.frame = CGRectIntegral(_statusMessageLabel.frame);
// Flex both the width and height as well as left and right margins
_statusMessageLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
// Set label text
_statusMessageLabel.text = _statusMessage;
[self addSubview:_statusMessageLabel];
}
self.statusViewType = type;
if (_statusImage != nil) {
// Create image view
_statusImageView = [[UIImageView alloc] initWithImage:_statusImage];
// Vertically center the image
CGPoint centerPoint = CGPointMake(_statusMessageLabel.frame.origin.x - kImageBufferWidth,
self.frame.size.height / 2);
_statusImageView.center = centerPoint;
[self addSubview:_statusImageView];
}
}
return self;
}
- (void)layoutSubviews {
CGSize constrainSize = CGSizeMake(self.frame.size.width - kImageBufferWidth - kConstrainSizeWidthOffset, self.frame.size.height);
// Find the size appropriate for this message
CGSize messageSize = [_statusMessage sizeWithFont:_statusMessageFont constrainedToSize:constrainSize];
// Create label and position at center of status view
CGRect labelFrame = CGRectMake(self.frame.origin.x,
self.frame.origin.y,
messageSize.width,
messageSize.height);
_statusMessageLabel.frame = labelFrame;
// Center the label
CGPoint centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
_statusMessageLabel.center = centerPoint;
// Gets rid of fuzziness
_statusMessageLabel.frame = CGRectIntegral(_statusMessageLabel.frame);
if (_statusImageView != nil) {
// Vertically center the image
CGPoint centerPoint = CGPointMake(_statusMessageLabel.frame.origin.x - kImageBufferWidth,
self.frame.size.height / 2);
_statusImageView.center = centerPoint;
}
}
#pragma mark - Custom setters
- (void)setStatusMessage:(NSString *)message {
if (_statusMessage == message) return;
_statusMessage = message;
_statusMessageLabel.text = _statusMessage;
// Force layout of subviews
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (void)setStatusViewType:(StatusViewType)statusViewType {
_statusViewType = statusViewType;
UIColor *bgColor = nil;
switch (_statusViewType) {
// Changes background and image based on type
}
self.backgroundColor = bgColor;
if (_statusImageView != nil) {
_statusImageView.image = _statusImage;
}
}
@end
Then in my view controller I can do this:
CGRect statusFrame = CGRectMake(self.mapView.frame.origin.x,
self.mapView.frame.origin.y,
self.mapView.frame.size.width,
kStatusViewHeight);
self.staticStatusView = [[StatusView alloc] initWithFrame:statusFrame message:@"600 records found :)" type:StatusViewRecordCountType];
self.staticStatusView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.view addSubview:self.staticStatusView];
... and later on I can change it up by doing this:
self.staticStatusView.statusMessage = @"No data was found here";
self.staticStatusView.statusViewType = StatusViewNoDataFoundType;
Now I've got a reusable class rather than 12 UIView instances floating around my NIB with various settings and properties.