RNCSafeAreaView.m 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. CGRect _currentFrame;
  16. BOOL _initialInsetsSent;
  17. }
  18. - (BOOL)isSupportedByOS
  19. {
  20. return [self respondsToSelector:@selector(safeAreaInsets)];
  21. }
  22. - (UIEdgeInsets)realOrEmulateSafeAreaInsets
  23. {
  24. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  25. if (self.isSupportedByOS) {
  26. if (@available(iOS 11.0, *)) {
  27. return self.safeAreaInsets;
  28. }
  29. }
  30. #endif
  31. return self.emulatedSafeAreaInsets;
  32. }
  33. - (UIEdgeInsets)emulatedSafeAreaInsets
  34. {
  35. UIViewController* vc = self.reactViewController;
  36. if (!vc) {
  37. return UIEdgeInsetsZero;
  38. }
  39. CGFloat topLayoutOffset = vc.topLayoutGuide.length;
  40. CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length;
  41. CGRect safeArea = vc.view.bounds;
  42. safeArea.origin.y += topLayoutOffset;
  43. safeArea.size.height -= topLayoutOffset + bottomLayoutOffset;
  44. CGRect localSafeArea = [vc.view convertRect:safeArea toView:self];
  45. UIEdgeInsets safeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0);
  46. if (CGRectGetMinY(localSafeArea) > CGRectGetMinY(self.bounds)) {
  47. safeAreaInsets.top = CGRectGetMinY(localSafeArea) - CGRectGetMinY(self.bounds);
  48. }
  49. if (CGRectGetMaxY(localSafeArea) < CGRectGetMaxY(self.bounds)) {
  50. safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea);
  51. }
  52. return safeAreaInsets;
  53. }
  54. - (void)safeAreaInsetsDidChange
  55. {
  56. [self invalidateSafeAreaInsets];
  57. }
  58. - (void)invalidateSafeAreaInsets
  59. {
  60. // This gets called before the view size is set by react-native so
  61. // make sure to wait so we don't set wrong insets to JS.
  62. if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
  63. return;
  64. }
  65. UIEdgeInsets safeAreaInsets = [self realOrEmulateSafeAreaInsets];
  66. CGRect frame = [self convertRect:self.bounds toView:nil];
  67. if (
  68. _initialInsetsSent &&
  69. UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale()) &&
  70. CGRectEqualToRect(frame, _currentFrame)
  71. ) {
  72. return;
  73. }
  74. _initialInsetsSent = YES;
  75. _currentSafeAreaInsets = safeAreaInsets;
  76. _currentFrame = frame;
  77. self.onInsetsChange(@{
  78. @"insets": @{
  79. @"top": @(safeAreaInsets.top),
  80. @"right": @(safeAreaInsets.right),
  81. @"bottom": @(safeAreaInsets.bottom),
  82. @"left": @(safeAreaInsets.left),
  83. },
  84. @"frame": @{
  85. @"x": @(frame.origin.x),
  86. @"y": @(frame.origin.y),
  87. @"width": @(frame.size.width),
  88. @"height": @(frame.size.height),
  89. },
  90. });
  91. }
  92. - (void)layoutSubviews
  93. {
  94. [super layoutSubviews];
  95. [self invalidateSafeAreaInsets];
  96. }
  97. @end