1

Is there a way to enable Safe Areas programmatically?

Context: The app i'm working on is stuck to iOS 7 as target, and that probably won't change for a while. Xcode won't accept Safe Areas enabled unless i drop iOS 7 and 8 compatibility. The majority of views are XIBs. A conditionally-enabled safe area would be ideal.

jscs
  • 63,694
  • 13
  • 151
  • 195
baguIO
  • 391
  • 2
  • 14

1 Answers1

0

Conditional Safe Areas support for Xib

No, there is no such thing as multi-platforms Xib. You may attempt to duplicate your Xibs, build them as Nibs independently and access them conditionally at runtime depending on iOS version, but it's certainly not recommended to do! You'd have twice more Xibs and complexity to handle. It's much easier to put constraints to Superview and change their constants from code depending on safeAreaInsets.

Actual Safe Areas support for code

Similar situation as in Add iPhone X layout support to old Xcode 7 / Swift 2 project:

The only way to have Safe Areas support is to build with Xcode 9. So you must drop support for iOS 7 and Swift 2.

Once you're using Xcode 9, then you may have conditional code:

Swift

extension UIApplication {
    class var safeAreaInsets: UIEdgeInsets {
        if #available(iOS 11.0, tvOS 11.0, *) {
            return UIApplication.shared.delegate?.window??.safeAreaInsets ?? UIEdgeInsets.zero
        }
        return UIEdgeInsets.zero
    }
}

ObjC

@interface UIApplication (extension)
+ (UIEdgeInsets)safeAreaInsets;
@end
@implementation UIApplication (extension)
+ (UIEdgeInsets)safeAreaInsets {
    if (@available(iOS 11.0, tvOS 11.0, *)) {
        UIWindow *window = UIApplication.sharedApplication.delegate.window;
        return window != nil ? window.safeAreaInsets : UIEdgeInsetsZero;
    }
    return UIEdgeInsetsZero;
}
@end

Simulated Safe Areas support for code

Now, let's face it, there aren't that many different device shapes to support (only three cases: portrait with top notch, landscape with top notch, others). So you could build your own Safe Areas values based on the machine model and it may help you with older versions of Xcode:

extension UIApplication {
    /// on iPhone X+ portrait: top : 44.0, left : 0.0, bottom : 34.0, right : 0.0
    /// on iPhone X+ landscape: top : 0.0, left : 44.0, bottom : 21.0, right : 44.0
    /// on olders: top : 20.0, left : 0.0, bottom : 0.0, right : 0.0
    class var safeAreaInsetsSimulated: UIEdgeInsets {
        let model: String
        if TARGET_OS_SIMULATOR != 0 {
            model = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] ?? ""
        } else {
            var size = 0
            sysctlbyname("hw.machine", nil, &size, nil, 0)
            var machine = [CChar](repeating: 0, count: size)
            sysctlbyname("hw.machine", &machine, &size, nil, 0)
            model = String(cString: machine)
        }
        // will need adjustment in 2019
        if model == "iPhone10,3" || model == "iPhone10,6" || model.starts(with: "iPhone11,") {
            switch UIApplication.shared.statusBarOrientation {
            case .landscapeRight, .landscapeLeft:
                return UIEdgeInsets(top: 0, left: 44, bottom: 21, right: 44)
            default:
                return UIEdgeInsets(top: 44, left: 0, bottom: 34, right: 0)
            }
        }
        return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
    }
}

Note the that in first piece of code I return UIEdgeInsets.zero by default and in last piece of code I return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) by default: this is just a matter of choice depending on how you want to handle it, because Apple made a choice to redefine it between iOS 11 and iOS 12, causing much pain for compatibility.

Cœur
  • 37,241
  • 25
  • 195
  • 267