Использование @StateObject в iOS 14.0 при поддержке iOS 13.0

#ios #swiftui #ios14

#iOS #swiftui #ios14

Вопрос:

Мне нужна помощь в поиске наилучшего способа поддержки нового @StateObject в iOS 14.0 и по-прежнему поддерживает некоторую альтернативу в iOS 13.0. По общему признанию, я не знаю, какой наилучший подход в iOS 13.0. Ниже то, что у меня есть в настоящее время.

У кого-нибудь есть идеи по лучшему подходу?

 struct HomeView: View {
    let viewModel: HomeViewModel

    var body: some View {
        if #available(iOS 14, *) {
            HomeViewWrapper(viewModel: viewModel)
        } else {
            CompatibleHomeViewWrapper(viewModel: viewModel)
        }
    }
}

@available(iOS 14, *)
private struct HomeViewWrapper: View {
    @StateObject var viewModel: HomeViewModel

    var body: some View {
        CompatibleHomeView(viewModel: viewModel)
    }
}

private struct CompatibleHomeViewWrapper: View {
    @State var viewModel: HomeViewModel

    var body: some View {
        CompatibleHomeView(viewModel: viewModel)
    }
}

struct CompatibleHomeView: View {
    @ObservedObject var viewModel: HomeViewModel

    var body: some View {
        Text(viewModel.someRandomName)
    }
}
 

Комментарии:

1. Использование @ObservedObject очень похоже и поддерживается в iOS 13

2. На самом деле в таком случае я вообще не вижу необходимости в HomeViewWrapper. Оболочка StateObject необходима, когда объект создается внутри, но у вас есть внешнее владение, даже вне родительского представления, поэтому объект состояния не нужен, поскольку HomeViewModel не воссоздается при перестроении представления.

3. @loremipsum. Да, @ObservedObject похоже, но во всей документации Apple и в видеороликах WWDC это то, что мы не должны использовать. @ObservedObject используется только тогда, когда у нас есть право собственности на ресурс.

4. @Asperi. Хотя HomeView не создает HomeViewModel, он имеет право собственности на него. HomeViewModel создается и передается в HomeView, который затем будет владеть им.

Ответ №1:

Вы можете получить поведение @StateObject, обернув пользовательскую оболочку свойств вокруг @State и @ObservedObject следующим образом:

 import Combine
import PublishedObject // https://github.com/Amzd/PublishedObject

/// A property wrapper type that instantiates an observable object.
@propertyWrapper
public struct StateObject<ObjectType: ObservableObject>
where ObjectType.ObjectWillChangePublisher == ObservableObjectPublisher {
    
    /// Wrapper that helps with initialising without actually having an ObservableObject yet
    private class ObservedObjectWrapper: ObservableObject {
        @PublishedObject var wrappedObject: ObjectType? = nil
        init() {}
    }
    
    private var thunk: () -> ObjectType
    @ObservedObject private var observedObject = ObservedObjectWrapper()
    @State private var state = ObservedObjectWrapper()
    
    public var wrappedValue: ObjectType {
        if state.wrappedObject == nil {
            // There is no State yet so we need to initialise the object
            state.wrappedObject = thunk()
        }
        if observedObject.wrappedObject == nil {
            // Retrieve the object from State and observe it in ObservedObject
            observedObject.wrappedObject = state.wrappedObject
        }
        return state.wrappedObject!
    }
    
    public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType) {
        self.thunk = thunk
    }
}
 

Я тоже использую это, поэтому буду обновлять его по адресу:
https://gist.github.com/Amzd/8f0d4d94fcbb6c9548e7cf0c1493eaff


Примечание: Наиболее одобренный комментарий заключался в том, что ObservedObject был очень похож, что совсем не соответствует действительности.

StateObject сохраняет объект между инициализациями представления и передает изменения объекта в представление через willChangeObserver.

ObservedObject только передает изменения в представление, НО если вы создаете его в инициализации представления, каждый раз, когда изменяется родительский вид, объект будет инициализироваться снова (теряя ваше состояние).

Это объяснение очень грубое, и, пожалуйста, ознакомьтесь с ним, поскольку оно является важной частью SwiftUI.