質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1220閲覧

【SwiftUI】ローカル通知(UserNotifications)を行う際のデータの受け渡し

liday

総合スコア12

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

1グッド

0クリップ

投稿2022/12/13 13:19

前提

ローカル通知機能付きのTodoリストを作成しています。

ご参照いただきたいファイルは以下2つです。
1.ListView.swift → Todoリストの入力と表示を行うビューファイル
2.NotificationManager.swift → ローカル通知の許可するメソッドと、通知を行うメソッドを内包したクラスを記載したファイル

実現したいこと

TextFieldに入力した内容を、ローカル通知でお知らせできるようにしたい。

※通知にはUserNotifications をインポートして使用しています。
※各Todoの登録はUserDefaultsの機能を使っています。

発生している問題

ListView.swift 内に記述したextension ListViewに
ローカル通知を設定するメソッド「scheduleNotification」を行うボタンを設置しましたが、
このメソッドの引数である以下3つに入れる値の記述方法がわかりません。

todoSubtitle
todoHour
todoMinute

該当のソースコード①(ListView.swift)

swift

1 2 3import SwiftUI 4 5struct ListView: View { 6 @State var newItem: String = "" 7 @State var toDoList: [String] = [] 8 9 @State var selectedValue1: Int = -1 10 @State var selectedValue2: Int = -1 11 12 var body: some View { 13 VStack { 14 15 //新規予定の登録 16 HStack { 17 TextField("新しい予定を入力してください", text: $newItem) 18 .textFieldStyle(RoundedBorderTextFieldStyle()) 19 .frame(width: 300) 20 21 Button { 22 toDoList.append(newItem) 23 newItem = "" 24 UserDefaults.standard.set(toDoList, forKey: "ToDoList") 25 } 26 label: { 27 ZStack { 28 RoundedRectangle(cornerRadius: 5) 29 .frame(width: 50, height: 30) 30 .foregroundColor(.green) 31 32 Text("追加").foregroundColor(.white) 33 } 34 } 35 } 36 37 //登録したTodoのリスト 38 List { 39 ForEach(toDoList, id: \.self) { item in 40 VStack{ 41 HStack{ 42 Text(item) 43 Spacer() 44 } 45 46 todoDatePicker 47 } 48 } 49 } 50 } 51 .onAppear() { 52 NotificationManager.instance.requestAutorization() 53 guard let defaultItem = UserDefaults.standard.array(forKey: "ToDoList") as? [String] 54 else { return } 55 toDoList = defaultItem 56 } 57 } 58} 59 60//通知をする時刻を設定するピッカー 61extension ListView { 62 var todoDatePicker : some View { 63 HStack{ 64 Picker("", selection: $selectedValue1) { 65 ForEach(-1 ..< 24, id: \.self) { hour in 66 VStack{ 67 if hour == -1 { 68 Text("未選択").tag(hour) 69 } else{ 70 Text("\(hour)").tag(hour) 71 } 72 } 73 } 74 } 75 76 Text("時") 77 78 Picker("", selection: $selectedValue2) { 79 ForEach(-1 ..< 60, id: \.self) { minute in 80 81 if minute == -1 { 82 Text("未選択").tag(minute) 83 } else { 84 Text("\(minute)").tag(minute) 85 } 86 } 87 } 88 89 Text("分") 90 91 Button { 92// !!!!!以下のメソッド内の引数の記述がわかりません!!!! 93 94 NotificationManager.instance.scheduleNotification(todoSubtitle: <#T##String#>, todoHour: <#T##Int#>, todoMinute: <#T##Int#>) 95 } label: { 96 Text("に通知") 97 } 98 } 99 } 100} 101 102//ファイルのプレビュー 103struct ListView_Previews: PreviewProvider { 104 static var previews: some View { 105 ListView() 106 } 107} 108

該当のソースコード②(NotificationManager.swift)

swift

1import SwiftUI 2import UserNotifications 3 4class NotificationManager { 5 6 static let instance = NotificationManager() 7 8// 通知の許可 9 func requestAutorization() { 10 let options: UNAuthorizationOptions = [.alert, .sound, .badge] 11 UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in 12 if let error = error { 13 print("ERROR: \(error)") 14 } else { 15 print("SUCCESS") 16 } 17 } 18 } 19 20// 設定した時・分にローカル通知を行う 21 func scheduleNotification(todoSubtitle: String, todoHour: Int, todoMinute: Int) { 22 23 let content = UNMutableNotificationContent() 24 content.title = "Todoリストからの通知" 25 content.subtitle = todoSubtitle 26 content.sound = .default 27 content.badge = 1 28 29 30 var dateComponents = DateComponents() 31 dateComponents.hour = todoHour 32 dateComponents.minute = todoMinute 33 34 let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) 35 36 let request = UNNotificationRequest( 37 identifier: UUID().uuidString, 38 content: content, 39 trigger: trigger) 40 UNUserNotificationCenter.current().add(request) 41 } 42}

ListView.swift のプレビュー

イメージ説明

試したこと

▼[SwiftUI] SwiftUIのForEach内でBinding変数を渡したい
https://software.small-desk.com/development/2020/04/13/swiftui-foreach-binding/

上記リンクの記事を参考に、ListView.swift ファイルのextension ListView内のForEach部分の記述を
以下配列の変数を宣言した後にall indicesを使って変更してみたのですが、
エラー表示になり解決できておりません。

@State var selectedValues1 = [-1 ..< 24]

Picker("", selection: $selectedValue1) { ForEach(selectedValues1.indices) { hour in VStack{ // ←エラー(Type '()' cannot conform to 'View') if hour == -1 { Text("未選択").tag(hour) } else{ Text("\(hour)").tag(hour) } selectedValue1 = selectedValues1[hour] // ←エラー (Cannot assign value of type 'Range<Int>' to type 'Int') } } }

補足情報

Xcode Version 14.1

funaki👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

恐らくですが、
todoDatePicker が
何番目の要素か分かるよう組むことができれば
解決すると思われます。

サンプルコードを添付しますが、
一度 userDefaults を初期化しないと動かないと思われます。

強引にリストを更新しているため
ここはもっとうまく記述できる方法があるかもしれません。

追記:
ボタンがうまく押せないため
無理矢理ボタンを押せるようにして試してみました。
ボタンを正常に機能させる方法は、申し訳ございませんがわかりません。
通知が機能するのは確認しました。

SwiftUI

1import SwiftUI 2import UserNotifications 3 4struct ContentView: View { 5 @State var newItem: String = "" 6 @State var toDoList: [String] = [] 7 8 @State var selectedValue1: [Int] = [] 9 @State var selectedValue2: [Int] = [] 10 11 12 var body: some View { 13 VStack { 14 //新規予定の登録 15 HStack { 16 TextField("新しい予定を入力してください", text: $newItem) 17 .textFieldStyle(RoundedBorderTextFieldStyle()) 18 .frame(width: 300) 19 20 Button { 21 toDoList.append(newItem) 22 selectedValue1.append(-1) 23 selectedValue2.append(-1) 24 newItem = "" 25 UserDefaults.standard.set(toDoList, forKey: "ToDoList") 26 UserDefaults.standard.set(selectedValue1, forKey: "selectedValue1") 27 UserDefaults.standard.set(selectedValue2, forKey: "selectedValue2") 28 } label: { 29 ZStack { 30 RoundedRectangle(cornerRadius: 5) 31 .frame(width: 50, height: 30) 32 .foregroundColor(.green) 33 34 Text("追加").foregroundColor(.white) 35 } 36 } 37 } 38 39 //登録したTodoのリスト 40 List { 41 ForEach(0 ..< toDoList.count) { index in 42 VStack{ 43 HStack{ 44 Text(toDoList[index]) 45 Spacer() 46 } 47 48 todoDatePicker(index) 49 } 50 } 51 } 52 .id(UUID()) //強引にリストを更新 53 } 54 .onAppear() { 55 NotificationManager.instance.requestAutorization() 56 guard let defaultItem = UserDefaults.standard.array(forKey: "ToDoList") as? [String] 57 else { return } 58 guard let defaultSelectedValue1 = UserDefaults.standard.array(forKey: "selectedValue1") as? [Int] 59 else { return } 60 guard let defaultSelectedValue2 = UserDefaults.standard.array(forKey: "selectedValue2") as? [Int] 61 else { return } 62 toDoList = defaultItem 63 selectedValue1 = defaultSelectedValue1 64 selectedValue2 = defaultSelectedValue2 65 } 66 } 67} 68 69//通知をする時刻を設定するピッカー 70extension ContentView { 71 func todoDatePicker(_ index: Int) -> some View { 72 return HStack { 73 Picker("", selection: $selectedValue1[index]) { 74 ForEach(-1 ..< 24, id: \.self) { hour in 75 VStack{ 76 if hour == -1 { 77 Text("未選択").tag(hour) 78 } else{ 79 Text("\(hour)").tag(hour) 80 } 81 } 82 } 83 } 84 85 Text("時") 86 87 Picker("", selection: $selectedValue2[index]) { 88 ForEach(-1 ..< 60, id: \.self) { minute in 89 90 if minute == -1 { 91 Text("未選択").tag(minute) 92 } else { 93 Text("\(minute)").tag(minute) 94 } 95 } 96 } 97 98 Text("分") 99 100 Button { 101 //ここに記述するとなぜかうまくいかないため、一時的に無理矢理 onTapGesture で記述 102 } label: { 103 Text("に通知") 104 } 105 .onTapGesture { 106 NotificationManager.instance.scheduleNotification(todoSubtitle: toDoList[index], todoHour: selectedValue1[index], todoMinute: selectedValue2[index]) 107 } 108 } 109 } 110} 111 112 113class NotificationManager { 114 115 static let instance = NotificationManager() 116 117 // 通知の許可 118 func requestAutorization() { 119 let options: UNAuthorizationOptions = [.alert, .sound, .badge] 120 UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in 121 if let error = error { 122 print("ERROR: \(error)") 123 } else { 124 print("SUCCESS") 125 } 126 } 127 } 128 129 // 設定した時・分にローカル通知を行う 130 func scheduleNotification(todoSubtitle: String, todoHour: Int, todoMinute: Int) { 131 132 let content = UNMutableNotificationContent() 133 content.title = "Todoリストからの通知" 134 content.subtitle = todoSubtitle 135 content.sound = .default 136 content.badge = 1 137 138 139 var dateComponents = DateComponents() 140 dateComponents.hour = todoHour 141 dateComponents.minute = todoMinute 142 143 let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) 144 145 let request = UNNotificationRequest( 146 identifier: UUID().uuidString, 147 content: content, 148 trigger: trigger) 149 UNUserNotificationCenter.current().add(request) 150 } 151} 152 153 154struct ContentView_Previews: PreviewProvider { 155 static var previews: some View { 156 ContentView() 157 } 158}

投稿2022/12/14 05:16

編集2022/12/14 05:46
uni2

総合スコア256

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

liday

2022/12/15 11:01

uni2様 ご回答いただきありがとうございます! お書きいただいたコードで、無事動作するようになりました!! ボタンが正常に動作しない部分は私も色々試してみたのですが、解決できません…。 1つ目のPickerの対象範囲が横幅いっぱいになっているので、ZStack等を使ってみたのですが、それもダメでした。 恐れ入ります、1点追加でおうかがいしたいのですが、 List{}に対して、.id(UUID()) を付けて「//強引にリストを更新」と解説してくださっておりますが、 これはどういう意味なのでしょうか? ネットで検索してUUID()の意味は出てくるのですが、このようにしてリストに付ける説明が見つけれませんでした。。 お忙しい中申し訳ございません、もし可能でしたらお教えください。 よろしくお願いいたします。
uni2

2022/12/16 01:08

Identifiable に関しては 私も分かっているとは言い難いです。 恐らくキャッシュ的な考え方だとは思います。 List を再描画する際に 前回と id が同じならば 前回の内容が主に使われて 更新が簡易化されるのだと思っています。 なのでサンプルコードでは id を新しいものにし続けて 更新を簡易化させないようにしています。 (簡易化すると恐らく動かない端末があると思われます) なお、毎フレーム UUIID() を実行すると 時間がかかると思われるため 実際はフィールドに id を持たせて 更新したい時にフィールドの id を 変更させる仕組みが必要と思われます。 ---- フィールド ---- var uuid = UUID() ---- body ---- List{ }.id(uuid) Button { //NotificationManager の処理 uuid = UUID() } label: { Text("に通知") } ---- 等でしょうか? ということで、 UUID に関してはよく分からないということで 力になれません。 申し訳ございません。 長文にて失礼します。
liday

2022/12/16 10:12

丁寧にご説明いただきありがとうございます! 度々お手数をおかけしてしまい、申し訳ございませんでした。
uni2

2022/12/20 04:22

完全な挙動ではありませんが、 List に .buttonStyle(.borderless) を付与することで ボタンが押せるようになることが分かりましたので 追記しておきます。 listStyle を変更しても 押せるようになるようですが 見た目が変わるためおすすめしません。 --------------- //登録したTodoのリスト List { ForEach(0 ..< toDoList.count, id: \.self) { index in VStack{ HStack{ Text(toDoList[index]) Spacer() } todoDatePicker(index) } } } .buttonStyle(.borderless) //追記 .id(UUID()) //強引にリストを更新
liday

2022/12/23 11:08

uni2様 ボタン押せました!! 自分では絶対解決できなかったと思います。 何度もお助けいただき、本当にありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問