Skip to content
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

Ideas for orientation change support #9

Open
vitalets opened this issue Apr 26, 2016 · 51 comments
Open

Ideas for orientation change support #9

vitalets opened this issue Apr 26, 2016 · 51 comments

Comments

@vitalets
Copy link
Owner

vitalets commented Apr 26, 2016

After implementing media queries in #5 I'm thinking about supporting orientation change somehow.
As it will allow to get full power of media queries.

Technically I see these steps:

  1. catch orientation change
  2. re-calculate styles
  3. re-render components

I quickly searched for that topic but did not found ready to use concept how it can be implemented in RN.
If you have some ideas or experience, please feel free to share it here and we will think how to add it.

Thanks!

@amarchen
Copy link

amarchen commented May 23, 2016

@vitalets, shall it actually be not a stylesheet business to watch for the possible orientation change and even command the components rendering?

I am playing with the extended stylesheets at the moment and while full automation of orientation handling wouldn't hurt, on demand calculation could be not that bad either.

For example, EStyleSheet.create() or a similar API point could return not a finalized sheet, but a singletone-like function that guarantees to have all the media queries caclulated when returns and behind the scenes caches in a memoize()-like way or even precaches well in-advance (as orientation change is quite an expected event). For example,

var getStyles = EStyleSheet.createRotationReadyStyle({
    debugObj: {
      backgroundColor: 'green',
    },
    '@media (orientation: landscape)': {
      debugObj: {
        backgroundColor: 'yellow',
        fontSize: 32
      }
    },
  });
  ...
  <Text style={getStyles().debugObj}>Hello</Text>
  ...
  // getStyles() is guaranteed to return a stylesheet valid for "right now"
  // caching is the implementation detail, yet in practice styles are 
  // probably calculated just once per orientation
  //
  // very efficient createRotationReadyStyle() probably even precalculated
  // portrait and landscape versions before getStyles() was ever called

@vitalets
Copy link
Owner Author

vitalets commented May 31, 2016

hi @amarchen ! thanks for your ideas!
I believe that while having percentage values and media-queries supported, the library should keep this values in sync with screen size itself. So developer should only declaratively describe styles and no code for listening orientation.

Currently I've got working proto with the same approach as you described here #13 - using View with onLayout subscription. I like this because it does not require any native code and works with both platforms.

Of course, it should automatically memoize two calculated stylesheets (portrait and landscape), so developer should not care about it.

I'm going to make orientation support as option:

  • automatic
  • manual: you may call something like EStyleSheet.update(layoutWidth, layoutHeight)
  • no support (as current)

@sampurcell93
Copy link

Hi, any update on this?

@vitalets
Copy link
Owner Author

Hi @sampurcell93

trying to find time to finalize that. But still it is not ready to be published.

@sampurcell93
Copy link

Is there any way I can help? This orientation change feature would be huge for our company.

Thanks for the hard work!

abdallahm added a commit to abdallahm/react-native-extended-stylesheet that referenced this issue Jul 27, 2016
@yvbeek
Copy link

yvbeek commented Sep 19, 2016

@abdallahm That looks great!
@vitalets What do you think of abdallhm's commit?

@vitalets
Copy link
Owner Author

@Zyphrax

I'll repost here my answer from pr:

I've pushed my drafts on this feature to separate branch orientation-support, lets move our work there.

I suggest creating layout module that will do all job for processing layout changes (not in utils as in your pr)
Here it is in more or less ready state: https://github.com/vitalets/react-native-extended-stylesheet/blob/orientation-support/src/layout.js

Also I suggest to provide component. Those developers who want to listen orientation change, should just wrap application into it.

AppContainer itself: https://github.com/vitalets/react-native-extended-stylesheet/blob/orientation-support/src/components/app-container.js

Example of wrapping app:
https://github.com/vitalets/react-native-extended-stylesheet/blob/orientation-support/examples/orientation-change/app.js

The problem is to automatically update react components after orientation change.
For that my thought was to register each component in tracker that will push updates after orientation change:
https://github.com/vitalets/react-native-extended-stylesheet/blob/orientation-support/src/components/tracker.js
This approach was not finally ready. So it woulb be great if play with stuff in orientation-change branch and make PR if got it working..

@tuckerconnelly
Copy link

Hey, Uranium supports orientation changes :)

@vitalets
Copy link
Owner Author

hi @tuckerconnelly

could you share with us which approach you are using to detect orientation change?

@tlvenn
Copy link
Contributor

tlvenn commented Nov 21, 2016

Hi @vitalets , I believe it leverages https://github.com/walmartlabs/react-native-orientation-listener to do that.

@tuckerconnelly
Copy link

tuckerconnelly commented Nov 21, 2016

Oh sorry, how did I miss that notification? Yeah it's that ^, and then on top of that https://github.com/tuckerconnelly/react-native-match-media

I've thought about this quite a bit--you can also poll Dimensions if you don't want a third-party dependency. The React Native team also has better support for orientation on the roadmap, if you want to wait.

@fermuch
Copy link

fermuch commented Dec 2, 2016

What's the current way of listening to orientation changes? Wrapping the component as described by @vitalets ?

@jimthedev
Copy link

I'm using this for my orientation change stuff. It works pretty well so far.

https://gist.github.com/jimthedev/edcfe00d3f27da2a248b97559ce6e29f

@vitalets
Copy link
Owner Author

@jimthedev thanks for sharing!
Actually as I see you are using subscription on onLayout event.

A user MUST bind this function explicitly as an onLayout

@jimthedev
Copy link

Correct

@mehcode
Copy link

mehcode commented Mar 23, 2017

I took a look at #21. Not a fan of the approach so far.

Instead of a very manual API ...

var updatedStyles = EStyleSheet.orientationUpdate(event, styles); 

... how about a more automatic API using property accessors?

Take styles.card. This should be a (memoized) property accessor that returns the style value that is correct according to the current orientation. That's literally all we need to do in this package.

class example extends React.Component {
  render() {
    return (
      <View style={styles.card}>
          {/* ... */}
      </View>
    );
  }
}

A user interested in responding to orientation would then need to re-render their views on orientation change.


After looking at existing solutions for this.. I feel we should make a slight change to EStyleSheet.build and allow the user to provide a function that returns the current orientation (this function would be called when styles.card is accessed.

Here is how I would use this.

import {observable} from "mobx";
import Orientation from "react-native-orientation";

class OrientationState {
  @observable _current = Orientation.getInitialOrientation();
  
  constructor() {
    Orientation.addOrientationListener((orientation) => {
      this._current = orientation;
    });
  }

  get = () => { 
    return this._current; 
  }
}

// ----

import EStyleSheet from "react-native-extended-stylesheet";

EStyleSheet.build({
  orientation: OrientationState.get,
});

And that's it. Any components that care about reacting to the orientation would just need to be marked @observer as styles._ is accessing OrientationState._current.

@vitalets
Copy link
Owner Author

I agree with you and moreover I think developer should not care about passing orientation fn to EStyleSheet. It should be detected inside lib as well as media queries in desktop browsers work - we don't pass orientation to it, we just declare rules how everything should be rendered..

@mehcode
Copy link

mehcode commented Mar 23, 2017

I think developer should not care about passing orientation fn to EStyleSheet.

Oh, I agree. The passing in EStyleSheet.build was more to override what otherwise would be detected so I could tap into the orientation changing via mobx. Looking back at what I wrote.. it is a touch wordy for something that should be as transparent as possible.

The key problem is in pure components. It'd be nice to just re-render the root on a layout change but that isn't possible if children are pure. Some more ideas:

  • Custom HoC like observesOrientation
  • Provide EComponent that is to be used instead of Component

@raarts
Copy link

raarts commented Jul 6, 2017

It'd be nice to just re-render the root on a layout change but that isn't possible if children are pure.

(RN beginner here). Can you explain why that isn't possible?

@mehcode
Copy link

mehcode commented Jul 6, 2017

(RN beginner here). Can you explain why that isn't possible?

Pure react components will only re-render if props or state changes. Unless Orientation is passed through as props all the way down your tree you couldn't easily re-render the whole tree based on orientation.


For what it's worth, I dropped this package and am using a scaled viewport. I don't need this scale to change if the orientation changes. Flexbox already handles resizing and repositioning everything when the orientation changes. Really I think that the idea of "I need to re-layout on orientation change" is a misnomer as I can't think of many reasons why you really want to.

My app is a huge business app and I have a manual orientation listener in one place (a video player) that detects when you rotate the phone to simulate full screen by disabling things like the notification bar.

By a scaled viewport, I mean I define all my sizes as if the only phone in existence was the iPhone 6 and then multiply the size by a ratio of the difference in size of the actual phone to the iPhone 6.

import {Dimensions} from "react-native";

const d = Dimensions.get("window");

const width = 375;
const height = 667;

const vw = (d.width / 100);
const vh = (d.height / 100);

const vmin = Math.min(vw, vh);

function vu(size) {
  return ((size / width) * vmin) * 100;
}

All of my sizes are defined like width: vu(30).

@raarts
Copy link

raarts commented Jul 6, 2017

Thanks. Yes, this is one of such cases where you decide early, and changing it afterwards turns out a huge amount of work.

I also think if you really need to re-layout on orientation change, you will pass down the state.

@vitalets
Copy link
Owner Author

vitalets commented Jul 7, 2017

@mehcode thanks for sharing the idea!
Do you have different layouts on different orientations or you always only stretch?

@raarts
Copy link

raarts commented Jul 7, 2017

@mehcode: how do you handle heights?

@mehcode
Copy link

mehcode commented Jul 7, 2017

Same function. It works by applying a ratio.

Width of your device is exactly vu(375) and the height is vu(667).

@vitalets
Copy link
Owner Author

vitalets commented Jul 8, 2017

Thanks @mehcode ! I think we can go the same scale-only way.

@raarts
Copy link

raarts commented Jul 10, 2017

@mehcode : what's the reason for using the 100-factor? I think it can be done like this as well:

// iPhone 6 sizes
const width = 375;
const height = 667;

const screen = Dimensions.get("screen");
const vmin = Math.min(screen.width, screen.height); // compensate for landscape mode
const scale = vmin / width;

function vu(size) {
  return size * scale;
}

BTW I changed 'window' into 'screen' because they differ on Android, and screen seems to be the most appropriate

@mehcode
Copy link

mehcode commented Jul 10, 2017

@raarts Because I used to export vw and vh before React Native added % support. You're totally right it can be simplified as the extra variables aren't needed.

FYI that snippet is released as rn-viewport ( https://github.com/mehcode/rn-viewport ). I'd accept those changes as a PR if you're willing to contribute them.

It does need a README.


I disagree on the screen bit though. Screen is the entire display. Window is the screen space reserved for your app. Window is more correct. Here is a blip from docs (that haven't been merged in). I'd want my app to try and scale down to the window size.

On iOS, window and screen are the same values. On Android, the values for window may be smaller than the values of screen, as Android 7.0+ supports running multiple applications simultaneously each taking a portion of the device screen size.

@raarts
Copy link

raarts commented Jul 10, 2017

@mehcode : any remaining advice on where to apply the vu() function. I am currently implementing it, and I seem to be applying it to everything but borderWidth. Sometimes I doubt about padding and margin though. Do you radically apply it everywhere?

@raarts
Copy link

raarts commented Jul 10, 2017

After testing on an iPad Air 2 everything doubles in size, including screen headers, fonts in many places (but not in others, where I didn't specify specific font size). Which proves the vu() function works. But still, it wasn't entirely what I expected. Some more thought needs to go into this. This may need some guidelines.

@miguelocarvajal
Copy link

Adding my 2 cents to this...

I am currently testing react-native-extended-stylesheet with react-native-web and my basic tests show it works well so far.

It would be nice that any forthcoming support for orientation changes plays well with react-native-web.

@corysimmons
Copy link

Any update on this?

@raarts
Copy link

raarts commented Feb 4, 2018

BTW I'm also using this with react-native-web, and now scaling became a problem. You can rotate your phone to landscape, and scaling will work, but if you project that same landscape screen in a web browser, now suddenly everything gets really big.

Also, you cannot rotate a monitor, or laptop (at least the system won't get an event), but you can scale the browser window dynamically.

For laying out components, there seems to be only one consideration: width and height. No need to pass those details down, but I'm now considering defining 5 virtual layouts:

  • phone size where rotating does not make a difference, because everything is small anyway
  • tablet size where more components fit on the screen, thus rotating might need re-ordering
  • screen size, like tablet size, but with a different layout

And creating different navigation stacks, where each stack defines screens with a tailored layout.
Seems like a lot of work, but the code re-use on the other hand is a definite win.

@stale
Copy link

stale bot commented Dec 12, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Dec 12, 2018
@vitalets vitalets added pinned and removed wontfix labels Dec 12, 2018
@magrinj
Copy link

magrinj commented Mar 14, 2019

@vitalets I'm not sure about my suggestion, but maybe by using Dimensions listener from react-native you should be able to detect if device move from portrait to landscape or the other way around just by checking if the width is higher than the height, you don't even need to have it inside a component and just listen globally
Hope that's can help and sorry if what I say is not a solution

import { Dimensions } from "react-native";

let dims = Dimensions.get("window");
const handler = (newDims) => (dims = newDims);

Dimensions.addEventListener("change", this.handler);

export const isLandscape = () => (dims.width > dims.height);

@vitalets
Copy link
Owner Author

@magrinj looks like good solution! 👍
There was no Dimensions.addEventListener before. Do you know in what version of RN it appeared? And does it work on both ios/android?

@magrinj
Copy link

magrinj commented Mar 18, 2019

@vitalets From what I read it appeared from the version 0.43 of React-Native and yes is works on both iOS and Android :)
I guess you can use it and add a warn for people using react-native under this version tag (to avoid breaking changes), as it's actually 16 versions earlier

@awdhootlele
Copy link

Is there any workaround to achieve this? I am currently updating the Dimensions using the addEventListener event -

Dimensions.addEventListener('change', ({window: {width, height}}) => {
  Screen.width = width;
  Screen.height = height;
  // emitter to detect the orientation changes - the View.onLayout alternative can be used
  emitter.emit(layout_change);
});

export const Screen = {
  width: Dimensions.get('window').width,
  height: Dimensions.get('window').height
};

And on the component, updating the View's width and height to match to that of the Screen when the layout changes -

this.layoutChangeSubscriber = emitter.on(
      'layout_change',
      (layout) => {
        // update local state to trigger component re render
       this.setState({width: Screen.width, height: Screen.height});
      });

render(){
const {width, height} = this.state;

return (
<View testID="IdleScene" pointerEvents={pointerEvents} 
      style={[styles.absFill, {width, height}]}>
{/* all nested components using EStyleSheet */}
</View>
);
}

But, it's not updating the layout as per defined by EStyleSheet.create() in each subsequent components. Am I missing something?

@mahajan8
Copy link

mahajan8 commented Oct 5, 2019

let {height, width} = Dimensions.get('window');
const wid = width<height? width:height

EStyleSheet.build({
$rem: wid/360
});

For a 360pixel standard width device rem would be 1 and you can use '10rem' instead of the static 10 pixel, and for corresponding lower width device, rem decreases and the opposite for larger devices. So, the design will remain same on every device and both orientations as well.

@imAliAzhar
Copy link

I created a small library for this purpose which we are using at work and faced issues with event listening approach.
When using Dimensions.addEventListener it's not enough to just to switch the styles. We also need to force the component to re-render to pick up the updated styles. And when the component unmounts, the event handler needs to be removed with Dimensions.removeEventListener as well.
This makes you dependent on life cycle hooks and you would also need to pass component's reference to stylesheet manager so it can call component.forceUpdate().
I ended up using onLayout to update the styles but it needs you to wrap all JSX in View.

This is how we specify orientation specific styles.

const styles = {
  container: {
    backgroundColor: "yellow",
    portrait: {
      width: "10vw",
      height: "5vh",
    },
    landscape: {
      width: "5vh",
      height: "10vw",
    },
  },
};

@vitalets
Copy link
Owner Author

I created a small library for this purpose which we are using at work and faced issues with event listening approach.

@imAliAzhar Could you share the link to your library?

@ItsWendell
Copy link

If it ever might help someone, I recently implemented orientation-change-support by creating a wrapper component that if there are any media queries, it will create a new stylesheet which cases the component to re-render with the proper styling.

Code of the wrapper component can be found here.

I'm using this library together with Emotion JS (CSS in JS), to support more advanced CSS properties in React Native, for example media queries in my recently released package: emotion-native-extended

@eswarant
Copy link

eswarant commented Dec 23, 2020

is someone still looking solution for this feature(Dynamic orientation change)?

@raarts
Copy link

raarts commented Dec 23, 2020

I think this needs to be managed outside extended stylesheet. BTW did you take over maintenance of this repo?

@eswarant
Copy link

I think this needs to be managed outside extended stylesheet. BTW did you take over maintenance of this repo?

I want to use this library to create React Native app that should be responsive for ipad & mobile. If possible please share the code or idea to apply media query when orientation is changed.

@raarts
Copy link

raarts commented Dec 26, 2020 via email

@anmol242
Copy link

anmol242 commented Jul 5, 2023

Hey @vitalets , any suggestion how we can handle this . Currently I have made a hook which adds an event listener to Dimensions but the problem is this is not something which I am big fan of as I need to call this hook in every component and I am re-rendering my component whenever orientation changes. Can't we do this on global level that I don't need to call useEffect on orientation change in every component

@vitalets
Copy link
Owner Author

vitalets commented Jul 7, 2023

Can't we do this on global level that I don't need to call useEffect on orientation change in every component

I agree it should be the way it works. Maybe .forceUpdate can help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests