如何在 NativeScript 7 中使用原生 SwiftUI 视图

How to use a native SwiftUI View in NativeScript 7

在我的 NativeScript (Angular) 应用程序中,我使用 RadListView 创建一个列表,每个元素都有许多不同的信息要显示。看起来是这样

由于 Whosebug 和其他来源的许多提示,我尽可能地减少了嵌套布局(StackLayout、GridLayout 等)的数量,以使 RadListView 更快。在 Android 上使用列表的性能比在 iOS 上好得多。使用 iPad Pro (2020),滚动时列表的渲染不流畅。如果用户改变设备的方向,屏幕会冻结并且在侧面或底部出现黑条片刻。冻结时间取决于每行显示的元素数量。 ListView 中的相同行布局要快得多,但与原生 (SwiftUI) 不同,并且缺少滑动和拉动刷新等功能。

对不起歌词,但我想一些背景解释了为什么我尝试下一步。

为了改善用户体验,我使用 SwiftUI 和几乎相同的行布局制作了一个小型本机测试应用程序。感觉好多了,首次加载速度快,滚动流畅,方向变化没有延迟。我的下一个想法是在 SwiftUI 中为 show/render RadListView 的每一行创建一个原生组件(如果可能)

<RadListView [items]="items">
    <ListViewLinearLayout tkListViewLayout></ListViewLinearLayout>
    <ng-template tkListItemTemplate let-item="item" let-i="index" let-odd="odd">
        <MyNativeSwiftUIComponentElement data="item.rowData"></MyNativeSwiftUIComponentElement>
    </ng-template>
</RadListView>

或使用 SwiftUI 中的列表 show/render 整个列表

<ActionBar title="Objects"></ActionBar>
<MyNativeSwiftUIListComponent data="items"></MyNativeSwiftUIListComponent>

寻找文档和示例很困难。我发现这个非常简短的建议 Adding Objective-C/Swift code and the linked tutorial there for Objective-C (Adding Objective-C Code to a NativeScript App) and some questions on Whosebug but there all about classes and not SwiftUI (with struct and views). One question was about SwiftUI: Is it possible to display a View written with SwiftUI with NativeScript 不幸的是答案对我没有帮助(顺便说一句。感谢@Manoj 在 Whosebug 对 NativeScript 的大力支持!)。

如何在我的 {N} 应用程序中使用 SwiftUI 视图作为本机组件? 有没有人提供提示,link 到教程或 link 到 public 存储库以获得 app/plugin?欢迎任何小费。

您或许可以使用 Nativescript 的 placeholder 组件(有关该组件的更多信息 here

所以你会在你的模板上有 Placeholder 标签,并使用 creatingView 事件来添加原生 UI

<Placeholder creatingView="creatingView"/>
import { CreateViewEventData } from "@nativescript/core";

export function creatingView(args: CreateViewEventData) {
    let nativeView = new UILabel();  // where this would be your native UI
    nativeView.text = "Native";
    args.view = nativeView;
}

一段时间后,我放弃了在项目 ({N}+Angular) 中直接使用 SwiftUI 的尝试,而是尝试 <Placeholder> @William-Juan 建议的组件。但看起来 <Placeholder> 在 Angular 风格中不受官方支持 - 请参阅 github issue #283

为了继续,我查看了 NativeScript 插件的示例并构建了一个可行的解决方案。如果有人感兴趣,完整的示例源代码在这个存储库中:https://github.com/teha-at/sample-nativescript-native-ui-component

首先,创建一个 class 扩展 @nativescript/core/View class 并有一个项目来获取将要显示的数据。

// object-list-item.d.ts
// [...]
export class ObjectListItem extends View {
    item: ObjectModel;
}

export const itemProperty: Property<ObjectListItem, string>;

然后创建一个抽象基础 class,它还扩展了 @nativescript/core/View class,这为 Android 和 iOS[=25= 创建了基础]

// object-list-item.common.ts
// [...]

export const itemProperty = new Property<ObjectListItemBase, string>({
    name: 'item',
    defaultValue: null,
    affectsLayout: isIOS,
});

export abstract class ObjectListItemBase extends View {
    item: PortalObjectModel;
}

// defines 'item' property on the ObjectListItemBase class
itemProperty.register(ObjectListItemBase);

ObjectListItemBase.prototype.recycleNativeView = 'auto';

因为我只是在寻找 iOS 的组件,所以 object-list-item.android.ts 非常简单:

// object-list-item.android.ts
import { ObjectListItemBase } from './object-list-item.common';
export class ObjectListItem extends ObjectListItemBase {}

对于iOS有更多的行,完整的文件内容请看github repo

/// object-list-item.ios.ts
// [...]

export class ObjectListItem extends ObjectListItemBase {

    // added for TypeScript intellisense.
    nativeView: UIView;
    
    // [...]

    /**
     * Creates new native button.
     */
    public createNativeView(): Object {
        const mainUiStackView = UIStackView.new();
    // [...]
    }

    /**
     * Initializes properties/listeners of the native view.
     */
    initNativeView(): void {
        // Attach the owner to nativeView.
        // When nativeView is tapped we get the owning JS object through this field.
        (<any>this.nativeView).owner = this;
        super.initNativeView();
    }

    /**
     * Clean up references to the native view and resets nativeView to its original state.
     * If you have changed nativeView in some other way except through setNative callbacks
     * you have a chance here to revert it back to its original state
     * so that it could be reused later.
     */
    disposeNativeView(): void {
        // Remove reference from native listener to this instance.
        (<any>this.nativeView).owner = null;

        // If you want to recycle nativeView and have modified the nativeView
        // without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
        // you have to reset it to its initial state here.
        super.disposeNativeView();
    }

    [itemProperty.setNative](item: ObjectModel) {
        this.item = item;
        // [...]
    }

}

添加 Angular 指令

// object-list-item.directives.ts
@Directive({
    selector: 'ObjectListItem',
})
export class ObjectListItemDirective {
}

export const ObjectListItemDirectives = [ObjectListItemDirective];

至少在 Angular 模块中注册组件。

// object-list-item.module.ts
// [...]

@NgModule({
    imports: [],
    declarations: [
        ObjectListItemDirectives,
    ],
    schemas: [NO_ERRORS_SCHEMA],
    exports: [
        ObjectListItemDirectives,
    ],
    entryComponents: [],
})
export class ObjectListItemModule {
}

registerElement('ObjectListItem', () => ObjectListItem);

完成所有这些步骤后,在模板中调用新组件

<!-- [...] -->
<RadListView #myListView [items]="items$ | async">
  <ng-template tkListItemTemplate let-item="item">
    <StackLayout margin="0" padding="0" class="-separator m-y-5" height="90">
      <android>
        <!-- [...] -->
      </android>
      <ios>
          <ObjectListItem [item]="item"></ObjectListItem>
      </ios>
    </StackLayout>
  </ng-template>
</RadListView>
<!-- [...] -->

所有这些工作都花得值。 UI 速度更快,感觉更像是本机应用程序。同时我在Swift和SwiftUI中构建了一个原型作为原生iOS应用程序,当然这个纯原生应用程序更流畅一些,但在我使用 {N}-App 和本机组件的那一刻。希望这个示例对某人有用。