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()
}

2

There are 2 best solutions below

0
On BEST ANSWER

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!

0
On

You are mixing up a view and a view model.

The source of truth must be created in the parent view and is passed through with a Binding

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

struct FileManagerView: View {
    
    @Binding var directoryFiles : String
    
    func selectFolder() {
        directoryFiles = "Test"
    }
    
    var body: some View {
        Button(action: selectFolder) {
            Text("Select Folder")
        }
    }
}

An alternative with a real view model is a class conforming to @ObservableObject but it follows the same rule that the source of truth is created in the parent view

class AFileManager : ObservableObject {
    @Published var directoryFiles = ""
}

struct HomeContentView: View {

    @StateObject private var fileManager = AFileManager()
    
    var body: some View {
        VStack {
            Text("Select a file directory to generate photo albums from.")
                .padding(5)
                .multilineTextAlignment(.center)
            
            if fileManager.directoryFiles.isEmpty {
                FileManagerView(fileManager: fileManager)
            } else if fileManager.directoryFiles == "Test" {
                Text("Test is selected")
            } else {
                Text(fileManager.directoryFiles)
            }
        }
    }
}

struct FileManagerView: View {
    
    @ObservedObject var fileManager : AFileManager
    
    func selectFolder() {
        fileManager.directoryFiles = "Test"
    }
    
    var body: some View {
        Button(action: selectFolder) {
            Text("Select Folder")
        }
    }
}