Есть ли передовые методы подготовки облегченных моделей просмотра с фиктивными данными, которые можно было бы использовать в предварительных версиях SwiftUI?
Прямо сейчас у меня есть модель просмотра для экрана покупок в приложении, который должен извлекать продукты из магазина приложений, но для имитации его в предварительном просмотре (без необходимости всего кода извлечения) я добавил второй init()
это позволяет мне просто предварительно заполнить все свойства и поместить их внутрь #if DEBUG
раздел, чтобы предотвратить его случайный вызов из производственного кода.
Однако это вынудило меня иметь все ссылки на состояние приложения (которое мне нужно вызвать loadproducts () в модели) в качестве optional var
(потому что фиктивный init их не устанавливает) вместо let
(что я бы предпочел, потому что тогда нет шансов, что они случайно станут нулевыми в производстве).
На других языках я мог бы иметь абстрактные ProductViewModelBase
(который будет хранить и предоставлять все свойства, необходимые для представления), и из этого производственный ProductViewModel
и манекен DummyProductViewModel
(для превью) может наследовать, но в Swift нет абстрактных классов.
Хотя следующее решение работает и работает быстро, у меня в основном есть два разных класса в одном и я различаю их с помощью свойств nil, что похоже на « быстрое выполнение javascript » вместо использования типов для выражения семантики и запрета представления недопустимых состояний : /.
Есть ли способ лучше?
Пример:
ProductViewModel.swift
import Combine
import Foundation
class ProductViewModel: ObservableObject {
@Published fileprivate(set) var isLoaded: Bool
@Published fileprivate(set) var stateLabel: String
@Published fileprivate(set) var product: MyProduct
@Published fileprivate(set) var title: String = ""
@Published fileprivate(set) var description: String = ""
@Published fileprivate(set) var price: String = ""
@Published fileprivate(set) var priceTimeUnit: String = ""
public var transactionHandler: TransactionHandler?
var transactionStateCancellable: AnyCancellable?
init(transactionHandler: TransactionHandler, inAppPurchaseManager: InAppPurchaseManager, product: MyProduct) {
self.transactionHandler = transactionHandler
self.product = product
self.isLoaded = false
self.stateLabel = ""
// all the loading etc.
self.transactionStateCancellable = transactionHandler.$productsState.receive(on: RunLoop.main).sink { [weak self] newState in
guard let self = self else { return }
switch newState {
case .initial:
self.stateLabel = ""
case .productsAreLoading:
self.stateLabel = "Loading (Constants.ELLIPSIS)"
case .productsLoaded:
self.stateLabel = ""
self.isLoaded = true
case .productsLoadingError:
self.stateLabel = "Error loading products"
}
if let skProduct = inAppPurchaseManager.getAvailableProduct(id: product.id) {
self.price = skProduct.formatProductPriceForButton()
self.title = skProduct.localizedTitle
self.description = product.description()
self.priceTimeUnit = product.priceTimeUnit()
self.isLoaded = true
}
}
}
#if DEBUG
/// Dummy init for preview
init(dummyProduct: MyProduct, title: String, price: String) {
self.isLoaded = true
self.stateLabel = "Product loaded"
self.product = dummyProduct
self.title = title
self.description = dummyProduct.description()
self.price = price
self.priceTimeUnit = dummyProduct.priceTimeUnit()
}
#endif
}
Затем я могу использовать его в предварительном просмотре:
struct ProductView_Previews: PreviewProvider {
static var previews: some View {
Group {
// Calling dummy init
ProductView(viewModel: ProductViewModel(dummyProduct: .monthlySub, title: "Monthly", price: "$0.99"))
}
}
}
Хотя в продакшене это будет называться так:
// Calling real init()
ProductView(viewModel: ProductViewModel(transactionHandler: AppState.DummyAppState.transactionHandler, inAppPurchaseManager: AppState.DummyAppState.inAppPurchaseManager, product: .monthly))
.environmentObject(AppState.DummyAppState)
.environmentObject(WindowState.DummyWindowState)