skip to Main Content

In my project, the user can have two favourite products. They can change them by tapping on one(ProductView). After tapping one, a view called ‘ProductSheet’ is displayed. I try to carry the id from the previous view but it is not there. I need to carry the id across to ‘ProductSheet’ and ‘ProductRowView’ in order to carry out the function of changing the favourite product.

I don’t understand why product_id has no value in the ‘ProductSheet’ & ‘ProductRowView’ views. Here is a shortened simplified version of the code:

struct FavouriteProducts: View {
    
    @StateObject var favouriteProductsModel = FavouriteProductsViewModel()
    @State private var showingSheet = false
    @State var selectedProduct: String?
    @State var product_id: String?
        
    
    var body: some View {
        
        ZStack {
            let defaults = UserDefaults.standard
            let keyString: String? = defaults.string(forKey: "key") ?? ""
            
            VStack {
                
                HStack {
                    
                    Text("Tap to change favourite product")
                }
                
                HStack(spacing: 25) {
                    
                    ForEach(favouriteProductsModel.favourite_products, id: .id){ product in
                        
                        VStack {
                                                                                                                
                            Image("ProductImage")
                                .resizable()
                                                    
                            Text(String(product.product_name ?? ""))

                        }
                        .onTapGesture {
                            selectedProduct = product.id
                            self.product_id = product.id ?? ""
                            self.showingSheet = true
//selectedProduct does have a value in this print statement
                            print("Tapped Product ID: (String(describing: selectedProduct))")
                        }
                        .sheet(item: $selectedProduct) {
                            favouriteProductsModel.fetchFavouriteProducts(key: keyString ?? "")
                        } content: { item in
                            ProductSheet(product_id: selectedProduct ?? "")
                        }
                    }
                }
            }
        }
        .onLoad {
            let defaults = UserDefaults.standard
            let keyString: String? = defaults.string(forKey: "key") ?? ""
            self.favouriteProductsModel.fetchFavouriteProducts(key: keyString ?? "")
        }
    }
}
struct ProductSheet: View {
    
    @StateObject var marketplaceModel = MarketplaceViewModel()
    @State var product_id: String?

    var body: some View {


        VStack {
            
            // The product_id here is not being displayed, because the variable has no value
            Text(product_id ?? "")
            
            ForEach(marketplaceModel.filteredProduct, id:.self){ product in
                
                ProductRowView(productData: product, product_id: product_id ?? "")
            }
                        
        }
    }
}
struct ProductRowView: View {
    
    @Environment(.dismiss) var dismiss
    @StateObject var favouriteProductsModel = FavouriteProductsViewModel()
    @State private var isShowingDetailView = false
    @State var selectedProduct : ProductTile?
    @State var new_id = ""
    var productData: ProductRow
    var product_id: String
    
    var body: some View {
        
        VStack {
            
            HStack {
                Text(productData.title)
                
                HStack {
                    NavigationLink("", isActive: $isShowingDetailView) {
                        ProductSeeAllView(productData: productData, product_id: productData.id)
                    }.navigationTitle("")
                    
                    Button("See All") {
                        isShowingDetailView = true
                    }
                    Image(systemName: "chevron.right")
                }
            }
            
            HStack {
                ScrollView {
                    HStack {
                        ForEach(productData.products ?? []) { product in
                            ProductView(productData: product)
                                .onTapGesture {
                                    new_id = product.id ?? ""
                                    selectedProduct = product
                                    print("Product id: (selectedProduct?.id ?? "")")
                                    let defaults = UserDefaults.standard
                                    let keyString: String? = defaults.string(forKey: "key") ?? ""
                                    
                                    favouriteProductsModel.changeFavouriteProducts(key: keyString ?? "", current_id: product_id, new_id: new_id ) { (success) in
                                        if success {
                                            dismiss()
                                        } else {
                                            print("Unsuccessful...")
                                        }
                                    }
                                }
                        }
                    }
                }
            }
        }
    }
}

When initialising ‘ProductSheet’ & ‘ProductRowView’ I should be passing the product_id through but it doesn’t work. Why is this happening?

EDIT:
With further testing, it seems that the id is passed through if the sheet is initialised for a second time. It never seems to work first time though.

3

Answers


  1. Chosen as BEST ANSWER

    The issue was, the data was not being passed through to the sheet the first time it was initialised. The following code stopped that bug:

    @State private var showingSheet = false
    
        .sheet(isPresented: Binding(
                                            get: { showingSheet },
                                            set: { showingSheet = $0 }
                                        )) {
                                            ProductSheet(product_id: selectedProduct ?? "")
                                        }
    

  2. I simplified your code to show you a working solution.

    The sheet is created at the init of the view so the ProductSheet is initialized with a nil product. You have to add @Binding to obtain the desired behavior.

    struct Product: Identifiable {
        var id: Int
        var name: String
    }
    
    let products = [
        Product(id: 1, name: "Product 1"),
        Product(id: 2, name: "Product 2")
    ]
    
    struct FavouriteProducts: View {
        @State private var showingSheet = false
        @State private var selectedProduct: Product?
    
        var body: some View {
            VStack {
                Text("Tap to change favourite product")
    
                ForEach(products){ product in
                    Text(product.name)
                        .onTapGesture {
                            selectedProduct = product
                            showingSheet = true
                        }
                }
            }
            .sheet(isPresented: $showingSheet) {
                ProductSheet(product: $selectedProduct)
            }
        }
    }
    
    struct ProductSheet: View {
        @Binding var product: Product?
    
        var body: some View {
            Text(product?.name ?? "?")
        }
    }
    
    Login or Signup to reply.
  3. It should be let instead of @State var. With a let, body will be called automatically then the value passed in changed from the last time this View struct was init, e.g.

    struct ProductSheet: View {
    
        // Bad code! don't misuse StateObject for view model objects just use the View structs.
        //@StateObject var marketplaceModel = MarketplaceViewModel()
    
        let productID: String
    
    

    Fyi we don’t need view model objects in SwiftUI because that is the job of the View struct. @StateObject is for when you need to store a reference type in an @State, e.g. when doing something async. If you use @StateObject to try to implement view models you’ll run into the kind of consistency bugs SwiftUI’s design of immutable View structs for view data was designed to eliminate.

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