You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI中复用基于Combine构建的Loading状态与ViewModel?

Can I reuse my custom LoadingState architecture from UIKit/Combine in SwiftUI?

Absolutely! You can reuse your existing LoadingState setup in SwiftUI—you just need to fix how state changes are communicated to SwiftUI's view update system. Here's why your current code isn't working, and two straightforward fixes:

Why your current SwiftUI implementation isn't updating views

SwiftUI relies on ObservableObject's objectWillChange publisher to know when to refresh views. Your custom @Loading property wrapper uses a CurrentValueSubject to emit state changes, but it doesn't trigger the objectWillChange notification on your MyViewModel. That means SwiftUI never gets the signal to re-render when myDataLoadingState updates.

Fix 1: Subscribe to Loading state changes in your ViewModel (no changes to Loading wrapper)

This approach keeps your existing Loading property wrapper intact, and adds a simple subscription in your ViewModel to trigger SwiftUI updates:

public class MyViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    
    @Loading<MyData, MyError> var myDataLoadingState = .idle
    
    public init() {
        // Tell SwiftUI to update whenever the Loading state changes
        $myDataLoadingState
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
    
    public func getMyData(ID: String) {
        myDataLoadingState = .loading
        myDataUseCase.execute(ID: ID)
            .receive(on: DispatchQueue.main)
            .sink { completion in
                guard case .failure(let error) = completion else { return }
                self.myDataLoadingState = .failure(error)
            } receiveValue: { myData in
                self.myDataLoadingState = .success(myData)
            }
            .store(in: &cancellables)
    }
}

Fix 2: Modify the Loading wrapper to support ObservableObject updates

If you want a more encapsulated solution (so you don't have to add the subscription to every ViewModel), update your Loading property wrapper to trigger objectWillChange directly:

Step 1: Update the Loading property wrapper

@propertyWrapper
public class Loading<T, E: Swift.Error> {
    public typealias State = LoadingState<T, E>
    
    // Weak reference to the host ObservableObject's update publisher
    public weak var objectWillChange: ObservableObjectPublisher?
    
    public var wrappedValue: State {
        willSet {
            subject.send(newValue)
            // Trigger the host ViewModel's update notification
            objectWillChange?.send()
        }
    }
    
    public init(wrappedValue: State) {
        self.wrappedValue = wrappedValue
    }
    
    private lazy var subject = CurrentValueSubject<State, Never>(wrappedValue)
    
    public var projectedValue: AnyPublisher<State, Never> {
        return subject.eraseToAnyPublisher()
    }
}

Step 2: Bind the wrapper to your ViewModel's objectWillChange

public class MyViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    
    @Loading<MyData, MyError> var myDataLoadingState = .idle
    
    public init() {
        // Connect the Loading wrapper to the ViewModel's update system
        _myDataLoadingState.objectWillChange = self.objectWillChange
    }
    
    // Your existing getMyData method stays the same
    public func getMyData(ID: String) {
        myDataLoadingState = .loading
        myDataUseCase.execute(ID: ID)
            .receive(on: DispatchQueue.main)
            .sink { completion in
                guard case .failure(let error) = completion else { return }
                self.myDataLoadingState = .failure(error)
            } receiveValue: { myData in
                self.myDataLoadingState = .success(myData)
            }
            .store(in: &cancellables)
    }
}

While not strictly required for the fixes above, making your LoadingState conform to Equatable adds type safety and aligns with Combine/SwiftUI best practices:

enum LoadingState<T, E: Error>: Equatable where T: Equatable, E: Equatable {
    case idle
    case loading
    case success(T)
    case failure(E)
}

Final Check

Your ContentView code doesn't need any changes! With either fix applied, when myDataLoadingState updates, SwiftUI will receive the objectWillChange notification and re-render the appropriate view based on the new state.

内容的提问来源于stack exchange,提问作者nunzio giulio caggegi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 03:07:48