0

I am trying to send data from iOS application to watchOS app and widget (using WatchConnectivity). Former receives data just fine, but I wasn't able to figure out why data does not make it to the widget on watchOS.

Here's some code:

My iOS app that sends data:

struct ContentView: View {
    var shared = Shared()
    var body: some View {
        VStack {
            Button {
                self.sendMessage(username: "David")
            } label: { HStack {Text("David").font(.title).padding()} }
            Button {
                self.sendMessage(username: "John")
            } label: { HStack {Text("John").font(.title).padding()} }
        }
        .padding()
    }

    private func sendMessage(username: String) {
        self.shared.session.transferCurrentComplicationUserInfo(["username": username])
    }
}

Here comes watchOS app:

struct ContentView: View {
    @ObservedObject var shared = Shared()

    var body: some View {
        VStack {
            HStack {
                if let username = self.shared.username {
                    Text("Hello \(username)")
                } else {
                    Text("Check iPhone app.")
                }
            }
        }
    }
}

On watchOS extension the most important part is getTimeline function so I'll omit the rest for now:

struct Provider: IntentTimelineProvider {
    var shared = Shared()

...

    func getTimeline(for configuration: ConfigurationIntent, in context: Context,
                     completion: @escaping (Timeline<Entry>) -> Void) {
        shared.fetchData { user in
            let entries = [
                SimpleEntry(user: user)
            ]
            let timeline = Timeline(entries: entries, policy: .never)
            completion(timeline)
        }
    }

...
}

Finally, there is the Shared class that is shared between all applications and extension (File inspector -> Target Membership (checked all three).

import WatchConnectivity
import WidgetKit

final class Shared: NSObject, ObservableObject {
    @Published var username: String?
    var session: WCSession

    init(session: WCSession  = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
        session.activate()
    }

    func fetchData(completion: @escaping (String) -> Void) {
        completion(self.username ?? "")
    }
}

extension Shared: WCSessionDelegate {
    #if os(iOS)
    func sessionDidBecomeInactive(_ session: WCSession) {}

    func sessionDidDeactivate(_ session: WCSession) {}
    #else
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
        DispatchQueue.main.async {
            self.username = userInfo["username"] as? String
            WidgetCenter.shared.reloadAllTimelines()
        }
    }
    #endif

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState,
                 error: Error?) {}

}

I suspect this might be related to widget being kept in the background, although I know nothing about application lifecycle on iOS whatsoever. I came across WKWatchConnectivityRefreshBackgroundTask but couldn't make it work. If it helps, I placed this example project on my Github. I'd really appreciate any help on this one. Cheers

Mr. 0xCa7
  • 100
  • 7
  • 2
    You can't use WCSession from the widget extension. Your watch app needs to save data in a file shared via an application group on your watch. Your widget extension can then read this data – Paulw11 Jun 09 '23 at 21:57
  • Okay, and how about NSUserDefaults? Also, what if username changes dynamically on iOS and I want to display that change on watchOS widget? I'd need to open the application on watchOS each time, not really comfortable from user perspective. Is there a better approach? – Mr. 0xCa7 Jun 10 '23 at 11:00
  • 1
    Yes, you can use user defaults in the application group to communicate between your watch app and the widget extension. Your watch app can trigger a refresh of your widget. You can use wcsession to communicate between your iOS app and watch app when there is new data. – Paulw11 Jun 10 '23 at 11:48
  • Thanks. I know watch app can trigger a widget refresh using `reloadAllTimelines()`, but what if the app is in background, or not running at all? – Mr. 0xCa7 Jun 10 '23 at 12:52
  • 1
    Also another question. How comes watchOS extension does not use WCSession if there is a function `transferCurrentComplicationUserInfo` specifically to transfer extension-like data? – Mr. 0xCa7 Jun 10 '23 at 13:09
  • WCSession will deliver updates to your watch app in the background. Your watch app will generally be running if you are displaying widget complications. Widget kit replaces complications. – Paulw11 Jun 10 '23 at 13:11

0 Answers0