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

How to get ref's style or width? #953

Closed
leecade opened this issue Apr 21, 2015 · 23 comments
Closed

How to get ref's style or width? #953

leecade opened this issue Apr 21, 2015 · 23 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@leecade
Copy link

leecade commented Apr 21, 2015

<View ref='view' style={{width: 100, marginTop: 10}}>
  <View ref='inner' style={{flex: 1}} />
</View>

I want to get the offsetWidth of refs.view, thanks

@gabro
Copy link
Contributor

gabro commented Apr 22, 2015

I'm interested in this as well.
Especially when dealing with Images (that do not autosize) I would need to manually set a size according the computed "box size" of the container.

This is tightly related to #494

@brentvatne
Copy link
Collaborator

@gabro @leecade - this is possible with the measure function: https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js#L61-L63

for example:

'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
} = React;


var TestIt = React.createClass({
  measureWelcome() {
    this.refs.welcome.measure(this.logWelcomeLayout);
  },

  logWelcomeLayout(ox, oy, width, height, px, py) {
    console.log("ox: " + ox);
    console.log("oy: " + oy);
    console.log("width: " + width);
    console.log("height: " + height);
    console.log("px: " + px);
    console.log("py: " + py);
  },

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome} ref="welcome">
          Welcome to React Native!
        </Text>
        <TouchableOpacity onPress={this.measureWelcome}>
          <Text>Measure it</Text>
        </TouchableOpacity>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('TestIt', () => TestIt);

The result of clicking the button:

2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "ox: 72"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "oy: 313"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "width: 231.5"
2015-04-22 15:34:44.970 [info][tid:com.facebook.React.JavaScript] "height: 24"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "px: 72"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "py: 313"

measure uses the RCTUIManager measure function. Notice the return values to the callback:

    callback(@[
      @(frame.origin.x),
      @(frame.origin.y),
      @(frame.size.width),
      @(frame.size.height),
      @(pagePoint.x),
      @(pagePoint.y)
    ]);

You may also be interested in measureLayout, which gives you:

callback(@[@(leftOffset), @(topOffset), @(width), @(height)]);

@brentvatne
Copy link
Collaborator

See also #977 where I get an element's opacity from over the bridge. Closing this as I believe the above comments resolve the issue, feel free to ping me if not and I will reopen. 💥

@gabro
Copy link
Contributor

gabro commented Apr 23, 2015

Thank you @brentvatne, that really helps!
Still, I haven't figured out which is the proper way of hooking into the layout events.
For instance, if I need to dynamically position a View after another has rendered, when should I perform the measure?

I tried in componentDidMount but it gives 0 for each measure...

@brentvatne brentvatne reopened this Apr 23, 2015
@brentvatne
Copy link
Collaborator

cc @vjeux

@gabro
Copy link
Contributor

gabro commented Apr 23, 2015

Ok, I managed to achieve my goal by measuring the view asynchronously (with a setTimeout)

  componentDidMount() {
    setTimeout(this.measureHeader);
  },

  measureHeader() {
    this.refs.header.measure((ox, oy, width, height) => {
      this.setState({headerHeight: height});
    });
  },

then I use this.state.headerHeight in my render method to computer another element's position.

Still feels like a hack though...

@vjeux
Copy link
Contributor

vjeux commented Apr 23, 2015

What you've done is currently the best way to do it. It's far from being ideal but works well enough that we were able to solve all the problems we've thrown at it so far.

There's definitely a better abstraction that exists somewhere.

@gabro
Copy link
Contributor

gabro commented Apr 23, 2015

Fair enough, thank you for the clarification @vjeux ;)

@brentvatne
Copy link
Collaborator

Thanks for the update @gabro, glad it worked out for you

@sahrens
Copy link
Contributor

sahrens commented Apr 24, 2015

We've had a couple ideas here but no time to implement them.

One idea was to predeclare what you want to measure, then native would populate the value after computing layout so you can access it synchronously in React.

The other idea (which also has other benefits), is to have JS make an explicit call to run the layout rather than native choosing when to do it (currently "at the end"). This would let us sequence the measure calls after the layout calls so you don't have to do the setTimeout in componentDidMount to get the right data.

@sahrens
Copy link
Contributor

sahrens commented Apr 24, 2015

If anyone wants to make either of these changes, let me know :)

@brentvatne
Copy link
Collaborator

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?

@sahrens
Copy link
Contributor

sahrens commented Apr 24, 2015

Haven't really thought through the details yet, but on the fly: I think it would be ideal to bake it into the react core and build it in such a way that it would also work in web with DOM measure APIs, utilized via a prop on the component you want to measure (maybe pass it a function that is invoked with the new measure values, a la the new functional refs?

Maybe like

<View onLayoutChange={dims => this._dims = dims} />

Or

<View onLayoutChange={dims => this.setState({dims})} />

The advantage here is that you don't have to wait for a ref

Another option is as an explicit subscription:

<View ref={ref => ref.subscribeToLayout(dims => this.setState({dims}))}} />

Then implement subscribeToLayout in NativeMethodsMixin to register the reactTag with native, which would then either emit one layout event for all registered views which JS infra would then de-multiplex to the right callbacks per reactTag/nodeHandle.

It might make sense to build the first API (does anyone prefer the second?), but in a way more like the second behind the scenes rather than emitting separate events for every view, like we do for normal onChange events - not sure if that would be a pre-mature optimization, but seems like batching all the updates from one layout run into one event would be good.

On Apr 23, 2015, at 9:02 PM, Brent Vatne notifications@github.com wrote:

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?


Reply to this email directly or view it on GitHub.

@fatuhoku
Copy link

fatuhoku commented Jul 7, 2015

@gabro I've tried to measure the root view with the code you posted above but no avail:

class MainComponent extends Component {
  componentDidMount() {
    // https://github.com/facebook/react-native/issues/953
    setTimeout(this.measureMainComponent);
  }
  measureMainComponent() {
    this.refs.rootView.measure((ox, oy, width, height) => {
      this.setState({rootViewHeight: height});
    });
  }
  render() {
    return (
      <View ref='rootView'/>
    );
  }
}

ios simulator screen shot 7 jul 2015 15 39 17

What am I doing wrong?

@brentvatne
Copy link
Collaborator

@fatuhoku - you need to bind the fn you pass in to setTimeout to the component:

setTimeout(this.measureMainComponent.bind(this));

or

requestAnimationFrame(this.measureMainComponent.bind(this));

@fatuhoku
Copy link

@brentvatne Thanks binding seemed to work.

@sahrens
Copy link
Contributor

sahrens commented Sep 4, 2015

Also note we now have onLayout implemented, so you can use that in some cases.

@oknixus
Copy link

oknixus commented Nov 27, 2015

mark

@uc-spr
Copy link

uc-spr commented Aug 16, 2016

How can we get the x, y coordinate of a view according to the screen without using ref. If anyone knows please suggest me it is highly needed to me.

@aleclarson
Copy link
Contributor

@uc-spr See this issue: #1374

@PaulAndreRada
Copy link

@brentvatne I'm trying to use measure in ES6 (I'm still a bit new to it) syntax but mixins ( like the NativeMethodsMixin required for your example ) don't seem to be an option on es6. Do you happen to know an alternative?

@tobycox
Copy link
Contributor

tobycox commented Sep 23, 2016

@PaulAndreRada It's bit uglier, but:

import { findNodeHandle } from 'react-native';
const RCTUIManager = require('NativeModules').UIManager;

RCTUIManager.measure(findNodeHandle(this.refs.myRef), (x, y, width, height, pageX, pageY) => {
      // Got me some dimensions
});

is working for me.

@anshul-kai
Copy link

The suggestion to use UIManager works great on iOS but returns undefined on Android on the latest React-Native version 0.42.0.

@facebook facebook locked as resolved and limited conversation to collaborators May 29, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests