整合 NativeScript 代码与 Swift 代码
只要 Swift 的结构能够暴露给 Objective-C,NativeScript 就可以访问它们,并且操作起来非常直接明了。在 NativeScript 中,我们可以完全访问 Java、Kotlin、Objective-C 和 Swift 这些平台原生 API。每种平台语言都带来了自己独特的语法结构。使用 Swift 时,只要你打算使用的语法结构将其类型暴露给了 Objective-C,你就可以使用它的全部功能。
那么,如果某个 Swift 代码库中的结构没有暴露给 Objective-C,我们该如何将其集成到我们的 NativeScript 应用中呢?
示例代码库 (Pod)
我们将要使用的示例 Swift 代码库是这个:github.com/SwiftKickMo… ,我们需要集成它。它能以多种方式便捷地展示信息卡片。
同时,这个库也无法从 Objective-C 中调用。
搭建项目并添加代码库 (Pod)
我们先创建一个示例项目:
ns create SwiftSample --template @NativeScript/template-hello-world-ts
cd SwiftSample
ns run ios
这样我们就得到了那个可爱的“点击按钮”界面。
添加代码库 (Pod)
现在,我们在应用中添加对这个 Pod 的引用:
- 在
App_Resources/iOS/目录下创建一个名为Podfile的文件。 - 在文件中添加以下文本:
pod 'SwiftMessages'
类型定义在哪里?
此时,我们可以生成类型定义文件,从而获得对 Pod 内容的强类型访问。
ns typings ios
这将在 typings/ios/<architecture> 目录下生成一个文件。
我们确实会得到一个 objc!SwiftMessages.d.ts 文件,但这里有个问题。
如果我查阅 SwiftMessages 的文档,一个简单的用法是 SwiftMessages.show(view: myView),但在我们的类型定义文件里,SwiftMessages 类在哪里呢?文件里根本找不到。问题就出在这里:SwiftMessages 类并没有暴露给 Objective-C,因此无法从 NativeScript 中访问。您可以在此处了解更多信息。
包装它!
那么解决方案是什么呢?借助 NativeScript 的强大功能,我们可以:
- 在项目中放入我们自己的 Swift 文件。
- 让这个文件可以被 Objective-C 访问。
- 然后,通过它来调用代码库 (Pod) 中类的方法。
首先,我们在项目中添加一个新的 Swift 文件,路径为App_Resources/iOS/src/NSCSwiftMessages.swift。然后添加一些代码(代码复制自 SwiftMessages 的 GitHub 示例),以便我们能从 NativeScript 中调用它。
注意: 您可以在这里使用任何类前缀,但我们建议使用 NSC 前缀。这是为了确保您的类不会与大量存在于 iOS 代码中的 NS 前缀类发生冲突,而这些 NS 前缀源于整个 iOS 平台的底层 NeXTStep 系统。您可以将 NSC 理解为 “NativeScript” 或 “NativeScript Compiler” 的缩写,这样可能更容易记住。同时,这也帮助规范了那些仅为 NativeScript 而暴露的平台代码的命名约定。
// 导入 Foundation 和 SwiftMessages 框架
import Foundation
import SwiftMessages
// 使用 @objcMembers 和 @objc 注解将此类及其成员暴露给 Objective-C。
// 类名在 Objective-C 中将显示为 NSCSwiftMessages。
@objcMembers
@objc(NSCSwiftMessages)
public class NSCSwiftMessages: NSObject {
// 我们可以从 TypeScript 中为此属性赋值一个回调函数。
@objc public var onDoneCallBack: ((String)-> Void)? = nil;
// 定义一个公开方法,用于从 NativeScript 调用,传入标题和正文。
public func showMessage(title: String, body: String) {
// 从预设的 nib 文件布局中实例化一个消息视图。SwiftMessages 会优先在主包中查找 nib 文件,
// 因此您可以轻松地将它们复制到您的项目中并进行修改。
let view = MessageView.viewFromNib(layout: .cardView)
// 使用成功 (success) 样式主题来配置消息元素。
view.configureTheme(.success)
view.button?.isHidden=true; // 隐藏按钮
// 为视图添加阴影效果。
view.configureDropShadow()
view.button?.isHidden=true; // 再次隐藏按钮 (重复行)
// 设置消息的标题、正文和图标。这里,我们用一个随机的表情符号覆盖默认的警告图片。
let iconText = ["🤔", "😳", "🙄", "😶"].randomElement()!
view.configureContent(title: title, body: body, iconText: iconText)
// 增加卡片周围的外部边距。通常,此设置的效果取决于给定布局如何约束于布局边距。
view.layoutMarginAdditions = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
// 减小圆角半径(适用于具有圆角特性的布局)。
(view.backgroundView as? CornerRoundingView)?.cornerRadius = 10
// 创建一个 SwiftMessages 配置对象。
var config = SwiftMessages.Config()
// 指定一个或多个事件监听器,以响应显示和隐藏事件。
config.eventListeners.append() { event in
if case .didHide = event { // 当消息被隐藏时
// 如果回调函数已设置,则执行它。
self.onDoneCallBack?("Message Alert Hidden");
}
}
// 显示消息。
SwiftMessages.show(config: config, view: view)
}
}
这里重要的地方在于,我们通过添加 @objcMembers 和 @objc 注解,将我们的 Swift 代码暴露给了 Objective-C。现在,我们可以再次运行类型定义生成命令,这次会出现一个新文件 objc!nsswiftsupport.d.ts。在这个新文件里,我们就能看到我们新建的类的类型定义了:
declare class NSCSwiftMessages extends NSObject {
static alloc(): NSCSwiftMessages; // inherited from NSObject
static new(): NSCSwiftMessages; // inherited from NSObject
onDoneCallBack: (p1: string) => void;
showMessageWithTitleBody(title: string, body: string): void;
}
我们将这个文件复制到项目的根目录(或其他任何合适的位置),并在 ./references.d.ts 文件中添加一行对该文件的引用。
/// <reference path="./node_modules/@nativescript/types/index.d.ts" />
/// <reference path="./objc!nsswiftsupport.d.ts" />
调用我们的新代码
现在我们可以调用我们的代码了。在 app/main-view-model.ts 文件中,将 tap 方法修改为:
onTap() {
// 创建一个我们编写的 Swift 类的实例。
const message = NSCSwiftMessages.new();
// 指定一个回调函数,以便在消息关闭时收到通知。
message.onDoneCallBack = (msg: string) => { this.message = msg; };
// 显示实际的消息。
message.showMessageWithTitleBody("This is the Title", "Hello There!");
}
现在,当我们运行应用并点击按钮时,我们漂亮的新消息就会显示出来!
并且当消息被隐藏时,我们会收到一个回调,这个回调会改变界面上标签的文字。
这个示例项目可以在 GitHub 上找到。
如何在 NativeScript 中使用 Swift 或 Objective C 委托
iOS 委托是非常有用且基础的概念,在创建自定义平台行为时必须掌握。让我们来看看如何在 NativeScript 中创建和使用委托。
什么是委托(Delegate)?
委托是指当某些重要事件发生时应当收到通知的任意对象。这些"重要事件"的具体含义取决于上下文环境:比如,表格视图的委托会在用户点击某一行时收到通知,而导航控制器的委托则会在用户在不同视图控制器间切换时收到通知。
ColorPicker 示例
让我们来看看 UIColorPickerViewController,它可以提供一种方式来展示用户可以选择的颜色选项。当控制器检测到用户选择了颜色时,它需要一种方式来告知您的应用程序所选择的颜色。它是通过委托来实现这一点的。苹果为不同的用途提供了不同的协议。对于 UIColorPickerViewController,它提供了 UIColorPickerViewControllerDelegate 协议。
使用这个委托,让我们在 NativeScript 中更新 StackLayout 的背景色。为了从选择器接收选定的颜色,我们要遵循 UIColorPickerViewControllerDelegate 协议,而且总是在两个阶段来进行控制器 / 委托设置。
注意:请务必了解 NativeScript 中委托使用的重要最佳实践:委托、委托、委托!!
第 1 阶段:创建委托实现
创建一个委托实现类,我们称之为ColorPickerDelegateImpl,它继承自 NSObject 并遵循(即implements)委托协议 UIColorPickerViewControllerDelegate。
@NativeClass()
class ColorPickerDelegateImpl
extends NSObject
implements UIColorPickerViewControllerDelegate {
// 告知 NativeScript 连接协议
static ObjCProtocols = [UIColorPickerViewControllerDelegate];
// 最佳实践:拥有者弱引用
owner: WeakRef<HelloWorldModel>;
// 使用静态初始化实现的常见模式
static initWithOwner(owner: WeakRef<HelloWorldModel>) {
const delegate = <ColorPickerDelegateImpl>ColorPickerDelegateImpl.new();
delegate.owner = owner;
return delegate;
}
// 在此处实现委托方法 ...
}
当使用 NativeScript 创建平台类实现时,我们总是用@NativeClass()装饰它们。@NativeClass()装饰器确保符合 NativeScript 运行时的要求,您可以在此处了解更多相关信息。
我们通常继承 NSObject,因为它提供了我们的实现所需的所有常见基础 iOS 行为,同时也是因为我们的委托只是一个协议,也就是interface,无法被扩展(只能由实现来遵循)。
静态数组static ObjCProtocols可以包含我们希望实现使用的任意数量的协议,并告知 NativeScript 代表我们连接指定的协议。
一个常见的最佳实践是允许委托实现在拥有者弱引用。拥有者是与此委托进行通信的类。您可以在此处了解更多关于 WeakRef 的信息。
实例化实现类有很多方法,但使用 static initWithOwner(owner: WeakRef<HelloWorldModel>) 模式已经变得相当普遍,因为它允许您在不干扰平台(父级)构造函数链的情况下,根据需要将其他引用作为附加方法参数传递。值得注意的是,ColorPickerDelegateImpl.new()是一个便捷的简单构造函数,NativeScript 会将其添加到所有平台类中,避免处理特定的初始化参数(您可能希望稍后处理),并返回一个 NSObject,这就是为什么我们可以简单地将其转换为我们类型<ColorPickerDelegateImpl>的原因。
当希望在委托和其拥有者之间来回传递事件和数据时,拥有者弱引用就发挥作用了。让我们通过实现 UIColorPickerViewControllerDelegate 提供的几种方法来实现这一点:
import { Color } from '@nativescript/core';
@NativeClass()
class ColorPickerDelegateImpl
extends NSObject
implements UIColorPickerViewControllerDelegate
{
// ...
// all delegate methods come from Apple documentation:
// https://developer.apple.com/documentation/uikit/uicolorpickerviewcontrollerdelegate#3635512
colorPickerViewControllerDidFinish(
viewController: UIColorPickerViewController
) {
// did close/finish event
this.owner?.deref()
.changeColor(Color.fromIosColor(viewController.selectedColor));
}
colorPickerViewControllerDidSelectColorContinuously(
viewController: UIColorPickerViewController,
color: UIColor,
continuously: boolean
) {
// selecting colors event
}
}
第 2 阶段:使用委托
创建将使用您的委托的控制器:
const picker = UIColorPickerViewController.alloc().init();
初始化我们上面创建的委托实现,同时按照最佳实践将其分配给实例属性:
this.colorDelegate = ColorPickerDelegateImpl.initWithOwner(
new WeakRef(this)
);
设置控制器所需的委托属性:
picker.delegate = this.colorDelegate;