1.1 Backyard Birds:用 SwiftData 和小组件构建应用
原文链接: https://developer.apple.com/documentation/swiftui/backyard-birds-sample
1.1 Backyard Birds:用 SwiftData 和小组件构建应用
创建一个带持久化数据、可交互小组件和全新应用内购买体验的应用。
概述
Backyard Birds 提供了一个丰富的环境,让你观察飞临后院花园的鸟儿。你可以监控它们的水和食物供应,确保它们总有清水喝、总有东西吃;也可以借助应用内购买来升级游戏,为鸟儿提供更美味的食物。
这个示例用 SwiftData 实现数据模型以完成持久化,并通过 Observation 协议与 SwiftUI 无缝集成。游戏的小组件实现了 App Intents,从而提供可交互、可配置的小组件。应用内购买体验使用了 StoreKit 中的 ProductView 和 SubscriptionStoreView。
你可以在 GitHub 上获取这个示例的源代码。
注意:这个示例代码项目与 WWDC23 的 102 号场次 State of the Union 相关。
要配置 Backyard Birds 应用以便在你的设备上运行,请按以下步骤操作:
- 用 Xcode 15 或更高版本打开该项目。
- 编辑多平台目标的 scheme,在 Options 标签页中选择
Store.storekit 文件作为 StoreKit 配置。 - 对 watchOS 目标的 scheme 重复上一步。
- 选中顶层的 Backyard Birds 项目。
- 对所有目标,在 Signing & Capabilities 面板的 Team 菜单中选择你的团队,让 Xcode 自动管理你的描述文件。
创建数据驱动的应用
应用通过 Model() 宏让模型对象遵循 PersistentModel,从而定义它的数据模型。把 Attribute(_:originalName:hashModifier:) 宏与 unique 选项一起使用,可以确保 id 属性是唯一的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @Model public class BirdSpecies {
@Attribute(.unique) public var id: String
public var naturalScale: Double
public var isEarlyAccess: Bool
public var parts: [BirdPart]
@Relationship(deleteRule: .cascade, inverse: \Bird.species)
public var birds: [Bird] = []
public var info: BirdSpeciesInfo { BirdSpeciesInfo(rawValue: id) }
public init(info: BirdSpeciesInfo, naturalScale: Double = 1, isEarlyAccess: Bool = false, parts: [BirdPart]) {
self.id = info.rawValue
self.naturalScale = naturalScale
self.isEarlyAccess = isEarlyAccess
self.parts = parts
}
}
|
当水和食物即将耗尽时,Backyard Birds 会呈现一个用于补充后院物资的 Button,从而展示可交互小组件。应用的做法是把一个 Button 放进小组件的视图里,并把 ResupplyBackyardIntent 实例传给 init(intent:label:) 构造器:
1
2
3
4
5
6
7
8
| Button(intent: ResupplyBackyardIntent(backyard: BackyardEntity(from: snapshot.backyard))) {
Label("Refill Water", systemImage: "arrow.clockwise")
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.padding(.horizontal, 12)
.background(.quaternary, in: .containerRelative)
}
|
应用通过实现 WidgetConfigurationIntent 协议,让小组件可以配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| struct BackyardWidgetIntent: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "Backyard"
static let description = IntentDescription("Keep track of your backyards.")
@Parameter(title: "Backyards", default: BackyardWidgetContent.all)
var backyards: BackyardWidgetContent
@Parameter(title: "Backyard")
var specificBackyard: BackyardEntity?
init(backyards: BackyardWidgetContent = .all, specificBackyard: BackyardEntity? = nil) {
self.backyards = backyards
self.specificBackyard = specificBackyard
}
init() {
}
static var parameterSummary: some ParameterSummary {
When(\.$backyards, .equalTo, BackyardWidgetContent.specific) {
Summary {
\.$backyards
\.$specificBackyard
}
} otherwise: {
Summary {
\.$backyards
}
}
}
}
|
提供全新的应用内购买体验
示例应用使用 ProductView 在货架上显示若干可以购买的不同鸟食升级项。为了突出展示某个应用内购买项,应用使用了 productViewStyle(_:) 修饰符:
1
2
3
4
5
6
7
| ProductView(id: product.id) {
BirdFoodProductIcon(birdFood: birdFood, quantity: product.quantity)
.bestBirdFoodValueBadge()
}
.padding(.vertical)
.background(.background.secondary, in: .rect(cornerRadius: 20))
.productViewStyle(.large)
|
Backyard Birds Pass 页面使用 SubscriptionStoreView 视图显示可续期订阅。应用把 PassMarketingContent 视图用作 SubscriptionStoreView 的内容:
1
2
3
4
5
6
7
8
9
10
11
| SubscriptionStoreView(
groupID: passGroupID,
visibleRelationships: showPremiumUpgrade ? .upgrade : .all
) {
PassMarketingContent(showPremiumUpgrade: showPremiumUpgrade)
#if !os(watchOS)
.containerBackground(for: .subscriptionStoreFullHeight) {
SkyBackground()
}
#endif
}
|