ForEach - Index out of range?
Let me give you a simple example to show you what happened:
struct ContentView: View {
@State private var lowerBound: Int = 0
var body: some View {
ForEach(lowerBound..<11) { index in
Text(String(describing: index))
}
Button("update") { lowerBound = 5 }.padding()
}
}
if you look at the upper code you would see that I am initializing a ForEach JUST with a Range like this: lowerBound..<11
which it means this 0..<11
, when you do this you are telling SwiftUI, hey this is my range and it will not change! It is a constant Range! and SwiftUI says ok! if you are not going update upper or lower bound you can use ForEach without showing or given id
! But if you see my code again! I am updating lowerBound of ForEach and with this action I am breaking my agreement about constant Range! So SwiftUI comes and tell us if you are going update my ForEach range in count or any thing then you have to use an id
then you can update the given range! And the reason is because if we have 2 same item with same value, SwiftUI would have issue to know which one you say! with using an id
we are solving the identification issue for SwiftUI! About id you can use it like this: id:\.self
or like this id:\.customID
if your struct conform to Hash-able protocol, or in last case you can stop using id if you confrom your struct to identifiable protocol! then ForEach would magically sink itself with that.
Now see the edited code, it will build and run because we solved the issue of identification:
struct ContentView: View {
@State private var lowerBound: Int = 0
var body: some View {
ForEach(lowerBound..<11, id:\.self) { index in
Text(String(describing: index))
}
Button("update") { lowerBound = 5 }.padding()
}
}
SwiftUI - Index out of range
Never retrieve items by hard-coded indices in a ForEach
expression.
Do count
the array, the loop is skipped (safely) if the array is empty.
ForEach(0..<answers.count) { number in
Or - still simpler – enumerate the items rather than the indices
ForEach(answers, id: \.self) { answer in
Text("\(answer)")
}
Thread 1: Fatal error: Index out of range In SwiftUI
ForEach
with an index-based approach is dangerous in SwiftUI. Instead, make your model identifiable.
class User: ObservableObject, Identifiable {
var id = UUID()
//...
Then, change your loop:
ForEach(appUser.friendAnfrage) { item in
SingleFriendView(user: item)
}
Unrelated to this exact issue, but generally SwiftUI does better with using a struct
for a model instead of a class
. If a User
in friends
is updated with your current code, because it's a nested ObservableObject
, your View
will not get automatically updated.
Got Fatal error: Index out of range : show index in list item for swiftui
@State is property wrapper, which will force the View in which it is defined to recalculate its body.
In your case, if you remove the item at index,
HStack {
Text("\(index)")
Spacer()
Text("\(self.list[index].name)")
}
.background(Color.gray.opacity(0.001))
.onTapGesture {
self.list.remove(at: index)
}
the Text inside HStack
Text("\(self.list[index].name)")
crash, just because list[index] doesn't exist any more.
Using
ForEach(list.indices, id:\.self) { index in ... }
instead of
ForEach(list.indices) { index in ... }
will force SwiftUI to recreate TestView (see the id:\.self in ForEach constructor)
SwiftUI will make fresh copy of TestView while using fresh value of property wrapped in @State property wrapper.
UPDATE
Please, don't update your question ...
Your last code version 4 is total mess, so I rewrote it to something you able to copy - paste - run
import SwiftUI
struct Name: Identifiable, Hashable {
var id: String = UUID().uuidString
var name: String
var marked: Bool
init(_ name: String, marked: Bool = false) { self.name = name; self.marked = marked }
}
struct ContentView: View {
@State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3", marked: true), Name("test4"), Name("test5", marked: true), Name("test6"), Name("test7"), Name("test8")]
@State private var showMarkedOnly = false
var body: some View {
VStack{
Toggle(isOn: $showMarkedOnly) {
Text("show marked only")
}.padding(.horizontal)
List {
ForEach(Array(zip(0..., list)).filter({!self.showMarkedOnly || $0.1.marked}), id: \.1.id) { index, name in
HStack {
Text("\(index)").foregroundColor(name.marked ? .red : .gray)
Spacer()
Text("\(name.name)")
}
.background(Color.gray.opacity(0.001))
.onTapGesture {
self.list.remove(at: index)
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
it should looks like
UPDATE based on discussion
ForEach different versions of constructors use internally different functionality of ViewBuilder
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Provides support for "if" statements in multi-statement closures, producing an `Optional` view
/// that is visible only when the `if` condition evaluates `true`.
public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View
/// Provides support for "if" statements in multi-statement closures, producing
/// ConditionalContent for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures, producing
/// ConditionalContent for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}
This is about "implementation details" and hopefully it will be documented in next release. SwiftUI is still in very early stage of development, we have to be careful.
Lets try to force SwiftUI to follow our own way!
First separate RowView
struct RowView: View {
var showMarkedOnly: Bool
var index: Int
var name: Name
//@ViewBuilder
var body: some View {
if !self.showMarkedOnly || name.marked {
HStack {
Text(verbatim: index.description).foregroundColor(name.marked ? .red : .gray)
Spacer()
Text(verbatim: name.name)
}
.background(Color.gray.opacity(0.001))
}
}
}
Compiler complains with
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Uncomment the line to wrap our body
struct RowView: View {
var showMarkedOnly: Bool
var index: Int
var name: Name
@ViewBuilder
var body: some View {
if !self.showMarkedOnly || name.marked {
HStack {
Text(verbatim: index.description).foregroundColor(name.marked ? .red : .gray)
Spacer()
Text(verbatim: name.name)
}
.background(Color.gray.opacity(0.001))
}
}
}
Now we can use the code the way you like :-)
struct ContentView: View {
@State private var list: [Name] = [Name("test1"), Name("test2"), Name("test3", marked: true), Name("test4"), Name("test5", marked: true), Name("test6"), Name("test7"), Name("test8")]
@State private var showMarkedOnly = false
var body: some View {
VStack{
Toggle(isOn: $showMarkedOnly) {
Text("show marked only")
}.padding(.horizontal)
List {
ForEach(Array(zip(0..., list)), id: \.1.id) { (index, name) in
RowView(showMarkedOnly: self.showMarkedOnly, index: index, name: name).onTapGesture {
self.list.remove(at: index)
}
}
}
}
}
}
The final result uses now buildIf<Content>
construct and all code works again (the result looks exactly the same as shown above)
SwiftUI: Index out of range when last element gets removed
I found a solution, I changed my forEach to:
ForEach(checklistItems.indices, id: \.self) { index in
HStack {
RowView(
checklistItem:
Binding(
get: { self.checklistItems[index] },
set: { self.checklistItems[index] = $0 }
),
viewModel: viewModel
)
.padding(.leading, 12)
if checklistEditMode {
Button {
checklistItems.remove(at: index)
} label: {
Image("XCircle")
.resizable()
.frame(width: 18, height: 18)
}
.padding(.trailing, 12)
}
}
.padding(.horizontal, 12)
.padding(.top, 12)
.padding(.bottom, 4)
Divider()
.frame(width: 311)
}
and it works now with no crashes, Thanks to this
SwiftUI .onDelete throws Fatal Error: Index out of range
Found a solution. With respect to the this answer to a related question, it came clear. Apperantly, with using the .indices
to fullfill ForEach
makes onDelete
not to work. Instead, going through directly Elements
of an array and creating proxy Bindings
is working.
To be it more clear here are the ContentView
, DetailView
and an extension for Binding
to avoid cluttering view.
ContentView
struct ContentView: View {
@EnvironmentObject var store: DataStore
var body: some View {
NavigationView {
List {
ForEach(self.store.data, id: \.id /*with or without id*/) { (data) in
NavigationLink(destination: DetailView(data: /*created custom initializer*/ Binding(from: data))) {
Text(data.name)
}
}.onDelete { (set) in
self.store.data.remove(atOffsets: set)
}
}.navigationBarTitle("List")
}
}
}
DetailView
struct DetailView: View {
@Binding var data: MyData
var body: some View {
List {
TextField("name", text: self.$data.name)
}
.navigationBarTitle(self.data.name)
.listStyle(GroupedListStyle())
}
}
Binding
extension
extension Binding where Value: MyData {
init(from data: MyData) {
self.init(get: {
data as! Value
}) {
dataStore.data[dataStore.data.firstIndex(of: data)!] = $0
}
}
}
This way, both onDelete
and publishing changes by directly updating the object inside detail view will work.
Using ForEach loop with Binding causes index out of range when array shrinks (SwiftUI)
Finally got the ins and outs of that issue that I was experiencing myself.
The problem is architectural. It is 2 folds:
- You are making a copy of your unique source of truth. ForEach loops Textfield but you are passing a copy through Binding. Always work on the single source of truth
- Combined with ForEach ... indices is supposed to be a constant range (hence the out of range when you remove an element)
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
Fatal error: Index out of range in SwiftUI
Instead of getting the number of items in an array and using that index to get items from your array of objects, you can get each item in the foreach instead. In your content view, change your For each to this
ForEach(store.items, id:\.self) { item in
NavigationLink(destination: Detail(item: item)) {
VStack {
Text(item.title)
}
}
}
And Change your detail view to this:
struct Detail: View {
@EnvironmentObject var store: CPStore
@State var item: CPModel
var body: some View {
VStack {
//Error is here
TextField("Recording", text: $item.title)
}
}
}
Related Topics
Issues While Lightweight Core Data Migration
How to Catch an Exception in Swift
How to Use The Spritekit Method Body(At: Cgpoint)
Animation Triggered Using a Button Stops a Repeatforever Animation Added Onappear
iOS 14 Widget Detect System Theme Change
Completion Handler in For/In Loop - Swift
Swift Ensembles Set Up & Ubiquitycontaineridentifier
How to Change/Modify The Displayed Title of an Nspopupbutton
Pdf417 Decode and Generate The Same Barcode Using Swift
Cleanly Handling /Usr/Local/ with Swift Package Manager and Libevent
Combining Scenekit and Spritekit in a Single Screen
Swift Pattern Match on Array<Any>
How to Convert Curl Request to Swift Using Alamofire
How to Load Lcr Image in Tvos Apps
How to Write a Generic Function for Floating Point Values in Swift
Pinterest-iOS 2.3 and Swift: Calling Createpinwithimageurl Results in Exc_Bad_Access