Allow video on landscape with only-portrait app

2019-01-21 16:21发布

I have a UIWebView included in a UIViewController which is a descendant of UINavigationController. It looks like this:

Main view

The app is portrait only. When I play the video I want the user to be able to rotate the device and see the video in landscape mode. I use this code to allow it:

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    id presentedViewController = [self topMostController];
    NSString *className = presentedViewController ? NSStringFromClass([presentedViewController class]) : nil;

    if ([className isEqualToString:@"MPInlineVideoFullscreenViewController"] ||
        [className isEqualToString:@"MPMoviePlayerViewController"] ||
        [className isEqualToString:@"AVFullScreenViewController"]) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }

    return UIInterfaceOrientationMaskPortrait;
}

- (UIViewController *)topMostController {
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

And then in my UINavigationController (so when the video finishes the view is not presented in landscape but only in portrait):

- (BOOL)shouldAutorotate
{
    return NO;
}

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return UIInterfaceOrientationPortrait;
}

Everything works perfectly:

Video portrait Video landscape

But then the video is done playing (or the user taps ‘Done’) and the screens return to the underlying view, this is what happens:

Navigation bar issue

As you can see, the navigation bar slips under the status bar. Additionally, I get a lot of auto-layout errors in the logs: http://pastebin.com/09xHzmgJ

Any idea about how to solve this?

8条回答
再贱就再见
2楼-- · 2019-01-21 17:00

My answer on this question works great. Here It will play the video inside your WebView normally but if you tilt your phone it will play in Landscape!

Also important to note is if you include youtube.com as the Base URL it will load much quicker.

Make a UIWebView in your storyboard and connect the @property to it, then reference below.

    CGFloat width = self.webView.frame.size.height;
    CGFloat height = self.webView.frame.size.width;
    NSString *youTubeVideoCode = @"dQw4w9WgXcQ";
    NSString *embedHTML = @"<iframe width=\"%f\" height=\"%f\" src=\"http://www.youtube.com/embed/%@\" frameborder=\"0\" style=\"margin:-8px;padding:0;\" allowfullscreen></iframe>";
    NSString *html = [NSString stringWithFormat:embedHTML, width, height, youTubeVideoCode];
    self.webView.scrollView.bounces = NO;
    [self.webView loadHTMLString:html baseURL:[NSURL URLWithString:@"http://www.youtube.com"]];
查看更多
疯言疯语
3楼-- · 2019-01-21 17:02

I temporarily solved (through a hack) with the following code in the viewDidLoad of my controller. I have to specify that the code is specifically made for my case: since I explicitly disallow landscape orientation of my UINavigationController (see code above), the usual notification “UIDeviceOrientationDidChange” is not called when the playback finished and the window goes back to portrait. However, I hope there is a better option and this is a bug of the SDK, since it does not appear on iOS 7 and given the amount of auto-layout errors I get related to the video player (on which I have no control).

- (void)viewDidLoad
{
    [super viewDidLoad];

    // […]

     /* 
     Hack to fix navigation bar position/height on iOS 8 after closing fullscreen video

     Observe for “UIWindowDidRotateNotification” since “UIDeviceOrientationDidChangeNotification” is not called in the present conditions
     Check if the notification key (“UIWindowOldOrientationUserInfoKey”) in userInfo is either 3 or 4, which means the old orientation was landscape
     If so, correct the frame of the navigation bar to the proper size.

     */
    [[NSNotificationCenter defaultCenter] addObserverForName:@"UIWindowDidRotateNotification" object:nil queue:nil usingBlock:^(NSNotification *note) {
        if ([note.userInfo[@"UIWindowOldOrientationUserInfoKey"] intValue] >= 3) {
            self.navigationController.navigationBar.frame = (CGRect){0, 0, self.view.frame.size.width, 64};
        }
    }];
}

And then…

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:@"UIWindowDidRotateNotification"];
}
查看更多
We Are One
4楼-- · 2019-01-21 17:07

Swift version:

//AppDelegate:
    func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {

        var presentedVC = application.keyWindow?.rootViewController
        while let pVC = presentedVC?.presentedViewController
        {
            presentedVC = pVC
        }
        if let pVC = presentedVC
        {
            if contains(["MPInlineVideoFullscreenViewController", "MPMoviePlayerViewController", "AVFullScreenViewController"], pVC.nameOfClass)
            {
                return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
            }
        }

        return Int(UIInterfaceOrientationMask.Portrait.rawValue)
    }

//Extension:
public extension NSObject{
    public class var nameOfClass: String{
        return NSStringFromClass(self).componentsSeparatedByString(".").last!
    }

    public var nameOfClass: String{
        return NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
    }
}

//View controller:
    override func supportedInterfaceOrientations() -> Int {
        return Int(UIInterfaceOrientationMask.Portrait.rawValue)
    }

    override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
        return UIInterfaceOrientation.Portrait
    }

    override func shouldAutorotate() -> Bool {
        return false
    }

    override func viewWillLayoutSubviews() {
        UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: UIStatusBarAnimation.None)
    }
查看更多
闹够了就滚
5楼-- · 2019-01-21 17:15

On iOS 11 accepted solution didn't work for me. Seems that navigation bar stops reflecting for frame changes. But there is a workaround. At first, we need to modify supportedInterfaceOrientationsForWindow method to return UIInterfaceOrientationMaskLandscape for video controllers instead of UIInterfaceOrientationMaskAllButUpsideDown. In my case when I tap on embedded YouTube video, system always opens AVFullScreenViewController, so I removed other checks from original example. Code:

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    __kindof UIViewController *presentedViewController = [self topMostController];

    // Allow rotate videos
    NSString *className = presentedViewController ? NSStringFromClass([presentedViewController class]) : nil;
    if ([className isEqualToString:@"AVFullScreenViewController"]) {
        return UIInterfaceOrientationMaskLandscape;
    }

    return UIInterfaceOrientationMaskPortrait;
}

This didn't changes behaviour of AVFullScreenViewController on iOS 10 and less, but fixes navigation bar on iOS 11, so there is no need to update frame (also there is a side-effect on iOS 11 that video rotates from landscape when starts playing, but it's a tradeoff). Next, we need to add check in UIWindowDidBecomeHiddenNotification method:

- (void)videoDidExitFullscreen {
    if (@available(iOS 11, *)) {
        // Fixes status bar on iPhone X
        [self setNeedsStatusBarAppearanceUpdate];
    } else {
        self.navigationController.navigationBar.frame = CGRectMake(0, 0, self.view.bounds.size.width, statusAndNavBarHeight);
    }
}

Without setNeedsStatusBarAppearanceUpdate text in status bar will not appear on iPhone X, for other devices it's not needed.

查看更多
我只想做你的唯一
6楼-- · 2019-01-21 17:16

I was having the same issue and using @entropid solution I was able to fix the navigation bar problem. But my view remains below the nav bar which I fix using "sizeToFit".

[[NSNotificationCenter defaultCenter] addObserverForName:@"UIWindowDidRotateNotification" object:nil queue:nil usingBlock:^(NSNotification *note) {
    if ([note.userInfo[@"UIWindowOldOrientationUserInfoKey"] intValue] >= 3) {
        [self.navigationController.navigationBar sizeToFit];
        self.navigationController.navigationBar.frame = (CGRect){0, 0, self.view.frame.size.width, 64};
    }
}];

I haven't tested it properly but its working for me right now

查看更多
ら.Afraid
7楼-- · 2019-01-21 17:20

I encountered exactly the same issue and used the same code as @entropid did. However the accepted solution did not work for me.

It took me hours to come up with the following one-line fix that made things working for me:

- (void)viewWillLayoutSubviews {
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
}
查看更多
登录 后发表回答