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
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:
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.It should be
let
instead of@State var
. With alet
,body
will be called automatically then the value passed in changed from the last time this View struct was init, e.g.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 immutableView
structs for view data was designed to eliminate.