A universal iOS app with React Native

Gertjan Reynaert · 12 May 2015

Almost anyone in the React community has heard of React Native by now, so this blog post won’t tell you how brilliant the framework is.

The first few days after the public release of React Native I started exploring this new framework by creating a simple contact list app. While showing of my work to some coworkers, I got an interesting question:

Is there an equivalent to iOS size classes in react-native?

Getting started

If you want to follow along during this blogpost you can check out my contact list app on github, or create your own example app by following the React Native getting started guide.

Two layouts

At the time the question came in, I already had my iPad layout. Why iPad? Because the master detail layout seemed easier to me than creating an app with the NavigatorIOS component used on the iPhone.

The master detail layout actually boils down to three components: A ContactList component for the master view, a ContactDetail component for the detail view, and a parent component.

<View style={styles.screen}>
  <View style={styles.master}>
    <ContactList contacts={this.state.list} setContact={this.setSelected} />
  </View>
  <View style={styles.detail}>
    <ContactDetail contact={this.state.selected} />
  </View>
</View>

Organizing the views in a split way was achieved by just styling the two views with a flexbox layout.

var MASTER_WIDTH = 350;

var styles = StyleSheet.create({
  screen: {
    flexDirection: 'row',
  },
  master: {
    width: MASTER_WIDTH,
    paddingTop: 30
  },
  detail: {
    flex: 1,
    padding: 20
  }
});

Universal app

If you want a universal app, you also need to have an iPhone layout. Since there wasn’t any way to avoid using the NavigatorIOS component, I just dug into it. As it turned out, using this was easier than expected.

The NavigatorIOS component kind of manages your iPhone view workflow. So all I needed to do was passing the ContactList as initial component. I also created a callback which would set the ContactDetail view if a contact was clicked. Returning from the detail view to the list view is handled by the NavigatorIOS component itself.

A universal iOS app with React Native

Detecting devices

After manually switching the layouts in the code I had to find a way to detect the current device. While exploring the React Native guides for any device specific code, I stumbled upon the PixelRatio class, which lets you detect how dense the pixels of the device are.

The PixelRatio class itself wasn’t of much use to me, but internally uses the Dimensions class. This class gives back the devices height, width and scale, which was exactly what I needed to detect the current device.

With this knowledge I could now easily check if the current device was an iPad or iPhone, and render a specific layout accordingly.

render: function() {
  var x = Dimensions.get('window').width;
  var y = Dimensions.get('window').height;

  var iPad = [768, 1024];

  if (iPad.indexOf(x) > -1 && iPad.indexOf(y) > -1) {
    return <MasterDetail list={this.state.contacts} />;
  } else {
    return <IphoneLayout list={this.state.contacts} />;
  }
}

Introducing react-native-device

In order to save you some typing, I extracted the device detection into easily installable npm package:

npm install react-native-device --save

After installing react-native-device in your own project, you can start using it like this:

var Device = require('react-native-device');

render: function() {
  if (Device.isIpad()) {
    return <MasterDetail list={this.state.contacts} />;
  } else {
    return <IphoneLayout list={this.state.contacts} />;
  }
}

The package also has the ability to detect the various iPhone screen sizes, which might come in handy to leverage those extra iPhone 6 plus pixels.

For more information check out react-native-device.