From 3949e937cc0c032965cbffa0c7b6c0f702f12e01 Mon Sep 17 00:00:00 2001 From: Vladimir Timofeev Date: Thu, 23 Aug 2018 11:58:55 -0700 Subject: [PATCH] Make SafeAreaView to work on iOS < 11 (#18534) Summary: Currently `SafeAreaView` works only on iOS 11, because implemented in terms of native safeArea API, that not exists in older iOS versions. But this make it hard to use the component in real applications, because content will be under top bars on older versions of iOS and no reliable way to workaround this in js. More motivation in #17638 This changeset emulate safe area in terms of `UIViewController` layout guides API if safeArea not available. Fixes #17638, #18255 I run RNTester with these simulators: iPhone6 (9.3), iPhone6 (10.0), iPhone6 (11.2), iPhoneX (11.2) - Start RNTester application - Look on top header, it should not overlap status bar - Go to the `` example, open modal - Modal area should not overlap status bar [IOS] [BUGFIX] [SafeAreaView] - Make SafeAreaView to work on iOS < 11 Pull Request resolved: https://github.com/facebook/react-native/pull/18534 Reviewed By: PeteTheHeat, shergin Differential Revision: D9166052 Pulled By: hramos fbshipit-source-id: c086e1ae4af13110a7453b770ca75b6e0d5321ea --- React/Views/SafeAreaView/RCTSafeAreaView.m | 44 ++++++++++++++++++---- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/React/Views/SafeAreaView/RCTSafeAreaView.m b/React/Views/SafeAreaView/RCTSafeAreaView.m index e4fe4b6d171..593210d75ca 100644 --- a/React/Views/SafeAreaView/RCTSafeAreaView.m +++ b/React/Views/SafeAreaView/RCTSafeAreaView.m @@ -29,8 +29,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder) RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) { return ABS(insets1.left - insets2.left) <= threshold && @@ -39,25 +37,57 @@ static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIE ABS(insets1.bottom - insets2.bottom) <= threshold; } +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ + - (void)safeAreaInsetsDidChange { if (![self respondsToSelector:@selector(safeAreaInsets)]) { return; } - UIEdgeInsets safeAreaInsets = self.safeAreaInsets; + [self setSafeAreaInsets:self.safeAreaInsets]; +} +#endif + +// Emulate safe area for iOS < 11 +- (void)layoutSubviews +{ + [super layoutSubviews]; + if ([self respondsToSelector:@selector(safeAreaInsets)]) { + return; + } + UIViewController* vc = self.reactViewController; + if (!vc) { + return; + } + CGFloat topLayoutOffset = vc.topLayoutGuide.length; + CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length; + CGRect safeArea = vc.view.bounds; + safeArea.origin.y += topLayoutOffset; + safeArea.size.height -= topLayoutOffset + bottomLayoutOffset; + CGRect localSafeArea = [vc.view convertRect:safeArea toView:self]; + UIEdgeInsets safeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0); + if (CGRectGetMinY(localSafeArea) > CGRectGetMinY(self.bounds)) { + safeAreaInsets.top = CGRectGetMinY(localSafeArea) - CGRectGetMinY(self.bounds); + } + if (CGRectGetMaxY(localSafeArea) < CGRectGetMaxY(self.bounds)) { + safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea); + } + + [self setSafeAreaInsets:safeAreaInsets]; +} + +- (void)setSafeAreaInsets:(UIEdgeInsets)safeAreaInsets +{ if (UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) { return; } _currentSafeAreaInsets = safeAreaInsets; - RCTSafeAreaViewLocalData *localData = - [[RCTSafeAreaViewLocalData alloc] initWithInsets:safeAreaInsets]; + RCTSafeAreaViewLocalData *localData = [[RCTSafeAreaViewLocalData alloc] initWithInsets:safeAreaInsets]; [_bridge.uiManager setLocalData:localData forView:self]; } -#endif - @end