2

I've recently come across the need to write a Mock of a Class, as it causes the SwiftUI preview from working. Unfortunately, I get the error:

Property type 'T' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject'

In the View struct:

struct ContentView<T>: View {
    @EnvironmentObject var mockFoobar: T
    ...
}

And also the error:

Type of expression is ambiguous without more context

For the Preview struct:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let mockFoobar: MockFoobar = MockFoobar()
        return ContentView<MockFoobar>()
            .environmentObject(mockFoobar)
    }
}

The MockFoobar class is:

class MockFoobar: ObservableObject {
  ...
}

As the user @Asperi kindly provided, tested the following as suggested:

class Foobar: ObservableObject {
    @Published var param: Bool = false
    func start() {
        self.param = true
    }
}

struct MyFoobarView<T: ObservableObject>: View {
    @EnvironmentObject var foobar: T
    
    var body: some View {
        VStack {
            Text("Hello Foobar")
        }
        .onAppear {
            self.foobar.start()
        }
    }
}

struct MyFoobarView_Previews: PreviewProvider {
    static var previews: some View {
        let foobar: Foobar = Foobar()
        return MyFoobarView()
            .environmentObject(foobar)
    }
}

But I get the following errors (the first in the .onAppear and the second in the PreviewProvider):

Cannot call value of non-function type 'Binding<Subject>'

Generic parameter 'T' could not be inferred
Asperi
  • 228,894
  • 20
  • 464
  • 690
punkbit
  • 7,347
  • 10
  • 55
  • 89
  • If it works fine what’s the question? And what’s the “mock” part? Looks real to me. – matt May 23 '20 at 13:22
  • You're right, let me delete the Simulator stuff, as that was meant to show that the initialisation process worked in that context; but is not a Generic. The "mock" part is mainly to explain the need to use a Generic to represent the environmentObject; Mock as in, has the same interface as Class "Foobar"; but I need to mock "Foobar" to avoid some CoreAudio stuff that stops Preview from working – punkbit May 23 '20 at 13:25

1 Answers1

7

The EnvironmentObject must be ObservableObject, so here is fix

struct ContentView<T: ObservableObject>: View {
    @EnvironmentObject var mockFoobar: T
    
    // .. other code here

Update: added demo with introduced model protocol

protocol Foobaring {
    var param: Bool { get set }
    func start()
}

class Foobar: ObservableObject, Foobaring {
    @Published var param: Bool = false
    func start() {
        self.param = true
    }
}

struct MyFoobarView<T: ObservableObject & Foobaring>: View {
    @EnvironmentObject var foobar: T

    var body: some View {
        VStack {
            Text("Hello Foobar")
        }
        .onAppear {
            self.foobar.start()
        }
    }
}

struct MyFoobarView_Previews: PreviewProvider {
    static var previews: some View {
        let foobar: Foobar = Foobar()
        return MyFoobarView<Foobar>()
            .environmentObject(foobar)
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks! Even when the Class MockFoobar is of type ObservableObject? It's not inferred? – punkbit May 23 '20 at 13:51
  • @punkbit, tested & worked with Xcode 11.4 / iOS 13.4. I assume there is something else out of provided code in question. – Asperi May 23 '20 at 13:57
  • Ok cool! So, I double-checked but I get two error messages (updated the original post). I'll keep looking into this to figure it out and hopefully update it with an answer. – punkbit May 23 '20 at 14:13
  • @punkbit, see updated - added demo with specific protocol. – Asperi May 23 '20 at 14:26
  • Yeh, appreciate it! Conforming to Protocol and ObservableObject is the solution! – punkbit May 23 '20 at 14:37
  • how could we do something like `.onReceive(foobar.$param)` ? – richy Feb 11 '22 at 12:56