如何在 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 和本机组件的那一刻。希望这个示例对某人有用。
在我的 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 和本机组件的那一刻。希望这个示例对某人有用。