I want to load data from an API, then pass that data to several child views.
Here's a minimal example with one child view (DetailsView
). I am getting this error:
Cannot convert value of type 'Binding<Subject>' to expected argument type 'BusinessDetails'
import Foundation
import SwiftUI
import Alamofire
struct BusinessView: View {
var shop: Business
class Observer : ObservableObject{
@Published public var shop = BusinessDetails()
@Published public var loading = false
init(){ shop = await getDetails(id: shop.id) }
func getDetails(id: String) async -> (BusinessDetails) {
let params = [
id: id
]
self.loading = true
self.shop = try await AF.request("https://api.com/details", parameters: params).serializingDecodable(BusinessDetails.self).value
self.loading = false
return self.shop
}
}
@StateObject var observed = Observer()
var body: some View {
if !observed.loading {
TabView {
DetailsView(shop: $observed.shop)
.tabItem {
Label("Details", systemImage: "")
}
}
}
}
}
This has worked before when the Observed object's property wasn't an object itself (like how the loading
property doesn't cause an error).
CodePudding user response:
The error message indicates that the DetailsView initializer is expecting an instance of the BusinessDetails type for the shop parameter, but it is receiving a Binding instead.
A Binding is a type that represents a reference to a value that can be read and written. It is used in SwiftUI to pass a reference to a value between views, so that the child view can read and update the value.
In your code, you are passing the shop property of the Observer object as a Binding to the DetailsView initializer, like this:
DetailsView(shop: $observed.shop)
This is causing the error because the DetailsView initializer is expecting an instance of the BusinessDetails type, but it is receiving a Binding instead.
To fix this, you can simply pass the value of the shop property instead of the Binding, like this:
DetailsView(shop: observed.shop)
This will pass the actual value of the shop property to the DetailsView initializer, which should resolve the error.
In general, when passing a value to a child view in SwiftUI, you should be careful to pass the correct type of value. In this case, the error occurred because the parent view was passing a Binding to the child view, but the child view was expecting a simple value. By passing the value of the Binding instead, you can avoid this type of error.
CodePudding user response:
When using async/await you should use the .task
modifier and remove the object. The task will be started when the view appears, cancelled when it disappears and restarted when the id
changes. This saves you a lot of effort trying to link async task lifecycle to object lifecycle. e.g.
struct BusinessView: View {
let shop: Business
@State var shopDetails = BusinessDetails()
@State var loading = false
var body: some View {
if loading {
Text("Loading")
}
else {
TabView {
DetailsView(shop: shopDetails)
.tabItem {
Label("Details", systemImage: "")
}
}
}
.task(id: shop.id) {
loading = true
shopDetails = await Self.getDetails(id: shop.id) // usually we have a try catch around this so we can show an error message
loading = false
}
}
// you can move this func somewhere else if you like
static func getDetails(id: String) async -> BusinessDetails{
let params = [
id: id
]
let result = try await AF.request("https://api.com/details", parameters: params).serializingDecodable(BusinessDetails.self).value
return result
}
}
}