51

In iOS 9, is it possible to detect when an app is running in iOS 9's Slide Over or Split View mode?

I've tried reading through Apple's documentation on iOS 9 multitasking, but haven't had any luck with this…

I ask because I might have a feature in my app that I'd like to disable when the app is opened in a Slide Over.

Matias Korhonen
  • 886
  • 2
  • 8
  • 21

16 Answers16

41

Just check if your window occupies the whole screen:

BOOL isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);

If this is false, then you're running in a split view or a slide over.

Here is the code snipped which will automatically maintain this flag irrespective of rotation

-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
 // simply create a property of 'BOOL' type
 isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
}
Sam92
  • 45
  • 4
Tamás Zahola
  • 9,271
  • 4
  • 34
  • 46
  • 3
    I tried this just now and don't always get the right results. Specifically in this scenario: start app, press home, open Safari, slide from right edge to open split view, select my app. The `window.frame` still shows as 1024x768 and thus is not treated as being in split view... – cleardemon Oct 29 '15 at 10:52
  • @cleardemon which part of the application lifecycle are you querying `window.frame`? – Tamás Zahola Oct 29 '15 at 12:25
  • I am querying this in my application delegate on activation (so `applicationDidBecomeActive:`). Do you think this is too early? – cleardemon Oct 29 '15 at 13:20
  • 4
    @cleardemon I've checked it with a test app and in your specific example it indeed calls `applicationDidBecomeActive:` first with a fullscreen window, then calls `viewWillTransitionToSize:` with the slideover size on the root viewcontroller, then calls `setFrame:` on the window with the slideover size. So I would recommend either overriding `viewWillTransitionToSize:` on the root VC, or the window's `setFrame:` and react to the sizes passed in appropriately. Note that in other cases the window frame is already set correctly in `applicationDidBecomeActive:`... Strange. – Tamás Zahola Oct 29 '15 at 14:56
  • Yup, it is strange! But overriding `setFrame:` on the UIWindow worked for me. Thanks for the help :) – cleardemon Oct 29 '15 at 16:37
  • For me both the [UIApplication sharedApplication].delegate.window.frame and [UIApplication sharedApplication].delegate.window.screen.bounds) giving same value. I am using a notification of UIApplicationWillResignActive when the slideover comes into picture. Please help – Swayambhu Nov 03 '16 at 11:41
27

Just another way to repackage all of this

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

then you can just

  • UIApplication.shared.isSplitOrSlideOver in Swift
  • UIApplication.sharedApplication.isSplitOrSlideOver in Objective-C

Note that, in Swift, the window object is a double optional... WTF!

For iOS 13+ (note, I haven't tested the iOS 13 code myself yet)

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)
    }
}
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 2
    Upvoted but had to spend ages converting this to Obj-c. This is the sort of code that makes me scared for when I am eventually forced to move to Swift. – amergin Jan 20 '17 at 14:43
  • 1
    @amergin you could've asked for help: in Objective-C, because there's no issue with optionals, the code is MUCH simpler. The only weird thing is that you have to use the `CGRectEqualsRect` stuff. So most of this code is just making sure to handle optionals correctly. You could just as easily handle all of this by using `!` in Swift and making some real runtime assumptions. Thx! – Dan Rosenstark Jan 23 '17 at 23:30
  • Thanks Dan. I should just put in the time and learn Swift but I've been too busy. It wasn't a criticism of your code that it took me ages, more my lack of ability (and probably an old guy fighting the inevitable as well) – amergin Jan 24 '17 at 00:19
  • 1
    Translating code to code is also probably a hard a way to learn. Building something in Swift -- just like you probably did in Objective-C originally -- might be a more sensible way to go. – Dan Rosenstark Jan 24 '17 at 06:08
10

I'm late to the party, but if you want a property that works independent of the orientation, try this one:

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
    }
}
the Reverend
  • 12,305
  • 10
  • 66
  • 121
lars
  • 239
  • 2
  • 9
4

I recently had to determine display style of an application based including, not only if it changed to split view or slide-over, but also what portion of the screen was being utilized for the application (full, 1/3, 1/2, 2/3). Adding this to a ViewController subclass was able to solve the issue.

/// 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())
}
Michael Voccola
  • 1,827
  • 6
  • 20
  • 46
  • 1
    Works great for landscape but not so accurate in portrait I found. Could this answer be improved a little by adding a check if the screen is portrait and, if so, calculate 1/3 or 2/3 depending of if the app is less than or greater than half the screen width? – bcl Jan 29 '19 at 17:57
4

Like the solution by Dan Rosenstark, but changed to work on the new iPad Pro's that seem to report a different frame and screen.bounds height based on if it's ran directly on the device through Xcode, or if it is compiled and released through TestFlight or App Store. The height would return 980 when through AS or TF, rather than 1024 as it was supposed to like through Xcode causing it to be impossible to return 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)
    }
}
eskimo
  • 304
  • 1
  • 3
  • 15
3

Here is a simpler and less fragile way with no constants, that I use in an iPhone/iPad iOS app.

This code also distinguishes between slide over and split view.

I'm returning String values here for clarity, feel free to use enum values and to merge the two cases of fullscreen as suits your app.

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"
  }
}
w0mbat
  • 2,430
  • 1
  • 15
  • 16
2

You can watch both -willTransitionToTraitCollection:withTransitionCoordinator: for the size class and viewWillTransitionToSize:withTransitionCoordinator: for the CGSize of your view. Hardcoding in size values isn't recommended though.

stevethomp
  • 106
  • 6
  • I don't think this is called with Slide Over because the app views are not resized. At least it wasn't for me with an app built with the iOS 8.4 SDK. – arlomedia Aug 12 '15 at 22:49
  • This is definitely called on a split view transition in iOS 9. – shokaveli Sep 25 '15 at 20:51
1

The horizontal size class will be compact when in slide over or 33% split view. I don't think you can detect once you go to 50% or 66% though.

jrturton
  • 118,105
  • 32
  • 252
  • 268
1

I made an edit to @Michael Voccola solution which fixed the problem for orientation

I used this way in my situation to detect all iPad split screen state and handling layout Just call determineLayout() to get current 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
    }
}
Faisal Alneela
  • 166
  • 2
  • 8
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
    }
}

here is the code for those who don't want to see swift lint complaints for deprecated keyWindow

timetraveler90
  • 336
  • 2
  • 16
0

There is project on GitHub AbsoluteFrameDemo which uses AppAbsoluteFrame module. You can use it to detect absolute frame of your app. Also you can add this extension to get application multitasking mode:

extension AppAbsoluteFrame {
    enum AppMode {
        case fullscreen, slideOver, splitScreen
        init(frame: CGRect, screenBounds: CGRect) {
            let appHasSameHeight = frame.height == screenBounds.height
            let appHasSameWidth = frame.width == screenBounds.width
            switch (appHasSameHeight, appHasSameWidth) {
            case (true, true):
                self = .fullscreen
            case (true, false):
                self = .splitScreen
            case (false, _):
                self = .slideOver
            }
        }
    }
    var appMode: AppMode {
        AppMode(frame: frame, screenBounds: screenBounds)
    }
}

The module needs UIWindowScene to identify the exact window and its mode.

Misha
  • 1
  • 3
-1

After much 'tinkering', I have found a solution for my App that may work for you:

In AppDelegate.swift, create the following variable:

var slideOverActive: Bool = false

Then, in ALL of your view controllers, add the UIApplicationDelegate to the Class definition, create an appDelegate variable, and then add the below traitCollectionDidChange function:

class myViewController: UIViewController, UIApplicationDelegate {

var appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {

        let screenWidth = UIScreen.mainScreen().bounds.width

        if previousTraitCollection != nil {
            let horizontalSizeClass: Int = previousTraitCollection!.horizontalSizeClass.rawValue

            if screenWidth == 1024 || screenWidth == 768 { // iPad

                if horizontalSizeClass == 2 { // Slide Over is ACTIVE!

                    appDelegate.slideOverActive = true

                } else {

                    appDelegate.slideOverActive = false

                }
            }
        }
    }

}

Then, wherever in your code you wish to check whether the slide-over is active or not, simply check:

if appDelegate.slideOverActive == true {

    // DO THIS

} else {

    // DO THIS

}

It's a bit of a workaround, but it works for me at the moment.

Happy trails!

AT3D
  • 857
  • 1
  • 8
  • 13
-1

Adding to @Tamas's answer:
Here is the code snippet that will automatically maintain this flag irrespective of rotation.

 -(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
 // simply create a property of 'BOOL' type
 isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
}
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Sam92
  • 45
  • 4
-1

Trying [UIScreen mainScreen].bounds, self.window.screen.bounds, self.window.frame, UIApplication.sharedApplication.keyWindow.frame and so on, the only working solution was deprecated method

CGRect frame = [UIScreen mainScreen].applicationFrame;

Which I fixed this way

CGRect frame = [UIScreen mainScreen].applicationFrame;
frame = CGRectMake(0, 0, frame.size.width + frame.origin.x, frame.size.height + frame.origin.y);
self.window.frame = frame;
FunkyKat
  • 3,233
  • 1
  • 23
  • 20
-2

And I'm really late to the party! But nonetheless, here's a simple, swifty solution to the problem. Using let width = UIScreen.mainScreen().applicationFrame.size.width we can detect the width of my app's window, and then have things occur when it is smaller than a certain number (i.e. on iPhone screen or in split view), useful to make different things happen on smaller screens. To have the computer check the width over and over again, we can run an NSTimer every hundredth of a second, then do stuff if the width is higher/lower than something.

Some measurements for you (you have to decide what width to make stuff occur above/below): iPhone 6S Plus: 414.0mm
iPhone 6S: 375.0mm
iPhone 5S: 320.0mm
iPad (portrait): 768.0mm
iPad (1/3 split view): 320.0mm
iPad Air 2 (1/2 split view): 507.0mm
iPad (landscape): 1024.0mm

Here's a code snippet:

class ViewController: UIViewController {

var widthtimer = NSTimer()

func checkwidth() {

var width = UIScreen.mainScreen().applicationFrame.size.width

if width < 507 { // The code inside this if statement will occur if the width is below 507.0mm (on portrait iPhones and in iPad 1/3 split view only). Use the measurements provided in the Stack Overflow answer above to determine at what width to have this occur.

    // do the thing that happens in split view
    textlabel.hidden = false

} else if width > 506 {

    // undo the thing that happens in split view when return to full-screen
    textlabel.hidden = true

}
}


override func viewDidAppear(animated: Bool) {

widthtimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "checkwidth", userInfo: nil, repeats: true) 
// runs every hundredth of a second to call the checkwidth function, to check the width of the window.

}

override func viewDidDisappear(animated: Bool) {
widthtimer.invalidate()
}

}

I hope this can help anyone who comes peeking!

owlswipe
  • 19,159
  • 9
  • 37
  • 82
  • Creating a timer to watch for size changes is incredibly wasteful. In your case, you'd be much better off overriding viewWillLayoutSubviews (don't forget to call super) and updating the hidden property there. – Ryan Pendleton Jun 24 '20 at 21:12
-2

By using below code you can check splitViewController is Collapsed or Not

if splitViewController?.isCollapsed ?? false {
    // splitview collapsed
} else {
    // splitview not collapsed
 }
Ashish
  • 706
  • 8
  • 22
  • 1
    The question is about using "two apps at the same time with Split View" https://support.apple.com/en-us/HT207582 — not `UISplitViewController`. – PDK Apr 21 '20 at 15:36