Search code examples
iosswiftswiftuienvironmentobject

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")
    }
}

Solution

  • 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.