objective-cxcodeios12iphone-x

Identify iPhones with Notch Programmatically


I am trying to identify iPhones with the notch programmatically. I am aware of the screen.size method, but when you have a universal app supporting all interface orientations, it becomes a mess (counting all the possible variations). So I am looking for a simpler and more elegant way to detect the newer iPhones X models.

I have stumbled upon a method online that is supposed to work. In it, you measure the bottom inset of the safe area and if it is not zero, you have an iPhone X model. This makes sense in theory since, the safe area does not reach the bottom of the screen on the iPhone X handsets but does on all other devices. I do the check this way:

if (@available( iOS 11.0, * )) {
    if ([[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0) {
        // iPhone with notch
    }
} else {
    // Regular iPhone
}

This, however, does not work. Can someone point out my mistake in the implementation or confirm if this method is even viable?


Solution

  • I had to dig around quite a bit but found the answer (I'd like to shout out to user6788419 whose right answer was berried deep in another thread).

    First of all, the above code is correct. Checking if the safe area's bottom inset is larger than zero will accurately identify iPhones with a notch (as of this writing). So, this is correct:

    if (@available( iOS 11.0, * )) {
        if ([[[UIApplication sharedApplication] keyWindow] safeAreaInsets].bottom > 0) {
            // iPhone with notch
        }
    }
    

    However, it matters where you put the above statement in your code because UIWindow is not available until the first run loop has concluded. That means, if you check the notch in your viewDidLoad or before it concluded (in your init for example), the bottom inset will always be zero.

    If you, similarly to me, need this check to setup your main view, you can just move all your setup into separate function (such as postViewDidLoad) and call it once viewDidLoad has concluded:

    [self performSelector:@selector(postViewDidLoad) withObject:nil afterDelay:0.0f];
    

    Or, alternatively, you enclose it:

    dispatch_async(dispatch_get_main_queue(), ^{
        // Check notch here
    });
    

    UPDATE: code for iOS 13 and up (does the same thing). "areaPosition" is a string argument when you call the function. "top" checks for the notch, everything else checks for the presence of the bottom home indicator.

    - (UIWindow *) keyWindow {
        UIWindow *foundWindow   = nil;
        NSArray *windows        = [[UIApplication sharedApplication]windows];
        for (UIWindow *window in windows) {
            if (window.isKeyWindow) {
                foundWindow     = window;
                break;
            }
        }
        return foundWindow;
    }
    
    - (BOOL) checkSafeArea:(NSString *)areaPosition {
       if (@available(iOS 13.0, *)) {
           if ([areaPosition isEqualToString:@"top"]) {
               return [self keyWindow].safeAreaInsets.top > 20.0f;
           } else {
               return [self keyWindow].safeAreaInsets.bottom > 0.0f;
           }
       } else {
           if ([areaPosition isEqualToString:@"top"]) {
               return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top > 20.0f;
           } else {
               return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.bottom > 0.0f;
           }
       }
       return  NO;
    }