question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Navigator] width is not updated when orientation is changed, so switch-animation stops halfway and component vanishes

See original GitHub issue

I found a bug when you create a simple app that uses only the <Navigator /> component with iOS when you change the orientation from portrait to landscape. You will see that the back-animation (when switching states) keeps it old target-x-position to animate to.

out

I expect that the back-animation will stop at the far end/border of the landscape-screen (“outside” the view).

While trying to set the width/height to the view & navigator, which not solved it, I suspect that because it only happens in landscape mode the width of the navigator view is not updated when the orientation changes so the old target-value (x-position) is used to animate the old state to.

You can test it following these steps:

  • react-native init ORIENTATION
  • copy this example into index.ios.js

(will fail for React-Native 0.7.1 & 0.8.0-rc.2)

Anyone have an idea where to look? I am not an native iOS developer, but any tips/fixes are welcome.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:11 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
grittathhcommented, Mar 7, 2016

I managed to hack together something that works. Basically I pass in the width and the height from the parent view of the Navigator using the style property. Then, in Navigator.js, I removed the maincontainer style, and replaced all instances of SCREEN_WIDTH, SCREEN_HEIGHT, SCENE_DISABLED_NATIVE_PROPS, and disabledScene, with equivalent objects containing props.style.width and props.style.height.

Props.style.width and props.style.height are set in the parent view (in my case, index.js) and get reset by onLayout.

This completely doesn’t address SceneConfig issues, but I’ll live with this for now.

1reaction
antseburovacommented, Sep 14, 2016

There is a way to get it working properly without a need to change the Navigator’s source code. It doesn’t look very nice, but, in my opinion, it’s not a bad temporary workaround.

So, there are two issues which you need to deal with.

Proper resizing of scene views

In order to get your scenes properly resized after a device orientation has been changed, you can create custom scene configurations, where you could use a proper app layout width. Once your device orientation is changed, you can reset every scene with a new route stack and render the scenes using updated scene configs.

To do so, you can copy all of the styles you need for your scene transitions from the NavigatorSceneConfigs.js file (https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js), paste them into your custom scene configs, and replace all of the SCREEN_WIDTH and Dimensions.get('window').width occurrences with a passed width value. I preferred to keep all of the custom scene config related stuff in a separate file. You may have something like this if you would like to use HorizontalSwipeJump config option:

import {Navigator, PixelRatio} from 'react-native';
import buildStyleInterpolator from 'buildStyleInterpolator';

export default function(appLayoutWidth) {
  const BaseConfig = Navigator.SceneConfigs.HorizontalSwipeJump;
  const windowWidth = appLayoutWidth;
  const stillCompletionRatio = 1 / 10;

  const FromTheRight = {
    opacity: {
      value: 1.0,
      type: 'constant',
    },

    transformTranslate: {
      from: {x: appLayoutWidth, y: 0, z: 0},
      to: {x: 0, y: 0, z: 0},
      min: 0,
      max: 1,
      type: 'linear',
      extrapolate: true,
      round: PixelRatio.get(),
    },

    translateX: {
      from: appLayoutWidth,
      to: 0,
      min: 0,
      max: 1,
      type: 'linear',
      extrapolate: true,
      round: PixelRatio.get(),
    },

    scaleX: {
      value: 1,
      type: 'constant',
    },
    scaleY: {
      value: 1,
      type: 'constant',
    },
  };

  const ToTheLeft = {
    transformTranslate: {
      from: {x: 0, y: 0, z: 0},
      to: {x: -appLayoutWidth, y: 0, z: 0},
      min: 0,
      max: 1,
      type: 'linear',
      extrapolate: true,
      round: PixelRatio.get(),
    },
    opacity: {
      value: 1.0,
      type: 'constant',
    },

    translateX: {
      from: 0,
      to: -appLayoutWidth,
      min: 0,
      max: 1,
      type: 'linear',
      extrapolate: true,
      round: PixelRatio.get(),
    },
  };

  const CustomSceneConfig = {
    ...BaseConfig,
    gestures: {
      jumpBack: {
        ...BaseConfig.gestures.jumpBack,
        edgeHitWidth: windowWidth,
        stillCompletionRatio: stillCompletionRatio,
      },

      jumpForward: {
        ...BaseConfig.gestures.jumpForward,
        edgeHitWidth: windowWidth,
        stillCompletionRatio: stillCompletionRatio,
      },
    },

    animationInterpolators: {
      into: buildStyleInterpolator(FromTheRight),
      out: buildStyleInterpolator(ToTheLeft),
    },
  };

  return CustomSceneConfig;
}

Then, you need to call the specified above function within the configureScene property of your navigator:

<Navigator 
  configureScene={() => customNavigatorSceneConfig(this.props.appLayout.width)} 
  ref="navigator" 
/>

this.props.appLayout.width should contain a relevant value of your layout width.

If you use Redux to update the layout width, you can catch the moment of an orientation being changed and use the following code to reset every scene with a new stack of routes. In the following example, just one scene is added to the stack:

componentWillReceiveProps(nextProps) {
    if (this.props.appLayout.width !== nextProps.appLayout.width) {
      this.refs.navigator.immediatelyResetRouteStack([currentRoute]);
    }
  }

Next, we’ll fix the second issue which relates to styling disabled scenes.

Applying proper styles to disabled scenes

If you have a few routes in your stack, you will probably notice a rendering issue when you switch from a landscape view to portrait. A part of your next scene will be visible at the bottom of your current scene. The issue occurs due to the fact that all of the scenes, except for the current one, are hidden with CSS (styles.disabledScene, https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/Navigator.js#L114) which uses a layout height, that was evaluated just once in Navigator.js The indicated CSS is used within the _renderScene function. So, you may consider overriding it with your code which uses an up-to-date layout height. To do so, you can extend the Navigator component with your own:

import {Navigator} from 'react-native';
import sceneNavigatorOverride from '../lib/sceneNavigatorOverride';

export default class extends Navigator {

  constructor(props) {
    super(props);
    this._renderScene = sceneNavigatorOverride;
  }
}

Within your sceneNavigatorOverride.js file, you will need to have something like this:

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

export default function(route, i) {
  let disabledSceneStyle = null;
  let disabledScenePointerEvents = 'auto';
  if (i !== this.state.presentedIndex) {
    disabledSceneStyle = {
      top: this.props.appLayout.height * 2,
      bottom: -this.props.appLayout.height * 2,
    };
    disabledScenePointerEvents = 'none';
  }

  return (
    <View
      key={'scene_' + _getRouteID(route)}
      ref={'scene_' + i}
      onStartShouldSetResponderCapture={() => {
        return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null);
      }}
      pointerEvents={disabledScenePointerEvents}
      style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
      {this.props.renderScene(
        route,
        this
      )}
    </View>
  );
}

function _getRouteID(route) {
  if (route === null || typeof route !== 'object') {
    return String(route);
  }

  const key = '__navigatorRouteID';

  if (!route.hasOwnProperty(key)) {
    Object.defineProperty(route, key, {
      enumerable: false,
      configurable: false,
      writable: false,
      value: getuid(),
    });
  }
  return route[key];
}


let __uid = 0;

function getuid() {
  return __uid++;
}


const styles = StyleSheet.create({
  baseScene: {
    position: 'absolute',
    overflow: 'hidden',
    left: 0,
    right: 0,
    bottom: 0,
    top: 0,
  },
});

That’s it! I hope it was helpful.

Please let me know if you have any questions.

Thanks @lazarmat and @zharley for cooperation and great job in figuring out the described above solution.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Trigger a device orientation change when screen is removed ...
The problem is when the user returns from the slideshow to the "Main Screen", the orientation does not switch again. However if the...
Read more >
KeyShot 9 Manual - KeySoft
The Import dialog and workflow has been updated in KeyShot 9 to speed up your Import efficiency. Updates include the ability to import...
Read more >
WorldToolKit Reference Manual (R9)
WorldToolKit is so named because your applications can resemble virtual worlds, where objects can have real-world properties and behavior.
Read more >
KeyShot 9 Manual
The Import dialog and workflow has been updated in KeyShot 9 to speed up your Import efficiency. Updates include the ability to import...
Read more >
Changelog | ArcoDesign
Fixed the bug that the popup layer did not disappear when the content property of the Tooltip component changed from a true value...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found