-
-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useSafeAreaInsets emits too many results during device rotation #172
Comments
Android Galaxy S8
|
Hey! Where did you log this from? There is code that is supposed to prevent extra renders if the value did not change. Maybe something else could cause the extra logs? Could you try logging here https://github.com/th3rdwave/react-native-safe-area-context/blob/master/src/SafeAreaContext.tsx#L69? Could also be caused by nested safe area views, maybe post a simplified version of how you are using the library. |
@janicduplessis sure I will check it |
Having the same issue here. Did anyone find a solution yet? |
1 similar comment
Having the same issue here. Did anyone find a solution yet? |
In my case, I just made native module for me. This module reduces some ignorable event emission but not 100% SafeArea.swift@objc(SafeAreaModule)
class SafeAreaModule: RCTEventEmitter{
static var shared: SafeAreaModule? = nil
static func viewSafeAreaInsetsDidChange(){
guard let shared = SafeAreaModule.shared else { return }
guard shared.getWindow() != nil else { return }
shared.sendEvent(shared.getSafeAreaInsets())
}
// MARK: - RCTEventEmitter
override func supportedEvents() -> [String]! {
return ["viewSafeAreaInsetsDidChange"]
}
private func sendEvent(_ insets: Dictionary<String, Int>){
guard bridge != nil else { return }
self.sendEvent(withName: "viewSafeAreaInsetsDidChange", body: insets)
}
@objc
override func constantsToExport() -> [AnyHashable: Any]!{
return [:]
}
@objc
override static func requiresMainQueueSetup() -> Bool {
return true
}
override init() {
super.init()
SafeAreaModule.shared = self
}
@objc func getSafeAreaInsets(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
DispatchQueue.main.async { [weak self] in
guard let self = self else {
reject("self not found", "self not found", nil)
return
}
resolve(self.getSafeAreaInsets())
}
}
private func getSafeAreaInsets() -> Dictionary<String, Int>{
var ret = ["top": 0, "bottom": 0, "left": 0, "right": 0]
let window = getWindow()
if let insets = window?.safeAreaInsets{
ret["top"] = Int(insets.top)
ret["bottom"] = Int(insets.bottom)
ret["left"] = Int(insets.left)
ret["right"] = Int(insets.right)
}
return ret
}
private func getWindow() -> UIWindow?{
if #available(iOS 13.0, *) {
return UIApplication.shared.windows[0]
}else{
return UIApplication.shared.keyWindow
}
}
} MainViewController.swiftimport UIKit
class MainViewController : UIViewController{}
// MARK: -Lifecycle
extension MainViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewSafeAreaInsetsDidChange() {
SafeAreaModule.viewSafeAreaInsetsDidChange()
}
override func viewDidLayoutSubviews() {
}
}
Appdelegate.m@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG && TARGET_OS_SIMULATOR
InitializeFlipper(application);
#endif
...
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"MathKing"
initialProperties:appProperties];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [MainViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
} Modules.m@interface RCT_EXTERN_MODULE(SafeAreaModule, RCTEventEmitter)
RCT_EXTERN_METHOD(
getSafeAreaInsets: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
@end SafeAreaModuleIOS.tsimport { NativeEventEmitter, NativeModules } from 'react-native';
import { EdgeInsets } from 'react-native-safe-area-context';
const COMPONENT_NAME = 'SafeAreaModule';
const NativeSafeAreaModule = NativeModules[COMPONENT_NAME];
const EventEmitter = new NativeEventEmitter(NativeSafeAreaModule);
type Listener = (insets: EdgeInsets) => void;
class SafeAreaModuleIOS {
private subscription;
private listeners: Listener[] = [];
constructor() {
this.subscription = EventEmitter.addListener('viewSafeAreaInsetsDidChange', (event: EdgeInsets) => {
this.listeners.forEach((listener: Listener) => {
listener(event);
});
});
}
addListener(listener: Listener) {
this.listeners.push(listener);
}
removeListener(listener: Listener) {
const idx = this.listeners.indexOf(listener);
if (idx === -1) {
return;
}
this.listeners.splice(idx, 1);
}
async getSafeAreaInsets(): Promise<EdgeInsets> {
return NativeSafeAreaModule.getSafeAreaInsets();
}
}
export default new SafeAreaModuleIOS(); useSafeAreaInsets.ios.tsimport { useCallback, useEffect, useState } from 'react';
import { EdgeInsets } from 'react-native-safe-area-context';
import SafeAreaModuleIOS from './SafeAreaModuleIOS';
import { SentryWrapper } from '../log/SentryWrapper';
import { useMount } from '../../hooks/useMount';
export default function useSafeAreaInsets() {
const [insets, setInsets] = useState<EdgeInsets>({ top: 0, bottom: 0, left: 0, right: 0 });
const listener = useCallback((insets: EdgeInsets) => {
setInsets(insets);
}, []);
useMount(() => {
SafeAreaModuleIOS.getSafeAreaInsets()
.then(setInsets)
.catch((e) => {
SentryWrapper.throw('safe area not found', e);
});
});
useEffect(() => {
SafeAreaModuleIOS.addListener(listener);
return () => {
SafeAreaModuleIOS.removeListener(listener);
};
}, [listener]);
return insets;
} |
Does anyone have any other ideas on how to avoid this? |
a custom debounced useSafeAreaInsets hook works nicely: import {useEffect, useState} from 'react';
import {useWindowDimensions} from 'react-native';
import {
EdgeInsets,
initialWindowMetrics,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
const _debounceDelay = 200; // 200ms
export const useDebouncedSafeAreaInsets = () => {
const _insets = useSafeAreaInsets();
const [insets, setInsets] = useState<EdgeInsets>(_insets);
useEffect(() => {
let timer: number | null = null;
timer = setTimeout(() => {
setInsets(_insets);
}, _debounceDelay);
return () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
}, [_insets]);
return {...insets};
};
|
Thank you for the inspiration. But didn't work for me because the nested
Feel free to further optimize it :) |
Not sure if this helps the issue, but here are the logs added to When rotation there seems to trigger a "intermediate" stage with only the
|
Because of the device performance issue, the repeated emission of the same insets or useless emission from
useSafeAreaInsets
should be skipped.When I rotated my device with
iPhone X
, the console printed out like the following.AS-IS
The 6 emissions of the processing are useless and cause performance issues because every layout of components is re-calculated with each inset values.
TO-BE
The text was updated successfully, but these errors were encountered: