将React Native集成到Xamarin项目中

时间:2016-12-08 17:02:57

标签: xamarin react-native

我的任务是查看是否可以将React Native集成到Xamarin.Forms项目中。
我认为我已经非常接近实现这一点,但我无法肯定地说。我知道这是一个奇怪/倒退的解决方案,但无论如何我还想去看看它是否可以击败它...

简介
我的雇主想要看看是否可以使用React Native for UI并使用C#作为业务逻辑。它正在作为一种解决方案进行探索,以便UI / UX团队可以与RN合作,我们(开发团队)可以将逻辑链接到它。

到目前为止我尝试了什么
我通过将终端的本地节点服务的依赖性移除到项目目录并运行react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios(取自this博客文章),获取了React Native输出和启动的Xcode项目。然后,我更改了AppDelegate行,寻找main.jsbundle文件。
然后我添加了一个静态库作为项目的目标。与应用程序的构建阶段相比,我添加了所有相同的链接库enter image description here
在此之后,我创建了一个Xamarin.Forms解决方案。因为我只创建了iOS库,所以我创建了一个iOS.Binding项目。我添加了Xcode .a lib作为本机引用。在ApiDefinition.cs文件中,我使用以下代码

创建了接口
BaseType(typeof(NSObject))]
    interface TheViewController
    {
        [Export("setMainViewController:")]
        void SetTheMainViewController(UIViewController viewController);
    }

在Xcode项目中,创建了一个TheViewController类。 setMainViewController:以下列方式实施:

-(void)setMainViewController:(UIViewController *)viewController{

  AppDelegate * ad = (AppDelegate*)[UIApplication sharedApplication].delegate;

  NSURL * jsCodeLocation = [NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:@"main" ofType:@"jsbundle"]];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"prototyper"
                                               initialProperties:nil
                                                   launchOptions:ad.savedLaunchOptions];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  ad.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  viewController.view = rootView;
  ad.window.rootViewController = viewController;
  [ad.window makeKeyAndVisible];
}

我有效地尝试从Xamarin传递UIViewController以获取React Native的内容以添加自己。
我用以下方式从Xamarin.iOS中调用它:

private Binding.TheViewController _theViewController;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            _theViewController = new TheViewController();
            _theViewController.SetTheMainViewController(this);
        }

这个类正在实现PageRenderer,覆盖了Xamarin.Forms' ContentPage使用

[assembly:ExportRenderer(typeof(RNTest.MainViewController), typeof(RNTest.iOS.MainViewController))]

好吧,经过所有这些,我去了部署到设备,并且,预计会遇到一些错误。 AOT编译器进入我的lib并尝试做它的魔法并在React Native项目中抛出许多链接器错误,如下所示。

enter image description here
Pastebin dump of full Build Output

我打算在绑定中设置更多方法来设置回调等,以开始构建一些关于使用Objective-C来回传递信息的功能,我将通过一些本机代码链接传递给React Native 。

摘要
我知道它已经很长时间了,但是如果我们能够实现这一点,那么我们基本上可以在C#中完成所有(相当复杂的)业务逻辑,并将所有UI更改留给专门的UI团队,对React Native有强烈的偏好(足够公平,他们的原型状况非常好)。真的,它只是我为应用程序的下一个主要版本组装的另一个POC。
如果有人能想到更好的方法,我会全力以赴。当然,我已经对一些细节进行了釉面处理,所以如果有任何需要澄清的话,请询问,我会推荐。
很多,非常感谢。
路加

2 个答案:

答案 0 :(得分:8)

我能够通过以下步骤实现这一目标。这里有很多,所以如果我错过了一个细节,请原谅我。

构建静态库

  1. 在Xcode中创建 Cocoa Touch静态库项目。
  2. 在同一目录中安装React Native。

    npm install react-native
    
  3. 将所有React Xcode项目添加到项目中。 (Screenshot)您可以查看现有React Native应用程序的.pbxproj文件,以获取有关如何查找所有这些内容的线索。
  4. 将React添加到目标依赖关系构建阶段。 (Screenshot
  5. Link Binary With Libraries 构建阶段中包含所有React目标。 (Screenshot
  6. 请务必在其他链接标记构建设置中加入-lc++
  7. 使用lipo创建通用库(胖文件)。请参阅Xamarin documentation
  8. 中的构建通用本机库部分

    创建Xamarin应用程序

    1. 在Visual Studio中创建新的iOS 单一视图应用项目/解决方案。 (Screenshot
    2. 将iOS Bindings Library 项目添加到解决方案中。 (Screenshot
    3. 将通用静态库添加为绑定库项目的本机引用。
    4. 在本机参考的属性中将框架设置为JavaScriptCore,将链接标记设置为-lstdc++修复了原始问题中提到的链接器错误。还启用强制加载。 (Screenshot
    5. 将以下代码添加到ApiDefinition.cs。请确保包含usingSystemFoundation的{​​{1}}个语句。

      UIKit
    6. 将以下代码添加到Structs.cs。请确保包含// @interface RCTBundleURLProvider : NSObject [BaseType(typeof(NSObject))] interface RCTBundleURLProvider { // +(instancetype)sharedSettings; [Static] [Export("sharedSettings")] RCTBundleURLProvider SharedSettings(); // -(NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName; [Export("jsBundleURLForBundleRoot:fallbackResource:")] NSUrl JsBundleURLForBundleRoot(string bundleRoot, [NullAllowed] string resourceName); } // @interface RCTRootView : UIView [BaseType(typeof(UIView))] interface RCTRootView { // -(instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions; [Export("initWithBundleURL:moduleName:initialProperties:launchOptions:")] IntPtr Constructor(NSUrl bundleURL, string moduleName, [NullAllowed] NSDictionary initialProperties, [NullAllowed] NSDictionary launchOptions); } // @protocol RCTBridgeModule <NSObject> [Protocol, Model] [BaseType(typeof(NSObject))] interface RCTBridgeModule { } usingSystem的{​​{1}}个语句。

      System.Runtime.InteropServices
    7. 在应用项目中添加对绑定库项目的引用。
    8. 将以下代码添加到AppDelegate.cs中的Foundation方法。不要忘记为绑定库的命名空间添加[StructLayout(LayoutKind.Sequential)] public struct RCTMethodInfo { public string jsName; public string objcName; public bool isSync; } public static class CFunctions { [DllImport ("__Internal")] public static extern void RCTRegisterModule(IntPtr module); } 语句,并指定React Native应用程序的名称。

      FinishedLaunching
    9. 将以下内容添加到Info.plist。

      using
    10. 此时,您应该能够通过在相应目录中启动React Packager(var jsCodeLocation = RCTBundleURLProvider.SharedSettings().JsBundleURLForBundleRoot("index", null); var rootView = new RCTRootView(jsCodeLocation, "<Name of your React app>", null, launchOptions); Window = new UIWindow(UIScreen.MainScreen.Bounds); Window.RootViewController = new UIViewController() { View = rootView }; Window.MakeKeyAndVisible(); )来运行任何React Native应用程序。以下部分将向您展示如何从React Native调用C#。

      创建本机模块

      1. 在iOS应用项目中添加课程。
      2. 让类继承<key>UIViewControllerBasedStatusBarAppearance</key> <false/> <key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>localhost</key> <dict> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict> (来自绑定库)。

        react-native start
      3. RCTBridgeModule方法添加到您的班级。将返回的值更改为您想在JavaScript中调用该类的任何值。您可以指定空字符串以使用原始字符串。

        public class TestClass : RCTBridgeModule
        
      4. ModuleName方法添加到您的班级。我认为如果您实现本机(UI)组件,则需要返回[Export("moduleName")] public static string ModuleName() => "TestClass";

        RequiresMainQueueSetup
      5. 编写要导出的方法(从JavaScript调用)。这是一个例子。

        true
      6. 对于您导出的每个方法,请编写一个返回有关它的信息的其他方法。每个方法的名称都需要以[Export("requiresMainQueueSetup")] public static bool RequiresMainQueueSetup() => false; 开头。只要它是唯一的,名称的其余部分无关紧要。我能找到的唯一方法是返回[Export("test:")] public void Test(string msg) => Debug.WriteLine(msg); 而不是__rct_export__。以下是一个例子。

        IntPtr
        • RCTMethodInfo是您要从JavaScript调用方法的名称。您可以指定空字符串以使用原始字符串。
        • [Export("__rct_export__test")] public static IntPtr TestExport() { var temp = new RCTMethodInfo() { jsName = string.Empty, objcName = "test: (NSString*) msg", isSync = false }; var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(temp)); Marshal.StructureToPtr(temp, ptr, false); return ptr; } 是该方法的等效 Objective-C 签名。
        • 我不确定jsName是什么。
      7. 在AppDelegate.cs中启动视图之前注册您的课程。类的名称将是带有下划线而不是点的完全限定名称。这是一个例子。

        objcName
      8. 从JavaScript调用本机模块

        1. isSync导入您的JavaScript文件。

          CFunctions.RCTRegisterModule(ObjCRuntime.Class.GetHandle("ReactTest_TestClass"));
          
        2. 调用您导出的其中一种方法。

          NativeModules

答案 1 :(得分:1)

以下是用于创建胖静态库和Xamarin绑定项目的完全自动化脚本,以将其包装到Xamarin绑定.dll中:

https://github.com/voydz/xamarin-react-native