Search code examples
iosswiftswiftuicontentview

How Can I Dynamically Display Content of an Outer View Based on the State in a Nested View in SwiftUI?


First time using swift and this is what I've gathered so far. I'm not sure why the HomeContentView isn't displaying the content dynamically based on filemanager.directoryFiles. Is there a simple way to display the content of an Outer view dynamically based on a state present within an Inner view?

import SwiftUI

struct FileManagerView: View {
    
    @State private var directoryFiles: String
    
    fileprivate init(directoryFiles: String) {
        self.directoryFiles = directoryFiles
    }
    
    func getDirectoryFiles() -> String {
        return self.directoryFiles
    }
    
    func selectFolder() {
        self.directoryFiles = "Test"
    }
    
    var body: some View {
        Button(action: {
            self.directoryFiles = "Test"
        }) {
            Text("Select Folder")
        }
    }
}

struct HomeContentView: View {

    let filemanager = FileManagerView(directoryFiles: "")
    
    var body: some View {
        VStack {
            Text("Select a file directory to generate photo albums from.")
                .padding(5)
                .multilineTextAlignment(.center)
            
            if (filemanager.getDirectoryFiles() == "") {
                filemanager
            } else if (filemanager.getDirectoryFiles() == "Test") {
                Text("Test is selected")
            } else {
                Text(filemanager.getDirectoryFiles())
            }
        }
    }
}

#Preview {
    HomeContentView()
}


Solution

  • Okay so hopefully you managed to get this to work using a binding on your own. Here is my solution to compare it to:

    struct HomeContentView: View {
        @State private var fileSelectedViaFileManagerView = ""
        
        var body: some View {
            VStack {
                Text("Select a file directory to generate photo albums from.")
                    .padding(5)
                    .multilineTextAlignment(.center)
                
                if fileSelectedViaFileManagerView == "" {
                    FileManagerView(selectedFile: $fileSelectedViaFileManagerView)
                } else {
                    Text("\(fileSelectedViaFileManagerView) is selected")
                }
            }
        }
    }
    
    struct FileManagerView: View {
        @Binding var selectedFile: String
        
        var body: some View {
            Button("Select Folder") {
                selectedFile = "Test"
            }
        }
    }
    

    You should read this amazing article on passing data between views in SwiftUI. It's all you'll ever need: https://www.vadimbulavin.com/passing-data-between-swiftui-views/

    In summary:

    • From Parent to Direct Child: Use Initializer
    • From Parent to Distant Child: Use @Environment
    • From Child to Direct Parent: Use @Binding and Callbacks
    • From Child to Distant Parent: Use PreferenceKey
    • Between Children: Lift the State Up (have the parent contain the @State property)

    The reason why your view wasn't updating earlier was because SwiftUI only finds out when the value of properties have changed. So using the return value of a function like

    if (filemanager.getDirectoryFiles() == "") {
        filemanager
    } else if (filemanager.getDirectoryFiles() == "Test") {
        Text("Test is selected")
    } else {
        Text(filemanager.getDirectoryFiles())
    }
    

    wouldn't trigger an update as those aren't properties. Best of luck learning Swift!