回答編集履歴

2

回答追記

2020/04/06 14:57

投稿

TakeOne
TakeOne

スコア6299

test CHANGED
@@ -183,3 +183,105 @@
183
183
  }
184
184
 
185
185
  ```
186
+
187
+
188
+
189
+ ---
190
+
191
+ (4/6 23:55追記)
192
+
193
+
194
+
195
+ コメントを受けて、追加確認しました。
196
+
197
+ 確かに`@EnvironmentObject var test: Test`を追加するとAppDelegateでIndex out of rangeのエラーが発生します。
198
+
199
+
200
+
201
+ それで紹介していただいた記事等を読んだ上でよく考え直してみたのですが、前回提示したコードでRowViewに受け渡している `self.$test.datas[i]` は、一見、配列の要素であるDataだけを受け渡しているように見えますが、実は内部では`test`全体を共有した状態で受け渡しているのではないかと考えるようになりました。DataはstructなのでDataの要素を書き換えるにはDataインスタンスの差し替えが必要で、Dataインスタンスの差し替えをするには、共有されたData配列全体の何番目の要素を差し替えるか知っていないといけないからです。
202
+
203
+ つまり、前回示したコードは、RowViewの中で配列全体や配列番号にアクセスするようなコードは自分で明示的に書いていませんが、実は`$data`の内部で配列全体へのアクセスが発生しており、削除された配列番号にアクセスしてしまうという問題は結局解決されていないのではないかと思います。
204
+
205
+
206
+
207
+ EnvironmentObjectの有無でエラーが発生するかしないかは、ListからViewが削除される前にViewを再構築するか否かという微妙な動きの違いで、たまたまエラーが発生したりしなかったりするというだけなのではないかと思います。
208
+
209
+
210
+
211
+ そのため、受け渡し先で配列要素を更新するには、配列全体の中から、これから更新するData位置を検索して、存在していればそれを更新し、削除されて既に存在していなければ更新しないという形で処理するしかない(それが最も安全なやり方である)と思うようになりました。これはつまり、Appleのチュートリアルでやっているやり方(RowView2のやり方)と同じ考え方です。ただ、Appleのチュートリアルは、データの削除がないという前提のコードなのでfirstIndexメソッドの結果を `!`でアンラップしていて、そこでエラーが発生しています。データの削除があることを前提にするなら、それがnilだった場合は何もしないようにしたので良さそうに思います。それを踏まえて、配列要素内の値をStepperで変更したい場合、自分ならこうするというのをもう一度書き直してみました。
212
+
213
+ ``` swift
214
+
215
+ struct RowView: View {
216
+
217
+ @EnvironmentObject var test: Test
218
+
219
+ let data: Data
220
+
221
+
222
+
223
+ @State var count: Int = 0
224
+
225
+
226
+
227
+ var body: some View {
228
+
229
+ Stepper(value: $count, onEditingChanged: {touchDown in
230
+
231
+ if !touchDown {
232
+
233
+ if let index = self.test.datas.firstIndex(where: {$0.id == self.data.id}) {
234
+
235
+ self.test.datas[index].count = self.count
236
+
237
+ }
238
+
239
+ }
240
+
241
+ }) {
242
+
243
+ Text(data.name + "(data.count)")
244
+
245
+ }.onAppear {
246
+
247
+ self.count = self.data.count
248
+
249
+ }
250
+
251
+ }
252
+
253
+ }
254
+
255
+
256
+
257
+ struct ContentView: View {
258
+
259
+ @EnvironmentObject var test: Test
260
+
261
+
262
+
263
+ var body: some View {
264
+
265
+ List {
266
+
267
+ ForEach(test.datas) { data in
268
+
269
+ RowView(data: data)
270
+
271
+ }.onDelete { offsets in
272
+
273
+ self.test.datas.remove(atOffsets: offsets)
274
+
275
+ }
276
+
277
+ }
278
+
279
+ }
280
+
281
+ }
282
+
283
+
284
+
285
+ ```
286
+
287
+ 今回の件は、とても勉強になりました。ありがとうございます。

1

回答追記

2020/04/06 14:57

投稿

TakeOne
TakeOne

スコア6299

test CHANGED
@@ -117,3 +117,69 @@
117
117
  RowView(data: data)
118
118
 
119
119
  ```
120
+
121
+
122
+
123
+ ---
124
+
125
+ (4/1 23:40追記)
126
+
127
+
128
+
129
+ 配列要素内の変数値をStepper等で変更したい場合、
130
+
131
+ 私だったら次のよう@Bindingで配列要素内のDataを受け渡しして処理します。
132
+
133
+ 配列全体(`test.datas`)をRowView1に参照させるべきではないと思います。
134
+
135
+
136
+
137
+ ``` swift
138
+
139
+ struct RowView1: View {
140
+
141
+ @Binding var data: Data
142
+
143
+
144
+
145
+ var body: some View {
146
+
147
+ Stepper(value: $data.count) {
148
+
149
+ Text(data.name + "(data.count)")
150
+
151
+ }
152
+
153
+ }
154
+
155
+ }
156
+
157
+
158
+
159
+ struct ContentView: View {
160
+
161
+ @EnvironmentObject var test: Test
162
+
163
+
164
+
165
+ var body: some View {
166
+
167
+ List {
168
+
169
+ ForEach(0..<test.datas.count, id: .self) { i in
170
+
171
+ RowView1(data: self.$test.datas[i])
172
+
173
+ }.onDelete { offsets in
174
+
175
+ self.test.datas.remove(atOffsets: offsets)
176
+
177
+ }
178
+
179
+ }
180
+
181
+ }
182
+
183
+ }
184
+
185
+ ```