将React Native集成到Xamarin项目中

7

我被委派来探索将React Native集成到Xamarin.Forms项目中的可能性。

我认为我已经接近实现它了,但我不能确定。我知道这有点奇怪/反向的解决方案,但无论如何我想试一试看我能否做到...

介绍
我的雇主希望看看是否可以使用React Native进行UI设计,并使用C#进行业务逻辑。正在探索这种解决方案,以便UI / UX团队可以使用RN生成工作,我们(开发团队)可以将其链接到逻辑中。

到目前为止我尝试过的方法
我接着拿到React Native输出的Xcode项目开始,通过在终端中cd到项目目录并运行react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios(摘自这篇博客文章)来删除本地Node服务的依赖项。然后我更改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编译器进入我的库并尝试进行魔法操作,在React Native项目中引发了一系列链接错误,如下图所示:

enter image description here
完整构建输出的Pastebin转储

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

总结
我知道讲得挺长,但如果我们能启动这个项目,那么基本上可以使用C#完成所有复杂的业务逻辑,并将所有UI变化留给专门的UI团队,他们强烈倾向于React Native(因为他们的原型设计非常好)。实际上,这只是我正在准备下一个主要版本的应用程序的另一个POC而已。
如果有人能想到更好的方法,请告诉我。当然,我已经忽略了一些细节,如果需要澄清任何内容,请问我并进行修改。
非常感谢。
Luke


1
你在这个领域有更进一步的发展吗?我也是一样。我想要使用React或NativeScript来处理用户界面,而使用F#来进行逻辑处理。 - mamcx
1
@mamcx 啊。我以为我已经回复你了 - 可能是被分心了。基本上,不行。它有点消失了,我停止把所有的时间都花在上面了。我们已经换了平台,所以自那以后我就没有想过它 - 抱歉 :/ - mylogon
2个回答

11

以下是我采用的步骤,最终实现了功能。由于内容较多,如果我遗漏了某些细节,请您见谅。

构建静态库

  1. 在Xcode中创建一个Cocoa Touch Static Library项目。
  2. 在同一目录下安装React Native。

  3. npm install react-native
    
  4. 将所有React Xcode项目添加到您的项目中。 (截图)您可以查看现有React Native应用程序的.pbxproj文件,以了解如何找到所有这些内容。
  5. 将React添加到目标依赖项构建阶段。 (截图
  6. 在“链接二进制文件和库”构建阶段中包括所有React目标。 (截图
  7. 确保在“其他链接器标志”构建设置中包括-lc ++
  8. 使用lipo创建通用库(fat文件)。 请参见Xamarin文档中的“构建通用本机库”部分。

创建Xamarin应用程序

  1. 在Visual Studio中创建新的iOS Single View App项目/解决方案。(截图
  2. 向解决方案添加iOS Bindings Library项目。 (截图
  3. 将您的通用静态库作为本机引用添加到绑定库项目中。
  4. 在本机引用的属性中将框架设置为JavaScriptCore ,将链接器标志设置为-lstdc ++(这修复了原始问题中提到的链接器错误。)还启用强制加载。 (截图
  5. 将以下代码添加到ApiDefinition.cs中。请务必为SystemFoundationUIKit包含using语句。

  6. // @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
    {
    
    }
    
    将以下代码添加到 Structs.cs。确保包含 SystemSystem.Runtime.InteropServicesFoundationusing语句。
    [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);
    }
    
  7. 在应用项目中添加对绑定库项目的引用。
  8. 将以下代码添加到AppDelegate.cs文件的FinishedLaunching方法中。不要忘记为您的绑定库命名空间添加using语句并指定您的React Native应用程序的名称。

  9. 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();
    
    将以下内容添加到Info.plist文件中。
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>localhost</key>
            <dict>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
        </dict>
    </dict>
    

现在,您应该能够通过在相应目录中启动React Packager(react-native start)来运行任何React Native应用程序。以下部分将向您展示如何从React Native调用C#。

创建本机模块

  1. 将类添加到iOS应用程序项目中。
  2. 使该类继承RCTBridgeModule(来自您的绑定库)。

public class TestClass : RCTBridgeModule
ModuleName方法添加到您的类中。将返回的值更改为您想要在JavaScript中调用的类名称。您可以指定空字符串以使用原始名称。
[Export("moduleName")]
public static string ModuleName() => "TestClass";
RequiresMainQueueSetup 方法添加到您的类中。我认为 如果您实现了本机(UI)组件,则需要返回true
[Export("requiresMainQueueSetup")]
public static bool RequiresMainQueueSetup() => false;
编写您希望导出的方法(从JavaScript调用)。这是一个示例。
[Export("test:")]
public void Test(string msg) => Debug.WriteLine(msg);
对于每个导出的方法,编写一个提供有关该方法信息的附加方法。这些方法的名称将需要以 "__rct_export__" 开头。其余部分可以是任何唯一的名称。我只能找到通过返回 IntPtr 而不是 RCTMethodInfo 来使其工作的方法。以下是一个示例。
[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;
}
  • jsName是您想从JavaScript调用该方法的名称。您可以指定空字符串来使用原始名称。
  • objcName是该方法在Objective-C中的等效签名。
  • 我不确定isSync是什么。

在AppDelegate.cs中启动视图之前注册您的类。类名称将是具有下划线而非点的完全限定名称。以下是一个示例。

CFunctions.RCTRegisterModule(ObjCRuntime.Class.GetHandle("ReactTest_TestClass"));

从JavaScript调用本地模块

  1. 在JavaScript文件中导入NativeModules模块。

import { NativeModules } from 'react-native';
调用您导出的方法之一。
NativeModules.TestClass.test('C# called successfully.');

1
我连读完都还没,就已经兴奋不已了。10/10的答案。最终,我们选择了Unity,出乎意料地解决了这个专业问题。然而,这正好为我的下一个个人项目提供了帮助。感谢你们的时间和努力。 - mylogon
@mylogon 我意识到我漏掉了几个东西,所以我更新了我的答案。 - Kenny McClive
这里有一份来自Facebook的好指南,教你如何手动链接所有内容:https://facebook.github.io/react-native/docs/linking-libraries-ios.html - Mando
@AlexeyStrakh 我认为我遇到了类似的问题。我从未弄清楚如何从命令行构建它。我使用Xcode GUI构建各种体系结构,然后使用lipo将二进制文件组合成一个fat文件。如果您能解决这个问题,请告诉我们。 - Kenny McClive
@AlexeyStrakh 你想了解什么?如何创建Xcode项目还是如何构建fat文件? - Kenny McClive
显示剩余5条评论

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接