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.