Handling Multiple Native iOS Views in React Native

This article based on creating native ui components and native modules.

Intro

As a React Native developer with native background, I wanted to create an iOS view component of react-native-viewpager. This component allows the user to swipe left and right through pages of data. I opened the docs and followed it step by step, which seemed to work just fine. I was wondering, what about the case, when the parent view will have multiple native children views?

Problem

Let’s consider the following case. React Native view can have more than one child view in the view tree eg.

<View>
<MyNativeView />
<ButtonContainer>
<Button title="Previous" />
<Button title="Next" />
</ButtonContainer>
<MyNativeView />
<ButtonContainer>
<Button title="Previous" />
<Button title="Next" />
</ButtonContainer>
</View>
view raw gistfile1.txt hosted with ❤ by GitHub

Class MyNativeView is a wrapper for NativeComponent and exposes methods, which will be called on the iOS side. A component rendering two MyNativeView’s inside a View and it looks like as follow:

screenshot presenting a component rendering two MyNativeView’s inside a View

The above picture shows example application, which contains two MyNativeView’s and 2 ButtonContainer’s, which contain two buttons called next and previous. These buttons in ButtonContainer controls the behavior of the respective views.

When the user interacts with the component, like pressing the button or swipe, MyNativeView should change the page. In this case, the component would not know which MyNativeView should be handled and which MyNativeView should change page. Below you will find a solution to this problem:

<View>
<MyNativeView ref={this.myNativeView} />
<ButtonContainer>
<Button
title="Previous"
onPress={() => {
this.myNativeView.current.goToPreviousPage();
}}
/>
<Button
title="Next"
onPress={() => {
this.myNativeView.current.goToNextPage();
}}
/>
</ButtonContainer>
<MyNativeView ref={this.myNativeView2} />
<ButtonContainer>
<Button
title="Previous"
onPress={() => {
this.myNativeView2.current.goToPreviousPage();
}}
/>
<Button
title="Next"
onPress={() => {
this.myNativeView2.current.goToNextPage();
}}
/>
</ButtonContainer>
</View>
view raw gistfile1.txt hosted with ❤ by GitHub

Now the above component has a reference to a particular MyNativeView, which allows us to use a specific instance of MyNativeView. Now the button can control which MyNativeView should change its page.

class MyNativeView extends React.Component<Props> {
goToNextPage = () => {
NativeModules.RNCMyNativeView.goToNextPage();
};
goToPreviousPage = () => {
NativeModules.RNCMyNativeView.goToPreviousPage();
};
render() {
return <NativeComponent ref={NATIVE_COMPONENT_REF} />;
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

Let’s assume, that goToNextPage and goToPreviousPage changes pages of viewpager and call Objective-C method in iOS component. The documentation says, that it should be implemented like above.

But another question is, which view should call one of the above methods? NativeComponent does not know, which view should change the page in this case. I’ve found the answer in React Native repository.

Solution

Let’s change implementation of goToNextPage and goToPreviousPage using UIManager:

class MyNativeView extends React.Component<Props> {
goToNextPage = () => {
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(this),
UIManager.getViewManagerConfig('RNCMyNativeView').Commands.goToNextPage,
[]
);
};
goToPreviousPage = () => {
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(this),
UIManager.getViewManagerConfig('RNCMyNativeView').Commands.goToPreviousPage,
[]
);
};
render() {
return <NativeComponent ref={NATIVE_COMPONENT_REF} />;
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

dispatchViewManagerCommand needs 3 parameters:

  • <rte-code>(nonnull NSNumber *)reactTag <rte-code>— id of react view.
  • <rte-code>commandID:(NSInteger)commandID<rte-code> — Id of native method, that should be called
  • <rte-code>commandArgs:(NSArray<id>*)commandArgs<rte-code> — Args of native method, that we can pass from JS to Native side.

Now let’s describe the iOS part. To expose this method in JS, NativeModule needs export function:

#import <React/RCTViewManager.h>
#import <React/RCTUIManager.h>
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(goToPreviousPage:(nonnull NSNumber*) reactTag) {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
NativeView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[NativeView class]]) {
RCTLogError(@"Cannot find NativeView with tag #%@", reactTag);
return;
}
[view goToPreviousPage];
}];
}
RCT_EXPORT_METHOD(goToNextPage:(nonnull NSNumber*) reactTag) {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
NativeView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[NativeView class]]) {
RCTLogError(@"Cannot find NativeView with tag #%@", reactTag);
return;
}
[view goToNextPage];
}];
}
view raw gistfile1.txt hosted with ❤ by GitHub

Here both methods are defined in the RNCMyNativeViewManager.m. Remember, that react bridge adds <rte-code>(nonnull NSNumber*)<rte-code> reactTag as the first parameter of each exported functions. This exported functions will find a particular view using addUIBlock, which contains the viewRegistry parameter and returns the component based on reactTag, allowing it to call the method on the correct component. Now we are sure, that an appropriate view is handled correctly.

Summary:

  • Use ref to create reference to your view component
  • Method <rte-code>UIManager.dispatchViewManagerCommand<rte-code> call exported method from native module
  • Remember to add <rte-code>(nonnull NSNumber*) reactTag<rte-code> as first parameter of exported function
  • Checkout github repo to see example usage

If you like the idea that I described in this article or want to talk about it, please contact me on Twitter. If you need more help in React Native or Open Source, feel free to contact us at Callstack.

Insights

Learn more about iOS

cover

Nov 25Article

Integrating Swift Package Manager With React Native Libraries

Learn how to use Swift Package Manager with React Native libraries, including support for both remote and local SPM packages. This guide explains the spm_dependency helper, monorepo workflows, and current limitations.

cover

Oct 23Live Stream

Building Real-Time Live Activities in Expo

Learn how to add real-time LiveActivity updates to your Expo app using Swift native modules. In this stream, we implemented Apple’s Live Activities with Evan Bacon’s library, explored ActivityKit, and discussed cross-platform development with Solito 5 and Expo UI.

cover

Sep 11Article

How to Use Liquid Glass in React Native

Learn how to bring Apple’s new Liquid Glass design material into your React Native apps. This guide covers design best practices, performance tips, and examples using LiquidGlassView and LiquidGlassContainerView.

cover

Feb 27Article

Exposing SwiftUI Views to React Native: An Integration Guide

Learn how to seamlessly integrate SwiftUI views in React Native and enhance your app’s iOS capabilities with SwiftUI superpowers.

cover

Jan 16Article

Solving Swift Modular Header Issues in React Native for Good

Learn how to resolve Swift modular header errors in React Native. Explore solutions like static frameworks and a custom CocoaPods plugin.

cover

Nov 16Article

React Native Meets iPhoneX

Check this practical guide on adapting your React Native application to the new iPhone X, specifically when using react-native-navigation.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.