9

I have a form to select some users and assign them a int value.

The model:

class ReadingTime: Identifiable, Hashable {
    var id: Int
    @State var user: User
    @Published var value: Int

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: ReadingTime, rhs: ReadingTime) -> Bool {
        lhs.id == rhs.id
    }
    
    init(id: Int, user: User, value: Int) {
        self.id = id
        self._user = State(wrappedValue: user)
        self.value = value
    }
}

The view:

@Binding var times: [ReadingTime]
@State var newUser: User?

func didSelect(_ user: User?) {
    if let user = user {
        readingTime.append(ReadingTime(id: readingTime.nextMaxId,
                                       user: user,
                                       value: 0))
    }
}

// In the body:
VStack(alignment: .leading, spacing: 0) {
    HStack {
        Picker("Select a user", selection: $newUser.onChange(didSelect)) {
                    ForEach(users) {
                        Text($0.name).tag(Optional($0))
                    }
                }
                .id(users)
            }
            VStack(spacing: 8) {
                ForEach(0..<times.count, id: \.self) { i in
                    HStack(spacing: 0) {                            
                        Text(times[i].user.name)
                        TextField("ms", value: $times[i].value, formatter: NumberFormatter())
                        Button(action: {
                            NSApp.keyWindow?.makeFirstResponder(nil)
                            if let index = times.firstIndex(where: { $0 == times[i] }) {
                                times.remove(at: index)
                            }
                            newUser = nil
                        }, label: {
                            Text("REMOVE")
                        })
                    }
                }
            }
        }
    }
}

It looks like this:

enter image description here

However, when deleting an entry in the list, I get this error:

Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift

What's going on here?

2

2 Answers 2

7

Modifying the number of items of an array while being enumerated is an evil trap.

0..<times.count creates a temporary static range.

If you remove the first item in the array index 1 becomes 0, index 2 becomes 1 and so on.

Unfortunately there is no index times.count-1 anymore and you get the Index out of range crash when the loop reaches the last index.

You can avoid the crash if you enumerate the array reversed.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your answer. I perfectly understand the issue, but can you explain how to "enumerate the array reversed"?
Instead of starting at index 0 start at the last index and count backwards.
@vadian in 2023 also facing this issue. I think using reversed() worked for me if I delete the last element from the array. But If I delete the first element, crash happens.
1

Since there is no accepted answer:

Don't use the index of your array to get the specific object inside your ForEach. Like you did before, you're able to access your objects without using count. Using count causes your Index out of range error.

Do it like this:

ForEach(times) { time in
   // ...
   Text(time.user.name)
   // ...
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.