skip to Main Content

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

Answers


  1. 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")
            }
        }
    }
    
    Login or Signup to reply.
  2. 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!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search