Search code examples
swiftuistructviewmodel

How to add a variable to a struct that will contain data from CoreData


I'm creating a viewmodel where I'm putting the data i retrieve from my database.
After retrieving the data, I map it and I create an array of a wrapper type.

The following is my view-model

class FoldersVM : ObservableObject {
    
    let manager = PersistenceController()
   
    @Published var folders: [FolderWrapper] = []

    func retrieveFolders() {
        folders = manager.retrieveFolders().map(FolderWrapper.init) 
        
    }
}

The folder wrapper is the following:

struct FolderWrapper: Hashable {

    var folder: Folder

    var name: String {
        return folder.name ?? "error"
    }

    var index: Int {
        return folder.index
    }

}

Now, I want to add a "isSelected" variable to FolderWrapper and I want to be able to modify it by doing isSelected.toggle() in my View.

What I do in the view is the following:

struct FolderView1: View {
    var folders: [FolderWrapper]
    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 10))]) {
            ForEach(folders, id: \.self) { folder in
                fw(folder: folder)
            }
            
            
        }
    }
    func fw(folder: FolderWrapper) -> some View{
        Image(systemName: "folder")
            .onTapGesture {
                //Here I want to select but if I do i get an error saying that folder is immutable
            }
    }
}

So how can I create the isSelected variable and make it mutate in the view as well?

I tried making it binding but then i get an error saying the struct doesn't conform to hashable and Equatable. If i create a mutating method to change the variable then it says i can't call a mutating function on an immutable element (folder)


Solution

  • There are a number of ways you could add isSelected to FolderWrapper and toggle it in a child view. I present two ways here, both involve using @Binding var folders: [FolderWrapper]

    struct FolderWrapper: Hashable {
    
        var folder: Folder
        
        var isSelected: Bool  // <---
    
        var name: String {
            return folder.name ?? "error"
        }
    
        var index: Int {
            return folder.index
        }
    
    }
    
    struct FolderView1: View {
        
        @Binding var folders: [FolderWrapper]  // <---
        
        var body: some View {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 30))]) {
                ForEach($folders, id: \.self) { $folder in  // <--- $
                    fw(folder: $folder)
                        .foregroundColor(folder.isSelected ? .red : .blue)
                }
            }
        }
        func fw(folder: Binding<FolderWrapper>) -> some View{ // <---
            Image(systemName: "folder")
                .onTapGesture {
                    folder.wrappedValue.isSelected.toggle() // <---
                }
        }
    }
    
    struct FolderView2: View {
        
        @Binding var folders: [FolderWrapper]   // <---
        
        var body: some View {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 30))]) {
                ForEach(folders.indices, id: \.self) { index in  // <---
                    fw(index: index)
                        .foregroundColor(folders[index].isSelected ? .red : .blue)
                }
            }
        }
        func fw(index: Int) -> some View {
            Image(systemName: "folder")
                .onTapGesture {
                    folders[index].isSelected.toggle()  // <---
                }
        }
    }
    
    struct ContentView: View {
        @StateObject var vm = FoldersVM()
    
        var body: some View {
            FolderView1(folders: $vm.folders)
        }
    }