Skip to content

Native iOS UI views with a React Native backend.

License

Notifications You must be signed in to change notification settings

houserater/react-native-ui-native

Repository files navigation

React Native UI Native

Native iOS UI views with a React Native backend.

Getting Started

Install React Native UI Native:

$ yarn add react-native-ui-native

Then link it to your project:

$ react-native link

Once the project is linked, ensure that your AppDelegate class stores the RCTBridge as an AppDelegate().bridge property.

// AppDelegate.h
@property (nonatomic, strong) RCTBridge *bridge;

// AppDelegate.m
#import <RNUINative/RNUINativeManager.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // ...

    self.bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation
                                        moduleProvider:nil
                                         launchOptions:launchOptions];

    // ...
}

Note: If you're rendering an RCTRootView for your app, you will replace your existing constructor to utilize the new bridge rather than the jsCodeLocation directly using this initWithBridge::: code.

RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:self.bridge
                                                 moduleName:@"HelloWorld"
                                          initialProperties:nil];

Usage

This project is meant for native apps using React Native as the primary data source for backend services. That means you're able to use advanced features in Xcode & Interface Builder (i.e. Storyboards and XIBs), while still communicating to the JS backend of your application.

There generally are two entry points to native code--the app launching directly to native code...or the RCTBridge triggering a modal or swap of main content into native code.

[RNUINativeManager addEventListener:@"HandlerName.launchNativeModal" eventBlock:^(id data) {
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
    UIViewController *initialVC = [storyboard instantiateInitialViewController];
    
    [self.window.rootViewController presentViewController:initialVC animated:YES completion:nil];
    // or use `self.window.rootViewController = initialVC;` to replace the screen.
} withController:self];

The ^ above code will listen for an event (typically emitted by a button) to launch a modal containing native code. Instead, you can also configure the Main Interface of your project in the Xcode project tab, and remove any self.window code from the AppDelegate as your app launches (since a Main Interface already declares self.window).

Handlers, like the one used above, are defined and imported into your project by extending the RNUINative.Handler class.

// MyHandler.js
import RNUINative from "react-native-ui-native";
import MyService from "../services/MyService";

class MyHandler extends RNUINative.Handler {
    registerEventListeners() {
        MyService.addEventListener('service-event', this.buildEmitter('launchNativeModal'));
    }
    
    async getServiceData() {
        const tableData = await MyService.getData();
        
        return tableData.map((item) => ({
            title: item.title,
            id: item.databaseID
        }));
    }
}
RNUINative.registerHandler('HandlerName', () => MyHandler);

// index.ios.js
import "./handlers/MyHandler";

Don't forget ^ that your index.ios.js file needs to import the handler files (either directly or indirectly), otherwise the JS code will not actually be included by the Metro bundler.

Now, using standard UIViewController subclasses, you can load data and listen to events utilizing the JS bridge with loadData:::.

[RNUINativeManager loadData:@"HandlerName.getServiceData()" arguments:@[ @"arg1", @{"arg2": 1 } ] completionBlock:^(id data, NSError *error) {
    if (error) {
        NSLog(@"Unable to load JS data", error);
        return;
    }
    
    self.myTableData = data;
    [self.tableView reloadData];
}];

You can also send a nil completion block, in order to notify JS code of events occurring natively.

[RNUINativeManager loadData:@"HandlerName.notifyService()", arguments:@[ @"mydata" ] completionBlock:nil];

The project allows you to transmit any JSON-serializable data between JS and native code quickly, with simple JS handlers as entry-points to your JS bridge.

Usage in Swift

Swift controllers provide improved static typing and abstract-ability over Objective-C, and can make the usage of react-native-ui-native even more powerful. Just make sure that you add #import <RNUINative/RNUINativeManager.h> to your Bridging-Header.h file in order to access the project.

Using projects like Unbox or Argo, you can make the decoding of id data much easier.

private var myTableData: [MyTableData]

override func viewDidLoad() {
    RNUINativeManager.loadData(withHandler: "HandlerName.getServiceData", arguments: nil) { data, error in
        if let err = error {
            print("Unable to load JS data \(err)")
            return
        }
        
        do {
            self.myTableData = try unbox(data: data)
            self.tableView.reloadData()
        } catch {
            print("Unable to parse JS data \(error)")
        }
    }
}

You can also listen for events using the bridged addEventListener::: method.

RNUINativeManager.addEventListener("HandlerName.launchNativeModal", eventBlock: { [unowned self] data in
    do {
        let event: MyEvent = try unbox(data: data)
        // ...
    } catch {
        print("Unable to parse JS data \(error)")
    }
}, withController:self)

Finally, notifying JS code about events from native actions can be done using loadData::: in Swift with a nil completion block.

RNUINativeManager.loadData(withHandler: "HandlerName.notifyService()", arguments: [ "mydata" ], completionBlock: nil)

Known Considerations

  • We use withController:self on event listeners, so that the handler can automatically be removed when the view controller (or any generic object) is de-allocated.
  • This project depends on reading the AppDelegate().bridge property, rather than containing some injection function, such as RNUINativeManager.setBridge()
  • This project does not currently have Android support--HouseRater currently only publishes apps for iOS.

Running the Example App

You can run an example project for react-native-ui-native to see how this project works by launching the examples/ios/RNUINativeExamples.xcodeproj file.

In Xcode 10, you may see a build failure the first time you build. This is because of React Native's lack of support for the new Xcode build system. After the first build, Xcode will have ran the postinstall script of react-native, which installs and configures the third-party modules, and therefore allows a successful Xcode build with the new build system.

Notes

  • This module is currently in initial stages of development, and it not recommended for large-scale use

License

MIT

About

Native iOS UI views with a React Native backend.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published