探索如何将 SwiftUI 集成到 React Native 应用

发表于 3年以前  | 总阅读数:627 次

2015 年,React Native 为跨平台移动开发带来了一种声明式组件方法。不久,这种面向组件的理念扩展到了类似的框架。现在,借助 SwiftUI 和 Jetpack Compose,这种声明式方法也可以在 native 平台上实现了。

SwiftUI 是一个 UI 框架,它将声明式组件方法引入到包括 iOS,macOS 和 watchOS 在内的 Apple 平台。

它可以与其先前的 UIKit 互换使用,这意味着开发人员可以将新框架无缝集成到其现有应用程序中。不过混合 UIKit 和 SwiftUI 需要中间件来桥接它们。

今天,我们将编写一个代理,允许开发人员在其 React Native 应用程序中使用 SwiftUI。

请记住,SwiftUI 仅在 iOS 13.0 及更高版本上可用(在撰写本文时,iOS 13 安装率达到了 94%)。如果您的应用程序必须支持以前的 iOS 版本,则不能使用 SwiftUI。

1.初始设置

让我们首先创建一个带有 native UI 组件的 React Native 应用程序(目前,一个简单的 UIView 就可以了)。

如果您不熟悉如何将 native 组件桥接到 React Native,强烈建议您先阅读文档中的相应文章,然后再继续。初始设置的代码也可以在 GitHub[1] 上找到。

2.桥接 Swift

当我们有了一个初始设置的应用程序后,我们可以创建一个 Swift 类(SwiftUIButtonProxy),该类将充当 SwiftUI 视图和 RCTViewManager 之间的代理。

在 Objective-C 项目中创建第一个 Swift 类之后,Xcode 会询问您是否要它创建桥接头文件。它要求将任意的 Objective-C 代码公开给我们以后使用的 Swift,因此请务必选择 Yes。

新视图类似于以下:

// SwiftUIButtonProxy.swift
@objc class SwiftUIButtonProxy : UIView {}

现在,让我们来修改 RCTViewManager 以便渲染它:

// SwiftUIButtonManager.m
-(UIView *)view {
  return [[SwiftUIButtonProxy alloc] init];
}

在这一步,您可能会注意到 Xcode 开始抱怨没有找到 SwiftUIButtonProxy。

发生这种情况的原因是,没有导入可访问 Swift 结构信息的头文件,Objective-C 无法识别任何 Swift 类:

#import "<ProjectName>-Swift.h"

请注意,<ProjectName> 只是您的项目名称的占位符。例如,如果您的项目名为 “FooBar”,则导入应如下所示:

#import "FooBar-Swift.h"

你可能会困惑并没有这个头文件。*.xcworkspace*.xcodeproj 中没有相应的文件,但是它是存在的。Xcode 将在构建步骤中根据您的 Swift 代码库的可见性来构建它。

导入头文件后,错误消失了,并且应用程序能够正常构建。如果您在执行此步骤后遇到任何问题,可以在我的教程库2中检出 “proxy-class” tag。

3.渲染 SwiftUI

尽管渲染 Swift 的视图应该非常简单,但桥接 SwiftUI 却并非如此。由于设计限制,从 UIKit 运行 SwiftUI 的唯一方法是使用UIHostingController。

到目前为止,我还没有提到在应用程序中使用 UIViewController 或任何其他控制器。实际上,这通常会使学习 React Native 的人感到困惑。

您会看到,在 native 开发中,ViewController 在管理应用程序的视图和数据中起着重要作用。在 React Native 中,native 组件是由 JavaScript 来接管。

但是,如果我们构建一个复杂的 native 组件,或者桥接一个 SwiftUI 视图,那么学习如何使用它们可能会派上用场。

如前所述,可以使用 UIHostingController 将 SwiftUI 视图层次结构集成到现有的应用程序中。它充当 UIKit 和 SwiftUI 框架之间的中介者,因此可以将 SwiftUI 视图呈现为 UIKit 视图层次结构的一部分。

让我们创建一个新的 SwiftUI 视图,并将其命名为 SwiftUIButton。

// SwiftUIButton.swift
struct SwiftUIButton : View {
  var body: some View {
    Text("Hello, world!")
  }
}

然后,我们调整 SwiftUIButtonProxy 类以使用新视图创建 UIHostingController。由于我们将创建一个视图控制器并公开其视图,因此无需让代理类继承自 UIView。

但是,由于我们无法将不继承自 NSObject 的类公开给 Objective-C,因此我们将 UIView 替换为 NSObject 以使其可访问。

// SwiftUIButtonProxy
@objcMembers class SwiftUIButtonProxy: NSObject {
  private var vc = UIHostingController(rootView: SwiftUIButton())
var view: UIView {
    return vc.view
  }
}

相应的管理类也需要更改:

// SwiftUIButtonManager.m
- (UIView *)view {
  SwiftUIButtonProxy *proxy = [[SwiftUIButtonProxy alloc] init];
  return [proxy view];
}

如您所见,我们没有返回 SwiftUIButtonProxy 本身,而是将其替换为通过 UIHostingController 代理的 SwiftUI 视图。

恭喜你!您刚刚桥接了第一个 SwiftUI 视图。

  1. 如何处理属性

大多数 native 组件都实现配置属性和回调。在本部分的教程中,我们将桥接两个简单的属性:count: IntonCountChange: RCTBubblingEventBlock

这可能是本教程中最复杂的部分。数据转发并非易事。

首先,我们无法使用标准的 RCT_EXPORT_VIEW_PROPERTY 宏,因为它会生成尝试访问视图实例的设置器。不幸的是,SwiftUI 视图无法直接暴露给Objective-C,因为 Swift 的 struct 没有相应的类型。

这就把我们推到这样一个点,即数据传递的唯一方式是通过自定义可以访问两个运行时的 Swift 代理:Objective-C 从 JavaScript 接收视图属性,而 Swift 将这些属性设置给相应的 SwiftUI 视图。

听起来很复杂,但请不要担心!下面是它的工作原理:

native 组件的管理器通过特殊的设置器接收新属性,这些设置器由宏(如 RCT_EXPORT_VIEW_PROPERTY)在预编译例程期间生成。

每次使用上述宏时,都会为包裹的属性生成一个自定义设置器,因此,当您从 JavaScript 发送一组新的属性时,React Native 会检查在管理器中是否有与新属性对应的设置器。

类似于 RCT_EXPORT_VIEW_PROPERTY 的宏会生成属性设置器,当用户将新属性分配给 native 组件的 JavaScript 表达式时,将调用这些属性设置器。

由于我们无法使用 RCT_EXPORT_VIEW_PROPERTY,因此我建议创建自己的宏。按照 RCT_EXPORT_VIEW_PROPERTY 命名约定,我将其称为 RCT_EXPORT_SWIFTUI_PROPERTY 和 RCT_EXPORT_SWIFTUI_CALLBACK。

这些宏均利用 RCT_CUSTOM_VIEW_PROPERTY 的功能,该功能允许开发人员创建自定义设置器。

如果您决定使用它们,请记住它们仅在以下条件下起作用:

• 您有一个代理类,该类提供了称为 storage 的静态 NSMutableDictionary。

• RCT_EXPORT_SWIFTUI_PROPERTY 仅支持基本属性,例如 int,string,bool 等。复杂类型需要正确的转换,并且超出了本文的讨论范围

如前所述,每个自定义宏都依赖于 React Native 的 RCT_CUSTOM_VIEW_PROPERTY 宏,该宏允许开发人员为属性创建自定义设置器。该宏需要一个用于定义设置器的块。

在该块的范围内,React Native 提供了三个参数:

• UIView *view -— 属性的接收者。

• UIView *defaultView -— 默认视图。

• id json —- 指向接收到的数据的指针。

我们主要对 json 和 view 感兴趣。你可能会注意到 view 的类型是不同的。UIHostingController 暴露给桥接的是一个普通的 UIView,而不是 SwiftUI 视图。不幸的是,这还不够。

要设置新的属性,我们需要获得对实际 SwiftUI 视图的引用。此时唯一的方法是通过 UIHostingController,但控制器本身却无法做到……

为了解决这个问题,我创建了一个 key:value 存储(NSMutableDicrionary)并将其作为静态属性分配给 SwiftUIButtonProxy。

现在,如果在创建视图时存储了一个 view:proxy 对,我将可以访问 setter 中的所需的 proxy。由于我们的代理是 UIHostingController 的容器,因此无需费劲就能获得对 SwiftUI 视图的正确引用。

但是,请不要忘记我们是在 Objective-C 进行操作的,这使得无法直接操作 SwiftUI 视图属性。

如果我们在 SwiftUIButtonProxy 类中定义了自定义 count 和 onCountChange 属性,则可以解决此限制,以便它们使用控制器的 rootView 属性传播新值:

// SwiftUIButtonProxy.swift
var count: Int {
  set { vc.rootView.props.count = newValue }
  get { return vc.rootView.props.count }
}

var onCountChange: RCTBubblingEventBlock {
  set { vc.rootView.props.onCountChange = newValue }
  get { return vc.rootView.props.onCountChange }
}

手动为每个属性编写 getter 和 setter 有点麻烦(特别是如果您有几个属性的话),但这是我发现的最简单的方法。

另外,请注意此处的 props:SwiftUI 组件有多种状态,包括本地状态 @State,对外暴露状态 @ObservedObject (props) 和共享状态@EnvironmentObject (context)。

在此示例中,我使用 @ObservedObject,因为我们需要从外部设置值。在 SwiftUI 视图中,它看起来像这样:

// SwiftUIButton.swift
@ObservedObject var props = ButtonProps()

现在,让我们向视图添加一个按钮。每次用户单击该按钮时,我们要使用新的递增计数器值调用 props.onCountChange。经过一些样式上的更改,SwiftUIButton 代码可能类似于以下:

struct SwiftUIButton : View {
  @ObservedObject var props = ButtonProps()

  var body: some View {
    VStack {
      Text("Count \(props.count)")
        .padding()

      Button(action: {
        self.props.onCountChange(["count": self.props.count + 1])
      }) {
        Image(systemName: "plus.circle.fill")
          .foregroundColor(.white)
          .padding()
          .background(Color.red)
          .clipShape(Circle())
      }
    }
    .padding()
    .clipShape(Circle())
  }
}

现在,为了结束本示例,我们只需要补充两个缺少的部分:ButtonProps 和 JavaScript 部分:

class ButtonProps : ObservableObject {
  @Published var count: Int = 0
  @Published var onCountChange: RCTDirectEventBlock = { _ in }
}

ButtonProps 是一个 ObservableObject,它保存我们从 JavaScript 传递过来的值。如果您对 @Published 感到困惑,那很好。该指令创建一个绑定,该绑定告诉 SwiftUI 如果已发布的属性之一已更改,则重新渲染。

JavaScript 部分的代码如下:

import React, {useState} from 'react';
import {requireNativeComponent} from 'react-native';
const SwiftUIButton = requireNativeComponent('SwiftUIButton');
const App = () => {
  const [count, updateCount] = useState(0);
  return (
    <SwiftUIButton
      style={styles.container}
      count={count}
      onCountChange={e => updateCount(e.nativeEvent.count)}
    />
  );
};

就是这些!这是一条漫长坎坷的路,但您已成功完成。

通过一些小的调整,例如在 SwiftUI 视图中添加一个按钮并使用回调函数连接相应的数据,您就会拥有一个从 0 到 正无穷大计数的应用程序。

最终的代码(带有一些本机UI调整)可以在教程库[3]中的 work-example 标记下找到。

5.总结

我们只是将一个 SwiftUI 组件桥接到一个 React Native 应用程序中 -- 尽管这开辟了巨大的可能性,但这个主题依然具有挑战性。

如果您需要时间考虑一下,那就很好。我花了几个不眠之夜想出一种方法,而且坦率地说,我不确定这是否是最好的方法。

解决方案的整体复杂性使我寄希望于将来在发布新的 React Native 架构时这些事情会变得更加容易。

参考

[1]https://github.com/Kureev/ReactNativeWithSwiftUITutorial/releases/tag/initial-setup [2]https://github.com/Kureev/ReactNativeWithSwiftUITutorial [3]https://github.com/Kureev/ReactNativeWithSwiftUITutorial

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/fdHeLqg8CIPa9bZz-qxb0Q

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录