Mohamed Salah
Mohamed Salah

Reputation: 1205

Pass EnvironmentObject as a weak reference to SwiftUI View

We are currently using coordinators to manage the navigation lifecycle of our SwiftUI app. For obvious scalability reasons, we chose the coordinator pattern. We now want to inject the coordinator object as an environment object; however, because the coordinator owns the parent navigation controller, which owns the children UIHostingViewControllers, we want to pass the coordinator as a weak EnvironmentObject. Is that even possible?

Coordinator code:

final public class RideCoordinator: PresentingCoordinator, ObservableObject {
    
    // MARK: - Start
    
    public override func start(options: [String : Any]? = nil) -> MSKit.CoordinatorNavigationViewController {
        _ = super.start(options: options)
        let welcomeView = MainScreen().environmentObject(self)
        startSwiftUIView(rootView: welcomeView, animated: false)
        return navigationController
    }
}

Example View:

struct MainScreen: View {
    
    // MARK: - Environment
    
    /// **Error** Property 'coordinator' with a wrapper cannot also be weak
    @EnvironmentObject weak var coordinator: RideCoordinator?
    
    // MARK: - View
    
    var body: some View {
        Text("Welcome")
    }
}

Upvotes: 1

Views: 139

Answers (1)

Mohamed Salah
Mohamed Salah

Reputation: 1205

So, I figured it out by wrapping the coordinator object inside an ObservableObject class and declaring a weak property within it:

public class WeakEnvObjectWrapper<T: AnyObject>: ObservableObject {
    public weak var weakRef: T?
    
    public init(_ weakRef: T? = nil) {
        self.weakRef = weakRef
    }
}

Usage:

final public class RideCoordinator: PresentingCoordinator {

    // MARK: - Start

    public override func start(options: [String : Any]? = nil) -> MSKit.CoordinatorNavigationViewController {
        _ = super.start(options: options)
        let welcomeView = MainScreen().environmentObject(WeakEnvObjectWrapper(self))
        startSwiftUIView(rootView: welcomeView, animated: false)
        return navigationController
    }
}

Reference inside SwiftUI Views:

struct MainScreen: View {

    // MARK: - Environment

    @EnvironmentObject var coordinator: WeakEnvObjectWrapper<RideCoordinator>

    // MARK: - View

    var body: some View {
        Button {
            coordinator.weakRef?.goToExample()
        } label: {
            Text("Welcome")
        }
    }
}

The code has been tested, and objects are released correctly.

Upvotes: 0

Related Questions