Home > Software engineering >  Swift: Error converting type 'Binding<Subject>' when passing Observed object's
Swift: Error converting type 'Binding<Subject>' when passing Observed object's

Time:12-03

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
        }
    }
}
  • Related