exyte / Grid

The most powerful Grid container missed in SwiftUI
MIT License
1.76k stars 93 forks source link

How do you refresh the grid? #26

Closed brendand closed 2 years ago

brendand commented 3 years ago

I have a macOS app that I'm adding Grid to, but when I pass in new data, the grid is not refreshed.

However, if I switch to using LazyVGrid, it does refresh. So I know the data is getting to the SwiftUI view. I'm integrating Grid using an NSHostingController and I'm monitoring changes to my object using @ObjectBinding. Anything outside the Grid will update when I change objects, but anything within the Grid does not.

Think of a master/detail view. The master is an AppKit NSTableView that shows a list of records. The detail view is my SwiftUI view that's using Grid to display a list of fields on screen. But when I switch records, the SwiftUI view's Grid never refreshes.

brendand commented 3 years ago

I tried turning off the cache, but that didn't work.

Grid(tracks: 3) {
// code
}.gridCache(.noCache)
brendand commented 3 years ago

So is there no way to refresh the grid when new data arrives?

The only way I've been able to make it work is by tearing down my entire NSHostingController, removing the view, then re-creating the NSHostingController and the SwiftUIView that contains the Grid and sending the data into it again.

But it works fine with LazyVGrid, just not with this Grid component. It just always shows the first piece of data that was passed to it.

There must be a better way to refresh the grid when new data is sent to my SwiftUI view.

brendand commented 3 years ago

So I noticed that the layout cache is for iOS only. I'm working on a Mac app at the moment. So the cache shouldn't affect me.

But why actually is the cache only for the iOS version? Just curious.

I'm still not understanding why I'm not seeing any new data appear on screen when I pass in new data. Yet with LazyV/HGrid, it does work to show new data.

brendand commented 3 years ago

Ok, so I'm going to post my code that I'm working with. If anyone sees anything wrong with my approach I'd love to know what I'm doing wrong. Basically I have a list of objects I'm iterating over and depending on their types, I'm generating different kinds of views.

When I pass in new data into the ObservableSelection, I do see that the code within the Grid block is getting executed and each of my views within the Switch statement are being called. Except the actual body of each of the individual views is never called (except when first displayed).

Maybe this has something to do with Grid, I'm not sure. All I know is if I change to LazyVGrid, it works. But I don't get the layout I want.

Any ideas?

struct RecordEditSwiftUIView: View {

    let mainContext : NSManagedObjectContext
    let databaseDocument : TFDatabaseDocument
    private var fields : [CDField] = []

    @State var selectedField: CDField?

    @State private var dateModified : Date?

    @ObservedObject private var selection : ObservableSelection

    init(mainContext: NSManagedObjectContext, databaseDocument: TFDatabaseDocument, selection : ObservableSelection) {
        self.selection = selection
        self.mainContext = mainContext
        self.databaseDocument = databaseDocument
        self.fields = selection.form.sortTheFields() ?? []
    }

    var body: some View {

        VStack {

            if let values = selection.record.formattedValues {

                Grid(tracks:3) {

                    ForEach(values, id: \.self) { value in

                        if let field : CDField = value.keys.first {

                            let fieldValue = value[field] ?? ""

                            switch field.fieldType {

                            case kFieldTypeNumber:
                                TextFieldView(record: selection.record, field: field, fieldValue: fieldValue, selectedField: $selectedField)
                                    .gridItemAlignment(.topLeading)
                                    .frame(maxHeight:70)

                            case kFieldTypePhoto:
                                PhotoFieldView(record: selection.record, field: field, selectedField: $selectedField)
                                    .gridSpan(row: 3)
                                    .gridItemAlignment(.topLeading)

                            case kFieldTypeNote:
                                let attributedValue : NSAttributedString? = selection.record.attributedValueFor(field: field)
                                NoteFieldView(record: selection.record, field: field, fieldValue: attributedValue ?? NSAttributedString(string: ""), selectedField: $selectedField)
                                    .gridSpan(column: 3)
                                    .gridItemAlignment(.topLeading)
                                    .frame(maxHeight:200)

                            case kFieldTypeSection:
                                SectionFieldView(record: selection.record, field: field, selectedField: $selectedField)
                                    .gridSpan(column: 3)
                                    .gridItemAlignment(.topLeading)
                                    .frame(maxHeight:40)

                            case kFieldTypeWebSite:
                                TextFieldView(record: selection.record, field: field, fieldValue: fieldValue, selectedField: $selectedField)
                                    .gridItemAlignment(.topLeading)
                                    .gridSpan(column: 2)
                                    .frame(maxHeight:70)

                            default:
                                TextFieldView(record: selection.record, field: field, fieldValue: fieldValue, selectedField: $selectedField)
                                    .gridItemAlignment(.topLeading)
                                    .frame(maxHeight:70)

                            }

                        }

                    }

                }.gridContentMode(.scroll)

            } else {
                Spacer()
                Text("no selected record")
                Spacer()
            }

        }.padding(.top)
            .padding(.leading)
            .padding(.trailing)
            .frame(minWidth: 600, maxWidth:.infinity)
            .onTapGesture {
                selectedField = nil
            }
            .background(Color(NSColor.controlBackgroundColor))
    }
}
brendand commented 3 years ago

So I found that if I add .id(arc4random()) to the Grid view, then it does refresh when I pass in new data. But it's slow compared to using LazyVGrid. Quite a bit slower in fact.

Is there a better solution?

brendand commented 2 years ago

Is this project dead though? Not one response. Hmm...

f3dm76 commented 2 years ago

Hey @brendand, no, the project isn't dead, but unfortunately we do not have time right now to address this issue. But rest assured we will most certainly address this once we find time. Have a nice day

brendand commented 2 years ago

Hi @f3dm76 Thanks so much for your response. That's good to know. Using the random .id does work, but it's slow, so hoping there's a better solution possible. Looking forward to seeing what you are eventually able to come up with. Thanks for creating this project. It's really great!

brendand commented 2 years ago

I'm going to close this issue because as I was working on other refresh problems with my SwiftUI layout (non Grid related), I learned more about Swift and found a better way of gathering the objects that I wanted to render from my Core Data object store. I had almost given up on Grid and was using LazyVGrid, but decided to give Grid another chance and low and behold, the refresh was working now without having to generate a random .id for the grid. And it's refreshing just as fast as LazyVGrid now too.

Previously I was just looping through an array of Core Data objects directly, then calling a custom bind function to get and set the values. Now I build a Swift array of structs out of those Core Data objects and iterate over those. That seems to have done the trick.

Medef commented 2 years ago

Hi @brendand have you by any chance come across the removal of items from LazyVGrid?

brendand commented 2 years ago

Hi @Medef I haven't done anything with this in a while actually. Still using Grid, but haven't actually looked at in in a while. I'll get back to it eventually once I'm finished with some updates to my underlying data model.