React Native diary #8: Objective-C

I haven’t written a new React Native diary blog post in a while because there hasn’t been anything noteworthy. We’ve shipped new versions of Epilogue for iOS and Android. Micro.blog 3.0 for iOS is almost ready — also a rewrite for React Native.

We did hit one feature that I wanted to preserve from the previous version of Micro.blog: Markdown and HTML syntax highlighting when writing a new blog post. The best way to preserve this was to port the Objective-C code over to React Native. (This feature won’t be available on Android yet.)

The “native” in React Native is because it uses native iOS and Android controls, even though they are driven from a JavaScript engine. This means we can make our own native components, written in Swift or Objective-C.

There are a few pieces of code to make this work:

  • MBHighlightingTextView: a UITextView subclass, but it could be any control.
  • MBHighlightingTextManager: a RCTViewManager class, helping us build an interface between JavaScript and native code.
  • HighlightingText: a React.Component, wrapping up the native control.

Here’s a snippet of the MBHighlightingTextView interface:

@interface MBHighlightingTextView : UITextView

@property (copy, nonatomic) RCTBubblingEventBlock onChangeText;
@property (copy, nonatomic) RCTBubblingEventBlock onSelectionChange;

@end

And the MBHighlightingTextManager interface:

@interface MBHighlightingTextManager : RCTViewManager <UITextViewDelegate>

@end

For the MBHighlightingTextManager implementation, the important bits are the macros that define what properties we care about:

@implementation MBHighlightingTextManager

RCT_EXPORT_MODULE(MBHighlightingTextView)

RCT_EXPORT_VIEW_PROPERTY(onChangeText, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTBubblingEventBlock)

RCT_CUSTOM_VIEW_PROPERTY(inputAccessoryViewID, NSString, MBHighlightingTextView)
{
  if (json) {
    NSString* input_id = [RCTConvert NSString:json];
    // ...
  }
}

- (UIView *) view
{
  // make a new MBHighlightingTextView and return it
  // ...
}

@end

Finally, the JavaScript side that loads the native component:

import * as React from 'react';
import { requireNativeComponent } from 'react-native';

const MBHighlightingTextView = requireNativeComponent("MBHighlightingTextView");

export default class HighlightingText extends React.Component {
  render() {
	return (
	  <MBHighlightingTextView {...this.props} />
	)
  }
}

Now we can simply use <HighlightingText> in place of <TextInput> in our XML when laying out the UI. Handlers like the property onChangeText will be referenced from Objective-C so we can call them in response to methods from our UITextView delegate.

I’m leaving some code out in the above examples for readability. And I have a bunch of code still to write, working in a branch of our project on GitHub. But already the basics are working, after (frankly) a lot of trial and error and sifting through the documentation, Stack Overflow, and even asking ChatGPT, which knows a surprising amount of how this works.

Manton Reece @manton