Why isn't a registered UIDeviceOrientationDidChangeNotification always called?
I wrote a simple view controller that shows a modal popup dialog. Because it creates a new window to lay over the entire screen, I've added a UIDeviceOrientationDidChangeNotification
observer that figures out how to rotate the view using CGAffineTransformMakeRotation()
. And it seems to work.
Mostly. The downside is that I can fiddle with the orientation and get it to end up sideways or upside down. Check out this 18s video demonstrating the issue. In debugging, it appears that sometimes the notification doesn't fire, or at least doesn't call the callback method that's listening for it.
I must be missing something, though, because if you look at the video again, you'll notice that the view behind it does rotate properly. That one is managed by -willAnimateRotationToInterfaceOrientation:duration:
. How is it that the iOS controller method is alway开发者_如何学运维s called properly (presumably managed by a UIDeviceOrientationDidChangeNotification
observer in UIViewController) but my own code is not?
I used UIApplicationWillChangeStatusBarOrientationNotification
in Kobo, which appears to do the trick. It's a lot more reliable, because you want to match the UI's chosen orientation, not necessarily where the device happens to be. In other words, you can let Apple decide when you should re-orient your window.
Here are a few snippets from the Kobo app's popup dialog code on the iPad (UIAlertView looks kinda dinky on iPad, so Richard Penner wrote a better one). After a few glitches with more recent iOS versions and having to display these dialogs in different situations (all around orientation IIRC), I had to tweak it to sit directly inside the UIWindow, which meant duplicating all the orientation transformation logic.
This code comes from a UIViewController whose view gets dropped straight onto the current window.
- (void) presentDialogWindow
{
// register for orientation change notification
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(orientationWillChange:)
name: UIApplicationWillChangeStatusBarOrientationNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(orientationDidChange:)
name: UIApplicationDidChangeStatusBarOrientationNotification
object: nil];
}
- (void) orientationWillChange: (NSNotification *) note
{
UIInterfaceOrientation current = [[UIApplication sharedApplication] statusBarOrientation];
UIInterfaceOrientation orientation = [[[note userInfo] objectForKey: UIApplicationStatusBarOrientationUserInfoKey] integerValue];
if ( [self shouldAutorotateToInterfaceOrientation: orientation] == NO )
return;
if ( current == orientation )
return;
// direction and angle
CGFloat angle = 0.0;
switch ( current )
{
case UIInterfaceOrientationPortrait:
{
switch ( orientation )
{
case UIInterfaceOrientationPortraitUpsideDown:
angle = (CGFloat)M_PI; // 180.0*M_PI/180.0 == M_PI
break;
case UIInterfaceOrientationLandscapeLeft:
angle = (CGFloat)(M_PI*-90.0)/180.0;
break;
case UIInterfaceOrientationLandscapeRight:
angle = (CGFloat)(M_PI*90.0)/180.0;
break;
default:
return;
}
break;
}
case UIInterfaceOrientationPortraitUpsideDown:
{
switch ( orientation )
{
case UIInterfaceOrientationPortrait:
angle = (CGFloat)M_PI; // 180.0*M_PI/180.0 == M_PI
break;
case UIInterfaceOrientationLandscapeLeft:
angle = (CGFloat)(M_PI*90.0)/180.0;
break;
case UIInterfaceOrientationLandscapeRight:
angle = (CGFloat)(M_PI*-90.0)/180.0;
break;
default:
return;
}
break;
}
case UIInterfaceOrientationLandscapeLeft:
{
switch ( orientation )
{
case UIInterfaceOrientationLandscapeRight:
angle = (CGFloat)M_PI; // 180.0*M_PI/180.0 == M_PI
break;
case UIInterfaceOrientationPortraitUpsideDown:
angle = (CGFloat)(M_PI*-90.0)/180.0;
break;
case UIInterfaceOrientationPortrait:
angle = (CGFloat)(M_PI*90.0)/180.0;
break;
default:
return;
}
break;
}
case UIInterfaceOrientationLandscapeRight:
{
switch ( orientation )
{
case UIInterfaceOrientationLandscapeLeft:
angle = (CGFloat)M_PI; // 180.0*M_PI/180.0 == M_PI
break;
case UIInterfaceOrientationPortrait:
angle = (CGFloat)(M_PI*-90.0)/180.0;
break;
case UIInterfaceOrientationPortraitUpsideDown:
angle = (CGFloat)(M_PI*90.0)/180.0;
break;
default:
return;
}
break;
}
}
CGAffineTransform rotation = CGAffineTransformMakeRotation( angle );
[UIView beginAnimations: @"" context: NULL];
[UIView setAnimationDuration: 0.4];
self.view.transform = CGAffineTransformConcat(self.view.transform, rotation);
[UIView commitAnimations];
}
- (void) orientationDidChange: (NSNotification *) note
{
UIInterfaceOrientation orientation = [[[note userInfo] objectForKey: UIApplicationStatusBarOrientationUserInfoKey] integerValue];
if ( [self shouldAutorotateToInterfaceOrientation: [[UIApplication sharedApplication] statusBarOrientation]] == NO )
return;
self.view.frame = [[UIScreen mainScreen] applicationFrame];
[self didRotateFromInterfaceOrientation: orientation];
}
We use it by loading this view controller, adding its view to a window, then calling -presentModalDialog, like so:
UIView *window = [[UIApplication sharedApplication] keyWindow];
[window addSubview: theController.view];
[theController presentDialogWindow];
I'm going to make a simple UIViewController subclass to implement the fiddly bits soon, and will post it on my github account. When I do, I'll edit this & link to it.
UIDeviceOrientation has a few values that don't map to UIInterfaceOrientation:
UIDeviceOrientationUnknown,
UIDeviceOrientationFaceUp, // Device oriented flat, face up
UIDeviceOrientationFaceDown // Device oriented flat, face down
Is it possible that the notification callback is firing, but these cases aren't handled?
If the view controller has access to the window - it can forward the didRotate callback to the window manually.
I fixed this problem by eliminating the use of UIDeviceOrientationDidChangeNotification
. What I do instead is create a subclass of UIViewController
and make set the custom view to its view. It then simply implements -willAnimateRotationToInterfaceOrientation:duration:
to do the rotation. This is actually less finicky code for me. It does feel like a hack, though, to create an arbitrary UIViewController
just to take advantage of autorotation in what is essentially a clone of presentModalViewController
. Seems like UIDeviceOrientationDidChangeNotification
ought to work properly. I expect there is a way to make it behave, but I've yet to find it. :-(
精彩评论