前回は、【iOS】ウィジェットとCoreDataを連携する入門〜ユーザーが構成可能なウィジェットを作成する〜という記事を書いたので、次はさらなる続編として、ウィジットから特定の画面に遷移する方法を記載したいと思います。
はじめに
下記の記事の続編になりますので、不明点があれば下記を参照していただければと思います。
作ったもの
環境
- Xcode 14.2
- iOS 16.2
選択されたウィジェットを識別できるようにURLをセットする
widgetURL(_:)
モディファイアを使用して、タップされたウィジェットを特定する為のURLをセットしています。
今回はURL(string: "example://widgetlink?item_id=\(intentItem.identifier ?? "")")
でクエリアイテムとしてItem
のid
をセットしています。
struct CoreDataWidgetEntryView : View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
let entry: Provider.Entry
var body: some View {
// 選択されたアイテムがある場合は表示
if let intentItem = entry.configuration.parameter {
VStack {
Text("選択中")
Text(intentItem.displayString)
// 追加
// widgetのURLをアプリ側でハンドリングできるようにする
.widgetURL(URL(string: "example://widgetlink?item_id=\(intentItem.identifier ?? "")"))
}
} else {
VStack {
Rectangle()
.fill(.blue.gradient)
.frame(height: 32)
Divider()
Spacer()
ForEach(items) { item in
Text(item.timestamp ?? Date(), style: .time)
}
Spacer()
}
}
}
}
リンクを設定する方法は、2通りあります。上述したwidgetURL(_:)
を使用する方法と、下記のようにLink
を使用する方法です。
Link(destination: URL(string: "example://widgetlink?item_id=\(intentItem.identifier ?? "")")!,
label: { Text(intentItem.displayString) })
ウィジェットサイズやリンクの違いについては[iOS 14]WidgetでDeep Link作成の中で詳しく書いてありましたので、こちらを確認いただければと思います。
アプリ側でURLをハンドリングする
URLからアイテムのIDを取得
今回のウィジェットから送られてくるURLは、example://widgetlink?item_id=
のような形式になる為、該当のURLならば、クエリパラメータのUUID
を取得します。
private func getItemId(from url: URL) -> UUID? {
guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true),
urlComponents.scheme == "example",
urlComponents.host == "widgetlink",
urlComponents.queryItems?.first?.name == "item_id",
let uuidString = urlComponents.queryItems?.first?.value else {
return nil
}
return UUID(uuidString: uuidString)
}
該当IDのアイテムをCoreDataから取得
UUID
が一致するアイテムを取得します。
private func fetchItem(id uuid: UUID) -> Item? {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %@", uuid as CVarArg)
do {
let items = try viewContext.fetch(fetchRequest)
guard let item = items.first
else { return nil }
return item
} catch let error as NSError {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
ContentViewで画面を表示させる
import SwiftUI
import CoreData
import WidgetKit
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults
// 表示する為のアイテム
@State private var selectedItemForPresenting: Item?
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
// URLのハンドリング
.onOpenURL { url in
let uuid = getItemId(from: url)
if let uuid,
let item = fetchItem(id: uuid) {
selectedItemForPresenting = item
}
}
// 表示する為のアイテムがある場合は、sheetで表示
.sheet(item: $selectedItemForPresenting) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
}
// ~以下省略
.opneURL
ウィジェットをタップして、リンクのURLが存在する場合は、onOpenURL
内の処理が実行されます。
// URLのハンドリング
.onOpenURL { url in
let uuid = getItemId(from: url)
if let uuid,
let item = fetchItem(id: uuid) {
selectedItemForPresenting = item
}
}
URLからUUID
を取得して、そのUUID
と一致するアイテムを取得して、@State
変数のselectedItemForPresenting
に値を代入しています。
.sheet(item:onDismiss:content:)
今回でいうと、@State
変数のselectedItemForPresenting
に値がある場合に、 モーダルで画面を表示するようにしています。
// 表示する為のアイテムがある場合は、sheetで表示
.sheet(item: $selectedItemForPresenting) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
以上で、ウィジェットを選択して特定の画面に遷移するところまでが出来ました。
おわりに
触れば触るほど、ウィジェットで何か楽しそうなことが出来そう気がムンムンします。
medium
やlarge
サイズのウィジットでは複数のリンクを設置することも出来るので、ウィジットだけで何か楽しいアプリが出来るのでは?
ワクワクは広がるばかり。これからもアプリ開発を楽しんでいきたいと思います。