react-native icon indicating copy to clipboard operation
react-native copied to clipboard

useWindowDimensions() returns swapped height and width in iOS

Open vicary opened this issue 5 years ago • 17 comments
trafficstars

Description

When tested with iPad simulator, the useWindowDimensions(...) hook returns incorrect width and height.

React Native version:

Run react-native info in your terminal and copy the results here.

info Fetching system and libraries information...
System:
    OS: macOS 10.15.5
    CPU: (4) x64 Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
    Memory: 24.79 MB / 8.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 12.18.0 - /var/folders/k_/twn4fnrn6g56kn0xjlkp3zx00000gn/T/yarn--1594122179123-0.9086048405049534/node
    Yarn: 1.22.4 - /var/folders/k_/twn4fnrn6g56kn0xjlkp3zx00000gn/T/yarn--1594122179123-0.9086048405049534/yarn
    npm: 6.14.5 - /usr/local/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.9.3 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.5, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
    Android SDK:
      API Levels: 23, 26, 28, 29
      Build Tools: 27.0.3, 28.0.3, 29.0.2, 30.0.0
      System Images: android-29 | Google Play Intel x86 Atom
      Android NDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6514223
    Xcode: 11.5/11E608c - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_242-release - /usr/bin/javac
    Python: 2.7.16 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: 16.11.0 => 16.11.0 
    react-native: ^0.62.2 => 0.62.2 
  npmGlobalPackages:
    *react-native*: Not Found
✨  Done in 11.68s.

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Use react-native init to create a new project.
  2. Edit App.js with the following code
    import React from "react";
    import { Text, View, useWindowDimensions } from "react-native";
    
    export const App = () => {
      const { width, height } = useWindowDimensions();
    
      return (
        <View
          style={{
            width: "100%",
            height: "100%",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <Text>{width > height ? "landscape" : "portrait"}</Text>
          <Text>{`width: ${width}, height: ${height}`}</Text>
        </View>
      );
    };
    

Expected Results

  1. At the first time I rotate the simulator to landscape, it should display "landscape" instead of nothing happens.
  2. At the second time the simulator rotates back to portrait position, it should render "portrait" on the screen.
  3. At the second time I rotate from portrait to landscape, it should display "landscape" instead of "portrait".

Snack, code example, screenshot, or link to a repository:

It is not easy to show device orientation features in snack, I'll attach screen recordings below.

Screen Recording 2020-07-07 at 7 39 07 PM

You may download the original video to pause for the rendered text. Screen Recording 2020-07-07 at 7.39.07 PM.zip

vicary avatar Jul 07 '20 12:07 vicary

Is it that useWindowDimensions is returning the wrong numbers, or is the component not rerendering in response to the changes in orientation? Have you validated that it's running (with a log statement or whatever) and reporting the wrong numbers? Have you tried this on Android?

chrisglein avatar Jul 07 '20 18:07 chrisglein

@chrisglein Android is working fine.

iOS is behaving strangely, not a simple unresponsive but kind of a “delayed” state.

  1. You start the app in portrait
  2. The first time it changes from portrait to landscape, nothing happens
  3. You rotate back to portrait
  4. It now returns landscape (width > height)
  5. Now you rotate to landscape again
  6. It now returns portrait (width < height)

Since step 3 the behaviour repeats, only by re-launching the app could you reproduce step 1 and 2.

vicary avatar Jul 08 '20 04:07 vicary

Adding to the context, the above behavior is also confirmed with console logs.

vicary avatar Jul 09 '20 18:07 vicary

This is problem too. The solution i did was to listen to view onlayoutevent to get the right width and height(which you store on state)

For orientation, i used react native locker and do Orientatio.addListener and store the result in state too.

Yeah unfortunately handling those is sometimes feels too manual

silencer07 avatar Jul 15 '20 21:07 silencer07

Only workaround is to work with custom native modules.

Even DeviceInfo uses the same logic for landscape detection than native values, the potential affected users is sizeable in that sense.

vicary avatar Jul 16 '20 11:07 vicary

I also noticed that the sizes are swapped for a very short amount of time when the iPad is unlocked and the app was still active. Though, I am not dealing with with Dimensions directly but instead have a root view that has an onLayout handler:

import React, { useCallback } from 'react'
import { LayoutChangeEvent, StyleSheet, View } from 'react-native'
import { useDebouncedCallback } from '../../../hooks'

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})

let i = 0
const MeasureView: React.FC = (props) => {
  const { children } = props

  // Create handlers
  const [setDimensions] = useDebouncedCallback(
    (width: number, height: number) => {
      alert('setDimensions ' + i + ': width=' + width)
      i++
    },
    500
  )
  const handleLayout = useCallback(
    (event: LayoutChangeEvent) => {
      const { width, height } = event.nativeEvent.layout

      alert('handleLayout ' + i + ': width=' + width)
      i++
      setDimensions(width, height)
    },
    [setDimensions]
  )

  return (
    <View style={styles.container} onLayout={handleLayout}>
      {children}
    </View>
  )
}

export default MeasureView

The output is:

handleLayout 0: width=1024
setDimensions 1: width=1024
(lock iPad, unlock iPad)
handleLayout 2: width=768
handleLayout 3: width=1024
setDimensions 4: width=1024

jaulz avatar Sep 22 '20 06:09 jaulz

@jaulz your issue may not be the same as this one, but the snippet could be a workaround of mine though!

vicary avatar Sep 22 '20 19:09 vicary

@vicary in fact this was my approach as a workaround but as you can see it doesn't solve the dimensions issue but just uses a debounce to avoid it. The original issue that dimensions are swapped is still a problem.

jaulz avatar Sep 23 '20 05:09 jaulz

I'm seeing similar problems on Android, except that after the 1st rotation, the values seem to be stuck on whatever the 2nd state was (e.g. if I started in portrait mode, after I rotate to landscape the width and height returned by Dimensions.get are pegged to landscape. If I started in landscape, after going to portrait mode Dimensions.get will always return portrait dimensions.)

tylerc avatar Nov 10 '20 23:11 tylerc

I am curious on how Facebook addresses this kind of issues, is it supposed to be fixed by the community via PR or shall we wait?

vicary avatar Nov 11 '20 16:11 vicary

I only have this issue with Android. If device is in portrait and I rotate it to landscape and quickly back to portrait the width and height numbers returns swap. This hurts my layout as my layout depends on these numbers

elmcapp avatar Mar 18 '22 14:03 elmcapp

Android landscape, fullscreen without system's Status Bar or Navigation Bar. https://github.com/facebook/react-native/issues/33735

// Scenario 01
// I noticed that the "red rectangle" had a kind of vertical margin,

const {width, height} = useWindowDimensions();
<View style={{position: 'absolute', backgroundColor: 'red', width: width, height: height}}/>

// Scenario 02 
// Solved it using the height as "100%"

<View style={{position: 'absolute', backgroundColor: 'red', width: '100%', height: '100%'}}/>
Screen Shot 2022-04-29 at 18 01 00 Screen Shot 2022-04-29 at 18 00 39

gomes042 avatar Apr 29 '22 21:04 gomes042

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar May 27 '23 05:05 github-actions[bot]

This issue is still valid to this date.

If any anyone is interested and want more information to start working, please let me know.

vicary avatar May 27 '23 07:05 vicary

I'm also getting incorrect info from useWindowDimensions().

I got here trying to reorient between portrait and landscape view for my app.

I'm unable to get the fire() function of Dimensions.addEventListener("change", () => fire()) to invoke/execute no matter how hard I rotate/turn the device.

In the process of trouble shooting I tested to see what the width/height would be in both landscape and portrait orientation by started the app while holding the phone in that orientation.

Both output the following to the console LOG {"height": 926, "width": 428} regardless of which way the app is started(device held in landscape or portrait orientation).

Device Info

$ react-native info                                                                                                                                                                                                                   [11:37:07]
warn Package @sentry/react-native contains invalid configuration: "dependency.platforms.ios.sharedLibraries" is not allowed,"dependency.hooks" is not allowed. Please verify it's properly linked using "react-native config" command and contact the package maintainers about this.
info Fetching system and libraries information...
System:
    OS: macOS 13.3.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 82.44 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.15.0 - ~/.volta/tools/image/node/18.15.0/bin/node
    Yarn: 3.5.0 - ~/.volta/tools/image/yarn/3.5.0/bin/yarn
    npm: 9.5.0 - ~/.volta/tools/image/node/18.15.0/bin/npm
    Watchman: 2023.05.15.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.12.1 - /Users/loi/.rvm/gems/ruby-3.0.0/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4
    Android SDK: Not Found
  IDEs:
    Android Studio: 2022.2 AI-222.4459.24.2221.9862592
    Xcode: 14.3/14E222b - /usr/bin/xcodebuild
  Languages:
    Java: javac 20 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.2.0 => 18.2.0 
    react-native: 0.71.6 => 0.71.6 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Example 1

import React, { useCallback } from 'react'
import { StyleSheet, View } from 'react-native'

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})

const App = (props) => {
  const { children } = props

  const handleLayout = useCallback(
    (event) => {
      const { width, height } = event.nativeEvent.layout
      console.log({width, height});
    },
    []
  )

  return (
    <View style={styles.container} onLayout={handleLayout}>
      {children}
    </View>
  )
}

export default App

Example 2

import React, { useEffect } from "react";

import {
  Text,
  View,
  StyleSheet,
  Dimensions,
  useWindowDimensions,
} from "react-native";

const App = ({children}) => {
  const { height, width } = useWindowDimensions();
  
  useEffect(() => {
    const subscription = Dimensions.addEventListener(
      "change",
      ({ window, screen }) => {
        console.log('Hi');
        console.log({ window, screen });
      }
    );
    return () => subscription?.remove();
  }, []);

  console.log({ height, width, foo: "bar" });

  return (
    <View style={styles.container}>
      <Text>Hi</Text>
      {children}
      <Text>Hi</Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "white"
  },
  header: {
    fontSize: 20,
    marginBottom: 12,
  },
});

export default App;

Please advise~! Appreciate your help and thanks in advance!

PrimeTimeTran avatar Jun 05 '23 15:06 PrimeTimeTran

Hello, i am experiencing a similar issue on Ipad ... "useWindowDimensions" is not returning new screen size values after the first 90deg device rotation. With the second rotation and any following, the hook is returning new values but swapped (incorrect). My suspicion is, that this is maybe related to the Scene Logic, we have implemented with Carplay. Did you have a similar core refactoring? Greets, Robert

zerobertelprivat avatar Jun 26 '24 11:06 zerobertelprivat

We experienced same issues with swapped widths and heights on iOS devices. We are running React Native code in a custom UIWindow where we noticed that the bounds for that UIWindow do not change. This could probably be an iOS bug?

We then proceeded to use Dimensions.get("screen") as screen size gets updated properly.

daholino avatar Jun 27 '24 13:06 daholino

I've also noticed this issue. Both Dimensions.get('window') and useWindowDimensions doesn't handle it properly, and as I can see it return previous values even if called multiple time after rotate is done... I found closed issue from 2016 that shows up the same problem. Does anyone know any way to handle it, maybe some custom hook?

gerwld avatar Sep 23 '24 10:09 gerwld

@gerwld A simple custom hook is not enough. It's likely caused by a delayed orientation change event, and in turn lead to a stale state returned by Dimensions.get('window') and useWindowDimensions.

You may try building a custom Native Module that directly listens to the orientation change event.

vicary avatar Sep 23 '24 11:09 vicary

+1 This also happens in my app, it messes up the layout heavily as it is dependent on screen size

girlboss-energy avatar Oct 10 '24 10:10 girlboss-energy

+1

scottsoif avatar Oct 11 '24 16:10 scottsoif

+1

Dylan0115 avatar Nov 13 '24 17:11 Dylan0115

+1000

philipheinser avatar Dec 02 '24 16:12 philipheinser

Any update on this one? It also happens in my project in iPad.

HeavenMin avatar Dec 04 '24 10:12 HeavenMin

Any update on this one? It also happens in my project in iPad.

Might be this: https://github.com/facebook/react-native/pull/46353

jameswilddev avatar Dec 30 '24 11:12 jameswilddev

I may not be testing this in the near future, not before expo picks it up and we plan to upgrade some of our projects.

If anyone is going to testing this in the upcoming RC, please post your succesful story here so I can close this issue.

vicary avatar Dec 31 '24 21:12 vicary

I found a workaround by detecting a change in orientation (which seems to fire correctly using expo-screen-orientation or some other library) and swapping the height/width values if they're incorrect https://gist.github.com/petrbela/d859308b22a09b4233fd72e26f2f9725

petrbela avatar Jan 17 '25 17:01 petrbela

Hi , So what is the solution ?this issue is open from 2020 and no solution ? ? @petrbela you got any method ?

vishnuc avatar Feb 19 '25 13:02 vishnuc

hello any solution ?

vishnuc avatar Mar 01 '25 13:03 vishnuc

If anyone is experiencing this, here's the patch my team used for react-native 0.76.5

diff --git a/node_modules/react-native/React/CoreModules/RCTDeviceInfo.mm b/node_modules/react-native/React/CoreModules/RCTDeviceInfo.mm
index 454a325..018b2f3 100644
--- a/node_modules/react-native/React/CoreModules/RCTDeviceInfo.mm
+++ b/node_modules/react-native/React/CoreModules/RCTDeviceInfo.mm
@@ -54,6 +54,11 @@ - (void)initialize

   _currentInterfaceDimensions = [self _exportedDimensions];

+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(interfaceOrientationDidChange)
+                                                 name:UIApplicationDidChangeStatusBarOrientationNotification
+                                               object:nil];
+
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(interfaceOrientationDidChange)
                                                name:UIApplicationDidBecomeActiveNotification

This was removed somewhere in between 0.73.6 and 0.76.5. UIApplicationDidChangeStatusBarOrientationNotification is deprecated on iOS, but in the meantime, this works!

gregbrinker avatar Mar 07 '25 22:03 gregbrinker