Home > Mobile >  Using same View for different data via NavigationLink - SwiftUI
Using same View for different data via NavigationLink - SwiftUI

Time:05-29

To reproduce as little code as possible I created an OwnListView that gets a title, subtitle, List of Objects, which I can choose from and a Continue button, but now I have the question how to best change the view using NavigationLinks to get the same view with different data.

Because I want to use the view not only 2 times, but more often...

So now to the execution: I want to display the view with the "Best Male Names" first, then the same view with the "Best Female Names" and then the same view with "Best Colors" and so on.

I don't find something exactly to my problem - I hope someone can help me

Here is my StartView, which is the first View and my ListModel is initialised

struct StartView: View {
   
   @StateObject var listModel = ListModel()
   
   var body: some View {
       NavigationView{
           NavigationLink(destination: {
               OwnListView(listModel: listModel,
                           title: "MaleNames",
                           items: ["Todd", "Liam", "Noah", "Oliver", "James", "William"],
                           choosedItems: $listModel.maleNames)
           }, label: {
               Text("Start")
                   .bold()
           })
       }
   }
}

Then it comes to my ListView from which I would like to have several so that I can make a "survey" or something like that:

struct OwnListView: View {

    //ListModel
    @ObservedObject var listModel: ListModel
    
    var title: String
    var items: [String]
    
    @Binding var choosedItems: [String]
    
    var body: some View {
        
        VStack{
            
            Text(title)
                .font(.largeTitle)
                .bold()
            
            ForEach(items, id: \.self){ item in
                
                let alreadyInList: Bool = choosedItems.contains(where: { $0 == item })
                
                Button(action: {
                    if alreadyInList {
                        choosedItems.removeAll(where: { $0 == item })
                    } else {
                        choosedItems.append(item)
                    }
                }, label: {
                    //Can be an own View, but for simplicity
                    ZStack{
                        Rectangle()
                            .fill(alreadyInList ? .black : .purple)
                            .frame(width: 250, height: 50)
                        
                        Text(item)
                            .bold()
                            .foregroundColor(.white)
                    }
                })
            }
            
            Spacer()
            
            //After Best Male Names - female names
            NavigationLink(destination: {
                OwnListView(listModel: listModel,
                            title: "Best Female Names",
                            items: ["Jessica", "Monica", "Stephanie"],
                            choosedItems: $listModel.femaleNames)
            }, label: {
                Text("Continue")
            })
            
            /*
            //After Best Female Names - colors
            NavigationLink(destination: {
             OwnListView(listModel: listModel,
                         title: "Best Colors",
                         items: ["Pink", "Blue", "Yellow", "Green"],
                         choosedItems: listModel.colors)
            }, label: {
                Text("Continue")
            })
             */
            
            Spacer()
            
        }.navigationBarTitleDisplayMode(.inline)
    }
}

CodePudding user response:

A way to have only one view that can reload is to have a dynamic way to define its contents. One might use an enum to save the state of the survey :

class ListModel: ObservableObject {
    // The enum is the list of tests
    enum Choosing {
        case male
        case female
        case color
        // Define each test title
        var title: String {
            switch self {
                case .male:
                    return "Male Names"
                case .female:
                    return "Female Names"
                case .color:
                    return "Color Names"
            }
        }
        // define each test possible values
        var items: [String] {
            switch self {
                case .male:
                    return ["Todd", "Liam", "Noah", "Oliver", "James", "William"]
                case .female:
                    return ["Jessica", "Monica", "Stephanie"]
                case .color:
                    return ["Pink", "Blue", "Yellow", "Green"]
            }
        }
        // choosing next test
        var next: Choosing? {
            switch self {
                case .male:
                    return .female
                case .female:
                    return .color
                case .color:
                    return nil
            }
        }
    }
    
    @Published var choosedItems: [Choosing:[String]] = [.male:[], .female:[], .color:[]]
}


struct StartView: View {
    @StateObject var listModel = ListModel()
    
    var body: some View {
        NavigationView{
            NavigationLink(destination: {
                // Just give model and first test
                OwnListView(listModel: listModel,
                            choosing: .male)
            }, label: {
                Text("Start")
                    .bold()
            })
        }
    }
}

The common view :

struct OwnListView: View {
    //ListModel
    @ObservedObject var listModel: ListModel
    
    var choosing: ListModel.Choosing
    // Use enum var to get title and items
    var title: String {
        choosing.title
    }
    var items: [String] {
        choosing.items
    }
    
    var body: some View {
        VStack{
            Text(title)
                .font(.largeTitle)
                .bold()
            
            ForEach(choosing.items, id: \.self){ item in
                // Use the current test result
                let alreadyInList: Bool = listModel.choosedItems[choosing]?.contains(where: { $0 == item }) ?? false
                
                Button(action: {
                    if alreadyInList {
                        listModel.choosedItems[choosing]?.removeAll(where: { $0 == item })
                    } else {
                        listModel.choosedItems[choosing]?.append(item)
                    }
                }, label: {
                    //Can be an own View, but for simplicity
                    ZStack{
                        Rectangle()
                            .fill(alreadyInList ? .black : .purple)
                            .frame(width: 250, height: 50)
                        
                        Text(item)
                            .bold()
                            .foregroundColor(.white)
                    }
                })
            }
            
            Spacer()
            
            // if still somthing test next
            if let next = choosing.next {
                NavigationLink(destination: {
                    OwnListView(listModel: listModel,
                                choosing: next)
                }, label: {
                    Text("Continue")
                })
            } else {
                // Here you can have a button to navigation link to go to end of survey
                Text("Finish")
            }
            
            Spacer()
            
        }.navigationBarTitleDisplayMode(.inline)
    }
}

Note: The enum and title and values could comes from external json file to make it more generic. Here was just a way to do it. To complete survey, just complete the enum definitions.

  • Related