Integrating UITableView into a React Native Setting

July 26, 2016 | Mobile

Facebook's React Native is the latest in a long line of frameworks that promise to facilitate cross-platform app development using web technologies. When it became open source in 2015, several people declared it to be the future of mobile. I tend to be skeptical of that level of hype because I have worked with other web-based frameworks that received similar praise but couldn't live up to it in real life projects. So what's different about React Native? Three things stand out:

  1. React Native doesn't simply bootstrap your HTML, CSS and JavaScript code into a web view. It compiles to native views so you get the look and feel of native UI. In fact, React Native doesn't really use HTML and CSS at all. It uses JSX to build the view hierarchy and a special StyleSheet implementation that provides access to a subset of CSS properties. The syntax isn't identical to HTML and CSS but it's close enough that anyone with front-end web chops should feel at home very quickly.

  2. React Native plays nice with vanilla JavaScript. It doesn't require that you adopt an all-encompassing framework that controls every aspect of your app's behaviour.

  3. React Native is only concerned with the view layer. You can combine it with other frameworks like Redux and Realm to build a complete cross-platform app, or just use it to build portable views for your app.

This last point is what really got me excited about React Native. I love Swift, Objective-C and the Cocoa Touch frameworks. They are extraordinarily powerful tools and I firmly believe that some applications should be 100% native, but that doesn't mean that all apps should be. Nor does it mean that everything about native iOS development is perfect. The view layer is a pretty contentious topic among iOS developers. Some maintain that all views should be implemented purely in code while others (myself included) prefer to minimize view code and use UIStoryboards. Either way, the development experience is somewhat lacklustre. It generally takes a lot of effort to build even a relatively simple UI and, if you plan to support Android, you can look forward to repeating that effort. React Native's simple declarative syntax and cross-platform compatibility has the potential to solve these problems and best of all, it's flexible. You can integrate React views into an existing native app or build components to use native features within a React Native app.

In this exercise we're going to look at how we can use native features within a React Native app by developing a component to integrate UITableView into a React Native view. I chose UITableView because it is complex enough to be interesting and because it would address one of my early complaints about React Native's built-in ListView, which is its lack of row ordering and deletion UI.

A React Native iOS component consists of a native layer (written in Objective-C) and a React layer (written in JavaScript). Let's get started by looking at the native layer.

1. ReactTableViewManager and ReactTableView (Objective-C)

Most of the work on the native layer is performed by an instance of a class called ReactTableViewManager. ReactTableViewManager is a subclass of RCTViewManager that will manage an instance of ReactTableView, and export properties and event handlers that we will use to pass data between the Objective-C and JavaScript layers. ReactTableView is a subclass of UITableView that we'll come back to later.

// Objective-C (ReactTableViewManager.h)

#import <Foundation/Foundation.h>
#import "ReactTableView.h"
#import "RCTViewManager.h"

@interface ReactTableViewManager : RCTViewManager <UITableViewDataSource, UITableViewDelegate>

@end
// Objective-C (ReactTableViewManager.m)

#import "ReactTableViewManager.h"

NSString * const kReactTableViewCellIdentifier = @"ReactTableViewCell";
CGFloat const kReactTableViewDefaultCellHeight = 44.0;

NSString * const kReactTableViewCellConfigPropertyTableData = @"tableData";
NSString * const kReactTableViewCellConfigPropertyTableDataMap = @"tableDataMap";
NSString * const kReactTableViewCellConfigPropertyCellClass = @"cellClass";
NSString * const kReactTableViewCellConfigPropertyCellStyle = @"cellStyle";
NSString * const kReactTableViewCellConfigPropertyCellNib = @"cellNib";
NSString * const kReactTableViewCellConfigPropertyCellHeight = @"cellHeight";

NSString * const kReactTableViewCellDataPropertyTitle = @"title";
NSString * const kReactTableViewCellDataPropertySubtitle = @"subtitle";

NSString * const kReactTableViewCellStyleDefault = @"default";
NSString * const kReactTableViewCellStyleValue1 = @"value1";
NSString * const kReactTableViewCellStyleValue2 = @"value2";
NSString * const kReactTableViewCellStyleSubtitle = @"subtitle";

@interface ReactTableViewManager ()

@property (nonatomic, retain) ReactTableView *tableView;
@property (nonatomic, retain) NSMutableArray *tableData;
@property (nonatomic, retain) NSDictionary *tableDataMap;
@property (nonatomic, retain) NSString *cellClass;
@property (nonatomic, retain) NSString *cellStyle;
@property (nonatomic, retain) NSString *cellNib;
@property (nonatomic, assign) CGFloat cellHeight;
@property (nonatomic, assign) BOOL ordering;

@end

@implementation ReactTableViewManager

+ (Class) tableViewCellClassFromString:(NSString *)string {
  Class cellClass;

  if (string) {
    cellClass = NSClassFromString(string);
  }

  if (!cellClass) {
    cellClass = [UITableViewCell class];
  }

  return cellClass;
}

+ (UITableViewCellStyle) tableViewCellStyleFromString:(NSString *)string
{
  UITableViewCellStyle style = UITableViewCellStyleDefault;
  NSString *lowercaseString = [string lowercaseString];

  if ([lowercaseString isEqualToString: kReactTableViewCellStyleValue1]) {
    style = UITableViewCellStyleValue1;
  } else if ([lowercaseString isEqualToString: kReactTableViewCellStyleValue2]) {
    style = UITableViewCellStyleValue2;
  } else if ([lowercaseString isEqualToString: kReactTableViewCellStyleSubtitle]) {
    style = UITableViewCellStyleSubtitle;
  }

  return style;
}

#pragma mark - Properties

- (ReactTableView *) tableView
{
  if (_tableView) {
    return _tableView;
  }

  _tableView = [[ReactTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
  _tableView.dataSource = self;
  _tableView.delegate = self;

  return _tableView;
}

- (NSMutableArray *) tableData
{
  if (_tableData) {
    return _tableData;
  }

  return [NSMutableArray arrayWithArray: @[]];
}

- (void) setOrdering:(BOOL)ordering
{
  _ordering = ordering;
  [self.tableView reloadVisibleRows];
}

#pragma mark - UITableViewDataSource

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
  return 1;
}

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return self.tableData.count;
}

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kReactTableViewCellIdentifier];
  if (cell == nil) {
    Class cellClass = [[self class] tableViewCellClassFromString:self.cellClass];
    UITableViewCellStyle cellStyle = [[self class] tableViewCellStyleFromString:self.cellStyle];
    cell = [[cellClass alloc] initWithStyle:cellStyle reuseIdentifier:kReactTableViewCellIdentifier];
  }

  NSDictionary *item = self.tableData[indexPath.row];

  if (self.tableDataMap) {
    for (NSString *key in [self.tableDataMap allKeys]) {
      [cell setValue:item[key] forKeyPath:self.tableDataMap[key]];
    }
  } else {
    cell.textLabel.text = item[kReactTableViewCellDataPropertyTitle];
    cell.detailTextLabel.text = item[kReactTableViewCellDataPropertySubtitle];
  }

  return cell;
}

- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
  return self.ordering;
}

- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
  if ([sourceIndexPath isEqual: destinationIndexPath]) {
    return;
  }

  NSDictionary *item = self.tableData[sourceIndexPath.row];
  [self.tableData removeObjectAtIndex:sourceIndexPath.row];
  [self.tableData insertObject:item atIndex:destinationIndexPath.row];

  ReactTableView *reactTableView = (ReactTableView*)tableView;
  if (reactTableView.onMoveRow) {
    reactTableView.onMoveRow(@{
                               @"sourceRowIndex": [NSNumber numberWithInteger:sourceIndexPath.row],
                               @"destinationRowIndex": [NSNumber numberWithInteger:destinationIndexPath.row]
                               });
  }
}

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  return self.cellHeight;
}

#pragma mark - UITableViewDelegate

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  ReactTableView *reactTableView = (ReactTableView*)tableView;
  if (reactTableView.onSelectRow) {
    reactTableView.onSelectRow(@{
                                 @"rowIndex": [NSNumber numberWithInteger:indexPath.row],
                                 @"rowData": self.tableData[indexPath.row]
                                 });
  }
}

- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
  if (editingStyle == UITableViewCellEditingStyleDelete) {
    [self.tableData removeObjectAtIndex:indexPath.row];
    [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

    ReactTableView *reactTableView = (ReactTableView*)tableView;
    if (reactTableView.onDeleteRow) {
      reactTableView.onDeleteRow(@{
                                   @"rowIndex": [NSNumber numberWithInteger:indexPath.row]
                                   });
    }
  }
}

#pragma mark - React Native Interface

RCT_EXPORT_MODULE()

RCT_EXPORT_VIEW_PROPERTY(onSelectRow, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDeleteRow, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMoveRow, RCTBubblingEventBlock)

RCT_CUSTOM_VIEW_PROPERTY(config, NSDictionary, ReactTableViewManager)
{
  NSDictionary *configInfo = [RCTConvert NSDictionary:json];

  // Validate and set config properties:

  id tableData = configInfo[kReactTableViewCellConfigPropertyTableData];
  if ([tableData isKindOfClass:[NSArray class]]) {
    self.tableData = [NSMutableArray arrayWithArray: tableData];
  }

  id tableDataMap = configInfo[kReactTableViewCellConfigPropertyTableDataMap];
  if ([tableDataMap isKindOfClass:[NSDictionary class]]) {
    self.tableDataMap = tableDataMap;
  }

  id cellClass = configInfo[kReactTableViewCellConfigPropertyCellClass];
  if ([cellClass isKindOfClass:[NSString class]]) {
    self.cellClass = cellClass;
  }

  id cellStyle = configInfo[kReactTableViewCellConfigPropertyCellStyle];
  if ([cellStyle isKindOfClass:[NSString class]]) {
    self.cellStyle = cellStyle;
  }

  id cellNib = configInfo[kReactTableViewCellConfigPropertyCellNib];
  if ([cellNib isKindOfClass:[NSString class]]) {
    self.cellNib = cellNib;
  }

  id cellHeight = configInfo[kReactTableViewCellConfigPropertyCellHeight];
  if ([cellHeight isKindOfClass:[NSNumber class]]) {
    self.cellHeight = [cellHeight floatValue];
  } else {
    self.cellHeight = kReactTableViewDefaultCellHeight;
  }

  // Register a nib file for custom cells, if provided:

  if (self.cellClass && self.cellNib) {
    UINib *nib = [UINib nibWithNibName:self.cellNib bundle:nil];
    [self.tableView registerNib:nib forCellReuseIdentifier:kReactTableViewCellIdentifier];
  }

  [self.tableView reloadData];
}

RCT_CUSTOM_VIEW_PROPERTY(editing, BOOL, ReactTableViewManager)
{
  self.tableView.editing = [RCTConvert BOOL:json];
}

RCT_CUSTOM_VIEW_PROPERTY(ordering, BOOL, ReactTableViewManager)
{
  self.ordering = [RCTConvert BOOL:json];
}

- (UIView *)view
{
  return self.tableView;
}

@end

The UITableView implementation in ReactTableViewManager.m should be familiar enough. ReactTableViewManager serves as both the delegate and datasource for tableView, which is populated with data stored in the NSArray tableData. The tableView:cellForRowAtIndexPath: method of UITableViewDataSource can return instances of UITableViewCell using any of the standard styles, or it can return a custom cell. We'll look at these features in more detail later, but let's start by exploring communication between the native and React layers.

React Native includes a series of macros to export the Objective-C module and properties to React. Begin by exporting the module:

// Objective-C (ReactTableViewManager.m)

RCT_EXPORT_MODULE()

Calling RCT_EXPORT_MODULE() will make your component visible to React. We also need to implement the view method to return a view that React Native will render. In our case, that view is our ReactTableView instance:

// Objective-C (ReactTableViewManager.m)

- (UIView *)view
{
  return self.tableView;
}

This might be all you need for a very basic component but our UITableView implementation isn't that simple. We also need to export some properties and event handlers.

Exporting Properties

Properties allow the React layer to pass data to the native layer. The editing and ordering booleans provide simple examples of custom properties:

// Objective-C (ReactTableViewManager.m)

RCT_CUSTOM_VIEW_PROPERTY(editing, BOOL, ReactTableViewManager)
{
  self.tableView.editing = [RCTConvert BOOL:json];
}

RCT_CUSTOM_VIEW_PROPERTY(ordering, BOOL, ReactTableViewManager)
{
  self.ordering = [RCTConvert BOOL:json];
}

The RCT_CUSTOM_VIEW_PROPERTY macro accepts the property name, type and owner as arguments, and automatically parses JSON data passed by the React layer. The parsed JSON data is stored in the json variable, which is available within the macro's scope. Our only responsibility is to convert the JSON object into the appropriate data type with RCTConvert. In the example above we're converting to BOOL so we use RCTConvert's BOOL: method. Refer to the RCTConvert header file for a complete list of methods and data types that RCTConvert supports.

The editing property controls the table view's editing state. The ordering property controls whether rows can be moved via the tableView:canMoveRowAtIndexPath: method of UITableViewDataSource. We can set either property from the React layer to enable or disable the corresponding UI:

// JavaScript (index.ios.js)
// Some view code has been removed for clarity.

render() {
    return (
        ...
        <ReactTableView
            ...
            editing={this.state.editing}
            ordering={this.state.editing}
            ...
        />
        ...
    );
}

We'll look at the React layer in more detail later on.

ReactTableViewManager has several properties that define the table view's content and appearance. We can make sure that these properties are set before the table view loads by combining them into a single config object:

RCT_CUSTOM_VIEW_PROPERTY(config, NSDictionary, ReactTableViewManager)
{
    NSDictionary *configInfo = [RCTConvert NSDictionary:json];

    // Validate and set config properties:

    id tableData = configInfo[kReactTableViewCellConfigPropertyTableData];
    if ([tableData isKindOfClass:[NSArray class]]) {
        self.tableData = [NSMutableArray arrayWithArray: tableData];
    }

    id tableDataMap = configInfo[kReactTableViewCellConfigPropertyTableDataMap];
    if ([tableDataMap isKindOfClass:[NSDictionary class]]) {
        self.tableDataMap = tableDataMap;
    }

    id cellClass = configInfo[kReactTableViewCellConfigPropertyCellClass];
    if ([cellClass isKindOfClass:[NSString class]]) {
        self.cellClass = cellClass;
    }

    id cellStyle = configInfo[kReactTableViewCellConfigPropertyCellStyle];
    if ([cellStyle isKindOfClass:[NSString class]]) {
        self.cellStyle = cellStyle;
    }

    id cellNib = configInfo[kReactTableViewCellConfigPropertyCellNib];
    if ([cellNib isKindOfClass:[NSString class]]) {
        self.cellNib = cellNib;
    }

    id cellHeight = configInfo[kReactTableViewCellConfigPropertyCellHeight];
    if ([cellHeight isKindOfClass:[NSNumber class]]) {
        self.cellHeight = [cellHeight floatValue];
    } else {
        self.cellHeight = kReactTableViewDefaultCellHeight;
    }

    // Register a nib file for custom cells, if provided:

    if (self.cellClass && self.cellNib) {
        UINib *nib = [UINib nibWithNibName:self.cellNib bundle:nil];
        [self.tableView registerNib:nib forCellReuseIdentifier:kReactTableViewCellIdentifier];
    }

    [self.tableView reloadData];
}

The setter for this property performs four tasks:

  1. Convert the json object to an instance of NSDictionary via RCTConvert's NSDictionary: method.
  2. Validate and update individual properties on ReactTableViewManager (see property definitions, below).
  3. Optionally register a nib file for custom UITableViewCell subclasses designed in Interface Builder (see property definitions, below).
  4. Reload the table view.

ReactTableViewManager Property Definitions

  • tableData: An array containing one object for each row in the table. If the table view renders cells in one of the standard UITableViewCell styles, each object should contain a title property and optional subtitle property. These properties will be mapped to the cell's textLabel and detailTextLabel, respectively, unless you provide a tableDataMap object (see below).
  • tableDataMap (optional): An object that maps arbitrary properties in tableData objects to arbitrary key paths in table cells. This facilitates the use of custom UITableViewCell subclasses. For example, populating tableDataMap with the object {title: 'titleLabel.text', subtitle: 'subtitleLabel.text'} tells ReactTableViewManager to set cell.titleLabel.text to tableData[n].title and set cell.subtitleLabel.text to tableData[n].subtitle. Refer to tableView:cellForRowAtIndexPath: in ReactTableViewManager to see how this works.
  • cellClass (optional): The class name to use for custom UITableViewCell subclasses. You can omit this property if you are using one of the standard UITableViewCell styles.
  • cellStyle (optional): The UITableViewCellStyle to use for standard UITableViewCell subclasses. This may be any one of the following strings: default (corresponds to UITableViewCellStyleDefault), subtitle (corresponds to UITableViewCellStyleSubtitle), value1 (corresponds to UITableViewCellStyleValue1), value2 (corresponds to UITableViewCellStyleValue2). You can omit this property if you are using a custom UITableViewCell subclass.
  • cellNib (optional): The nib name to use for custom UITableViewCell subclasses designed in Interface Builder.
  • cellHeight (optional): The cell height.

Exporting Event Handlers

The event handlers are quite simple:

RCT_EXPORT_VIEW_PROPERTY(onSelectRow, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDeleteRow, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMoveRow, RCTBubblingEventBlock)

RCT_EXPORT_VIEW_PROPERTY exports a property on the view returned by the view method. The type of this property is RCTBubblingEventBlock, which is basically an Objective-C representation of a JavaScript function. React Native will expect these properties to be available on the view at runtime, so we declare them in ReactTableView:

[include ReactTableView.h]
[include ReactTableView.m]

That's all we need to do to set up event handlers. Anytime we call onSelectRow(), onDeleteRow() or onMoveRow() on a ReactTableView instance, React Native will call the corresponding function in JavaScript:

// Objective-C (ReactTableViewManager.m)

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  ReactTableView *reactTableView = (ReactTableView*)tableView;
  if (reactTableView.onSelectRow) {
    reactTableView.onSelectRow(@{
        @"rowIndex": [NSNumber numberWithInteger:indexPath.row],
        @"rowData": self.tableData[indexPath.row]
    });
  }
}
// JavaScript (index.ios.js)
// Some view code has been removed for clarity.

render() {
    return (
        ...
        <ReactTableView
            ...
            onSelectRow={(e) => {
                var rowIndex = e.nativeEvent.rowIndex;
                var rowData = this.state.dataSource.itemAtIndex(rowIndex);
                Alert.alert('You Chose', rowData.title, [{text: 'Yay'}]);
            }}
            ...
        />
        ...
    );
}

Note that the NSDictionary literal passed to reactTableView.onSelectRow() in the Objective-C layer is represented by the e.nativeEvent object in the JavaScript layer. You can pass any JSON-serializable data through this interface.

2. ReactTableView (JavaScript)

The JavaScript layer for ReactTableView is very simple:

// JavaScript (react-table-view.ios.js)

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

var ReactTableView = requireNativeComponent('ReactTableView', ReactTableViewManager);

class ReactTableViewManager extends Component {

  constructor(props) {
    super(props)
  }

  render() {
    return <ReactTableView
      style={styles.tableView}
      config={this.props.config}
      editing={this.props.editing || false}
      ordering={this.props.ordering || false}
      onSelectRow={this.props.onSelectRow}
      onMoveRow={this.props.onMoveRow}
      onDeleteRow={this.props.onDeleteRow}
    />;
  }

}

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

ReactTableViewManager.propTypes = {
  tableData: React.PropTypes.array,
  config: React.PropTypes.object,
  editing: React.PropTypes.bool,
  ordering: React.PropTypes.bool,
  onSelectRow: React.PropTypes.func,
  onMoveRow: React.PropTypes.func,
  onDeleteRow: React.PropTypes.func
};

module.exports = ReactTableViewManager;

This file requires the native ReactTableViewManager module as ReactTableView and declares a new component called ReactTableViewManager. This component's render method simply renders the view returned by the native ReactTableViewManager module's view method and passes properties and event handlers through from the view implementation (see below). Notice that the ReactTableViewManager component also documents the expected type of each property in the static propTypes object. This allows React Native to validate that your view code provides the correct data types. You can read more about prop validation here.

3. View Implementation (JavaScript)

Finally, we can easily integrate the ReactTableView component into a React view:

// JavaScript (index.ios.js)

import React, { Component } from 'react';
import {
  NativeComponents,
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TouchableHighlight,
  Alert
} from 'react-native';

var ReactTableView = require('./react-table-view.ios.js');
var ReactTableViewDataSource = require('./react-table-view-datasource.ios.js');

class ReactUITableViewController extends Component {

  constructor(props) {
    super(props);

    var dataSource = new ReactTableViewDataSource();
    dataSource.items = [
      {title: "A title", subtitle: "A subtitle"},
      {title: "Another title", subtitle: "Another subtitle"},
      {title: "Yet another title", subtitle: "Yet another subtitle"}
    ];

    this.state = {
      dataSource: dataSource,
      editing: false
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={styles.header}>
          <TouchableHighlight
            underlayColor={'#e7e7e7'}
            onPress={() => { this.toggleEditing(); }}>
            <Text style={styles.headerButtonText}>{
              (() => {
                return (this.state.editing) ? 'Done' : 'Edit';
              })()
            }</Text>
          </TouchableHighlight>
        </View>
        <ReactTableView
          config={{
            tableData: this.state.dataSource.items,
            tableDataMap: {
              title: 'titleLabel.text',
              subtitle: 'subtitleLabel.text'
            },
            cellClass: 'CustomTableViewCell',
            cellStyle: 'subtitle',
            cellNib: 'CustomTableViewCell',
            cellHeight: 44.0
          }}
          editing={this.state.editing}
          ordering={this.state.editing}
          onSelectRow={(e) => {
            var rowIndex = e.nativeEvent.rowIndex;
            var rowData = this.state.dataSource.itemAtIndex(rowIndex);
            Alert.alert('You Chose', rowData.title, [{text: 'Yay'}]);
          }}
          onMoveRow={(e) => {
            var sourceIndex = e.nativeEvent.sourceRowIndex;
            var destinationIndex = e.nativeEvent.destinationRowIndex;
            this.state.dataSource.moveItem(sourceIndex, destinationIndex);
          }}
          onDeleteRow={(e) => {
            this.state.dataSource.deleteItemAtIndex(e.nativeEvent.rowIndex);
          }}
        />
      </View>
    );
  }

  toggleEditing() {
    this.setState({editing: !this.state.editing});
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 20
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#000'
  },
  headerButtonText: {
    fontSize: 16
  }
});

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

We import the ReactTableView component along with a custom datasource object (I have omitted the datasource implementation for brevity because it isn't particularly relevant to this discussion). The render method incorporates ReactTableView and a simple header view, both of which behave like any other React Native component.

4. Taking it Further

If you have much experience working with UITableView in Objective-C or Swift, you have probably noticed that this component barely scratches the surface of its capabilities. However, it's enough to demonstrate that it should be possible to integrate most if not all of the UITableView API. Here are a few features that would make it more useful in real life projects:

  • Support images in table cells, via URLs or bundled assets.
  • Support multiple table sections.
  • Support different cell classes, styles and heights for each section.
  • Support different cell classes, styles and heights for each row, falling back to default settings for unspecified rows.

A complete discussion of these features is beyond the scope of this article, so I'll leave it up to you to design your own solutions.

5. Conclusion: Is This a Good Idea?

This has been a fun experiment, but should we really use it in production? Let's look at the pros and cons:

Pro:

  • Provides native iOS row ordering and deletion UI with great look and feel.

Con:

  • UITableView is only available in iOS, so you'll need to use React Native's ListView or implement a comparable solution if you plan to support Android.
  • This solution relies on an in-memory data store for UITableView and may not scale well to large datasets. You can mitigate this problem to some extent by limiting the data you pass to config.tableData to the bare minimum necessary to render your cells.

The short answer is, it depends. If you are building a simple app with a relatively small dataset, it could be a viable solution. If your table view has to display thousands of rows or you need to achieve UI parity on Android, you might want to reconsider. Either way, building React Native components is a great way to learn more about the framework. Use this exercise as a guide to develop your own.


We are DevBBQ.
We are digital product creators with big appetites. For Hire.

Arrow circle down@2x 6412eee90b778a7dc0698b617fc13fb6c19fdd10f53c4479172d38bc5ce4e168
Therapia white 55d4657b58374a27a9634c3cb6db71c5efd7179c0fca9530931b0a015c78fade

At-home physiotherapy social network, appointment bookings, and payment processing.

Cooked up by DevBBQ

 

Contact Us Lets chat@2x 9e08eee888b6926761c1dfe33e7c84fb2f0345e4be974f2d9e4dca9c39d24980

 

DEVBBQ INCORPORATED