Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 270 additions & 5 deletions docs/_integration-with-existing-apps-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,16 @@ React Native initialization is now unbound to any specific part of an iOS app.

React Native can be initialized using a class called `RCTReactNativeFactory`, that takes care of handling the React Native lifecycle for you.

Once the class is initialized, you can either start a React Native view providing a `UIWindow` object, or you can ask for the factory to generate a `UIView` that you can load in any `UIViewController.`
:::note[`RCTAppDelegate` is deprecated]
`RCTAppDelegate` is deprecated and will be removed in a future version of React Native. New apps should bootstrap from an app-owned `SceneDelegate` (see [Bootstrapping with SceneDelegate](#6-bootstrapping-with-scenedelegate)). Existing apps that subclass `RCTAppDelegate` should migrate to `RCTReactNativeFactory` directly.
:::

Once the class is initialized, you can either:

In the following example, we will create a ViewController that can load a React Native view as it's `view`.
- bootstrap a full app from your `SceneDelegate` (see [Bootstrapping with SceneDelegate](#6-bootstrapping-with-scenedelegate)), or
- generate a `UIView` from the factory and load it in any `UIViewController` (the approach below).

In the following example, we will create a ViewController that can load a React Native view as its `view`.

#### Create the ReactViewController

Expand All @@ -268,7 +275,7 @@ Now open the `ReactViewController.m` file and apply the following changes
+#import <React/RCTBundleURLProvider.h>
+#import <RCTReactNativeFactory.h>
+#import <RCTDefaultReactNativeFactoryDelegate.h>
+#import <RCTAppDependencyProvider.h>
+#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>


@interface ReactViewController ()
Expand Down Expand Up @@ -461,7 +468,226 @@ Finally, make sure to add the `UIViewControllerBasedStatusBarAppearance` key int

![Disable UIViewControllerBasedStatusBarAppearance](/docs/assets/disable-UIViewControllerBasedStatusBarAppearance.png)

## 6. Test your integration
## 6. Bootstrapping with SceneDelegate

:::info[Requires React Native 0.88 or later]
The `SceneDelegate` APIs described in this section require React Native 0.88 or later. Earlier releases do not provide the `connectionOptions:` overloads of `startReactNative` nor the `SceneDelegate` methods on `RCTLinkingManager`.
:::

If your app uses the UIScene lifecycle (the default for apps created with recent versions of Xcode), React Native bootstrap happens in your app-owned `SceneDelegate`. Your `AppDelegate` stays as the process entry point (`@main`) and handles `UIApplication`-level callbacks such as push notifications.
Comment thread
artus9033 marked this conversation as resolved.

To bootstrap React Native with SceneDelegate:

1. Declare `UIApplicationSceneManifest` in `Info.plist` and point it at your `SceneDelegate` class.
2. Keep `UIApplicationSupportsMultipleScenes` set to `false`. React Native does not support multi-window / multi-instance apps yet.
3. Subclass `RCTDefaultReactNativeFactoryDelegate`, conform to `UIWindowSceneDelegate`, create an `RCTReactNativeFactory`, and call `startReactNativeWithModuleName:inWindow:connectionOptions:` from `scene:willConnectToSession:options:`.
4. Forward deep links from your `SceneDelegate` to `RCTLinkingManager`.
5. Keep push notifications and other `UIApplicationDelegate` callbacks on your `AppDelegate`.

### Info.plist

Add a scene manifest similar to the Community Template. At minimum you need:

```xml
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
```

For Objective-C apps, use your SceneDelegate class name directly instead of `$(PRODUCT_MODULE_NAME).SceneDelegate`.

### Create the SceneDelegate

<Tabs groupId="ios-language" queryString defaultValue={constants.defaultAppleLanguage} values={constants.appleLanguages}>
<TabItem value="objc">

Create `SceneDelegate.h`:

```objc title="SceneDelegate.h"
#import <RCTDefaultReactNativeFactoryDelegate.h>
#import <RCTReactNativeFactory.h>
#import <UIKit/UIKit.h>

@interface SceneDelegate : RCTDefaultReactNativeFactoryDelegate <UIWindowSceneDelegate>

@property (nonatomic, strong, nullable) UIWindow *window;
@property (nonatomic, strong, nullable) RCTReactNativeFactory *reactNativeFactory;

@end
```

Create `SceneDelegate.m`:

```objc title="SceneDelegate.m"
#import "SceneDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>

@implementation SceneDelegate

- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions
{
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}

self.dependencyProvider = [RCTAppDependencyProvider new];
self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self];

UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];

[self.reactNativeFactory startReactNativeWithModuleName:@"HelloWorld"
inWindow:self.window
connectionOptions:connectionOptions];
}

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
[RCTLinkingManager scene:scene openURLContexts:URLContexts];
}

- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
{
[RCTLinkingManager scene:scene continueUserActivity:userActivity];
}

- (NSURL *)bundleURL
{
#if DEBUG
return [RCTBundleURLProvider.sharedSettings jsBundleURLForBundleRoot:@"index"];
#else
return [NSBundle.mainBundle URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end
```

</TabItem>
<TabItem value="swift">

Create `SceneDelegate.swift`:

```swift title="SceneDelegate.swift"
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import UIKit

class SceneDelegate: RCTDefaultReactNativeFactoryDelegate, UIWindowSceneDelegate {
var window: UIWindow?
var reactNativeFactory: RCTReactNativeFactory?

func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else {
return
}

dependencyProvider = RCTAppDependencyProvider()
reactNativeFactory = RCTReactNativeFactory(delegate: self)
window = UIWindow(windowScene: windowScene)

reactNativeFactory?.startReactNative(
withModuleName: "HelloWorld",
in: window,
connectionOptions: connectionOptions
)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
RCTLinkingManager.scene(scene, openURLContexts: URLContexts)
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
RCTLinkingManager.scene(scene, continue: userActivity)
}

override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
```

</TabItem>
</Tabs>

### Application delegate responsibilities

Your `AppDelegate` can remain minimal and only handle callbacks that belong to the application object, such as push notifications:

<Tabs groupId="ios-language" queryString defaultValue={constants.defaultAppleLanguage} values={constants.appleLanguages}>
<TabItem value="objc">

```objc title="AppDelegate.m"
#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return YES;
}

@end
```

</TabItem>
<TabItem value="swift">

```swift title="AppDelegate.swift"
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
true
}
}
```

</TabItem>
</Tabs>

:::note
React Native currently supports a single instance per process, so multiple scenes are not supported. Keep `UIApplicationSupportsMultipleScenes` set to `false`.
:::

:::tip[`reactNativeFactory` field]
Store the `RCTReactNativeFactory` in a property on your `SceneDelegate` so that it is retained for the lifetime of the scene. If it is only held by a local variable, it is deallocated as soon as `scene:willConnectToSession:options:` returns and React Native tears down with it.
:::

## 7. Test your integration

You have completed all the basic steps to integrate React Native with your application. Now we will start the [Metro bundler](https://metrobundler.dev/) to build your TypeScript application code into a bundle. Metro's HTTP server shares the bundle from `localhost` on your developer environment to a simulator or device. This allows for [hot reloading](https://reactnative.dev/blog/2016/03/24/introducing-hot-reloading).

Expand Down Expand Up @@ -528,7 +754,7 @@ REACT_NATIVE_XCODE="$REACT_NATIVE_PATH/scripts/react-native-xcode.sh"

Now, if you build your app for Release, it will work as expected.

## 7. Passing initial props to the React Native view
## 8. Passing initial props to the React Native view

In some case, you'd like to pass some information from the Native app to JavaScript. For example, you might want to pass the user id of the currently logged user to React Native, together with a token that can be used to retrieve information from a database.

Expand Down Expand Up @@ -606,6 +832,45 @@ These changes will tell React Native that your App component is now accepting so

### Update the Native code to pass the initial properties to JavaScript.

#### SceneDelegate bootstrap

<Tabs groupId="ios-language" queryString defaultValue={constants.defaultAppleLanguage} values={constants.appleLanguages}>
<TabItem value="objc">

Modify `SceneDelegate.m` to pass initial properties when starting React Native:

```diff title="SceneDelegate.m"
[self.reactNativeFactory startReactNativeWithModuleName:@"HelloWorld"
inWindow:self.window
+ initialProperties:@{
+ @"userID": @"12345678",
+ @"token": @"secretToken"
+ }
connectionOptions:connectionOptions];
```

</TabItem>
<TabItem value="swift">

Modify `SceneDelegate.swift` to pass initial properties when starting React Native:

```diff title="SceneDelegate.swift"
reactNativeFactory?.startReactNative(
withModuleName: "HelloWorld",
in: window,
+ initialProperties: [
+ "userID": "12345678",
+ "token": "secretToken"
+ ],
connectionOptions: connectionOptions
)
```

</TabItem>
</Tabs>

#### Embedded view controller

<Tabs groupId="ios-language" queryString defaultValue={constants.defaultAppleLanguage} values={constants.appleLanguages}>
<TabItem value="objc">

Expand Down
1 change: 1 addition & 0 deletions docs/fabric-native-components-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Podfile
...
Demo
├── AppDelegate.swift
├── SceneDelegate.swift
...
// highlight-start
├── RCTWebView.h
Expand Down
2 changes: 1 addition & 1 deletion docs/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ Image decoding can take more than a frame-worth of time. This is one of the majo

## Configuring iOS Image Cache Limits

On iOS, we expose an API to override React Native's default image cache limits. This should be called from within your native AppDelegate code (e.g. within `didFinishLaunchingWithOptions`).
On iOS, we expose an API to override React Native's default image cache limits. Call this early during app startup from your `SceneDelegate` (in `scene:willConnectToSession:options:`) or from `AppDelegate` if you are not using the UIScene lifecycle (for example, within `application:didFinishLaunchingWithOptions:`).

```objectivec
RCTSetImageCacheLimits(4*1024*1024, 200*1024*1024);
Expand Down
Loading