I was able to solve this problem after setting the anchor point for my toolbar from the default of (.5, .5) to (0, 0).
// The toolbar is strong since this controller must maintain ownership as the toolbar is removed from the parent view when this view disappears.
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar;
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
// Set anchor point to the top-left corner instead of to the center (.5, .5) so that affine transformations during orientation changes are easier to calculate.
self.toolbar.layer.anchorPoint = CGPointMake(0, 0);
CGFloat height = CGRectGetHeight(self.tabBarController.tabBar.frame); // Completely cover the tab bar.
CGFloat x = window.frame.origin.x;
CGFloat y = window.frame.size.height - height;
CGFloat width = window.frame.size.width;
self.toolbar.frame = CGRectMake(x, y, width, height);
[window addSubview:self.toolbar];
Here's the corrected code where the translation parameters tx
and ty
are calculated as a function of the toolbar and window sizes rather than being hard-coded. I wrapped the logic in the new willRotateView:toInterfaceOrientation:duration:
function to make it easier to reuse and rotate an arbitrary view.
willRotateToInterfaceOrientation:duration:
just forwards the rotation request on to this function with my toolbar as the view parameter.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self willRotateView:self.toolbar toInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void)willRotateView:(UIView *)view toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
{
// First, apply identify transformation to simplify later calculations and also because we need the view's frame and Apple's docs say you cannot use a view's frame if the transform property is not the identity matrix.
view.transform = CGAffineTransformIdentity;
// Calculate affine parameters.
// Get the window's post-orientation change dimensions.
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGFloat rotatedWinWidth = window.frame.size.height;
CGFloat rotatedWinHeight = window.frame.size.width;
// Expand width to the window's height when in landscape mode, leaving the view's height unchanged. The scaling is done along the Y axis since the window's origin will change such that the Y axis will still run along the longer direction of the device.
CGFloat sx = 1.0;
CGFloat sy = rotatedWinWidth / rotatedWinHeight;
// Rotate 90 degrees in either direction depending on the orientation direction change.
CGFloat angle = M_PI_2;
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
angle = -M_PI_2;
}
// Reposition the view, assuming that view.layer.anchorPoint is (0, 0).
// Note that the height of the view is used as the X offset as this corresponds to the X direction since the rotated window's origin will also rotate. Also, the position has to take into account the width scale factor.
CGFloat xOffset = view.frame.size.height;
CGFloat tx = -(rotatedWinWidth - xOffset) / sy;
CGFloat ty = -view.frame.size.height;
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
tx = -xOffset / sy;
ty = rotatedWinHeight - view.frame.size.height;
}
// Apply affine transformation.
CGAffineTransform transform = CGAffineTransformMakeScale(sx, sy);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformTranslate(transform, tx, ty);
[UIView animateWithDuration:duration
animations:^{
view.transform = transform;
}
completion:NULL
];
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
{
[UIView animateWithDuration:duration
animations:^{
view.transform = CGAffineTransformIdentity;
}completion:NULL
];
}
else if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
DLog(@"Upside-down orientation not supported");
}
}