Nigel Gee
Nigel Gee

Reputation: 123

Saving UserInfo received via WatchConnectivity to CoreData

Hi I was wonder if anyone can help. I have watch app that does a check then passes two bit data to main app (a String and a Int). Then I want it to save them to CoreData. I have tried a few things. The Comment out code is want I need to save them to CoreData.

#if os(iOS)
// With This get *SwiftUI:0: Fatal error: No ObservableObject of type DataController found. A View.environmentObject(_:) for DataController may be missing as an ancestor of this view.*
@EnvironmentObject var dataController: DataController

// This does saved to CoreData but then crashes the iOS app!
//    @ObservedObject var dataController = DataController()

func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
    DispatchQueue.main.async {

    print("Data received \(userInfo)")
//            let viewContext = dataController.container.viewContext

    if let isDefect = userInfo["isDefect"] as? Bool {
        if isDefect {
// This is another data that save to different entity
            print("Defect")
//                let defect = Defect(context: viewContext)
        } else {
            print("Vehicle check")
//                let checkData = CheckData(context: viewContext)
//
            if let bonnet = userInfo["bonnet"] as? String {
//                    checkData.bonnet = bonnet
                print(bonnet)
            }
//
            if let numberDefect = userInfo["numberDefect"] as? Int {
//                    checkData.numberDefect = Int64(numberDefect)
                print("\(numberDefect)")
            }
//
//                checkData.dateCreated = Date()
        }
    }

//            dataController.save()
    }
}

// other Protocol methods that are required
#else
// watch Protocol method
#endif

Upvotes: 1

Views: 517

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29414

If you make all this available to your iOS and WatchKit Extension you will have a working sample

import SwiftUI

struct WatchConnectView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \CheckData.dateCreated, ascending: true)],
        animation: .default) private var items: FetchedResults<CheckData>
    let wcManager = WatchConnectivityManager.shared
    var body: some View {
        VStack{
            Button("send vehicle check", action: {
                wcManager.sendVehicleCheck()
            })
            List {
                ForEach(items) { item in
                    Text("CheckData at \(item)")
                }
            }
            
        }
    }
}

struct WatchConnectView_Previews: PreviewProvider {
    static var previews: some View {
        WatchConnectView()
    }
}
//This would handle all the work for watch connectivity regardless of target
import WatchConnectivity
class WatchConnectivityManager: NSObject, Identifiable {
    let id = UUID()
    private let session: WCSession = .default
    var isReachable: Bool{
        session.isReachable
    }
    //Need singleton because of delegate
    static let shared: WatchConnectivityManager = WatchConnectivityManager()
    //If you have target specific code it is easier to manage if it is separated
    let targetSpecifc = WatchConnectivityManagerTS()
    private override init() {
        super.init()
        session.delegate = self
        session.activate()
    }
    func sendUserInfo(userInfo: [String: Any]){
        if isReachable{
            session.transferUserInfo(userInfo)
        }
    }
    func sendVehicleCheck(bonnet: String = "Some Bonnet", numberDefect: Int = Int.random(in: 0...50)){
        var userInfo: [String: Any] = [:]
        userInfo["isDefect"] = false
        userInfo["bonnet"] = bonnet
        userInfo["numberDefect"] = numberDefect
        
        sendUserInfo(userInfo: userInfo)
    }
}
extension WatchConnectivityManager: WCSessionDelegate{
#if os(iOS)
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        
    }
#endif
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        if let error = error{
            print(error)
        }
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        if let isDefect = userInfo["isDefect"] as? Bool {
            if isDefect {
                print("Defect")
            } else {
                print("Vehicle check")
                targetSpecifc.saveVehicleCheck(userInfo: userInfo)
                
            }
        }
    }
    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

    }
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {

    }
}
//Asuming CoreData is only on iOS
#if !os(watchOS)
class DataController{
    //Access to the context depends on how you are creating the stack.
    //This is with the standard PersistenceController setup that
    //comes with XCode sample code
    let context = PersistenceController.shared.container.viewContext
    init(){}
    func newCheckData() -> CheckData{
        let new  = CheckData(context: context)
        new.dateCreated = Date()
        return new
    }
    
    func saveContext(){
        do{
            try context.save()
        }catch{
            print(error)
        }
    }
    
}
class WatchConnectivityManagerTS{
    let dataController = DataController()
    func saveVehicleCheck(userInfo: [String: Any]){
        let checkData = self.dataController.newCheckData()
        
        if let bonnet = userInfo["bonnet"] as? String {
            checkData.bonnet = bonnet
            print(bonnet)
        }
        
        if let numberDefect = userInfo["numberDefect"] as? Int {
            checkData.numberDefect = Int64(numberDefect)
            print("\(numberDefect)")
        }
        dataController.saveContext()
    }
}
#else
class WatchConnectivityManagerTS{
    //In case there is a reason for the watch to receive vechicle checks
    func saveVehicleCheck(userInfo: [String: Any]){
        print("save attempt on watch \(userInfo)")
    }
}
#endif

Upvotes: 2

Related Questions