WWDC Quick Look 💓 By SwiftGGTeam
Meet ActivityKit

Meet ActivityKit

元の動画を見る

ハイライト

ActivityKit を使用すると、開発者は SwiftUI と WidgetKit を使用して、ロック画面、スマート アイランド、スタンバイ上でリアルタイムのアクティビティを作成し、ローカル アップデートとリモート プッシュ アップデートをサポートできます。 iOS 17 では、iPad と対話型コントロールのサポートも追加されています。

主な内容

ユーザーは、いつでもテイクアウトできる店がどこにあるのか、ゲームのスコアは何なのか、タクシーが到着するまでにどれくらい時間がかかるのかを知りたいと考えています。以前は、この情報を表示するにはアプリを開くか、頻繁なプッシュ通知でユーザーの邪魔をする必要がありました。

Live Activity は、ロック画面とスマート アイランドにキー情報を継続的に表示することで、この問題を解決します。開始と終了が明確で、進行状況がリアルタイムで更新され、ユーザーは必要な情報を一目で取得できます。

iPhone 14 Pro および Pro Max では、ライブ アクティビティがスマート アイランドに表示されます。単一のアクティビティの場合はコンパクト フォームが表示され、2 つのアクティビティの場合は最小フォームが表示されます。長押しして展開フォームを展開すると、詳細が表示されます。

iOS 17 には 3 つの新機能が追加されています。ライブ アクティビティはスタンバイ モードで拡大されます。 iPad はライブ アクティビティもサポートしています。 WidgetKit を使用して、ボタン、スイッチ、その他の対話型コントロールを追加できます。

##詳細

アクティビティ属性を定義する

(05:40) 各ライブ アクティビティでは、静的データと動的データを定義する必要があります。

import ActivityKit

struct AdventureAttributes: ActivityAttributes {
    let hero: EmojiRanger  // 静态数据:创建后不变

    struct ContentState: Codable & Hashable {
        let currentHealthLevel: Double  // 动态数据:会更新
        let eventDescription: String
    }
}

キーポイント:

  • ActivityAttributesプロトコル定義の静的プロパティ
  • 入れ子になったContentState実装する必要がある動的状態を定義するCodableそしてHashable- 静的データは作成時に決定され、動的データはリアルタイムで更新できます

ライブアクティビティを開始する

(06:28) アプリのフォアグラウンドでライブ アクティビティを開始します。

let adventure = AdventureAttributes(hero: hero)

let initialState = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "Adventure has begun!"
)
let content = ActivityContent(
    state: initialState,
    staleDate: nil,
    relevanceScore: 0.0
)

let activity = try Activity.request(
    attributes: adventure,
    content: content,
    pushType: nil  // nil 表示仅本地更新
)

キーポイント:

  • ライブ アクティビティはアプリのフォアグラウンドでのみリクエストできます
  • 明示的なユーザーアクション (「追跡開始」など) の後に開始する必要があります。 -staleDateコンテンツの有効期限が切れたときにマークを付ける -relevanceScore複数のライブ アクティビティの順序を決定します。優先順位が高いほど、優先順位も高くなります。 -pushType: nilローカル コードのみを介して更新することを示します。 に設定されます。.tokenリモートプッシュを受信できる

ライブアクティビティを更新

(08:00) ステータスが変化したときに呼び出されますupdate

let heroName = activity.attributes.hero.name
let contentState = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "\(heroName) has taken a critical hit!"
)

var alertConfig = AlertConfiguration(
    title: "\(heroName) has taken a critical hit!",
    body: "Open the app and use a potion to heal \(heroName)",
    sound: .default
)

activity.update(
    ActivityContent<AdventureAttributes.ContentState>(
        state: contentState,
        staleDate: nil
    ),
    alertConfiguration: alertConfig
)

キーポイント:

  • update新しく渡すContentStateそしてオプションのAlertConfiguration- アラートはApple Watchに通知として表示され、iPhone/iPadでは指定したサウンドが再生されます
  • タイトルと本文は Apple Watch でのみ機能します

アクティビティのステータスを監視する

(09:30) リソースをクリーンアップするためのステータスの変化を観察します。

// 异步监听
func observeActivity(activity: Activity<AdventureAttributes>) {
    Task {
        for await activityState in activity.activityStateUpdates {
            if activityState == .dismissed {
                self.cleanUpDismissedActivity()
            }
        }
    }
}

// 同步获取
let activityState = activity.activityState
if activityState == .dismissed {
    self.cleanUpDismissedActivity()
}

4 つの州: -.started— 始めました -.finished— 終了しました -.dismissed— ユーザーが終了しました -.stale— コンテンツの有効期限が切れています

ライブアクティビティの終了

(10:03) タスクが完了したらアクティビティを終了します。

let hero = activity.attributes.hero

let finalContent = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "Adventure over! \(hero.name) has defeated the boss!"
)

let dismissalPolicy: ActivityUIDismissalPolicy = .default

activity.end(
    ActivityContent(state: finalContent, staleDate: nil),
    dismissalPolicy: dismissalPolicy
)

キーポイント:

  • .defaultこのポリシーにより、終了後のコンテンツを一定期間ロック画面に保持することが許可されます。
  • 最終ステータスを渡してユーザーに概要を提供できます

ライブ アクティビティを WidgetBundle に追加する

(10:50) ライブ アクティビティ設定にはウィジェット拡張機能を追加する必要があります。

import WidgetKit
import SwiftUI

@main
struct EmojiRangersWidgetBundle: WidgetBundle {
    var body: some Widget {
        EmojiRangerWidget()
        LeaderboardWidget()
        AdventureActivityConfiguration()
    }
}

ロック画面インターフェイスを定義する

(11:05) ロック画面とスタンバイは同じ UI を共有します。

struct AdventureActivityConfiguration: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AdventureAttributes.self) { context in
            AdventureLiveActivityView(
                hero: context.attributes.hero,
                isStale: context.isStale,
                contentState: context.state
            )
            .activityBackgroundTint(Color.navyBlue)
        } dynamicIsland: { context in
            // 灵动岛配置...
        }
    }
}

キーポイント:

  • ActivityConfiguration受信属性のタイプとクロージャ -context供給attributesstateisStale- サイズを指定する必要はありません。システムが自動的に決定します。
  • StandBy は、ロック画面 UI を画面全体に自動的に拡大します。

スマート アイランドのコンパクトな形式を定義する

(13:28) 単一アクティビティのコンパクトな形式:

dynamicIsland: { context in
    DynamicIsland {
        // expanded 形态...
    } compactLeading: {
        Avatar(hero: context.attributes.hero)
    } compactTrailing: {
        ProgressView(value: context.state.currentHealthLevel) {
            Text("\(Int(context.state.currentHealthLevel * 100))")
        }
        .progressViewStyle(.circular)
        .tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
    } minimal: {
        // minimal 形态...
    }
}

キーポイント:

  • compactLeadingそしてcompactTrailing嶺東島の両側に位置
  • スペースは限られているため、最も重要な情報のみを記載してください
  • ユーザーはこのコンテンツからどのアクティビティであるかを識別できる必要があります

スマート アイランドの最小形式を定義する

(14:42) 2 つのアクティビティが同時に存在する場合の最小形式:

minimal: {
    ProgressView(value: context.state.currentHealthLevel) {
        Avatar(hero: context.attributes.hero)
    }
    .progressViewStyle(.circular)
    .tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
}

キーポイント:

  • 最小限の形状スペースは非常に小さく、最も重要な情報のみを配置できます。
  • 1 つのアクティビティは TrueDepth カメラにアタッチされ、もう 1 つはデタッチされます

スマート アイランドの拡張形式を定義する

(15:26) 展開されたフォームを長押しします。

DynamicIsland {
    DynamicIslandExpandedRegion(.leading) {
        LiveActivityAvatarView(hero: hero)
    }
    DynamicIslandExpandedRegion(.trailing) {
        StatsView(hero: hero, isStale: isStale)
    }
    DynamicIslandExpandedRegion(.bottom) {
        HealthBar(currentHealthLevel: contentState.currentHealthLevel)
        EventDescriptionView(hero: hero, contentState: contentState)
    }
} compactLeading: {
    // ...
} compactTrailing: {
    // ...
} minimal: {
    // ...
}

キーポイント:

  • に分割して展開.leading.trailing.bottom3つのエリア -.centerシステム表示時間の残り領域
  • ビューにディープリンクを追加でき、クリックするとアプリの対応するページにジャンプします

重要なポイント

1.持ち帰り/配達の進捗状況の追跡

  • やるべきこと: ユーザーが注文すると、ライダーの位置と配達予定時間がロック画面とスマートアイランドにリアルタイムで表示されます。
  • 実行する価値がある理由: ユーザーは進行状況を確認するためにアプリを繰り返し開く必要がなくなり、エクスペリエンスが大幅に向上します。
  • 開始方法: 定義DeliveryAttributes注文番号 (静的) とライダーの位置、到着予定時刻 (動的)、使用状況が含まれます。ActivityConfigurationシンプルなプログレスバー UI をデザインする

2.スポーツ/フィットネスのリアルタイムデータ

  • 何をすべきか: ランニングまたはサイクリング中にスマートアイランドにペース、心拍数、距離を表示します
  • 価値がある理由: 運動中にスマートフォンを見るのは不便ですが、スマートアイランドで重要なデータを一目で取得できます
  • 開始方法: 静的データはエクササイズの種類と目標を表示し、動的データはリアルタイムのペースと距離を表示し、コンパクトなフォームは現在のペースを表示し、展開すると完全なデータパネルを表示します。

3.ライブゲーム/イベントスコア

  • やるべきこと: サッカーの試合をフォローし、ロック画面にスコアと主要なイベントを継続的に表示します。
  • 実行する価値がある理由: プッシュ通知よりも継続的で煩わしさが少ないため、ユーザーはいつでも一目でスコアを知ることができます
  • 開始方法: リモート プッシュ アップデート (10185 セッションを参照)、ゴール、レッド カード、イエロー カードなどのサーバー プッシュ イベント、およびローカル アップデート UI と組み合わせます。

4.タクシーを待っている/オンライン配車

  • 操作方法: 配車リクエスト後、車の距離、ナンバープレート番号、到着予定時刻を表示します。
  • 実行する価値がある理由: ユーザーが車の位置を確認するためにアプリを繰り返し開くという問題を解決します。
  • 開始方法: 静的データには目的地と車種が含まれ、動的データには車両の位置と到着予定時刻が含まれ、最小限の形式では推定分数のみが表示されます。

関連セッション

コメント

GitHub Issues · utterances