Лучшие практики для моделей просмотра для предварительного просмотра SwiftUI?

Есть ли передовые методы подготовки облегченных моделей просмотра с фиктивными данными, которые можно было бы использовать в предварительных версиях 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)

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *