回答編集履歴
2
回答追記
answer
CHANGED
@@ -90,4 +90,55 @@
|
|
90
90
|
}
|
91
91
|
}
|
92
92
|
}
|
93
|
-
```
|
93
|
+
```
|
94
|
+
|
95
|
+
---
|
96
|
+
(4/6 23:55追記)
|
97
|
+
|
98
|
+
コメントを受けて、追加確認しました。
|
99
|
+
確かに`@EnvironmentObject var test: Test`を追加するとAppDelegateでIndex out of rangeのエラーが発生します。
|
100
|
+
|
101
|
+
それで紹介していただいた記事等を読んだ上でよく考え直してみたのですが、前回提示したコードでRowViewに受け渡している `self.$test.datas[i]` は、一見、配列の要素であるDataだけを受け渡しているように見えますが、実は内部では`test`全体を共有した状態で受け渡しているのではないかと考えるようになりました。DataはstructなのでDataの要素を書き換えるにはDataインスタンスの差し替えが必要で、Dataインスタンスの差し替えをするには、共有されたData配列全体の何番目の要素を差し替えるか知っていないといけないからです。
|
102
|
+
つまり、前回示したコードは、RowViewの中で配列全体や配列番号にアクセスするようなコードは自分で明示的に書いていませんが、実は`$data`の内部で配列全体へのアクセスが発生しており、削除された配列番号にアクセスしてしまうという問題は結局解決されていないのではないかと思います。
|
103
|
+
|
104
|
+
EnvironmentObjectの有無でエラーが発生するかしないかは、ListからViewが削除される前にViewを再構築するか否かという微妙な動きの違いで、たまたまエラーが発生したりしなかったりするというだけなのではないかと思います。
|
105
|
+
|
106
|
+
そのため、受け渡し先で配列要素を更新するには、配列全体の中から、これから更新するData位置を検索して、存在していればそれを更新し、削除されて既に存在していなければ更新しないという形で処理するしかない(それが最も安全なやり方である)と思うようになりました。これはつまり、Appleのチュートリアルでやっているやり方(RowView2のやり方)と同じ考え方です。ただ、Appleのチュートリアルは、データの削除がないという前提のコードなのでfirstIndexメソッドの結果を `!`でアンラップしていて、そこでエラーが発生しています。データの削除があることを前提にするなら、それがnilだった場合は何もしないようにしたので良さそうに思います。それを踏まえて、配列要素内の値をStepperで変更したい場合、自分ならこうするというのをもう一度書き直してみました。
|
107
|
+
``` swift
|
108
|
+
struct RowView: View {
|
109
|
+
@EnvironmentObject var test: Test
|
110
|
+
let data: Data
|
111
|
+
|
112
|
+
@State var count: Int = 0
|
113
|
+
|
114
|
+
var body: some View {
|
115
|
+
Stepper(value: $count, onEditingChanged: {touchDown in
|
116
|
+
if !touchDown {
|
117
|
+
if let index = self.test.datas.firstIndex(where: {$0.id == self.data.id}) {
|
118
|
+
self.test.datas[index].count = self.count
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}) {
|
122
|
+
Text(data.name + "(data.count)")
|
123
|
+
}.onAppear {
|
124
|
+
self.count = self.data.count
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
struct ContentView: View {
|
130
|
+
@EnvironmentObject var test: Test
|
131
|
+
|
132
|
+
var body: some View {
|
133
|
+
List {
|
134
|
+
ForEach(test.datas) { data in
|
135
|
+
RowView(data: data)
|
136
|
+
}.onDelete { offsets in
|
137
|
+
self.test.datas.remove(atOffsets: offsets)
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
```
|
144
|
+
今回の件は、とても勉強になりました。ありがとうございます。
|
1
回答追記
answer
CHANGED
@@ -57,4 +57,37 @@
|
|
57
57
|
次のようにdataを渡してRowViewを初期化するだけで正常に動作します。
|
58
58
|
```
|
59
59
|
RowView(data: data)
|
60
|
+
```
|
61
|
+
|
62
|
+
---
|
63
|
+
(4/1 23:40追記)
|
64
|
+
|
65
|
+
配列要素内の変数値をStepper等で変更したい場合、
|
66
|
+
私だったら次のよう@Bindingで配列要素内のDataを受け渡しして処理します。
|
67
|
+
配列全体(`test.datas`)をRowView1に参照させるべきではないと思います。
|
68
|
+
|
69
|
+
``` swift
|
70
|
+
struct RowView1: View {
|
71
|
+
@Binding var data: Data
|
72
|
+
|
73
|
+
var body: some View {
|
74
|
+
Stepper(value: $data.count) {
|
75
|
+
Text(data.name + "(data.count)")
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
struct ContentView: View {
|
81
|
+
@EnvironmentObject var test: Test
|
82
|
+
|
83
|
+
var body: some View {
|
84
|
+
List {
|
85
|
+
ForEach(0..<test.datas.count, id: .self) { i in
|
86
|
+
RowView1(data: self.$test.datas[i])
|
87
|
+
}.onDelete { offsets in
|
88
|
+
self.test.datas.remove(atOffsets: offsets)
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
60
93
|
```
|