Hi,
Basically, I want to be able to use Mock when I work with preview in Xcode.
Here a simple situation :
- I have a business logic describe by an
ItemManager
protocol - I have a view
ItemView
to display and make operation on theseItem
- I have two
ItemManager
implementations :ItemManagerImpl
(which make the real stuff) andItemManagerMock
- I want to use the mock in Xcode preview and the real manager in my app
This pattern is familiar for me in UIKit, but I not able to write it elegantly in SwiftUI due to the fact that ObservaleObject is PAT.
Naive first try :
struct Item: Identifiable {
var id: String { name } // to keep this simple
var name: String
}
protocol ItemManager: ObservableObject {
@MainActor var items:[Item] { get }
}
@MainActor
class ItemManagerImpl: ItemManager {
//Network and other stuff to get the real items
@Published var items:[Item] = [Item(name: "itemManagerImpl")]
}
@MainActor
class ItemManagerMock: ItemManager {
@Published var items:[Item] = [Item(name: "itemManagerMock")]
}
struct ContentView: View {
@EnvironmentObject // Compiler error : Type 'any ItemManager' cannot conform to 'ObservableObject'
var itemManager: any ItemManager
var body: some View {
List(itemManager.items) { item in
Text(item.name)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
//.environmentObject(ItemManagerImpl())
.environmentObject(ItemManagerMock())
}
}
With this version, it's not possible to use ItemManager
as an EnvironmentObject
Finally I found a way to do this using an any type erasure, but this solution add boilerplate I would prefer to not have to write ...
struct Item: Identifiable {
var id: String { name } // to keep this simple
var name: String
}
protocol ItemManager: ObservableObject {
@MainActor var items:[Item] { get }
var objectWillChange: ObservableObjectPublisher { get }
}
class AnyItemManager: ItemManager {
let wrappedManager: any ItemManager
init(_ wrappedManager: any ItemManager) {
self.wrappedManager = wrappedManager
}
var items: [Item] { self.wrappedManager.items }
var objectWillChange: ObservableObjectPublisher { self.wrappedManager.objectWillChange }
}
@MainActor
class ItemManagerImpl: ItemManager {
//Network and other stuff to get the real items
@Published var items:[Item] = [Item(name: "itemManagerImpl")]
}
@MainActor
class ItemManagerMock: ItemManager {
@Published var items:[Item] = [Item(name: "itemManagerMock")]
}
struct ContentView: View {
@EnvironmentObject
var itemManager: AnyItemManager
var body: some View {
List(itemManager.items) { item in
Text(item.name)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
// .environmentObject(AnyItemManager(ItemManagerImpl()))
.environmentObject(AnyItemManager(ItemManagerMock()))
}
}
Is there a better way to achieve that ?