Backyard Birds:使用 SwiftData 和小部件构建应用程序

Backyard Birds:使用 SwiftData 和小部件构建应用程序

创建具有持久数据、交互式小部件和全新应用内购买体验的应用。

概述

Backyard Birds 提供了一个丰富的环境,您可以在其中观看访问您后院花园的鸟类。您可以监控它们的水和食物供应,以确保它们始终有淡水和充足的食物,或者使用应用程序内购买升级游戏,为鸟类提供更美味的食物。

该示例使用SwiftData实现其数据模型 以实现持久性,并使用该协议与 SwiftUI 无缝集成Observable游戏的小部件为交互式和可配置的小部件实施App Intents 。应用内购买体验使用ProductViewSubscriptionStoreView来自 StoreKit。

您可以在GitHub上访问此示例的源代码

配置示例代码项目

要将 Backyard Birds 应用配置为在您的设备上运行,请执行以下步骤:

  1. 在 Xcode 15 或更高版本中打开项目。
  2. 编辑多平台目标的方案,然后在“选项”选项卡上,选择Store.storekit用于 StoreKit 配置的文件。
  3. 对 watchOS 目标的方案重复上一步。
  4. 选择顶级 Backyard Birds 项目。
  5. 对于所有目标,从 Signing & Capabilities 窗格中的 Team 菜单中选择您的团队,以便 Xcode 可以自动管理您的配置文件。

创建数据驱动的应用程序

该应用程序通过使模型对象符合PersistentModel 使用Model宏来定义其数据模型。Attribute将宏与选项一起使用unique 可确保该id属性是唯一的。

@Model public class BirdSpecies {
    @Attribute(.unique) public var id: String
    public var naturalScale: Double = 1
    public var isEarlyAccess: Bool
    public var parts: [BirdPart]
    
    @Relationship(.cascade, inverse: \Bird.species)
    public var birds: [Bird]
    
    @Transient
    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:)初始化程序来完成此操作:

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 协议来配置小部件:

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(.large)

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

SubscriptionStoreView(
    groupID: passGroupID,
    visibleRelationships: showPremiumUpgrade ? .upgrade : .all
) {
    PassMarketingContent(showPremiumUpgrade: showPremiumUpgrade)
        #if !os(watchOS)
        .containerBackground(for: .subscriptionStoreFullHeight) {
            SkyBackground()
        }
        #endif
}

GitHub

查看 Github