この記事では、SwiftUIでのWidget_iOS14.0の実装方法、そしてより実践的な利用方法としてCoreDataとの連携方法を紹介しています。
◆動作検証環境
・XCode:12.1
・SwiftUI:2.0
・iOS:14.0
・Life Cycle:SwiftUI App
Widget_iOS14.0の実装方法【Widget機能の追加】
Widget機能を利用するには、まずホストとなるApp(Containing Appと呼んだりしますが、この記事ではホストAppとして説明しています)が必要となります。
通常の方法でプロジェクトを作成するか、Widget機能を利用するプロジェクトを開きます。
Widget_iOS14.0の実装条件
Widget_iOS14.0を利用するには、
- Interface:SwiftUI
- iOSバージョン:14.0以上
にする必要があります。
Widget機能の追加
Widget機能を追加するには、
- XCodeメニューの[File] -> [New] -> [Target]と選択
- Multiplatformで[iOS]を選択し、Application Extensionの中から[Widget Extension]を選択
Product Nameに任意の名前を指定します。
今回はInclude Configtation Intentのチェックを外して作成します(ユーザーが設定可能なWidgetを作成する場合は、こちらにチェックします)。
Activate “WidgetExtension” scheme? とメッセージが表示される場合は、[ Activate ]を選択します。
これで、Widget機能に関係するフォルダが作成されます。
Widget_iOS14.0の実装方法【Widgetの表示確認】
Widgetの追加を行ったら、ホーム画面でWidgetの表示確認を行います。
ビルドするターゲットが追加したWidgetになっている事を確認し、ビルドします。
デフォルトのままのコードでは、以下のように時刻が表示されます。
デフォルトのWidgetのファイル(この記事のサンプルではTestWidget.swift)のデフォルトのコードは以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | import WidgetKit import SwiftUI struct Provider: TimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date()) } func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date()) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date } struct TestWidgetEntryView : View { var entry: Provider.Entry var body: some View { Text(entry.date, style: .time) } } @main struct TestWidget: Widget { let kind: String = "TestWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in TestWidgetEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") } } struct TestWidget_Previews: PreviewProvider { static var previews: some View { TestWidgetEntryView(entry: SimpleEntry(date: Date())) .previewContext(WidgetPreviewContext(family: .systemSmall)) } } |
Widgetの表示に直接関係するのは、以下の箇所です。
1 2 3 4 5 6 7 8 9 | struct TestWidgetEntryView : View { var entry: Provider.Entry var body: some View { Text(entry.date, style: .time) } } |
試しに、以下のようにコードを変更して、表示を確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct TestWidgetEntryView : View { var entry: Provider.Entry var body: some View { VStack{ Text("Title") .font(.title) .fontWeight(.heavy) .foregroundColor(.blue) Divider() Text("discription discription discription") .padding() } .background(Color.green.opacity(0.1)) } } |
Widgetの表示は以下のようになります。
Widget_iOS14.0の実装方法【WidgetとCoreDataの連携】
Widgetと通常のAppは別のターゲットとなり、そのままではデータの共有はできません。
今回はWidget、ホストAppでCoreDataを通し、共通のデータを利用できるようにします。
ホストAppとCoreDataは、以前の記事で紹介したものを利用します。
詳しくはこちらの記事を参考にしてください。
この記事では、SwiftUIでのCoreData の基本的な実装方法【CRUD(作成、更新、削除)】を紹介しています。簡単なタスク管理App(タスク内容とスケジュールの登録)を作成しながら解説します。 ◆動[…]
App Groupの作成
おおもとのプロジェクト編集ファイルで、TARGETをWidgetにします。
[Siging & Capabilities]メニューで[+Capability] -> [App Groups]を選択します。
+マークを選択しAppGroupを作成します。
Group名はGroup.com.sample.CoreDataTest のようにします。
AppGroupの作成ができたら、ホストApp、Widget両方に同じAppGroupを指定します。
Persistence.swiftの編集
作成したAppGroup に対応できるようにPersistence.swift を以下のように編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import CoreData import Foundation class PersistenceController { static let shared = PersistenceController() private let persistentContainer: NSPersistentContainer = { let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("Task") let container = NSPersistentContainer(name: "CoreDataTest") container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)] container.loadPersistentStores(completionHandler: { storeDescription, error in if let error = error as NSError? { print(error.localizedDescription) } }) return container }() } extension PersistenceController { var managedObjectContext: NSManagedObjectContext { persistentContainer.viewContext } var workingContext: NSManagedObjectContext { let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) context.parent = managedObjectContext return context } } extension FileManager { static let appGroupContainerURL = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: "group.com.sample.CoreDataTest")! } |
Appファイルの編集
Persistence.swift の編集に対応するために、Applicationファイルを下記のように編集します。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import SwiftUI @main struct CoreDataTestApp: App { let persistenceController = PersistenceController.shared.managedObjectContext var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController) } } } |
関係ファイルのTarget Menbershipの指定
ホストAppのCoreDataに関係するファイルを編集し、WidgetファイルでもCoreDataのデータを利用できるようにします。
この記事で利用しているホストAppの場合、CoreDataTest.xcdatamodeld と、Persistence.swift のTargetMembershipを編集します。
これで、ホストAppのAppGroupへの対応は一時完了です。
ビルドを行って、今までどおりCoreDataからの読み込み、書き込みができるか確認します。
Widget_iOS14.0の実装方法【WidgetでCoreDataのデータを利用する】
ホストAppの準備が整った後は、Widget側の編集です。
Widget側のファイルを以下のように編集します。
今回は、CoreData内の1番最初Contentデータを表示するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import WidgetKit import SwiftUI import CoreData struct Provider: TimelineProvider { var moc = PersistenceController.shared.managedObjectContext init(context : NSManagedObjectContext) { self.moc = context } func placeholder(in context: Context) -> SimpleEntry { var task:[Task]? let request = NSFetchRequest<Task>(entityName: "Task") request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)] do{ let result = try moc.fetch(request) task = result } catch let error as NSError{ print("Could not fetch.\(error.userInfo)") } return SimpleEntry(date: Date(), task: task!) } func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { var task:[Task]? let request = NSFetchRequest<Task>(entityName: "Task") request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)] do{ let result = try moc.fetch(request) task = result } catch let error as NSError{ print("Could not fetch.\(error.userInfo)") } let entry = SimpleEntry(date: Date(), task: task!) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var task:[Task]? let request = NSFetchRequest<Task>(entityName: "Task") request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)] do{ let result = try moc.fetch(request) task = result } catch let error as NSError{ print("Could not fetch.\(error.userInfo)") } // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() let entries = [ SimpleEntry(date: currentDate, task: task!), ] let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let task:[Task] // } struct TestWidgetEntryView : View { var entry: Provider.Entry var body: some View { VStack{ Text(entry.task.first?.content ?? "") Text(entry.date, style: .time) } } } @main struct TestWidget: Widget { let kind: String = "TestWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in TestWidgetEntryView(entry: entry) .environment(\.managedObjectContext, PersistenceController.shared.managedObjectContext)// } .configurationDisplayName("My Widget") .description("This is an example widget.") } } |
これでホストApp側とWidget側でCoreDataのデータの連携が行えました。
以上、SwiftUIでのWidget_iOS14.0の実装方法、実践的な利用方法としてCoreDataとの連携方法を紹介しました。