只使用竖屏应用程序如何允许横屏视频?

32

我在一个继承自UINavigationController的UIViewController中包含了一个UIWebView。它的样子是这样的:

主视图

该应用仅支持竖屏。当播放视频时,我希望用户能够旋转设备并在横屏模式下观看视频。我使用以下代码实现:

- (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;
}

然后在我的UINavigationController中(这样当视频结束时,视图不会以横向呈现,而是仅以纵向呈现):

- (BOOL)shouldAutorotate
{
    return NO;
}

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return UIInterfaceOrientationPortrait;
}

一切运作完美:

视频纵向 视频横向

但是当视频播放完毕(或用户点击“完成”)并且屏幕返回到底层视图时,会发生以下情况:

导航栏问题

如您所见,导航栏滑动到状态栏下面。 此外,日志中出现了很多自动布局错误:http://pastebin.com/09xHzmgJ

有什么解决方法吗?


你的项目有哪些设置(代码之外)? - Van Du Tran
@entropid 你是否曾经为此打开了一个雷达?在iOS 10上仍然存在问题... - Ell Neal
8个回答

17

我在我的控制器的viewDidLoad中使用以下代码(通过一个hack)暂时解决了问题。需要说明的是,该代码是专门针对我的情况而制定的:由于我明确禁止我的UINavigationController横向方向(请参见上面的代码),当播放结束并窗口返回到竖向方向时,通常的通知“UIDeviceOrientationDidChange”不会被调用。然而,我希望有更好的选择,因为这是SDK的一个错误,因为它在iOS 7上没有出现,并且与视频播放器相关的自动布局错误的数量很多(我无法控制)。

- (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};
        }
    }];
}

然后...

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:@"UIWindowDidRotateNotification"];
}

你也可以考虑子类化UINavigationBar并重写setCenter/setFrame/setBounds方法。当我需要精细控制时,我就这样做了。 - Brian Nickel
我会尝试,但它是一个UINavigationController,所以我不知道我能在导航栏上拥有多少控制权。此外,高度的变化是由一些奇怪的约束引起的,这些约束会破坏,所以我对此并不太确定。 - entropid

5

我遇到了完全相同的问题,并使用了与@entropid相同的代码。然而,被接受的解决方案对我并没有起作用。

花费了几个小时后,我才想出以下一行修复代码,使得事情对我起作用:

- (void)viewWillLayoutSubviews {
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
}

我很快就会尝试这个,如果它有效的话,我会将其接受为“解决方案”;这比我的hack好多了。 - entropid
3
是的,这应该是被接受的解决方案。已确认可以正常工作。 - Anthony Persaud
你是个天才。完美地运作了。 - adev

4

Swift版本:

//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)
    }

4

我昨天遇到了这个问题,@entropid的答案适用于iOS 9及以下版本,但对于iOS 10则不行(因为iOS 10实际上隐藏了状态栏,在iOS 9及以下版本中只是UINavigationBar改变了其框架而没有隐藏状态栏,因此会重叠在那个栏上)。

此外,订阅 MPMoviePlayerControllerDidExitFullScreen 通知也无效,有时它根本没有被调用(在我的特定情况下,它是来自UIWebView的视频,使用了一个看起来类似于MPMoviePlayerController的不同类的播放器)。

所以我采用了像 @StanislavPankevich 建议的解决方法,但我订阅了当 UIWindow 已经隐藏时的通知(可以在多种情况下使用,例如当视频完成、或者UIActivityViewController关闭等其他情况),而不是使用 viewWillLayoutSubviews。对于我特定的情况(UINavigationController的一个子类),像viewDidAppearviewWillAppear等方法根本没有被调用。

viewDidLoad

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(videoDidExitFullscreen:)
                                                 name:UIWindowDidBecomeHiddenNotification
                                               object:nil];

    // Some other logic...
}

dealloc

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

最后,videoDidExitFullscreen
- (void)videoDidExitFullscreen:(NSNotification *)notification {
    // You would want to check here if the window dismissed was actually a video one or not.

    [[UIApplication sharedApplication] setStatusBarHidden:NO
                                            withAnimation:UIStatusBarAnimationFade];

}

我使用了UIStatusBarAnimationFade,因为从用户的角度来看,它比UIStatusBarAnimationNone更加平滑。


1
很简单,就像@Stanislav Pankevich所说的那样,但是在Swift 3版本中。
override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews();
    UIApplication.shared.isStatusBarHidden = false
}

0

我遇到了同样的问题,使用@entropid的解决方案,我成功地解决了导航栏的问题。但是我的视图仍然在导航栏下面,我使用"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};
    }
}];

我还没有进行充分的测试,但目前它对我来说是有效的。


0

在iOS 11上,接受的解决方案对我不起作用。似乎导航栏停止反映框架变化。但是有一个解决方法。首先,我们需要修改supportedInterfaceOrientationsForWindow方法,以便为视频控制器返回UIInterfaceOrientationMaskLandscape,而不是UIInterfaceOrientationMaskAllButUpsideDown。在我的情况下,当我点击嵌入式YouTube视频时,系统总是打开AVFullScreenViewController,因此我从原始示例中删除了其他检查。代码:

- (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;
}

这不会改变 iOS 10 及以下版本上 AVFullScreenViewController 的行为,但会修复 iOS 11 上导航栏的问题,因此无需更新框架(同时在 iOS 11 上的副作用是视频在播放时从横屏旋转,但这是一种权衡)。接下来,我们需要在 UIWindowDidBecomeHiddenNotification 方法中添加检查:
- (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);
    }
}

没有使用setNeedsStatusBarAppearanceUpdate,在iPhone X上状态栏文本将不会显示,但对于其他设备则不需要。


-1

我在这个问题上的回答非常有效。这里它将在您的WebView中正常播放视频,但如果您倾斜手机,它将以横向播放!

还要注意的是,如果您将youtube.com作为基本URL包含在内,它将加载得更快。

在您的storyboard中创建一个UIWebView并将@property连接到它,然后参考下面的内容。

    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"]];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接