I have an app that
- Individually extracts every element of an array (through indices)
- Then bind it to a struct that can make use of that single element (viewing and editing)
But every time the array reduces in size, it causes an index out of range error that is not directly because of my code
As far as I know, it's because: after the loop refreshes with the changed array, the views it created before somehow isn't completely removed and still trying access the out of range part. But that's all I can figure out myself
Here is my sample code:
import SwiftUI
struct test: View {
@State var TextArray = ["A","B","C"]
var body:some View {
VStack{
ForEach(TextArray.indices, id: \.self){index in
//Text View
TextView(text: self.$TextArray[index])
.padding()
}
//Array modifying button
Button(action: {
self.TextArray = ["A","B"]
}){
Text(" Shrink array ")
.padding()
}
}
}
}
struct TextView:View {
@Binding var text:String
var body:some View {
Text(text)
}
}
#if DEBUG
struct test_Previews: PreviewProvider {
static var previews: some View {
test()
}
}
#endif
Is there any better way to satisfy the two requirements above without causing this problem or any way to circumvent this problem ? Any responses are really appreciated.
@State
does seem to not be able to handle this, but ObservableObject
works.
I do not claim to know why apart from my best guess, which is that @State
tries too hard to avoid redraws by anticipating what the user wants, but in so doing does not support this.
Meanwhile ObservableObject
redraws everything on each small change. Works.
class FlashcardData: ObservableObject {
@Published var textArray = ["A","B","C"]
func updateData() {
textArray = ["A","B"]
}
}
struct IndexOutOfRangeView: View {
@ObservedObject var viewModel = FlashcardData()
var body:some View {
VStack{
ForEach(viewModel.textArray.indices, id: \.self){ index in
TextView(text: self.$viewModel.textArray[index])
.padding()
}
Button(action: {
self.viewModel.textArray = ["A","B"]
}){
Text(" Shrink array ")
.padding()
}
}
}
}
struct TextView:View {
@Binding var text:String
var body:some View {
Text(text)
}
}
Finally got the ins and outs of that issue that I was experiencing myself.
The problem is architectural. It is 2 folds:
The below code works because it loops through the single source of truth without making a copy and always updates the single source of truth. I even added a method to change the string within the subview since you originally passed it as a binding, I imagine you wanted to change it at some point
import SwiftUI
class DataSource: ObservableObject {
@Published var textArray = ["A","B","C"]
}
struct Test: View {
@EnvironmentObject var data : DataSource
var body:some View {
VStack{
ForEach(self.data.textArray , id: \.self) {text in
TextView(text: self.data.textArray[self.data.textArray.firstIndex(where: {text == $0})!])
.padding()
}
//Array modifying button
Button(action: {
self.data.textArray.removeLast()
}){
Text(" Shrink array ")
.padding()
}
}
}
}
struct TextView:View {
@EnvironmentObject var data : DataSource
var text:String
var body:some View {
VStack {
Text(text)
Button(action: {
let index = self.data.textArray.firstIndex(where: {self.text == $0})!
self.data.textArray[index] = "Z"
}){
Text("Change String ")
.padding()
}
}
}
}
#if DEBUG
struct test_Previews: PreviewProvider {
static var previews: some View {
Test().environmentObject(DataSource())
}
}
#endif
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With