UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: LazyVGrid, ForEach, onDelete and inconsistent Crash

Forums > SwiftUI

I am trying to build a Grid Display of TextData with LazyGridView. My Data is imported from a json file. Some of the data may be modified by the user, while other are fixed.

The structure of my code looks like this :

LazyVGrid(columns: columns) {
        ForEach($observerableObject.catalog, id: \.id) { $model in                            // Catalog is a simple array of Model
                  if(model.fixed()) {
                          FixModelView(model: $model)                       // Used for Item that can't change
                  }
                  else {
                          DynamicModelView(model: $model, onDelete: removeModel)             // Used for Item that can be changed
                  }
        }
        AddDeviceModelView()                                                     // Used to add New Item in the list
        .onDelete(perform: removeModel)
        AddModelView()
}

func removeModel(at offsets: IndexSet) {
        observerableObject.catalog.remove(atOffsets: offsets)
}

The DynamicModelView has a bunch of TextFields and a button that triggers the deletion

func deleteModel(_ model: DeviceModel) {
        if let index = observerableObject.catalog.firstIndex(of: model) {
            self.onDelete(IndexSet(integer: index))
        }
    }

The LazyVGrid displays as expected. DynamicModelView allow to change Model content as expected. AddModelView works well to add new entry in the catalog that are saved backed to the originated field.

But there is inconsistent issue with the deletion. Most of the time the app crashed with an index out of bound error when I try to delete an item. But the json file is correctly updated with the updated data. And sometimes it works smoothly. It tried to identify when it works or break but I could not identify any specific pattern of actions.

At first I didn't implement onDelete modifier. Then I had a reproductible behavior : I could remove without issue any Item that were initialy present in the file. But not the "first" item added by AddDeviceModelView().

Is there a better way to do the deletion, or to secure the execution ?

2      

@karpok is having fun chasing an obscure bug.

At first, I was thinking I'd let @rooster or @nigel give this one a go! But then my eyes kept tripping over this line:

ForEach($observerableObject.catalog, id: \.id) { [...snip...] }

I was thinking that if your object is Identifiable, then you don't need to include the id: \.id code in your ForEach view builder.

So, why DO YOU have this in your ForEach? It might be helpful to see how you declare your observableObject. By the way, it will help you a lot if you give your observableObject a much cooler name.

When you delete items in a list via the .onDelete() modifier, SwiftUI needs to know exactly what line is being deleted. If you have two items in the list with the same id, this may cause the problem you describe.

For example, you swipe to delete the second item, but your code deletes the first item instead (they have the same id). This may cause your indexes to be out of bounds.

I am not totally sure about this.

Not sure, but one way to check is to ADD the model id to the FixedModelView and the DynamicModelViews. Do you see duplicates?

3      

Still new to SwitfUI, so my syntax may not always be optimal yet. Each model has an idea property of type UUID. I doubled check they are unique. And no duplicate are reporting when building the View.

Yet your suggestion may be a good shot. I realized my catalog may be reordered on the fly. So there may be a mismatch beetween the deleted entry (based on index) and the entry I expected to delete (id based). It could explain the random behaviour.

About observerableObject, it's quite basic for now :

class MySettings: Equatable, ObservableObject {
@Published var catalog: Array<Model>
}

2      

Did you solve the problem?

Some of us discussed a crash when deleting the last item in a ForEach, regardless of whether there were other items remaining, here: https://www.hackingwithswift.com/forums/swiftui/deleting-an-item-from-an-array-cause-an-index-out-of-range-in-view/7124/7166

2      

I've run into a similar issue which might be happening here, but I can't tell without looking at "FixModelView" and "DynamicModelView"

I used to create my subview an argument of ID and display data based on that ID. Now if my entry with that ID was deleted, my App would crash, IF I had looked at that item before (= view for that ID was created). The app wouldn't crash if the subview for that ID was never opened.

Maybe you are running into a similar problem, where you are trying to access your data on an index based on your model, which no longer is available?

I solved this by testing for validity of the ID within the subview

2      

Sorry have been busy on an other matter lately.

I finaly found exactly when the crash occurs. But not yet how to fix it.

Actualy add, delete and modify the model works fine whith my code.

But as I mentionned DynamicModelView is a bunch of TextFields used to modify a model. If I try to modify a model while a corresponding TextField is selected, it crashes.

Looks like I have a race between the delete and the modify actions.

2      

It was definitely a race. I updated the code so the TextField are now bound to a local copy of the original model. I added a test of existance before reflected the change back to the original instance.

And now it's good.

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.