RNCSafeAreaView.m 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Simplified version of https://github.com/facebook/react-native/blob/master/React/Views/SafeAreaView/RCTSafeAreaView.m
  2. #import "RNCSafeAreaView.h"
  3. #import <React/RCTBridge.h>
  4. #import <React/RCTUIManager.h>
  5. static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) {
  6. return
  7. ABS(insets1.left - insets2.left) <= threshold &&
  8. ABS(insets1.right - insets2.right) <= threshold &&
  9. ABS(insets1.top - insets2.top) <= threshold &&
  10. ABS(insets1.bottom - insets2.bottom) <= threshold;
  11. }
  12. @implementation RNCSafeAreaView
  13. {
  14. UIEdgeInsets _currentSafeAreaInsets;
  15. BOOL _initialInsetsSent;
  16. }
  17. - (BOOL)isSupportedByOS
  18. {
  19. return [self respondsToSelector:@selector(safeAreaInsets)];
  20. }
  21. - (UIEdgeInsets)realOrEmulateSafeAreaInsets
  22. {
  23. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  24. if (self.isSupportedByOS) {
  25. if (@available(iOS 11.0, *)) {
  26. return self.safeAreaInsets;
  27. }
  28. }
  29. #endif
  30. return self.emulatedSafeAreaInsets;
  31. }
  32. - (UIEdgeInsets)emulatedSafeAreaInsets
  33. {
  34. UIViewController* vc = self.reactViewController;
  35. if (!vc) {
  36. return UIEdgeInsetsZero;
  37. }
  38. CGFloat topLayoutOffset = vc.topLayoutGuide.length;
  39. CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length;
  40. CGRect safeArea = vc.view.bounds;
  41. safeArea.origin.y += topLayoutOffset;
  42. safeArea.size.height -= topLayoutOffset + bottomLayoutOffset;
  43. CGRect localSafeArea = [vc.view convertRect:safeArea toView:self];
  44. UIEdgeInsets safeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0);
  45. if (CGRectGetMinY(localSafeArea) > CGRectGetMinY(self.bounds)) {
  46. safeAreaInsets.top = CGRectGetMinY(localSafeArea) - CGRectGetMinY(self.bounds);
  47. }
  48. if (CGRectGetMaxY(localSafeArea) < CGRectGetMaxY(self.bounds)) {
  49. safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea);
  50. }
  51. return safeAreaInsets;
  52. }
  53. - (void)safeAreaInsetsDidChange
  54. {
  55. [self invalidateSafeAreaInsets];
  56. }
  57. - (void)invalidateSafeAreaInsets
  58. {
  59. // This gets called before the view size is set by react-native so
  60. // make sure to wait so we don't set wrong insets to JS.
  61. if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
  62. return;
  63. }
  64. UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets];
  65. if (_initialInsetsSent && UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) {
  66. return;
  67. }
  68. _initialInsetsSent = YES;
  69. _currentSafeAreaInsets = safeAreaInsets;
  70. self.onInsetsChange(@{
  71. @"insets": @{
  72. @"top": @(safeAreaInsets.top),
  73. @"right": @(safeAreaInsets.right),
  74. @"bottom": @(safeAreaInsets.bottom),
  75. @"left": @(safeAreaInsets.left),
  76. }
  77. });
  78. }
  79. - (void)layoutSubviews
  80. {
  81. [super layoutSubviews];
  82. [self invalidateSafeAreaInsets];
  83. }
  84. @end