React-Native UI Native components in Swift and Java

React-Native UI Native components in Swift and Java

React Native already provides the basic components you would use to build your application: views, labels, buttons, tables, etc.

However, there are times when these components aren't enough. In this tutorial we’ll explain how we build our own native component that can send messages back and forth between Javascript and the iOS/Android code.

Note: Most tutorials focus on how to do this in Objective-C, but we'll be using Swift instead.

We will also be using Xcode 10.2 and Android Studio 3.3.2, running in Mac OS 10.14.4

Building the Demo application

Make sure you have the React Native CLI installed:

npm install -g react-native-cli Next, let's create our project react-native init AwesomeProject And now let's run our application react-native run-ios --simulator="iPhone 12"

Creating a Native UI Component

iOS

Open AwesomeProject/ios/AwesomeProject.xcodeproj in Xcode

Defining out custom view in iOS

Create a new file: File -> New -> File... -> Cocoa Touch Class

Note: When asked, choose Create Bridging Header.

Patch for SWIFT Development

Since React-Native was built with ObjectiveC in mind, we need to do some tricks to be able to make it work with Swift.

First let's create the same file, but in ObjectiveC

And delete the MyCustomView.h since we won't be using it.

Select the file AwesomeProject-Bridging-Header.h and add this content:

#import <React/RCTBundleURLProvider.h> #import <React/RCTRootView.h> #import <React/RCTComponent.h> #import <React/RCTBridgeModule.h> #import <React/RCTViewManager.h> #import <React/RCTDevLoadingView.h>

Defining our view

Open MyCustomView.swift and paste the following code to define our view:

class MyCustomView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() } private func setupView() { // in here you can configure your view } }

Besides this view, we also need an RCTViewManager subclass that will be the one creating and manipulating this view.

Paste this at the bottom of MyCustomView.swift :

@objc (RCTMyCustomViewManager) class RCTMyCustomViewManager: RCTViewManager { override static func requiresMainQueueSetup() -> Bool { return true } override func view() -> UIView! { return MyCustomView() } }

Now, the only thing missing is to let ReactNative know about this manager. In order to do this, open MyCustomView.m and replace it's content with the following:

#import <React/RCTViewManager.h> @interface RCT_EXTERN_MODULE(RCTMyCustomViewManager, RCTViewManager) @end

Adding properties to our custom view

Let's bridge over some native properties. For this we'll create a simple prop and a function.

Open MyCustomView.swift and add the following to your CustomView class:

@objc var status = false { didSet { self.setupView() } } @objc var onClick: RCTBubblingEventBlock? override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let onClick = self.onClick else { return } let params: [String : Any] = ["value1":"react demo","value2":1] onClick(params) }

And replace setupView with the code below:

private func setupView() { self.backgroundColor = self.status ? .green : .red self.isUserInteractionEnabled = true }

Now, to make ReactNative aware of these new properties, open MyCustomView.m and replace it with the following:

@interface RCT_EXTERN_MODULE(RCTMyCustomViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(status, BOOL) RCT_EXPORT_VIEW_PROPERTY(onClick, RCTBubblingEventBlock) @end

Android

Import AwesomeProject/android/ into Android Studio

Defining our custom view in Android

Create a new file: File -> New -> File... and name it MyCustomView.java

Open MyCustomView.java and paste the following code to define our view:

public class MyCustomView extends RelativeLayout { public MyCustomView(Context context) { super(context); } public MyCustomView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); } public void onFinishInflate(){ super.onFinishInflate(); } }

Besides this view, we also need a view manager that will be the one creating and manipulating this view.

Create a file called MyCustomViewManager.java and paste the following :

public class MyCustomViewManager extends SimpleViewManager<MyCustomView> { public static final String REACT_CLASS = "RCTMyCustomView"; String eventName = "onClick"; @Override public String getName() { return REACT_CLASS; } @Override public MyCustomView createViewInstance(ThemedReactContext context) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); MyCustomView view = (MyCustomView)inflater.inflate(R.layout.custom_view, null); return view; } }

Now, the only thing missing is to let ReactNative know about this manager. To do this, create a file called MyCustomViewReactPackage.java and paste the following:

public class MyCustomViewReactPackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Arrays.<ViewManager>asList( new MyCustomViewManager() ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); return modules; } }

In the MainApplication.java file in your project let's add the MyCustomViewReactPackage like this:

public class MainApplication extends Application implements ReactApplication { ... @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new MyCustomViewReactPackage() // new line!!! ); } @Override protected String getJSMainModuleName() { return "index"; } }

Adding properties to our custom view

Now, let's bridge over some native properties. For this, we'll create the same prop and function like in iOS.

Open MyCustomView.java and add this to your class:

private boolean status = false; public void onFinishInflate() { super.onFinishInflate(); this.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { MyCustomView.this.onClick(); } }); } public void setStatus(boolean status) { this.status = status; setBackgroundColor( this.status ? Color.GREEN : Color.RED); } public void onClick() { WritableMap event = Arguments.createMap(); event.putString("value1","react demo"); event.putInt("value2",1); ReactContext reactContext = (ReactContext)getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "onClickEvent", event); }

Observe how in onClick() we will send an event to Javascript but we still need the connections of the status prop and the onClickEvent to react-native. In order to do this, open the MyCustomViewManager.java and paste the following:

@ReactProp(name = "status") public void setStatus(MyCustomView view, Boolean status) { view.setStatus(status); } public Map getExportedCustomBubblingEventTypeConstants() { String eventName = "onClickEvent"; String propName = "onClick"; Map onClickHandler = MapBuilder.of("phasedRegistrationNames",MapBuilder.of("bubbled", propName)); Builder events = MapBuilder.builder(); events.put(eventName, onClickHandler); return events.build(); }

Javascript

Defining our custom view in Javascript

Let's create a simple wrapper for our custom view:

// MyCustomView.js import React from 'react'; import { requireNativeComponent } from 'react-native'; const RCTCustomView = requireNativeComponent('RCTMyCustomView', MyCustomView, {}); class MyCustomView extends React.PureComponent { _onClick = (event) => { if (!this.props.onClick) { return; } // process raw event this.props.onClick(event.nativeEvent); } render() { return <RCTCustomView {...this.props} onClick={this._onClick}/> } }

export default MyCustomView;


Using our custom view

To use it you can do the following:

// App.js import MyCustomView from './MyCustomView.js'; export default class App extends Component { state = { status: false } onClick = (event: Object) => { alert("Received params: " + JSON.stringify(event)) this.setState({status: !this.state.status}) } render() { return ( <View style={styles.container}> <MyCustomView status={this.state.status} onClick={this.onClick} style={{ width: 100, height: 100 }} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } });

In Javascript we map the native views RCTMyCustomView into our MyCustomView component using the requireNativeComponent function. It receives two props: a simple boolean value called status and a function called onClick. The two props are defined in iOS and Android respectively.

Once the onClick event is triggered natively it will call the mapped prop to switch the status props coming into the MyCustomView.

Source Code

The full source code for this project is available in https://github.com/calitb/RN-NativeViews

How to Prepare New Developers for Success
Writing good Javascript: Let's not forget about performance

Suscribe to our newsletter

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.