在iOS 9中检测应用程序是否在Slide Over或Split View模式下运行

51

在iOS 9中,是否可以检测应用程序是否运行在iOS 9的滑动重叠或分屏视图模式下?

我已经尝试阅读了苹果关于iOS 9多任务处理的文档,但是没有找到实现方法...

我提问这个问题是因为当应用程序在滑动重叠模式下打开时,我想禁用应用程序中的某个功能。

16个回答

41

只需检查您的窗口是否占据整个屏幕:

BOOL isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
如果这是假的,那么您正在拆分视图或幻灯片浏览中运行。
以下是代码片段,可以自动维护此标志,而不受旋转的影响。
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
 // simply create a property of 'BOOL' type
 isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
}

3
我刚刚尝试了一下,但并不总是能得到正确的结果。具体来说,在以下情况下存在问题:打开应用程序,按 Home 键,打开 Safari,从右侧滑动以打开分屏视图,选择我的应用程序。 window.frame 仍显示为1024x768,因此未被视为处于分屏视图中... - cleardemon
@cleardemon,你查询window.frame的应用程序生命周期的哪个部分? - Tamás Zahola
我正在应用程序委托中的激活方法(即applicationDidBecomeActive:)中查询此内容。您认为这样做太早了吗? - cleardemon
4
@cleardemon 我已使用测试应用程序进行了检查,在您的特定示例中,它确实首先调用具有全屏窗口的applicationDidBecomeActive:,然后调用根视图控制器上的滑动大小的 viewWillTransitionToSize:,然后在窗口上使用滑动大小调用setFrame:。因此,我建议要么重写根VC上的viewWillTransitionToSize:方法,要么重写窗口的setFrame:方法并适当地对传递的大小做出反应。请注意,在其他情况下,窗口框架已正确设置在applicationDidBecomeActive:中...奇怪。 - Tamás Zahola
是的,这很奇怪!但在 UIWindow 上覆盖 setFrame: 对我有用。感谢您的帮助 :) - cleardemon
对我来说,[UIApplication sharedApplication].delegate.window.frame和[UIApplication sharedApplication].delegate.window.screen.bounds给出了相同的值。当滑动悬停出现时,我正在使用UIApplicationWillResignActive的通知。请帮忙。 - Swayambhu

28

只是另一种重新打包所有这些的方式

extension UIApplication {
    public var isSplitOrSlideOver: Bool {
        guard let w = self.delegate?.window, let window = w else { return false }
        return !window.frame.equalTo(window.screen.bounds)
    }
}

那么你只需使用以下代码:

  • UIApplication.shared.isSplitOrSlideOver (Swift)
  • UIApplication.sharedApplication.isSplitOrSlideOver (Objective-C)

请注意,在Swift中,window对象是一个双重可选项... 令人困惑

对于iOS 13+(请注意,我尚未亲自测试过iOS 13代码)

extension UIApplication {

    public var isSplitOrSlideOver: Bool {
        guard let window = self.windows.filter({ $0.isKeyWindow }).first else { return false }
        return !(window.frame.width == window.screen.bounds.width)
    }
}

2
点赞了,但是我花了很长时间将它转换为Obj-c。这种代码让我担心,因为最终我可能被迫转移到Swift。 - amergin
1
@amergin 你本可以寻求帮助:在Objective-C中,由于没有可选项的问题,代码要简单得多。唯一奇怪的是你必须使用CGRectEqualsRect这个东西。所以大部分代码只是确保正确处理可选项。你也可以通过在Swift中使用!并做出一些真正的运行时假设来轻松处理所有这些。谢谢! - Dan Rosenstark
谢谢Dan。我应该花时间学习Swift,但是我一直很忙。这并不是对你的代码的批评,而是因为我的能力不足(也可能是一个老人在与不可避免的东西斗争)。 - amergin
1
将代码翻译成代码也可能是一种较难的学习方式。使用Swift构建某些东西——就像您最初可能在Objective-C中所做的那样——可能是更明智的选择。 - Dan Rosenstark

10

虽然我来晚了,但如果你想要一个独立于方向的属性,可以尝试这个:

extension UIApplication 
{
    func isRunningInFullScreen() -> Bool
    {
        if let w = self.keyWindow
        {
            let maxScreenSize = max(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height)
            let minScreenSize = min(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height)
            let maxAppSize = max(w.bounds.size.width, w.bounds.size.height)
            let minAppSize = min(w.bounds.size.width, w.bounds.size.height)
            return maxScreenSize == maxAppSize && minScreenSize == minAppSize
        }

        return true
    }
}

如果keyWindow是一个大小不同的自定义窗口,则此方法无法正常工作。 - slow

5

像Dan Rosenstark的解决方案一样,但改为适用于新iPad Pro,因为它们似乎会根据通过Xcode直接运行还是通过TestFlight或App Store编译和发布而报告不同的帧和screen.bounds高度。当通过AS或TF传递时,高度将返回980,而不是像在Xcode中一样应该返回1024,这使得无法返回true。

extension UIApplication {
    public var isSplitOrSlideOver: Bool {
        guard let w = self.delegate?.window, let window = w else { return false }
        return !(window.frame.width == window.screen.bounds.width)
    }
}

4

最近,我需要确定一个应用程序的显示样式,这不仅包括它是否改变为分屏视图或滑动视图,还包括屏幕的哪个部分被用于该应用程序(全屏、1/3、1/2、2/3)。将此功能添加到ViewController子类中可以解决该问题。

/// Dismisses this ViewController with animation from a modal state.
func dismissFormSheet () {
    dismissViewControllerAnimated(true, completion: nil)
}

private func deviceOrientation () -> UIDeviceOrientation {
    return UIDevice.currentDevice().orientation
}

private func getScreenSize () -> (description:String, size:CGRect) {
    let size = UIScreen.mainScreen().bounds
    let str = "SCREEN SIZE:\nwidth: \(size.width)\nheight: \(size.height)"
    return (str, size)
}

private func getApplicationSize () -> (description:String, size:CGRect) {
    let size = UIApplication.sharedApplication().windows[0].bounds
    let str = "\n\nAPPLICATION SIZE:\nwidth: \(size.width)\nheight: \(size.height)"
    return (str, size)
}


func respondToSizeChange (layoutStyle:LayoutStyle) {
    // Respond accordingly to the change in size.
}

enum LayoutStyle: String {
    case iPadFullscreen         = "iPad Full Screen"
    case iPadHalfScreen         = "iPad 1/2 Screen"
    case iPadTwoThirdScreeen    = "iPad 2/3 Screen"
    case iPadOneThirdScreen     = "iPad 1/3 Screen"
    case iPhoneFullScreen       = "iPhone"
}

private func determineLayout () -> LayoutStyle {
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
        return .iPhoneFullScreen
    }
    let screenSize = getScreenSize().size
    let appSize = getApplicationSize().size
    let screenWidth = screenSize.width
    let appWidth = appSize.width
    if screenSize == appSize {
        return .iPadFullscreen
    }

    // Set a range in case there is some mathematical inconsistency or other outside influence that results in the application width being less than exactly 1/3, 1/2 or 2/3.
    let lowRange = screenWidth - 15
    let highRange = screenWidth + 15

    if lowRange / 2 <= appWidth && appWidth <= highRange / 2 {
        return .iPadHalfScreen
    } else if appWidth <= highRange / 3 {
        return .iPadOneThirdScreen
    } else {
        return .iPadTwoThirdScreeen
    }

}

override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    respondToSizeChange(determineLayout())
}

1
在横屏模式下效果很好,但在竖屏模式下准确度不够。如果增加一个检查屏幕是否为竖屏的条件,并根据应用程序宽度是否小于或大于屏幕宽度的一半来计算1/3或2/3,这个答案会不会更好呢? - bcl

3

这是一种更简单、更健壮的方法,没有常量,在我开发的一个iPhone/iPad iOS应用中使用。

此代码还区分了Slide Over和Split View。

为了清晰起见,我在此返回String值,你也可以自由地使用枚举值并根据你的应用合并全屏的两种情况。

func windowMode() -> String {
  let screenRect = UIScreen.main.bounds
  let appRect = UIApplication.shared.windows[0].bounds

  if (UIDevice.current.userInterfaceIdiom == .phone) {
    return "iPhone fullscreen"
  } else if (screenRect == appRect) {
    return "iPad fullscreen"
  } else if (appRect.size.height < screenRect.size.height) {
    return "iPad slide over"
  } else {
    return "iPad split view"
  }
}

2

你可以观察-willTransitionToTraitCollection:withTransitionCoordinator:方法,它与大小类有关,以及viewWillTransitionToSize:withTransitionCoordinator:方法,它与你的视图大小(CGSize)有关。但是建议不要硬编码尺寸值。


我认为这不被称为Slide Over,因为应用程序视图没有被调整大小。至少在我使用的iOS 8.4 SDK构建的应用程序中不是这样的。 - arlomedia
这绝对是在iOS 9中调用拆分视图转换。 - shokaveli

1
extension UIApplication {
func isRunningInFullScreen() -> Bool {
    if let w = UIApplication.shared.connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .compactMap({$0 as? UIWindowScene})
        .first?.windows
        .filter({$0.isKeyWindow}).first {
            let maxScreenSize = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
            let minScreenSize = min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
            let maxAppSize = max(w.bounds.size.width, w.bounds.size.height)
            let minAppSize = min(w.bounds.size.width, w.bounds.size.height)
            return maxScreenSize == maxAppSize && minScreenSize == minAppSize
    }
    return true
    }
}

以下是那些不想看到Swift Lint关于弃用keyWindow的投诉的代码:

1

当处于滑动或33%分屏模式时,水平尺寸类将变为紧凑型。但我认为一旦进入50%或66%模式,您就无法检测到了。


谢谢,但是我原本希望的是有一个官方 API,可能是我错过了。看来是没有的... - Matias Korhonen

1
我对@Michael Voccola的解决方案进行了编辑,解决了方向问题。
在我的情况下,我使用了这种方法来检测所有iPad分屏状态并处理布局。只需调用determineLayout()即可获取当前的layoutStyle
private func getScreenSize() -> CGRect { 
     let size = UIScreen.main.bounds
     return size
}

private func getApplicationSize() -> CGRect {
    let size = UIApplication.shared.windows[0].bounds
    return size
}

enum LayoutStyle: String {
    case iPadFullscreen = "iPad Full Screen"
    case iPadHalfScreen = "iPad 1/2 Screen"
    case iPadTwoThirdScreeen = "iPad 2/3 Screen"
    case iPadOneThirdScreen = "iPad 1/3 Screen"
    case iPhoneFullScreen = "iPhone"
}

func determineLayout() -> LayoutStyle {

    if UIDevice.current.userInterfaceIdiom == .phone {
        return .iPhoneFullScreen
    }

    let screenSize = getScreenSize().size
    let appSize = getApplicationSize().size
    let screenWidth = screenSize.width
    let appWidth = appSize.width

    if screenSize == appSize {
//       full screen
         return .iPadFullscreen
    }

    let persent = CGFloat(appWidth / screenWidth) * 100.0

    if persent <= 55.0 && persent >= 45.0 {
//      The view persent between 45-55 that's mean it's half screen
        return .iPadHalfScreen
    } else if persent > 55.0 {
//      more than 55% that's mean it's 2/3
        return .iPadTwoThirdScreeen
    } else {
//      less than 45% it's 1/3
        return .iPadOneThirdScreen
    }
}

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