返回博客首页
← 所有文章

使用 TypeScript 开发 Vision Pro 🥽 应用

2023 年 10 月 10 日 — 作者 技术指导委员会 (TSC)

NativeScript 8.6 为使用您喜欢的任何 JavaScript 版本开发 Vision Pro 应用提供了支持。它使您能够以令人兴奋的方式将 JavaScript 开发实践与 SwiftUI 混合使用。这篇文章将重点关注仅使用 TypeScript 以及 xml 视图标记的纯味开发。

有关其他偏好,请查看以下文章:

有关更多详细信息,请参考 使用 visionOS 开发指南

创建一个 visionOS TypeScript 应用

我们提供带有 vision 标签的 npm 包,以便在您为 visionOS 开发时保持事物的清晰区分。

我们将逐步完成一个相当复杂的 visionOS“Hello World”教程,以演示 NativeScript 8.6 支持的突破性开发可能性。

选择从 Apple 教程中“下载”源代码,因为我们将直接与提供的 SwiftUI 示例集成。

  1. 安装:npm install -g nativescript@vision
  2. 创建应用:ns create myapp --vision

我们现在拥有一个可以进行开发的 Vision Pro 项目。

构建“Hello World”主窗口

我们可以使用 TypeScript 构建此屏幕

Vision Pro Hello World Main Window

我们将需要使用从 visionOS“Hello World”教程 提供的一些资产,因此如果您已下载该项目,我们可以将几个资产移动到我们的 Vision Pro 项目中以供使用。

将以下资产从下载的示例中拖放:

  • EarthHalf.imageset
  • GlobeHero.imageset
  • SolarBackground.imageset
  • SolarHero.imageset
  • Starfield.imageset
  • SunSliver.imageset
  • TrailGradient.imageset

App_Resources/visionOS/Assets.xcassets

当我们运行我们的 Vision Pro 项目时,NativeScript CLI 将为我们配置这些资产,以便我们能够在开发中使用这些资产。

我们现在可以按照以下步骤构建主窗口的背景:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <GridLayout>
    <GridLayout>
        <Image src="res://SunSliver" class="align-top w-full" iosOverflowSafeArea="true" stretch="aspectFit" scaleY="1.6" scaleX="1.6" translateY="30"/>
        <Image src="res://EarthHalf" class="align-bottom w-full" iosOverflowSafeArea="true" stretch="aspectFill" scaleY="2" scaleX="2" translateY="540"/>
    </GridLayout>
  </GridLayout>
</Page>

当我们运行:ns run vision --no-hmr 时,我们应该看到以下内容:

Vision Pro Hello World Background

将 SwiftUI 与 NativeScript 混合使用

“Hello World”教程在显示主窗口内容之前使用了一种“打字机风格”的动画,当应用启动时,我们可以使用 NativeScript 动画 API 来创建这种动画,但我们也有 很多选择。例如,我们还可以将 SwiftUI 用于标题动画,以及我们由 TypeScript驱动的开发。

您可以通过两个步骤将任何 SwiftUI 集成到您的 NativeScript 项目中

  1. 创建一个提供者来使用标题动画。
  2. 在您的视图标记中使用 <SwiftUI />

只需确保您已安装插件 npm install @nativescript/swift-ui@vision

我们可以通过将任何 .swift 代码放在 App_Resources/{platform}/src 目录(位于任何 Apple 支持的平台内)中,将它移动到我们的 NativeScript 应用中。

由于我们正在为 visionOS 开发,因此我们可以将 .swift 文件从“Hello World”教程移动到 App_Resources/visionOS/src,NativeScript CLI 将构建我们的应用中用于任何地方的 .swift 文件。

了解有关将 SwiftUI 与 NativeScript 集成的更多信息,请访问 此处。您也可以在 Xcode 中打开 platforms/visionos/{project}.xcodeproj 来开发任何 .swift 文件。您将在 Xcode 中的 NSNativeSources 文件夹中找到位于 App_Resources/visionOS/src 中的任何本地源代码。

来自 Apple 教程的 TitleText 可以如下提供给 NativeScript:

import SwiftUI
import UIKit

@objc
class TitleViewProvider: UIViewController, SwiftUIProvider {
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  required public init() {
    super.init(nibName: nil, bundle: nil)
  }

  public override func viewDidLoad() {
    super.viewDidLoad()
    setupSwiftUIView(content: IntroText(finished: {
        self.onEvent?([:])
    }))
  }

  func updateData(data: NSDictionary) {}
  var onEvent: ((NSDictionary) -> ())?
}

private struct TitleText: View {
    var title: String
    var body: some View {
        Text(title)
            .monospaced()
            .font(.system(size: 50, weight: .bold))
    }
}

@Observable
class TitleViewModel {
    var titleText: String = ""
    var isTitleFinished: Bool = false
    var finalTitle: String = "Hello World"
}

struct IntroText: View {
    @State var model = TitleViewModel()
    var finished: (() -> Void)

    var body: some View {
        @Bindable var model = model
        
        // A hidden version of the final text keeps the layout fixed
        // while the overlaid visible version types on.
        VStack {
            // A hidden version of the final text keeps the layout fixed
            // while the overlaid visible version types on.
            TitleText(title: model.finalTitle)
                .padding(.horizontal, 70)
                .hidden()
                .overlay(alignment: .leading) {
                    TitleText(title: model.titleText)
                        .padding(.leading, 70)
                }
            Text("Discover a new way of looking at the world.")
                .font(.title)
                .opacity(model.isTitleFinished ? 1 : 0)
        }
        .typeText(
            text: $model.titleText,
            finalText: model.finalTitle,
            isFinished: $model.isTitleFinished,
            isAnimated: !model.isTitleFinished)
        .animation(.default.speed(0.25), value: model.isTitleFinished)
        .onChange(of: model.isTitleFinished) {
            if (model.isTitleFinished) {
                finished()
            }
        }
    }
}

onChange(of: model.isTitleFinished) 将在标题完成其动画序列时通知我们的 NativeScript 应用,以便我们能够无缝地淡入主窗口。

然后,我们可以将 SwiftUI 添加到我们的视图中,如下所示:

<Page xmlns="http://schemas.nativescript.org/tns.xsd"
  navigatingTo="navigatingTo"
  xmlns:sui="@nativescript/swift-ui">
  <GridLayout>
    <GridLayout loaded="{{ loadedBg }}">
        <Image src="res://SunSliver" class="align-top w-full" iosOverflowSafeArea="true" stretch="aspectFit" scaleY="1.6" scaleX="1.6" translateY="30"/>
        <Image src="res://EarthHalf" class="align-bottom w-full" iosOverflowSafeArea="true" stretch="aspectFill" scaleY="2" scaleX="2" translateY="540"/>
    </GridLayout>

    <sui:SwiftUI swiftId="title" swiftUIEvent="{{ onTitleFinished }}" class="align-middle" />
  </GridLayout>
</Page>

通过定义一些 loaded 事件来控制我们背景图像布局的不透明度,我们现在可以允许 SwiftUI 与我们的整个组合进行交互,以实现这一点

确实很不错!

继续使用体积窗口样式和沉浸式空间

我们可以使用所有这些技术来构建 NativeScript 中的“Hello World”教程的其余部分。例如,我们实际上可以将教程中的所有 .swift 资源移动到 App_Resources/visionOS/src 并与提供的 SwiftUI 进行交互。

我们可以扩展 App_Resources/visionOS/src/NativeScriptApp.swift 文件,以支持 体积 窗口样式和 沉浸式空间,以及 visionOS 中提供的新场景类型。

Apple 的“Hello World”教程利用了一个包含所有使用到的 3D 世界资产的本地 Swift 包。您可以将 Packages 文件夹移动到 NativeScript 项目的根目录。

使用 NativeScript 8.6,我们还可以通过使用以下内容修改 nativescript.config.ts 来配置我们的项目,以便在本地使用该 Swift 包:

// ...
ios: {
  SPMPackages: [
    {
      name: 'WorldAssets',
      libs: ['WorldAssets'],
      path: './Packages/WorldAssets' 
    },
  ]
}

这假设您将来自 Apple 教程的 Packages 文件夹放置在项目的根目录中。本地 Swift 包可以相对于您的项目目录进行引用。

然后,我们可以使用 Apple 教程作为指南,在 App_Resources/visionOS/src/NativeScriptApp.swift 中配置更多场景

import SwiftUI
import Observation
import WorldAssets

@main
struct NativeScriptApp: App {

    // The view model.
    @State private var model = ViewModel()

    // The immersion styles for different modules.
    @State private var orbitImmersionStyle: ImmersionStyle = .mixed
    @State private var solarImmersionStyle: ImmersionStyle = .full

    var body: some Scene {
        // Your NativeScript Main Window
        NativeScriptMainWindow()
        
        // A volume that displays a globe.
        WindowGroup(id: Module.globe.name) {
            Globe()
                .environment(model)
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 0.6, height: 0.6, depth: 0.6, in: .meters)

        // An immersive space that places the Earth with some of its satellites
        // in your surroundings.
        ImmersiveSpace(id: Module.orbit.name) {
            Orbit()
                .environment(model)
        }
        .immersionStyle(selection: $orbitImmersionStyle, in: .mixed)

        // An immersive Space that shows the Earth, Moon, and Sun as seen from
        // Earth orbit.
        ImmersiveSpace(id: Module.solar.name) {
            SolarSystem()
                .environment(model)
        }
        .immersionStyle(selection: $solarImmersionStyle, in: .full)
    }

    init() {
        // Register all the custom components and systems that the app uses.
        RotationComponent.registerComponent()
        RotationSystem.registerSystem()
        TraceComponent.registerComponent()
        TraceSystem.registerSystem()
        SunPositionComponent.registerComponent()
        SunPositionSystem.registerSystem()
    }
}

为了添加体积和沉浸式空间,请确保将以下设置添加到 App_Resources/visionOS/Info.plist 中:

<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
</dict>

这里有什么可能?

除了已经可能实现的功能之外,创新潜力相当惊人,因为这只是一个全新的世界的开始。@nativescript/core 库以及第三方插件可以提供更多 SwiftUI 提供者,以便在 TypeScript 中使用,从而实现令人兴奋且强大的开发工作流程。

示例仓库,体验新现实

您可以参考 此仓库 以获取更多集成详细信息。

一个新的现实正在等待您! 🛸