Pungenc
Pungenc

Reputation: 23

How to pass data to a sub view from ContentView in SwiftUI

Forgive me if this doesn't make sense, I am a total beginner at Swift. I am creating a recipe app that pulls data from an API and lists it out in navigation links. When the user clicks on the recipe I want it to move to sub view and display information from the API such as recipe name, image, ingredients, and have a button with a link to the webpage.

I was able to get the data pulled into the list with navigation links. However, now I do not know how to go about setting up the recipe details sub view with all of the information I listed above.

This is where I call the API:


class RecipeService {
    func getRecipes(_ completion: @escaping (Result<[Recipe], Error>) -> ()) {
        let url = URL(string: "http://www.recipepuppy.com/api")!
        URLSession.shared.dataTask(with: url) { (data, _, error) in
            
            if let error = error {
                return completion(.failure(error))
            }
            
            guard let data = data else {
                return completion(.failure(NSError(domain: "", code: -1, userInfo: nil)))
            }
            
            do {
                let response = try JSONDecoder().decode(RecipesResponses.self, from: data)
                completion(.success(response.results))
            } catch {
                completion(.failure(error))
            }
            
        }.resume()
    }
}

This is where I take in the recipe responses:


struct RecipesResponses: Codable {
    let title: String
    let version: Double
    let href: String
    let results: [Recipe]
}

struct Recipe: Codable {
    
    
    let title, href, ingredients, thumbnail: String
    
    var detail:  URL {
        URL(string: href)!
    }
    
    var thumb:  URL {
        URL(string: thumbnail)!
    }
}

This is my recipe ViewModel:


class RecipeViewModel: ObservableObject {
    
    @Published var recipes = [Recipe]()
    @Published var isLoading = false
    
    private let service = RecipeService()
    
    init() {
        loadData()
    }
    
    private func loadData() {
        isLoading = true
        service.getRecipes{ [weak self] result in
            DispatchQueue.main.async {
                self?.isLoading = false
                switch result {
                case .failure(let error):
                    print(error.localizedDescription)
                case .success(let recipes):
                    self?.recipes = recipes
                }
            }
        }
    }
    
    
}

This is my view where I list out the API responses:


struct ListView: View {
    @ObservedObject var viewModel = RecipeViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.recipes, id: \.href) { recipe in
                NavigationLink (destination: RecipeDetailView()) {
                    HStack{
                        CachedImageView(recipe.thumb)
                            .mask(Circle())
                            .frame(width: 80)
                        VStack(alignment: .leading)  {
                            Text(recipe.title)
                                .font(.largeTitle)
                                .foregroundColor(.black)
                                .padding()
                        }
                    }
                    .buttonStyle(PlainButtonStyle())
                }
            }
            .navigationBarTitle(Text("All Recipes"))
        }
    }
}

struct ListView_Previews: PreviewProvider {
    static var previews: some View {
        ListView()
    }
}

This is the view where I would like to list out the recipe details and link to the webpage. This is where I am struggling to be able to pull the API data into:


struct RecipeDetailView: View {
    @ObservedObject var viewModel = RecipeViewModel()
    
    var body: some View {
        Text("Detail View")
    }
}

struct RecipeDetailView_Previews: PreviewProvider {
    static var previews: some View {
        RecipeDetailView()
    }
}

Images of app

Upvotes: 2

Views: 567

Answers (1)

jnpdx
jnpdx

Reputation: 52615

You can change RecipeDetailView to accept a Recipe as a parameter:

struct RecipeDetailView: View {
    var recipe : Recipe
    
    var body: some View {
        Text(recipe.title)
        Link("Webpage", destination: recipe.detail)
        //etc
    }
}

Then, in your NavigationLink, pass the Recipe to it:

NavigationLink(destination: RecipeDetailView(recipe: recipe)) {

One thing I'd warn you about is force unwrapping the URLs in Recipe using ! -- you should know that if you ever get an invalid/malformed URL, this style of unwrapping will crash the app.


Update, to show you what the preview might look like:

struct RecipeDetailView_Previews: PreviewProvider {
    static var previews: some View {
        RecipeDetailView(recipe: Recipe(title: "Recipe name", href: "https://google.com", ingredients: "Stuff", thumbnail: "https://linktoimage.com"))
    }
}

Upvotes: 1

Related Questions