carekit-apple / CareKit

CareKit is an open source software framework for creating apps that help people better understand and manage their health.
https://www.researchandcare.org
Other
2.39k stars 441 forks source link

How to pass storeManager through swiftui views #414

Closed quindariuss closed 4 years ago

quindariuss commented 4 years ago

Looking to use UIKit integration with swiftui to make a contact card for healthcare but not sure how to pass the storeManger of class OCKSynchronizedStoreManager through the scene delegate when it asks for it.

//
//  ContentView.swift
//  Clot
//
//  Created by Quin’darius Lyles-Woods on 4/13/20.
//  Copyright © 2020 Quin’darius Lyles-Woods. All rights reserved.
//

import SwiftUI
import CareKitUI
import CareKit

struct ContentView: View {
    @Binding var storeManager: OCKSynchronizedStoreManager

    var body: some View {

        VStack {
            antibodies()
            SimpleContactView(storeManager: storeManager)
                   .frame(width: 300, height: 80)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    let storeManager: OCKSynchronizedStoreManager

    static var previews: some View {
        ContentView(storeManager: $storeManger)
    }
}
struct SimpleContactView: UIViewControllerRepresentable {

    let storeManager: OCKSynchronizedStoreManager

    func makeUIViewController(context: Context) -> OCKSimpleContactViewController {
        OCKSimpleContactViewController(contactID: "lexi-torres", storeManager: storeManager)
    }

    func updateUIViewController(_ viewController: OCKSimpleContactViewController, context: Context) {
    }
}

Any comments no matter how belittling are appreciated :)

erik-apple commented 4 years ago

You can see only line 40 and 41 of the sample app's SceneDelegate how to get access to the store manager that lives on the AppDelegate.

https://github.com/carekit-apple/CareKit/blob/610624a9f75f8233989b65956749209d0d21d335/OCKSample/OCKSample/SceneDelegate.swift#L34-L51

You would something like this.

let appDelegate = UIApplication.shared.delegate as! AppDelegate
let storeManager = appDelegate.synchronizedStoreManager
let content = ContentView(storeManager: storeManager)

Another route you could go would be to put the store manager in an environment value. This is a bit more advanced, but it has the advantage of letting you create the contact view without explicitly passing in store manager.

extension EnvironmentValues {
    var storeManager: OCKSynchronizedStoreManager {
        get { self[StoreManagerKey.self] }
        set { self[StoreManagerKey.self] = newValue }
    }
}

private struct StoreManagerKey: EnvironmentKey {
    typealias Value = OCKSynchronizedStoreManager

    // This is the value that will be injected into a view's environment
    // if no other value is explicitly set.
    static var defaultValue: OCKSynchronizedStoreManager {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let manager = appDelegate.synchronizedStoreManager
        return manager
    }
}

struct SimpleContactView: UIViewControllerRepresentable {
    typealias UIViewControllerType = OCKSimpleContactViewController

     // Reading the store manager from the environment
    @Environment(\.storeManager) private var storeManager

    let contactID: String

    func makeUIViewController(context: Context) -> OCKSimpleContactViewController {
        OCKSimpleContactViewController(contactID: contactID, storeManager: storeManager)
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

    }
}

// Now you can create contact cards without explicitly passing a store manager.
struct ContentView: View {
    var body: some View {
        VStack {
            SimpleContactView(contactID: "Dwight")
            SimpleContactView(contactID: "Jim")
        }
    }
}
quindariuss commented 4 years ago

Hmmm my error is saying that my appDelegate has no member named synchronizedStoreManager, could this be because of using persistent container with cloudkit? Still foggy on where the

let appDelegate = UIApplication.shared.delegate as! AppDelegate let storeManager = appDelegate.synchronizedStoreManager let content = ContentView(storeManager: storeManager) is going

will drop my scene delegate and contentview

//content view `// // ContentView.swift // Clot // // Created by Quin’darius Lyles-Woods on 4/13/20. // Copyright © 2020 Quin’darius Lyles-Woods. All rights reserved. //

import SwiftUI import CareKit

struct ContentView: View {

var body: some View {

    VStack {
        SimpleContactView(contactID: "Dwight")
                   SimpleContactView(contactID: "Jim")

    }
}

}

struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }

extension EnvironmentValues { var storeManager: OCKSynchronizedStoreManager { get { self[StoreManagerKey.self] } set { self[StoreManagerKey.self] = newValue } } }

private struct StoreManagerKey: EnvironmentKey { typealias Value = OCKSynchronizedStoreManager

// This is the value that will be injected into a view's environment
// if no other value is explicitly set.
static var defaultValue: OCKSynchronizedStoreManager {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let manager = appDelegate.synchronizedStoreManager
    return manager
}

}

struct SimpleContactView: UIViewControllerRepresentable { typealias UIViewControllerType = OCKSimpleContactViewController

 // Reading the store manager from the environment
@Environment(\.storeManager) private var storeManager

let contactID: String

func makeUIViewController(context: Context) -> OCKSimpleContactViewController {
    OCKSimpleContactViewController(contactID: contactID, storeManager: storeManager)
}

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

}

} `

//scenedelegate `// // SceneDelegate.swift // Clot // // Created by Quin’darius Lyles-Woods on 4/13/20. // Copyright © 2020 Quin’darius Lyles-Woods. All rights reserved. //

import UIKit import SwiftUI import CareKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

// let storeManager: OCKSynchronizedStoreManager

    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let storeManager = appDelegate.synchronizedStoreManager
    let content = ContentView(storeManager: storeManager)

    // Get the managed object context from the shared persistent container.
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
    // Add `@Environment(\.managedObjectContext)` in the views that will need the context.

    _ = ContentView().environment(\.managedObjectContext, context)

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
        self.window = window
        window.makeKeyAndVisible()
    }
}

func sceneDidDisconnect(_ scene: UIScene) {
    // Called as the scene is being released by the system.
    // This occurs shortly after the scene enters the background, or when its session is discarded.
    // Release any resources associated with this scene that can be re-created the next time the scene connects.
    // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}

func sceneDidBecomeActive(_ scene: UIScene) {
    // Called when the scene has moved from an inactive state to an active state.
    // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}

func sceneWillResignActive(_ scene: UIScene) {
    // Called when the scene will move from an active state to an inactive state.
    // This may occur due to temporary interruptions (ex. an incoming phone call).
}

func sceneWillEnterForeground(_ scene: UIScene) {
    // Called as the scene transitions from the background to the foreground.
    // Use this method to undo the changes made on entering the background.
}

func sceneDidEnterBackground(_ scene: UIScene) {
    // Called as the scene transitions from the foreground to the background.
    // Use this method to save data, release shared resources, and store enough scene-specific state information
    // to restore the scene back to its current state.

    // Save changes in the application's managed object context when the application transitions to the background.
    (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}

}

`

erik-apple commented 4 years ago

@quinwoods It seems like you haven't added the store manager into your AppDelegate yet. I'd recommend you look at the sample app we provide. You can even just copy the code from there if you want.

https://github.com/carekit-apple/CareKit/blob/610624a9f75f8233989b65956749209d0d21d335/OCKSample/OCKSample/AppDelegate.swift#L35-L53

erik-apple commented 4 years ago

We're going to go ahead and close this issue since there hasn't been any activity for awhile. Feel free to re-open it if you still have questions!