LoginSignup
18
11

[Swift] Journaling Suggestions API に関して

Last updated at Posted at 2024-02-08

はじめに

iOS 17.2 からジャーナル機能が提供されました!
(なんで、17.2 っていう中途半端なタイミングで...みたいなツッコミもありつつ...)

今回、機能だけでなく開発者向けの API も提供されました。

これを触ってみたので、ざっくりと紹介していきます。

Journaling Suggestions API に触れてみる

公式の動画では、以下のステップで実行できるとのこと。

Screenshot 2023-12-13 at 5.04.24.png

(引用:https://developer.apple.com/videos/play/tech-talks/111384/)

では、実際にやっていきます。

セットアップ

1. 環境の用意

  • Xcode 15.1~
  • iOS 17.2~ の実機(ここ重要)

Xcode 15の Beta にも入っている(入ってないものもあるらしい?)が、実機ビルドを考えると最新のものを入れておくと良いかと思います。

また、後々のコード面でも触れますが、ジャーナルの機能は実機でしか確認できないので、かならず実機を用意してください。

2. Capability の追加

Capability に Journaling Suggestions が新しく追加されています。

Screenshot 2023-12-12 at 23.36.22.png

これを追加すると、設定の一覧に追加されます。

Screenshot 2023-12-13 at 1.32.08.png

これで Journaling Suggestions の API を使えるようになります。

実装

- ジャーナルの提案画面を表示

Capability を追加していれば、JournalingSuggestions を import できます。

.swift
import JournalingSuggestions

あとは、ジャーナルのピッカーを呼び出すだけです。
JournalingSuggestionsPicker を使うだけで簡単に実装できます。

.swift
struct ContentView: View {
    var body: some View {
        JournalingSuggestionsPicker {
            Text("Picker Label")
        } onCompletion: { suggestion in
            // do some actions
        }
    }
}

実際の画面はこんな感じです。

ピッカーを呼び出すラベルを設定し、それをタップすればサジェストが自動で表示されます。

- 提案画面から得られるデータにアクセスする

ジャーナルの提案一覧から、詳細な情報を閲覧できます。

この詳細から 「追加」 を押すとデータが取得できます。
また、アイコン(オレンジの部分)をタップした場合は直接データを取得できます。

取得したデータは、JournalingSuggestionsPickeronCompletion に入ってきます。実際のデータはこのような形です。(先ほど提示した位置情報のジャーナルデータ)

.swift
JournalingSuggestions.JournalingSuggestion(
	items: [
		JournalingSuggestions.JournalingSuggestion.ItemContent(
			id: BD17F57B-EF6C-494A-AEAC-FB29DB89441B, 
			representations: [
				JournalingSuggestions.JournalingSuggestion.Location
			], 
			content: JournalingSuggestions.InternalAssetContent(
				providers: [
					JournalingSuggestions.InternalAssetContent.AssetProvider(
						type: JournalingSuggestions.JournalingSuggestion.Location, 
						loader: (Function)
					)
				])
			)
	], 
	title: "訪問(夜間、初台1丁目へ)", 
	date: Optional(2023-12-29 11:39:28 +0000 to 2023-12-29 14:35:55 +0000), 
	suggestionIdentifier: 4FFF438A-C99A-4EE6-8B93-823C58551DAA,
	suggestionHashValue: 10797189376
)

JournalingSuggestionsPicker のコールバックでは

  • let items: [JournalingSuggestion.ItemContent]
  • let title: String
  • let date: DateInterval?

の情報にアクセスできます。例えば、先ほどのデータではこうなります。

.swift
JournalingSuggestionsPicker {
    Text("Picker Label")
} onCompletion: { suggestion in
    print(suggestion.items) // [JournalingSuggestions.JournalingSuggestion.ItemContent(...)]
    print(suggestion.title) // "訪問(夜間、初台1丁目へ)"
    print(suggestion.date) // 2023-12-29 11:39:28 +0000 to 2023-12-29 14:35:55 +0000
}

items はサジェストされたジャーナルデータの情報を個別に持っています。
先ほどの例では1つでしたが、データが複数の場合はこのような配列で取得されます。

.swift
JournalingSuggestions.JournalingSuggestion(
	items: [
		JournalingSuggestions.JournalingSuggestion.ItemContent(
			representations: [
				JournalingSuggestions.JournalingSuggestion.MotionActivity
			], 
			..
		), 
		JournalingSuggestions.JournalingSuggestion.ItemContent(
			representations: [
				JournalingSuggestions.JournalingSuggestion.Location
			], 
			..
		), 
		JournalingSuggestions.JournalingSuggestion.ItemContent(
			representations: [
				JournalingSuggestions.JournalingSuggestion.LivePhoto,
				UIImage, 
				SwiftUI.Image
			], 
			..
		),
.
.

写真、位置情報、歩数など、ItemContent として個別のデータで格納されます。

- データの詳細にアクセスする

取得できる items(ItemContent の配列)から、詳細なデータにアクセスできます。

.swift
public struct ItemContent : Identifiable {
    public var id: UUID
    public var representations: [JournalingSuggestionAsset.Type]
    public func hasContent<Content>(ofType content: Content.Type) -> Bool where Content : JournalingSuggestionAsset
    public func content<Content>(forType content: Content.Type) async throws -> Content? where Content : JournalingSuggestionAsset
    public typealias ID = UUID
}

この ItemContent が持つ representations プロパティにはそれぞれの型情報があるため、この型を元にどの情報を取り出すかを func content で行います。

representations の中身は、少し前に記載あるのでそちらを参照してください。)

今回は、Location タイプを指定して、そのデータを取り出してみます。

.swift
JournalingSuggestionsPicker {
    Text("Picker Label")
} onCompletion: { suggestion in
    for item in suggestion.items {
        // バリデーション
        guard item.hasContent(ofType: JournalingSuggestion.Location.self) else {
            return
        }
        // 値の取得
        do {
            Task {
                let location = try await item.content(forType: JournalingSuggestion.Location.self)
                print("place", location?.place)
                print("city", location?.city)
                print("location", location?.location)
                print("date", location?.date)
            }
        }
    }
}

例えば、このようなデータが取得できます。

place: Optional("新宿3丁目")
city: Optional("新宿区")
location: Optional(<+35.69109060,+139.70486950> +/- 113.60m (speed -1.00 mps / course -1.00) @ 1/01/01, 9:18:59 GMT+09:18:59)
date: Optional(2024-01-17 10:38:57 +0000)

また、データが存在するかどうか、func hasContent を使ってあらかじめ確認できます。

個別のアイテムからデータを取り出しましたが、大元の suggestion 自体から値を型を指定して値を取得することもできます。

.swift
JournalingSuggestionsPicker {
    Text("Picker Label")
} onCompletion: { suggestion in
    do {
        Task {
            let locations = try await suggestion.content(forType: JournalingSuggestion.Location.self)
            print("locations:", locations)
        }
    }
}

以下は取得データ例です。

[
    JournalingSuggestions.JournalingSuggestion.Location(
        place: Optional("新宿3丁目"), 
        city: Optional("新宿区"), 
        location: Optional(<+35.69109060,+139.70486950> +/- 113.60m (speed -1.00 mps / course -1.00) @ 1/01/01, 9:18:59 GMT+09:18:59), 
        date: Optional(2024-01-17 10:38:57 +0000)
    )
]

今回は1つしかありませんが、全部のアイテムから、全ての指定した型のデータを取得できます。

- 取得できるデータに関して

取得できるデータは JournalingSuggestionAsset プロトコルに準拠しているものです。

.swift
@available(iOS 17.2, *)
public protocol JournalingSuggestionAsset {
    associatedtype JournalingSuggestionContent : JournalingSuggestionAsset = Self
}

JournalingSuggestionAsset は、いくつかの Struct 型があります。
中身はデータによって変わり、現状は11の型があります。

情報 関連
Workout ワークアウトの情報

-> アクティビティの種類、消費エネルギー、距離、
心拍数、時間、緯度経度、ワークアウトのアイコン
HealthKit
WorkoutGroup ワークアウトの情報
(ワークアウトが複数ある場合に配列で取得)
HealthKit
Contact 連絡先の情報

-> 名前、連絡先のアイコン
CallKit
Location 位置情報

-> 場所、都市名、緯度経度、日付
LocationGroup 位置情報
(位置情報が複数ある場合に配列で取得)
Song ライブラリからの音楽情報

-> 曲名、アーティスト名、アルバム名、アートワーク画像、
再生日
Podcast 聴いたポッドキャストの情報

-> エピソード名、番組名、アートワーク画像、再生日
Photo ライブラリからの写真情報

-> 画像、撮影日
Video ライブラリからの動画情報

-> 動画、撮影日
LivePhoto ライブラリからのライブフォト情報

-> 動画、撮影日
MotionActivity モーション活動の情報

-> 歩数、活動時間、アクティビティのアイコン

ジャーナルとして扱うこれらのデータは、SiriKitCallKitHealthKit からもサジェストされるため、多様な種類の型にマッピングできるようにが存在しているように思われます。(今後も増える気がする..🤔)

また、UIImageImageJournalingSuggestionAsset に準拠しており、データとして扱えるようになっています。(取得できるデータに画像の UI コンポーネントが返ってくることがある。)

- 実装における注意

JournalingSuggestions をインポートして何かを実装しようとした時
現状は Simulator では動かないためエラーが出ます。

Screenshot 2023-12-13 at 2.11.48.png

エラーの通りcanImportを使用して

.swift
#if canImport(JournalingSuggestions)
import JournalingSuggestions
#endif

とすることが推奨されます。

例えば、以下のような書き方です。

.swift
#if canImport(JournalingSuggestions)
import JournalingSuggestions
#endif

struct ContentView: View {
    var body: some View {
        #if canImport(JournalingSuggestions)
        JournalingSuggestionsPicker {
            Text("Picker Label")
        } onCompletion: { suggestion in
            // do some actions
        }
        #else
        Text("can not import JournalingSuggestions on Simulator")
        #endif
    }
}

(結局 Simlator でビルドできないだけなので、煩雑になるのが嫌な方はそのままでも...笑)

終わりに

データを提供してくれるのみで、こちらから書き込んだり追加したりできない点が、少し不憫だなというのが印象です。

かなり限定的な機能なため、この API 自体を他のアプリに入れ込むのも、使いどころが難しいなと思ったり🧐

何か間違いあればコメントいただけますとmm

18
11
0

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
18
11