The RN doco and other examples show how to launch a React-Native view from a native iOS view controller, but not the other way around. Can someone explain how I can do this?
3 Answers
I was able to figure this out. In my case, I am using an Obj-C base project (which is the RN default) with my own Swift native view controller. My solution is here in case this comes up for anyone else:
Simply put, the answer is to use an RCTBridge module to allow the RN JavaScript to call a native iOS method.
Here is an outline of the components, followed by the implementation:
AppDelegate.h
/.m
- Initialize the RN JavaScript index file for the initial RN view, also setup a method to swap the root view controller to a native view controller (this method will be called from the RTCBridge module.MyViewController.swift
- A normalUIViewController
with a standard implementation.MyProject-Bridging-Header.h
- providesObj-C <->
Swift communicationChangeViewBridge.h
/.m
- This provides the binding to allow you to call native iOS methods from the RN JavaScript.index.ios.js
- Initialize your custom RCTBridge module and call the bound method to switch to your native view with a button press.
AppDelegate.h
:
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate> {
NSDictionary *options;
UIViewController *viewController;
}
@property (nonatomic, strong) UIWindow *window;
- (void) setInitialViewController;
- (void) goToRegisterView; // called from the RCTBridge module
@end
AppDelegate.m
:
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "FidoTestProject-Swift.h" // Xcode generated import to reference MyViewController.swift from Obj-C
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
options = launchOptions;
[self setInitialViewController];
return YES;
}
- (void) setInitialViewController {
NSURL *jsCodeLocation;
jsCodeLocation = [NSURL URLWithString:@"http://192.168.208.152:8081/index.ios.bundle?platform=ios&dev=true"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"FidoTestProject" initialProperties:nil launchOptions:options];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
viewController = rootViewController;
[self.window makeKeyAndVisible];
}
// this method will be called from the RCTBridge
- (void) goToNativeView {
NSLog(@"RN binding - Native View - MyViewController.swift - Load From "main" storyboard);
UIViewController *vc = [UIStoryboard storyboardWithName:@"main" bundle:nil].instantiateInitialViewController;
self.window.rootViewController = vc;
}
@end
MyViewController.swift
:
class RegisterViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("MyViewController loaded...")
// standard view controller will load from RN
}
}
MyProject-Bridging-Header.h
:
@import Foundation;
@import UIKit;
@import CoreLocation;
@import AVFoundation;
#import "React/RCTBridge.h"
#import "React/RCTBridgeModule.h"
#import "React/RCTBundleURLProvider.h"
#import "React/RCTRootView.h"
#import "AppDelegate.h"
ChangeViewBridge.h
:
#import <React/RCTBridgeModule.h>
@interface ChangeViewBridge : NSObject <RCTBridgeModule>
- (void) changeToNativeView;
@end
ChangeViewBridge.m
:
#import "RegisterBridge.h"
#import "FidoTestProject-Swift.h"
#import "AppDelegate.h"
@implementation ChangeViewBridge
// reference "ChangeViewBridge" module in index.ios.js
RCT_EXPORT_MODULE(ChangeViewBridge);
RCT_EXPORT_METHOD(changeToNativeView) {
NSLog(@"RN binding - Native View - Loading MyViewController.swift");
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate goToNativeView];
}
@end
index.ios.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Alert,
Text,
View,
NativeModules,
TouchableHighlight
} from 'react-native';
export default class FidoTestProject extends Component {
constructor(props) {
super(props)
this.done = false;
}
_changeView() {
this.done = true;
this.render();
NativeModules.ChangeViewBridge.changeToNativeView();
}
render() {
if (!this.done) {
return (
<View style={styles.container}>
<TouchableHighlight onPress={() => this._changeView()}>
<Text color="#336699">
Press to Change to Native View
</Text>
</TouchableHighlight>
</View>
);
} else {
return (<View></View>);
}
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('FidoTestProject', () => FidoTestProject);

- 511
- 2
- 6
- 18
-
IXUAFDelegate from where we find this delegate? – seggy Jul 11 '19 at 14:21
-
1Thanks for pointing that out @seggy. This is a custom delegate I missed when I was copy/pasting my example code out of my working project. I have edited the post to remove it from the example. – MStrapko Aug 21 '19 at 14:43
-
See the updated answer from Simeryn Denis below: https://stackoverflow.com/a/63372245/5474176 – MStrapko Aug 12 '20 at 14:24
-
The default for RN modules is a static library target, so I don't understand how you can use an AppDelegate and Storyboard?? wouldn't that require an Application target? when I tried this I got an error that "AppDelegate is not in scope". – mutable2112 Nov 02 '20 at 10:08
-
I'm not getting the native VC and there's no error as well...Could you please help me on this – Ephrim Daniel Feb 10 '21 at 05:31
-
I know its been a long time, but is the `-void (goToRegisterView)` in `AppDelegate.h` meant to be `-void (goToNativeView)` instead? – Jarrett Feb 28 '21 at 03:11
-
How is this saying going to this particular view controller (MyViewController)? – Neo Aug 11 '21 at 22:15
An update to this answer with Swift 5. Thanks to
https://github.com/facebook/react-native/issues/1148#issuecomment-102008892
https://stackoverflow.com/a/46007680/7325179 - answer by MStrapko
https://codersera.com/blog/react-native-bridge-for-ios/?unapproved=2851&moderation-hash=77e42524b246d2fda0f763a496156db5#comment-2851 - an elaborate explanation and tutorial by William Dawson
Getting into the Solution:
In AppDelegate.swift
import Foundation
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let jsCodeLocation: URL
jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.js", fallbackResource:nil)
let rootView = RCTRootView(bundleURL: jsCodeLocation, moduleName: "RNModuleName", initialProperties: nil, launchOptions: launchOptions)
self.window = UIWindow(frame: UIScreen.main.bounds)
let reactNativeViewController = UIViewController()
reactNativeViewController.view = rootView
let reactNavigationController = UINavigationController(rootViewController: reactNativeViewController)
self.window?.rootViewController = reactNavigationController
self.window?.makeKeyAndVisible()
return true
}
// func goToReactNative() {
// window?.rootViewController?.dismiss(animated: true)
// }
func goNativeStoryboard() {
DispatchQueue.main.async {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
if let vc = vc {
(self.window?.rootViewController as? UINavigationController)?.pushViewController(vc, animated: true)
}
}
}
}
YourViewController.swift
Your regular code
YourApp-Bridging-Header. Please note there are some extra headers as well, that you might not need.
#import "React/RCTBridgeModule.h"
#import "React/RCTBridge.h"
#import "React/RCTEventDispatcher.h"
#import "React/RCTRootView.h"
#import "React/RCTUtils.h"
#import "React/RCTConvert.h"
#import "React/RCTBundleURLProvider.h"
#import "RCTViewManager.h"
#import "React/RCTEventEmitter.h"
ConnectingFile.swift
@objc(Connect)
class Connect: NSObject {
@objc func goToNative() -> Void {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.goNativeStoryboard()
}
}
}
}
Connect.m
#import "React/RCTViewManager.h"
@interface RCT_EXTERN_MODULE(Connect, RCTViewManager)
RCT_EXTERN_METHOD(goToNative)
@end
ReactNativeFile.js
import React, { Component } from 'react';
import { StyleSheet, View, NativeModules, Text, TouchableOpacity } from 'react-native';
const { Connect } = NativeModules;
export default class Feed extends Component {
constructor(props) {
super(props)
this.done = false;
}
_changeView() {
this.done = true;
Connect.goToNative()
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => this._changeView()}>
<Text color="#336699">
Press to Change to Native View
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'pink',
alignItems: 'center',
justifyContent: 'center',
}
});
That was it, it worked for me, hope it works for you as well. Thanks again to all the sources of references.

- 168
- 1
- 10
There is little improvement on this solution.With present solution there is no way to come back to React-Native from iOS.
If you want to come back again from iOS to React-Native.Do the below
// AppDelegate.h
- (void) goToNativeView {
UIViewController *vc = [InitialViewController new];// This is your native iOS VC
UINavigationController* navigationController = [[UINavigationController alloc] initWithRootViewController:vc];
dispatch_async(dispatch_get_main_queue(), ^{
// Never do the below, it will be difficult to come back to react-native
// self.window.rootViewController = navigationController;
// Do this instead
[self.window.rootViewController presentViewController:navigationController animated:true completion:NULL];
});
}
//InitialViewController.m
Create a button for go back to React-native and on button action dismiss this view controller:
// Dismiss the VC so controll go back from iOS to react-native
[self dismissViewControllerAnimated:TRUE completion:nil];

- 17,741
- 7
- 42
- 75

- 871
- 1
- 12
- 11
-
From my original question: "The RN doco and other examples show how to launch a React-Native view from a native iOS view controller, but not the other way around." There are many available examples of how to go from native to RN. This question and answer were to show how to go from RN to native. – MStrapko Aug 22 '19 at 21:04