Warm tip: This article is reproduced from stackoverflow.com, please click
ios macos swift swiftui

One view does not rerender when @State var changes based on Picker selection

发布于 2020-04-05 23:41:46

Trying to build a simple MacOS app using SwiftUI. I have a View that contains a Picker bound to a State var. As a sanity check I have added a Text Views (the dwarves one and the volumes...itemName) that should also change with the Picker changes. And they do, but the View I want to rerender (the FileList) does not.

I suspect it has something to do with how I am trying to pass the new FileSystemItem (internal class) to the FileList. Like when the FilePanel rerenders the volumeSelection is back to 0 and the state is applied afterwards. So my problem is that I seem to be missing a fundamental concept on how this data is supposed to flow. I have gone through the WWDC info again and been reading other articles but I am not seeing the answer.

The desired behavior is changing the selection on the picker should cause a new FileSystemItem to be displayed in the FileList view. What is the right way to get this to happen? To state it more generically, how to you get a child view to display new data when a Picker selection changes?

struct FilePanel: View 
{
    @State var volumeSelection = 0
    @State var pathSelection = 0

    var volumes = VolumesModel() //should be passed in?
    var dwarves = ["Schlumpy","Wheezy","Poxxy","Slimy","Pervy","Drooly"]

    var body: some View {
        VStack {
            Picker(selection: $volumeSelection, label:
                Text("Volume")
                , content: {
                    ForEach (0 ..< volumes.count()) {
                        Text(self.volumes.volumeAtIndex(index: $0).itemName).tag($0)
                    }
            })

            FileList(item:volumes.volumeAtIndex(index: volumeSelection)).frame(minWidth: CGFloat(100.0), maxHeight: .infinity)
            Text(dwarves[volumeSelection])
            Text(volumes.volumeAtIndex(index: volumeSelection).itemName)

        }
    }
 }

struct FileList: View {
    @State var item : FileSystemItem

    var body: some View {
        VStack {
        List(item.childItems){fsi in
            FileCell(expanded:false, item: fsi)
        }
        Text(item.itemName)
        }
    }
}
Questioner
vagrant
Viewed
57
nine stones 2020-02-01 11:39

@State is a state private to to the owning view, FileList will never see any change.

If VolumesModel was a simple struct turning FileList.item into a binding (in-out-state) may already work (the caller still needs to turn it's @State into a binding when passing it to the dependent using the $):

struct FileList: View {
  @Binding var item : FileSystemItem
  ...
}

However, it feels as if VolumesModel was a more complex object having an array of members.

If this is the case the above will not suffice:

  • VolumesModel needs to be a class adopting ObservableObject
  • VolumesModel's important members need a bindable wrapper (@Published)
  • FileList.item should be turned into @ObservedObject (instead of @State or @Binding)
  • FilePanel.volumes also to be @ObservedObject wrapped

Hope that helps or at least points you into the right direction.