69
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOS 14 アプリケーションのWidgetの追加(通常のWidget、Intentsを使用した構成可能なWidget)

Last updated at Posted at 2020-10-09

本記事では以下の内容を説明します。

  1. 既存のアプリケーションへのウィジェットの追加
  2. ⭐️ ユーザーがコンテンツを構成できる (都市の選択など) ウィジェットの追加
  3. ウィジェットの再読み込み

ezgif-6-bf9e4b4783c9.gif

完成したソースコードはこちらから確認できます。

既存のアプリケーションへのウィジェットの追加

アプリケーションターゲットの作成

既存の iOS アプリケーションへのウィジェットの追加は簡単です。ターゲット Widget Extension を追加してください。

image

さて、 Include Configuration Intent というボックスを選択していることを確認してください。この記事のパート2では、その構成ファイルが必要になるためです。

image

データ構造

これで WidgetExample-Widget という新しいフォルダーが表示されます。クリックしてファイル WidgetExample_Widget.swift を開き、そのファイルのコンテンツを削除して、このガイドに従います。

ウィジェットに表示したいデータのデータ構造を作成できます。この例では、猫の情報を表示します!

struct CatEntry: TimelineEntry {
    var date: Date
    
    var name: String
    var lastFed: Date
    var lastPlayedWith: Date
}

IntentTimelineProvider構造の作成

IntentTimelineProvider 構造は、次の3種類のコンテンツを提供します:
1> placeholder (プレースホルダー)は、ウィジェットがロードされているときに表示されます。
2> getSnapshot は、ウィジェットギャラリーに表示されます。
3> getTimeline は、実際のウィジェット表示に使用されます。

まず、タイプ IntentTimelineProvider に確認するプログラム構造体を作成し、次に Entry のタイプを定義します。

struct CatProviderStatic: TimelineProvider {
    typealias Entry = CatEntry

    func getSnapshot(in context: Context, completion: @escaping (CatEntry) -> Void) {
        //TODO
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
        //TODO
    }

    func placeholder(in context: Context) -> CatEntry {
        //TODO
    }

}

getSnapshot 機能については、あなたのウィジェットが何の情報を提供するかをユーザーが理解できるよう、ウィジェットビューに例の値を提供することができます:

func getSnapshot(in context: Context, completion: @escaping (CatEntry) -> Void) {
    let entry = CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
    completion(entry)
}

プレースホルダービューについては、以下のように空の値または例の値を表示できます:

func placeholder(in context: Context) -> CatEntry {
    return CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
}

そして、タイムライン表示については、表示する実際のコンテンツを提供できます。

この例では、ただ静的データ値を表示します。あなたのアプリケーションで、Core Dataそのデータを共有する方法についての記事はこちら)、またはオンラインから、CloudKit から、または UserDefaults からのコンテンツをフェッチできます。

func getTimeline(in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
    let entry = CatEntry(date: Date(), name: "ネコノヒー", lastFed: Date(), lastPlayedWith: Date())
    let timeline = Timeline(entries: [entry], policy: .atEnd)
    completion(timeline)
}

タイムラインに複数のアイテムを追加する

タイムラインに複数のアイテムを追加することもできます。ウィジェットがタイムラインのアイテムを自動的にチェックし、アイテムに示された時刻になったらリロードを行います。

func getTimeline(in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
    var timelineEntries = [CatEntry]()
    if let date1 = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) {
        let entry = CatEntry(date: date1, name: "ネコノヒー", lastFed: date1, lastPlayedWith: date1)
        timelineEntries.append(entry)
    }
    if let date2 = Calendar.current.date(byAdding: .hour, value: 2, to: Date()) {
        let entry = CatEntry(date: date2, name: "ネコノヒー", lastFed: date2, lastPlayedWith: date2)
        timelineEntries.append(entry)
    }
    let timeline = Timeline(entries: timelineEntries, policy: .atEnd)
    completion(timeline)
}

ウィジェットビューのデザイン

ウィジェットの SwiftUI ビューをデザインできるようになりました。

struct CatWidgetView: View {
    
    @Environment(\.widgetFamily) var family
    
    var entry: CatEntry
    
    var body: some View {
        
        VStack {
            
            if family == .systemMedium || family == .systemLarge {
                Image("kitty")
                    .resizable()
                    .frame(width: 50, height: 50)
                    .padding(.vertical, 5)
            }
            
            Text(entry.name)
                .font(.headline)
                .padding(1)
            
            Text("最後に遊んだ時間 " + entry.lastPlayedWith.getString())
                .font(.caption)
                .padding(.horizontal)
            
            Text("最後に餌をあげた時間 " + entry.lastFed.getString())
                .font(.caption)
                .padding(.horizontal)
            
        }
        
    }
    
}

ウィジェットのサイズを確認するには @Environment(\.widgetFamily) var family 変数を使用できます。

この例では、ウィジェットのサイズが画像の表示に十分な大きさである場合の猫の画像を表示しています。

if family == .systemMedium || family == .systemLarge {
    Image("kitty")
        .resizable()
        .frame(width: 50, height: 50)
        .padding(.vertical, 5)
}

ウィジェットアプリケーションをコーディングします

これで、ウィジェットアプリケーションをコーディングできるようになりました。

@main
struct CatWidget: Widget {
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("猫")
        .description("いつ猫に餌をあげたり遊んだりしたか見てみましょう。")
    }
    
}

これで、シミュレーターでアプリを実行して、設計したばかりのウィジェットを画面に追加できるようになりました。

image

ユーザーがウィジェットを構成できるようにします

お天気ウィジェットを使ったことがあれば、ウィジェットを長押しして構成することにより、表示される都市をユーザーが選択できることをご存じでしょう。Intents フレームワークとTargetを使用して、この機能を追加することができます。

Intents プログラムターゲットの追加

image

この段階では UI 要素は不要なため、オプション Include UI Extension のチェックは外して結構です。

image

作成された Intents ターゲットページで、Supported Intents という名前のセクションを見つけます。ConfigurationIntent という名前のアイテムを新規作成します。名前は [インテント名]Intent とします。インテント名は左側の .intentdefinition ファイル内に表示されます。

スクリーンショット 0002-10-09 15.16.55.png

スクリーンショット 0002-10-09 15.11.01.png

.intentdefinition の構成

次に、以前に作成したウィジェット内の WidgetExample_Widget.intentdefinition ファイルにつき、新しく作成された Intents 拡張機能をインテントのターゲットとして追加します。

画面左側の Configuration をクリックします。

image

右側で、以下の画像と構成が同じであることを確認してください。

image

次に、cat という名の新しいパラメータを追加します。

image

新たに作成された cat パラメータの設定画面で、猫の識別子を入力として使用するため、タイプ TypeString を選択します。

image

IntentHandler の構成

次に、IntentHandler.swift ファイルを開きます。このファイルでは、ユーザーがウィジェットの構成を行うオプションを提供します。この例では、オプションは猫の識別子となります。

クラスの INExtension タイプの隣に ConfigurationIntentHandling キーワードを追加すると、Xcode ソフトウェアが自動で次に追加すべき関数の名前を表示してくれます。

class IntentHandler: INExtension, ConfigurationIntentHandling {
    ...
}

この例では、完成した IntentHandler.swift ファイルは以下のとおりとなります。

class IntentHandler: INExtension, ConfigurationIntentHandling {
    
    
    func provideCatOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: @escaping (INObjectCollection<NSString>?, Error?) -> Void) {
        let catIdentifiers: [NSString] = [
            "ネコノヒー",
            "ムギ",
            "アズキ"
        ]
        let allCatIdentifiers = INObjectCollection(items: catIdentifiers)
        completion(allCatIdentifiers, nil)
    }
    
    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.
        
        return self
    }
    
}

関数 provideCatOptionsCollection では、値のリストを入力する必要があります。これらの値は、実際には User DefaultsCore Data、オンラインのいずれかからフェッチできます。この例では値がハードコーディングされています。

「App Extensions」で「Core Data」を使用する

IntentTimelineProvider を作成

本記事のパート1では、TimelineProvider を使用しました。今回は、IntentTimelineProvider を使用します。

IntentTimelineProviderTimelineProvider の間のデータ構造はほぼ同一です。追加の typealias を宣言する必要がある点が違いとなります。

typealias Intent = ConfigurationIntent

もう1つの違いは、次のとおりです。各関数で、インテントの選択を表す追加のパラメーター ConfigurationIntent を取得します。

struct CatProvider: IntentTimelineProvider {
    
    typealias Intent = ConfigurationIntent
    typealias Entry = CatEntry
    
    func placeholder(in context: Context) -> CatEntry {
        let entry = CatEntry(date: Date(), name: "", lastFed: Date(), lastPlayedWith: Date())
        return entry
    }
    
    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (CatEntry) -> Void) {
        let entry = CatEntry(date: Date(), name: "猫の名前", lastFed: Date(), lastPlayedWith: Date())
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<CatEntry>) -> Void) {
        let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())
        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }
    
}

configuration.cat プロパティを使用して、ユーザーが選択したオプションの値を読み取ることができます。

let entry = CatEntry(date: Date(), name: configuration.cat ?? "", lastFed: Date(), lastPlayedWith: Date())

CatWidget のコードを更新します。

パート1では StaticConfiguration を使用しましたが、このパートでは IntentConfigurationSupported Intents で設定した名前)を使用します。

@main
struct CatWidget: Widget {
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: "CatWidget", intent: ConfigurationIntent.self, provider: CatProvider()) { entry in
            CatWidgetView(entry: entry)
        }.configurationDisplayName("猫")
        .description("いつ猫に餌をあげたり遊んだりしたか見てみましょう。")
    }
    
}

シミュレーターでプログラムを実行できるようになりました。ウィジェットを長押しすると Edge Widget というオプションが表示され、猫の名前を変更できます。

ezgif-6-bf9e4b4783c9.gif

ウィジェットの再読み込み

ウィジェット内のコンテンツに変更が加えられた場合には、メインの iOS アプリケーションでウィジェットの再読み込み機能を手動でコールすることができます。

// import WidgetKit
WidgetCenter.shared.reloadAllTimelines()

例えば、ToDoアプリとToDo項目の数を表示するウィジェットを持っているとします。ユーザーがToDo項目を完了または追加したときに、ウィジェットをリロードすることができます。


「App Extensions」で「Core Data」を使用する


:relaxed: Twitter @MszPro

:sunny: 私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。

69
53
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?