React Native —在本机Swift视图上调用类方法

与React Native的工作非常像我认为结束粗俗婚姻的样子。 首先,这是所有的激情:

“只有几个Javascript文件,我有一个iOS和一个Android应用程序? 我一生都在哪里?”

随着时间的流逝,困难逐渐消失,您的应用变得越来越复杂

“但是我现在准备拥有多个有效的发行版! 我们可以等多久?”

或事情莫名其妙地停止工作。

“我更新为0.50.1,现在突然无法解析模块`AccessibilityInfo`? 你变了,我几乎不认识你了……”

当您开始在应用程序中实现本机模块和UI组件时,真正的挫败感就会来临。 尚无针对所有内容的JS React Native解决方案,有时您不得不烦恼底层的Android Studio和XCode项目。 令人愤慨的是,文档中对此功能的解释还不够。

当我不得不在应用程序中实现本机视频播放器时遇到麻烦。 经过一番努力后,我得以展示它,但我似乎还不太清楚如何在此本机组件上调用方法。 经过数小时的眼泪和啤酒,我终于弄明白了,并想与世界分享,这样您就不必经历我的痛苦。 我还没有准备好与React Native离婚。 让我们为孩子们(组件)聚在一起。

步骤1:创建一个新项目

本教程假定您熟悉创建和与React Native项目进行交互。 该项目的最终版本可以在GitHub上找到。

  react-native init MediumSwiftNativeExample 
cd MediumSwiftNativeExample
反应本机运行iOS

此时,您应该在iOS模拟器中看到默认的React Native屏幕。

步骤2:喝啤酒(您将需要啤酒)

步骤3:修改App.js

接下来,打开您的App.js文件,并将其替换为以下内容:

 从'react'导入React,{组件}; 
导入ReactNative,{
平台,
StyleSheet,
文本,
视图,
requireNativeComponent,
TouchableOpacity,
UIManager
}来自“ react-native”;
 导出默认类App扩展Component  { 
  render(){ 
返回(

<this.mySwiftComponent
myText = {'初始值'}
ref = {(component)=> this.mySwiftComponentInstance = component} />

);
}
}
 const styles = StyleSheet.create({ 
容器: {
弹性:1,
justifyContent:“中心”,
alignItems:'中心',
backgroundColor:'#F5FCFF',
}
});
  const SwiftComponent = requireNativeComponent('SwiftComponent'); 

如果尝试按原样运行该应用程序,则应看到:

好。 这很有意义,因为我们还没有创建本机组件SwiftComponent 。 让我们来照顾它。

步骤4:修改XCode项目

启动XCode,然后转到“文件”->“打开”。 选择MediumSwiftNativeExample/ios/MediumSwiftNativeExample.xcodeproj 。 这将在XCode中打开React Native应用程序的基础iOS项目。

我是组织的傻瓜,所以让我们在项目中创建一个新组(没有基础文件夹):

将其命名为MySwiftComponent。

我们将在此组中维护所有新文件。 如果我正在从事更大的项目,则可以使用文件夹创建该组,但这取决于您。

这是魔术开始发生的地方。

步骤5:创建Swift和Bridge文件

接下来,选择File-> New-> File…-> Swift File。 单击下一步,并将其命名为SwiftComponentManager 。 最后,单击创建。 您将看到一个弹出窗口,询问您是否要配置桥接头。

单击“创建桥接标题”。 这非常重要,因为如果您现在不创建它,我不知道如何使此提示返回。 我敢肯定有一种方法可以手动创建桥接头,但这是我不想解决的另一个麻烦。

打开新的SwiftComponentManager.swift文件,并将其内容替换为:

  @objc(SwiftComponentManager) 
类SwiftComponentManager:RCTViewManager {
  } 

只是为了拉屎,请尝试构建该项目。 您应该看到以下错误。

一切都非常合理,因为我们需要导入必要的Objective-C类,以便我们的Swift文件使用它们。 因此,打开桥接文件(MediumSwiftNativeExample-Bridging-Header.h)并将其内容替换为

  // 
//使用此文件导入要公开给Swift的目标的公共标头。
//
#import

繁荣。 如果执行干净(CMD + Shift + K)和另一个构建(CMD + B),则该项目应成功构建。 但是,这里我们要处理的是XCode,所以如果您遇到有关Mach-O Linker问题或其他无法解释的错误的消息,我不会感到惊讶。

(旁注:Apple员工实际上无法自己真正使用XCode,对吗?这就像XCode和Android Studio竞争着看谁可以成为最讨厌的糟糕IDE软件)

步骤6:再喝一杯啤酒

步骤7:实施RCTViewManager

无论如何,假设您已成功构建项目,请再次打开SwiftComponentManager.swift并将其修改为如下所示。

  @objc(SwiftComponentManager) 
类SwiftComponentManager:RCTViewManager {

覆盖func view()-> UIView! {
让labelView = MyLabelView()
labelView.textColor = UIColor.orange
labelView.textAlignment = NSTextAlignment.center
返回labelView
}
}
 类MyLabelView:UILabel { 

私人var _myText:String?
var myText:字符串? {
设置{
_myText = newValue
self.text = newValue
}
得到{
返回_myText
}
}
  } 

每当您首次渲染自定义本机组件时,React Native都会调用view()函数。 在此函数中,您必须创建并返回UIView的某些子类。 我们正在创建自己的UILabel子类MyLabelView,它本身是UIView的子类。

MyLabelView具有可以通过Javascript设置的属性。 这是myText属性。 使用Swift的速记设置器声明,我们可以将myText暴露给外界。 我们将实际值存储在名为_myText的私有_myText

如果尝试构建项目,则会看到与以前相同的红色错误(“ SwiftComponent的本地组件不存在”)。 是时候告诉React Native它可以使用的这个新视图了。

第8步:公开组件以响应本机

这是我们真正开始涉足Swift的未记录内容的地方。

转到文件->新建->文件…,然后选择Objective-C文件(带有“ m”图标)。 将其命名为SwiftComponentModule.m并将其更新为如下所示。

  #import  
#import
  @interface RCT_EXTERN_MODULE(SwiftComponentManager,RCTViewManager) 
  RCT_EXPORT_VIEW_PROPERTY(myText,NSString) 
  @结束 

RCT_EXPORT_VIEW_PROPERTY很不言自明,但这允许通过JS render()方法在本机组件上设置属性。 如果进行清理和构建,现在应该在应用程序中看到该组件!

步骤9:在MyLabelView上定义一个类方法

现在,我们可以呈现静态UI组件,我们甚至知道如何在这些组件上设置自定义属性(即myText属性)。 如果您热衷于在React Native中仅使用.setState()来更新和更改UI元素,则可以将setter用作调用其他逻辑的方式,但这对我来说有点麻烦。

将以下内容作为类方法添加到MyLabelView:

  func updateValue(){ 
self.backgroundColor = UIColor.red
self.myText =“更新的原始值!”
}

现在,我们希望从Javascript调用此方法,而不必调用.setState和更新属性。 (是的,我知道这是一个简单的例子,我们可以使用二传手)。 打开SwiftComponentModule.m并将其更改为如下所示:

  #import  
#import
  @interface RCT_EXTERN_MODULE(SwiftComponentManager,RCTViewManager) 
  RCT_EXPORT_VIEW_PROPERTY(myText,NSString) 
RCT_EXTERN_METHOD(updateValueViaManager :(非空NSNumber *)节点)
  @结束 

不幸的是,我无法找到与RCT_EXPORT_VIEW_PROPERTY等效的方法。 因此,我们必须通过Manager而非View本身来实现。 因此,请返回SwiftComponentManager.swift并将以下方法添加到SwiftComponentManager类中。

  func updateValueViaManager(_ node:NSNumber){ 
DispatchQueue.main.async {
让myLabel = self.bridge.uiManager.view(forReactTag:node)为! MyLabelView
myLabel.updateValue()
}
}

如果尝试构建,则会收到错误消息:

我的朋友们,这才是我真正开始尝试的地方。 根据我在源代码中阅读的内容以及一些博客文章/ GitHub的评论,我应该能够使用self.bridge的uiManager属性。 但是它不会显示出来。 经过数小时的尝试后,我随机尝试添加有效的导入。 在MediumSwiftNativeExample-Bridging-Header.h使其如下所示:

  // 
//使用此文件导入要公开给Swift的目标的公共标头。
//
#import
#import
#import
#import

清理并构建,然后TADA! 它应该工作!

我从没想过要添加这些导入,因为在Java或ES6 Javascript中,从导入中使用的类的任何实例都可以访问其所有公共属性。 由于某些原因,Swift + Objective-C不会这样。 您必须导入要使用的每个属性的每个类。 我想Swift无法跨过Objective-C桥梁并弄清楚它需要知道哪些其他类。 无论如何,将这些行添加到桥接头中会使uiManager神奇地显示出来。

步骤10:从Javascript调用方法

剩下的就是在App.js中添加用于调用该方法的逻辑。

 从'react'导入React,{组件}; 
导入ReactNative,{
平台,
StyleSheet,
文本,
视图,
requireNativeComponent,
TouchableOpacity,
UIManager
}来自“ react-native”;
 导出默认类App扩展Component  { 
  render(){ 
返回(

<SwiftComponent
宽度= {360}
身高= {180}
myText = {'初始值'}
ref = {(component)=> this.mySwiftComponentInstance = component} />

{'使用本地值更新'}


);
}
  onButtonClick(e){ 
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(this.mySwiftComponentInstance),
UIManager.SwiftComponent.Commands.updateValueViaManager,
[]
);
}
}
  const styles = StyleSheet.create({ 
容器: {
弹性:1,
justifyContent:“中心”,
alignItems:'中心',
backgroundColor:'#F5FCFF',
},
按钮:{
marginTop:50,
宽度:120,
高度:60,
borderRadius:12
borderWidth:1
borderColor:'#F0F',
backgroundColor:“#FFF”
}
});
  const SwiftComponent = requireNativeComponent('SwiftComponent'); 

最终,当您单击(非常丑陋)按钮时,将发生以下情况:

  1. App.js的onButtonClick函数被称为
  2. 依次调用UIManager的dispatchViewManagerCommand并传递本机视图的基础节点ID和要调用的命令。
  3. updateValueViaManager是有效命令,因为它是由SwiftComponentModule.m中的RCT_EXTERN_METHOD公开的。
  4. SwiftComponentManager的updateValueViaManager被调用并接收基础节点ID。
  5. 在主UI线程上,SwiftComponentManager使用self.bridge.uiManager.view(nodeId)查找MyLabelView的正确实例。
  6. 最后,它在其上调用.updateValue()

然后我们走了。 我们从React Native的Swift的本机视图中成功调用了一个类方法。 满嘴

步骤11:喝庆祝啤酒

你赚了

结论

使用React Native常常是一件痛苦的事情。 上述某些步骤在官方文档中找不到。 要弄清楚这样的事情,您需要耐心并且真正深入地研究GitHub的评论和博客文章。

非常感谢以下博客文章使我接近终点:

  • https://moduscreate.com/blog/swift-modules-for-react-native/
  • JP在https://medium.com/@jpdriver/vending-pure-swift-views-in-react-native-3f417349e3c6中的帖子

同样,可以在GitHub上的https://github.com/johndanek/MediumSwiftNativeExample中找到该项目。 如果发现任何错误,请随时向我提交请求请求或发表评论。 我敢肯定,某些​​较新版本的React Native会在任何时候破坏一切。