I'm working on an open source library to provide an iMessage-like text input view - MessageComposerView (relevant to question). The view will stick to the keyboard like an inputAccessoryView
and grow with text, but won't disappear when the keyboard does.
I have recently run into a maddening issue when rotating this custom UIView that only appears if the view has been instantiated via initWithFrame
. Basically at the point when a UIDeviceOrientationDidChangeNotification notification has been caught if the view has been instantiated via initWithFrame
, the UIInterfaceOrientation and frame have NOT yet been updated. Here are both ways instantiating.
loadNibNamed
:
self.messageComposerView = [[NSBundle mainBundle] loadNibNamed:@"MessageComposerView" owner:nil options:nil][0];
self.messageComposerView.frame = CGRectMake(0,
self.view.frame.size.height - self.messageComposerView.frame.size.height,
self.messageComposerView.frame.size.width,
self.messageComposerView.frame.size.height);
initWithFrame
:
self.messageComposerView = [[MessageComposerView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height-54, 320, 54)];
When init via loadNibNamed
and rotated to landscape, upon receiving a UIDeviceOrientationDidChangeNotification
notification:
UIDeviceOrientation
= [[notification object] orientation] = UIInterfaceOrientationLandscapeLeftUIInterfaceOrientation
= [UIApplication sharedApplication].statusBarOrientation = UIDeviceOrientationLandscapeRightself.frame.size
= (width=480, height=90)
Important to note here is that both the interface and device orientations are landscape and the width has already been changed to landscape width (testing on iPhone 6.1 simulator). Now performing the same test but using initWithFrame
:
UIDeviceOrientation
= [[notification object] orientation] = UIDeviceOrientationLandscapeRightUIInterfaceOrientation
= [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationPortraitself.frame.size
= (width=320, height=54)
This time notice that the interface orientation is still portrait and that the frame width has NOT changed to landscape width. If I set a breakpoint in the setFrame:(CGRect)frame
method I can see that the frame is set to the Landscape frame AFTER the UIDeviceOrientationDidChangeNotification has been caught and handled.
Both init methods have almost identical setup:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.frame = frame;
self.sendButton = [[UIButton alloc] initWithFrame:CGRectZero];
self.messageTextView = [[UITextView alloc] initWithFrame:CGRectZero];
[self setup];
[self addSubview:self.sendButton];
[self addSubview:self.messageTextView];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
With setup
doing necessary view tweaks:
- (void)setup {
self.backgroundColor = [UIColor lightGrayColor];
self.autoresizesSubviews = YES;
self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth;
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
CGRect sendButtonFrame = self.bounds;
sendButtonFrame.size.width = 50;
sendButtonFrame.size.height = 34;
sendButtonFrame.origin.x = self.frame.size.width - kComposerBackgroundRightPadding - sendButtonFrame.size.width;
sendButtonFrame.origin.y = kComposerBackgroundRightPadding;
self.sendButton.frame = sendButtonFrame;
self.sendButton.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin;
self.sendButton.layer.cornerRadius = 5;
[self.sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self.sendButton setTitleColor:[UIColor colorWithRed:210/255.0 green:210/255.0 blue:210/255.0 alpha:1.0] forState:UIControlStateHighlighted];
[self.sendButton setTitleColor:[UIColor grayColor] forState:UIControlStateSelected];
[self.sendButton setBackgroundColor:[UIColor orangeColor]];
[self.sendButton setTitle:@"Send" forState:UIControlStateNormal];
self.sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:14];
CGRect messageTextViewFrame = self.bounds;
messageTextViewFrame.origin.x = kComposerBackgroundLeftPadding;
messageTextViewFrame.origin.y = kComposerBackgroundTopPadding;
messageTextViewFrame.size.width = self.frame.size.width - kComposerBackgroundLeftPadding - kComposerTextViewButtonBetweenPadding - sendButtonFrame.size.width - kComposerBackgroundRightPadding;
messageTextViewFrame.size.height = 34;
self.messageTextView.frame = messageTextViewFrame;
self.messageTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
self.messageTextView.showsHorizontalScrollIndicator = NO;
self.messageTextView.layer.cornerRadius = 5;
self.messageTextView.font = [UIFont systemFontOfSize:14];
self.messageTextView.delegate = self;
[self addNotifications];
[self resizeTextViewForText:@"" animated:NO];
}
So my question is, why does my custom UIView init via initWithFrame
still have the portrait frame and interface orientation and the time when the UIDeviceOrientationDidChangeNotification is received. I use this notification to do my frame adjustment and need the width to have already been updated to the landscape width.
I'm hoping there is some kind of autorotation property that I'm missing in the programmatical setup that is buried somewhere in the XIB but just can't find it. I've gone through probably a dozen UIDeviceOrientationDidChangeNotification
stackoverflow posts without finding a solution.
self.frame = frame;
line in your initialization code. The call tosuper
should do that already. – Michael Enriquez