swiftswiftuiobservableios17

@Bindable usage with protocols


Prior to iOS 17, I was used to creating SwiftUI views with a generic approach where a view is constrainted to one view model, and that ViewModel can be a protocol. This was very nice for testing and wrapping all the UI-related things to the ViewModels interface.

@MainActor
protocol CityQueryViewModelInterface: ObservableObject {
    var text: String { get set }
    func fetchWeather() async throws -> WeatherItem
}
struct CityQueryView<ViewModel: CityQueryViewModelInterface>: View {    
    @ObservedObject var viewModel: ViewModel
}

However, when trying to achieve this in iOS 17 using the Observable macro with @Bindable and protocol, I am getting an error

protocol CityQueryViewModelInterface: Observable {
    var text: String { get set }
    func fetchWeather() async throws -> WeatherItem
}
@Observable
final class CityQueryViewModel: CityQueryViewModelInterface {
struct CityQueryView<ViewModel: CityQueryViewModelInterface>: View {
    @Bindable var viewModel: ViewModel
}

'init(wrappedValue:)' is unavailable: The wrapped value must be an object that conforms to Observable

Next to the Bindable annotation


Solution

  • The error says

    The wrapped value must be an object that conforms to Observable.

    Notice the highlight on "object".

    Bindable requires Observable and AnyObject so just add AnyObject to your protocol.

    protocol CityQueryViewModelInterface: Observable, AnyObject {
        var text: String { get set }
        func fetchWeather() async throws -> WeatherItem
    }