React-Native iOS - How can I navigate to a non-React-Native view (native iOS view controller) from a React-Native view with a button press?

前端 未结 3 1823
清歌不尽
清歌不尽 2020-12-03 05:37

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条回答
  • 2020-12-03 06:31

    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.

    0 讨论(0)
  • 2020-12-03 06:33

    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:

    1. 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.

    2. MyViewController.swift - A normal UIViewController with a standard implementation.

    3. MyProject-Bridging-Header.h - provides Obj-C <-> Swift communication

    4. ChangeViewBridge.h/.m - This provides the binding to allow you to call native iOS methods from the RN JavaScript.

    5. 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);
    
    0 讨论(0)
  • 2020-12-03 06:34
    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 like below.**
    
        // Dismiss the VC so controll go back from iOS to react-native
            [self dismissViewControllerAnimated:TRUE completion:nil];
    
    0 讨论(0)
提交回复
热议问题