WidgetKit iOS 17+:App Intentsによるインタラクティブウィジェット

WidgetKitとApp IntentsでインタラクティブなiOSウィジェットを作成する完全ガイド。2026年のiOS 17+向けボタン、トグル、アニメーション、ベストプラクティス。

モダンなiOSアプリケーション向けインタラクティブウィジェットとApp Intentsを備えたWidgetKit iOS 17+

iOS 17は、ネイティブなインタラクティブ性を導入することでWidgetKitに革命をもたらしました。ウィジェットはもはや静的な表示ではありません:アプリを開かずに、ホーム画面から直接ユーザーのアクションに応答できるようになりました。この大きな進化はApp Intentsフレームワークに依存しており、滑らかでモダンなユーザー体験を提供します。

この記事の内容

この記事では、プロジェクト設定からアニメーションと状態管理を伴う高度なパターンまで、iOS 17+インタラクティブウィジェットの完全な作成を紹介します。

インタラクティブウィジェットのアーキテクチャ

iOS 17+ウィジェットのインタラクティブ性は、App Intentsフレームワークを通じて機能します。アプリを開く従来のディープリンクとは異なり、App Intentsはウィジェットから直接コードを実行し、その後新しいデータで表示を自動的に更新します。

InteractiveWidgetArchitecture.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// アーキテクチャは3つの主要コンポーネントに基づいています:
// 1. Widget Timeline Provider - データを提供
// 2. Widget View - Button/Toggleでインターフェースを表示
// 3. App Intent - タップ時にアクションを実行

struct TaskWidget: Widget {
    // ウィジェットの一意の識別子
    let kind: String = "TaskWidget"

    var body: some WidgetConfiguration {
        // パラメータなしウィジェット用のStaticConfiguration
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                // App Intentsには必須
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("タスク")
        .description("ホーム画面からタスクを管理します。")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

ウィジェットは設定を宣言し、データを提供するプロバイダーを指定します。.containerBackground属性は、iOS 17以降のインタラクティブウィジェットには必須です。

Timeline Providerの作成

Timeline Providerは、ウィジェットが更新されるタイミングと方法を決定します。インタラクティブウィジェットでは、App Intentsによって引き起こされる変更にも対応する必要があります。

TaskTimelineProvider.swiftswift
import WidgetKit
import SwiftUI

// 特定の瞬間のウィジェットの状態を表すEntry
struct TaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // 視覚的フィードバック用のロード状態
    var isLoading: Bool = false
}

// アプリとウィジェットで共有されるデータモデル
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    var priority: Priority

    enum Priority: String, Codable {
        case low, medium, high
    }
}

struct TaskTimelineProvider: TimelineProvider {
    // 初期ロード中に表示されるプレースホルダー
    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(
            date: Date(),
            tasks: [
                Task(id: UUID(), title: "サンプルタスク", isCompleted: false, priority: .medium)
            ]
        )
    }

    // ウィジェットギャラリー用のスナップショット
    func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
        let entry = TaskEntry(
            date: Date(),
            tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
        )
        completion(entry)
    }

    // リフレッシュポリシー付きの完全なTimeline
    func getTimeline(in context: Context, completion: @escaping (Timeline<TaskEntry>) -> Void) {
        let tasks = TaskDataManager.shared.fetchTasks()
        let entry = TaskEntry(date: Date(), tasks: Array(tasks.prefix(3)))

        // 15分後またはユーザーアクション後にリフレッシュ
        let nextUpdate = Calendar.current.date(
            byAdding: .minute,
            value: 15,
            to: Date()
        ) ?? Date()

        let timeline = Timeline(
            entries: [entry],
            policy: .after(nextUpdate)
        )
        completion(timeline)
    }
}

プロバイダーは共有されたTaskDataManagerを使用してデータにアクセスします。このアプローチにより、メインアプリケーションとウィジェット間の同期が保証されます。

App Group必須

アプリとウィジェット間でデータを共有するには、プロジェクトのcapabilitiesでApp Groupを設定する必要があります。UserDefaultsまたはファイルはこの共有グループを使用する必要があります。

共有データマネージャー

アプリケーションとウィジェット間でのデータ共有には、App Group経由でアクセス可能な共通コンテナが必要です。

TaskDataManager.swiftswift
import Foundation

final class TaskDataManager {
    // グローバルアクセス用Singleton
    static let shared = TaskDataManager()

    // Xcodeで設定されたApp Group識別子
    private let appGroupID = "group.com.example.taskapp"

    // アプリとウィジェット間で共有されるUserDefaults
    private var sharedDefaults: UserDefaults? {
        UserDefaults(suiteName: appGroupID)
    }

    private let tasksKey = "tasks"

    private init() {}

    // 共有ストレージからタスクを取得
    func fetchTasks() -> [Task] {
        guard let data = sharedDefaults?.data(forKey: tasksKey),
              let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
            return []
        }
        return tasks
    }

    // ウィジェット通知付きで保存
    func saveTasks(_ tasks: [Task]) {
        guard let data = try? JSONEncoder().encode(tasks) else { return }
        sharedDefaults?.set(data, forKey: tasksKey)
    }

    // 特定のタスクを更新
    func updateTask(_ task: Task) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == task.id }) {
            tasks[index] = task
            saveTasks(tasks)
        }
    }

    // 完了状態を切り替え
    func toggleTaskCompletion(taskID: UUID) {
        var tasks = fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == taskID }) {
            tasks[index].isCompleted.toggle()
            saveTasks(tasks)
        }
    }
}

このマネージャーはすべての永続化ロジックをカプセル化し、アプリケーションとウィジェットのApp Intents両方で使用されます。

インタラクティブ用App Intentの作成

App Intentは、ユーザーがウィジェットと対話する際に実行されるアクションを定義します。iOSはこのアクションをバックグラウンドで実行し、その後ウィジェットを自動的に更新します。

ToggleTaskIntent.swiftswift
import AppIntents
import WidgetKit

// タスクの状態を切り替えるIntent
struct ToggleTaskIntent: AppIntent {
    // Siriショートカットに表示されるタイトル
    static var title: LocalizedStringResource = "タスクの状態を切り替え"

    // アクセシビリティ用の説明
    static var description = IntentDescription("タスクを完了または未完了としてマークします。")

    // パラメータ:変更するタスクのID
    @Parameter(title: "タスクID")
    var taskID: String

    // AppIntentに必要なinitializer
    init() {}

    // viewからの作成用のパラメータ付きinitializer
    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // アクションの実行
    func perform() async throws -> some IntentResult {
        // 文字列IDをUUIDに変換
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        // タスクの更新
        TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)

        // ウィジェット更新の要求
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

WidgetCenter.shared.reloadTimelinesの呼び出しは、アクション後にウィジェットを即座に更新し、即時の視覚的フィードバックを保証します。

iOSの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

インタラクティブボタン付きウィジェットView

ウィジェットViewは、intentをアクションとする標準のSwiftUI Buttonコンポーネントを使用します。iOS 17+はこれらのインタラクションを自動的にインターセプトしてApp Intentを実行します。

TaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct TaskWidgetView: View {
    let entry: TaskEntry

    // ウィジェットサイズへの適応
    @Environment(\.widgetFamily) var family

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // タイトルとカウンター付きヘッダー
            headerView

            // インタラクティブボタン付きタスクリスト
            ForEach(entry.tasks.prefix(tasksLimit)) { task in
                TaskRowView(task: task)
            }

            Spacer(minLength: 0)
        }
        .padding()
    }

    // サイズに応じたタスク数
    private var tasksLimit: Int {
        switch family {
        case .systemSmall: return 2
        case .systemMedium: return 3
        default: return 4
        }
    }

    private var headerView: some View {
        HStack {
            Text("タスク")
                .font(.headline)
                .fontWeight(.bold)

            Spacer()

            // 残りタスク数のバッジ
            let remaining = entry.tasks.filter { !$0.isCompleted }.count
            Text("\(remaining)")
                .font(.caption.bold())
                .foregroundStyle(.white)
                .padding(.horizontal, 8)
                .padding(.vertical, 4)
                .background(remaining > 0 ? Color.orange : Color.green)
                .clipShape(Capsule())
        }
    }
}

struct TaskRowView: View {
    let task: Task

    var body: some View {
        // App Intentをアクションとするボタン
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // 完了インジケーター
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                // タスクタイトル
                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    .lineLimit(1)

                Spacer()

                // 優先度インジケーター
                priorityIndicator
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(Color(.systemBackground).opacity(0.5))
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
    }

    @ViewBuilder
    private var priorityIndicator: some View {
        switch task.priority {
        case .high:
            Image(systemName: "exclamationmark.circle.fill")
                .foregroundStyle(.red)
        case .medium:
            Image(systemName: "minus.circle.fill")
                .foregroundStyle(.orange)
        case .low:
            EmptyView()
        }
    }
}

Button(intent:)構文は、ボタンをApp Intentに直接接続します。タップ時、iOSはperform()を実行し、その後ウィジェットを自動的に更新します。

ウィジェット用インタラクティブToggle

オン/オフ型のアクションには、ToggleコンポーネントがネイティブiOSスタイルでボタンの代替を提供します。

ToggleWidgetView.swiftswift
import SwiftUI
import AppIntents

// 明示的な状態を持つToggle専用のIntent
struct SetTaskCompletionIntent: AppIntent {
    static var title: LocalizedStringResource = "タスク状態の設定"

    @Parameter(title: "タスクID")
    var taskID: String

    // 目標状態:true = 完了、false = 未完了
    @Parameter(title: "完了")
    var isCompleted: Bool

    init() {}

    init(taskID: UUID, isCompleted: Bool) {
        self.taskID = taskID.uuidString
        self.isCompleted = isCompleted
    }

    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        var tasks = TaskDataManager.shared.fetchTasks()
        if let index = tasks.firstIndex(where: { $0.id == uuid }) {
            // 状態を明示的に設定(toggleではない)
            tasks[index].isCompleted = isCompleted
            TaskDataManager.shared.saveTasks(tasks)
        }

        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
        return .result()
    }
}

struct TaskToggleRowView: View {
    let task: Task

    var body: some View {
        HStack {
            Text(task.title)
                .font(.subheadline)
                .strikethrough(task.isCompleted)

            Spacer()

            // intent付きインタラクティブtoggle
            Toggle(
                isOn: task.isCompleted,
                intent: SetTaskCompletionIntent(
                    taskID: task.id,
                    isCompleted: !task.isCompleted
                )
            )
            .toggleStyle(.switch)
            .labelsHidden()
        }
        .padding(.vertical, 4)
    }
}

Toggleはバイナリ状態に対してより直感的なインタラクションを提供し、iOSデザインに自然に統合されます。

インタラクティブウィジェットの制限

ウィジェットはアラート、シート、ナビゲーションを表示できません。すべてのアクションは自己完結型で、可視状態を直接更新する必要があります。

更新アニメーションとトランジション

iOS 17+では、アクション後のウィジェット更新中にトランジションをアニメーション化できます。.contentTransition修飾子がこれらのアニメーションを制御します。

AnimatedTaskWidgetView.swiftswift
import SwiftUI
import WidgetKit

struct AnimatedTaskRowView: View {
    let task: Task

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // トランジションアニメーション付きアイコン
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .font(.title3)
                    .foregroundStyle(task.isCompleted ? .green : .secondary)
                    // 変更時のアイコンアニメーション
                    .contentTransition(.symbolEffect(.replace))

                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)
                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
                    // テキストアニメーション
                    .contentTransition(.opacity)

                Spacer()
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .fill(task.isCompleted ? Color.green.opacity(0.1) : Color.clear)
            )
            // 背景アニメーション
            .animation(.easeInOut(duration: 0.3), value: task.isCompleted)
        }
        .buttonStyle(.plain)
    }
}

// アニメーション無効化付きウィジェット
struct AnimatedTaskWidget: Widget {
    let kind: String = "AnimatedTaskWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: kind,
            provider: TaskTimelineProvider()
        ) { entry in
            AnimatedTaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("アニメーション付きタスク")
        .description("滑らかなアニメーション付きウィジェット。")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
        // コンテンツアニメーションの有効化
        .contentMarginsDisabled()
    }
}

struct AnimatedTaskWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            headerView

            ForEach(entry.tasks) { task in
                AnimatedTaskRowView(task: task)
            }

            Spacer(minLength: 0)
        }
        .padding()
    }

    private var headerView: some View {
        HStack {
            Text("タスク")
                .font(.headline.bold())

            Spacer()

            let completed = entry.tasks.filter(\.isCompleted).count
            let total = entry.tasks.count

            // アニメーション付き進捗
            Text("\(completed)/\(total)")
                .font(.caption.bold())
                .foregroundStyle(.secondary)
                .contentTransition(.numericText())
        }
    }
}

.symbolEffect(.replace).numericText()アニメーションは、状態間の滑らかなトランジションを作成し、ユーザー体験を大幅に向上させます。

AppIntentConfigurationによる設定可能なウィジェット

ユーザーがカスタマイズ可能なウィジェット(フィルター、カテゴリー)では、AppIntentConfigurationがStaticConfigurationを置き換えます。

ConfigurableTaskWidget.swiftswift
import WidgetKit
import SwiftUI
import AppIntents

// ユーザーに公開される設定
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "タスク設定"
    static var description = IntentDescription("タスクの表示をカスタマイズします。")

    // 優先度によるフィルター
    @Parameter(title: "優先度", default: .all)
    var priorityFilter: PriorityFilter

    // 完了タスクを表示
    @Parameter(title: "完了を表示", default: true)
    var showCompleted: Bool

    // タスクの最大数
    @Parameter(title: "タスク数", default: 3)
    var maxTasks: Int
}

// 優先度フィルター用Enum
enum PriorityFilter: String, AppEnum {
    case all
    case high
    case medium
    case low

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "優先度"

    static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
        .all: "すべて",
        .high: "高",
        .medium: "中",
        .low: "低"
    ]
}

// 設定に適応したプロバイダー
struct ConfigurableTaskProvider: AppIntentTimelineProvider {
    typealias Entry = TaskEntry
    typealias Intent = TaskWidgetConfigurationIntent

    func placeholder(in context: Context) -> TaskEntry {
        TaskEntry(date: Date(), tasks: [])
    }

    func snapshot(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> TaskEntry {
        let tasks = filteredTasks(for: configuration)
        return TaskEntry(date: Date(), tasks: tasks)
    }

    func timeline(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> Timeline<TaskEntry> {
        let tasks = filteredTasks(for: configuration)
        let entry = TaskEntry(date: Date(), tasks: tasks)

        let nextUpdate = Date().addingTimeInterval(15 * 60)
        return Timeline(entries: [entry], policy: .after(nextUpdate))
    }

    // 設定フィルターを適用
    private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
        var tasks = TaskDataManager.shared.fetchTasks()

        // 優先度によるフィルタリング
        if config.priorityFilter != .all {
            let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
            tasks = tasks.filter { $0.priority == priority }
        }

        // 必要に応じて完了をフィルタリング
        if !config.showCompleted {
            tasks = tasks.filter { !$0.isCompleted }
        }

        // 数の制限
        return Array(tasks.prefix(config.maxTasks))
    }
}

// ユーザー設定付きウィジェット
struct ConfigurableTaskWidget: Widget {
    let kind: String = "ConfigurableTaskWidget"

    var body: some WidgetConfiguration {
        // 設定可能ウィジェット用AppIntentConfiguration
        AppIntentConfiguration(
            kind: kind,
            intent: TaskWidgetConfigurationIntent.self,
            provider: ConfigurableTaskProvider()
        ) { entry in
            TaskWidgetView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
        }
        .configurationDisplayName("カスタムタスク")
        .description("タスクをフィルタリングしてカスタマイズします。")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

ユーザーは長押しでウィジェットを設定でき、アプリケーションに追加コードなしでパーソナライズされた体験を提供します。

エラー処理とロード状態

良いUXには、インタラクション中のエラーケースと中間状態の処理が必要です。

TaskIntentWithFeedback.swiftswift
import AppIntents
import WidgetKit

struct ToggleTaskWithFeedbackIntent: AppIntent {
    static var title: LocalizedStringResource = "フィードバック付きタスク切り替え"

    @Parameter(title: "タスクID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
        guard let uuid = UUID(uuidString: taskID) else {
            // サイレント失敗を返す
            return .result(value: false)
        }

        // 非同期操作のシミュレーション(例:サーバー同期)
        do {
            try await Task.sleep(for: .milliseconds(100))

            TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
            WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

            return .result(value: true)
        } catch {
            // エラー:ウィジェットを更新しない
            return .result(value: false)
        }
    }
}

// ロード状態付きView
struct TaskRowWithLoadingView: View {
    let task: Task
    @State private var isLoading = false

    var body: some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack(spacing: 12) {
                // 条件付きインジケーター
                Group {
                    if isLoading {
                        ProgressView()
                            .scaleEffect(0.8)
                    } else {
                        Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                            .foregroundStyle(task.isCompleted ? .green : .secondary)
                    }
                }
                .frame(width: 24, height: 24)

                Text(task.title)
                    .font(.subheadline)
                    .strikethrough(task.isCompleted)

                Spacer()
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 10)
            .background(Color(.systemBackground).opacity(0.5))
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
        .disabled(isLoading)
        .opacity(isLoading ? 0.6 : 1.0)
    }
}

即時の視覚的フィードバック(不透明度の低下、ロードインジケーター)は、ユーザーにそのアクションが登録されたことを知らせます。

ベストプラクティスと最適化

いくつかのパターンが、パフォーマンスと信頼性の高いインタラクティブウィジェットを保証します。

WidgetBestPractices.swiftswift
import WidgetKit
import SwiftUI

// 1. 変更後は常にキャッシュを無効化
final class WidgetRefreshManager {
    static func refreshAllWidgets() {
        // すべてのアプリウィジェットを更新
        WidgetCenter.shared.reloadAllTimelines()
    }

    static func refreshWidget(kind: String) {
        // 特定のウィジェットを更新
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
    }

    // データ変更後にアプリから呼び出す
    static func notifyDataChanged() {
        Task { @MainActor in
            refreshAllWidgets()
        }
    }
}

// 2. View複雑性を制限
struct OptimizedWidgetView: View {
    let entry: TaskEntry

    var body: some View {
        // GeometryReaderなしのシンプルなviewを優先
        VStack(alignment: .leading, spacing: 8) {
            ForEach(entry.tasks.prefix(3)) { task in
                // 軽量コンポーネント
                minimalTaskRow(task)
            }
        }
        .padding()
    }

    // パフォーマンス用最小限view
    @ViewBuilder
    private func minimalTaskRow(_ task: Task) -> some View {
        Button(intent: ToggleTaskIntent(taskID: task.id)) {
            HStack {
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                Text(task.title)
                    .lineLimit(1)
            }
        }
        .buttonStyle(.plain)
    }
}

// 3. シンプルな状態には@AppStorageを使用
struct QuickSettingsWidgetView: View {
    // 共有UserDefaultsへの直接アクセス
    @AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
    private var showCompleted = true

    var body: some View {
        // 状態は更新間で持続する
        Text(showCompleted ? "すべて表示中" : "完了を非表示")
    }
}

// 4. プロバイダーでデータを事前計算
struct OptimizedTaskEntry: TimelineEntry {
    let date: Date
    let tasks: [Task]

    // 事前計算されたデータ
    let completedCount: Int
    let pendingCount: Int
    let highPriorityCount: Int

    init(date: Date, tasks: [Task]) {
        self.date = date
        self.tasks = tasks

        // 計算は1回だけ実行される
        self.completedCount = tasks.filter(\.isCompleted).count
        self.pendingCount = tasks.filter { !$0.isCompleted }.count
        self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
    }
}

これらの最適化により、過度にバッテリーを消費しないレスポンシブなウィジェットが保証されます。

ウィジェットのデバッグ

デバッグにはXcodeのウィジェットスキームを使用してください。キャンバスプレビューにより、デバイスにインストールせずにさまざまなサイズと状態をテストできます。

結論

App Intents付きWidgetKit iOS 17+は、ウィジェットをiOSアプリケーションの真のインタラクティブな拡張に変えます。この宣言的アーキテクチャは、ネイティブで滑らかなユーザー体験を提供しながら、開発を大幅に簡素化します。

iOS 17+インタラクティブウィジェットチェックリスト

  • ✅ データ共有用にApp Groupを設定
  • ✅ 適切な更新を備えたTimeline Providerを作成
  • ✅ 各アクション用のApp Intentsを実装
  • ✅ インタラクティブ用にButton(intent:)またはToggle(intent:)を使用
  • ✅ 変更後にWidgetCenter.shared.reloadTimelinesを呼び出す
  • ✅ iOS 17+用に必須の.containerBackgroundを追加
  • ✅ 滑らかなトランジションアニメーションを実装
  • ✅ ロード状態とエラー状態を処理
  • ✅ バッテリーパフォーマンス用にviewを最適化
  • ✅ サポートされているすべてのウィジェットサイズでテスト

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#widgetkit
#ios
#app-intents
#swift
#widgets

共有

関連記事