Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I remove an item from a ForEach inside of a TabView using SwiftUI?

I am having a weird SwiftUI crash that I am not understanding. I have a TabView with a list of 3 images inside of it. I am trying to remove the first image from the list by tapping on the button on the screen, but I get this crash.

'NSInternalInconsistencyException', reason: 'attempt to delete and reload the same index path (<NSIndexPath: 0x968bfe135e5a98d1> {length = 2, path = 0 - 0})' terminating with uncaught exception of type NSException

If I remove the TabView from the code, it works as expected and removes the first item. Here is the minimum amount of code needed to reproduce this crash. I have also created a Git repo of this code here -> https://github.com/cameronhenige/TestCrash Could somebody help me figure out what is going on?

import SwiftUI

struct ContentView: View {
    @StateObject var testViewModel = TestViewModel()
    
    var body: some View {
        GeometryReader { proxy in
            
            ScrollView {
                
                VStack(alignment: .leading, spacing:0) {
                    
                    TabView {
                        
                        ForEach(testViewModel.images, id: \.self) { image in
                            Image(image)
                        }
                        
                    }.tabViewStyle(PageTabViewStyle())
                    .clipShape(RoundedRectangle(cornerRadius: 15))
                    .padding()
                    .frame(width: proxy.size.width, height: proxy.size.height/2.5)
                    
                }
                Button(action: {
                    testViewModel.removeFirst()
                }) {
                    Text("Remove first item from list")
                }
                
            }
            
            
        }.frame(maxWidth: .infinity).background(Color.black)
    }
}
import Foundation

class TestViewModel: NSObject, ObservableObject {
    
    @Published var images: [String] = ["dog", "cat", "bird"]
    
    func removeFirst() {
        images.remove(at: 0)
    }
}

like image 207
Cameron Henige Avatar asked Mar 02 '23 12:03

Cameron Henige


2 Answers

TabView {
    
    ForEach(testViewModel.images, id: \.self) { image in
        Image(image)
    }
    
}.tabViewStyle(PageTabViewStyle())
.clipShape(RoundedRectangle(cornerRadius: 15))
.padding()
.frame(width: proxy.size.width, height: proxy.size.height/2.5)
.id(testViewModel.images.count)

Adding an id to the TabView fixes this issue!

like image 153
Cameron Henige Avatar answered Jun 08 '23 19:06

Cameron Henige


For more of an explanation of why using .id(_:) fixed the issue for you, it's because when testViewModel.images.count changes (AKA the view is now using different data) you are invalidating the view because the ID has changed.

For more on invalidating views and how to fix weird view updates, see the Demystify SwiftUI - WWDC21 talk.

like image 39
George Avatar answered Jun 08 '23 19:06

George