The complication "updates immediately" problem in WatchOS
Here I have a problem about update the apple watch complication's data,In the Apple Official Reference There are 5 way to update the complications,but in my repository,I think the reloadTimeline() is the best way to do so.
But there is some problem here when the Mobile APP pass the data through the WatchConnectivity to watchAPP,when I receive the data,I set a didSet in the property to trigger reloadTimeline() and update the complications.
The result isn't good enough for complications to updated immediately,I have to emphasize the most important thing is "immediately".
In my case,I need to touch the watchFace template, get in the watchAPP then back to watchFace,the update would works which reload the infomation on the template. In my sight, I think reloadTimeline() just work while the action be done.
So, you can see the way I try to get the complication data reload,but not immediately, not the expect way.
I have been search for many reference, even thousand time the Apple official Reference,but seens like nothing works here.
Some Code in the context could help your considering.
- WatchConnectivityManager
import ClockKit
import Foundation
import WatchConnectivity
class WatchConnectivityManager: NSObject, ObservableObject {
static let shared = WatchConnectivityManager()
var isReachable: Bool = false
private let kMessageKey = "message"
private let k1MessageKey = "realtime_message"
private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
private var watchCoffeeStruct: WatchCoffeeStruct? {
didSet {
guard let watchCoffeeStruct = watchCoffeeStruct else { return }
let watchCoffeeManager = WatchCoffeeManager.shared
watchCoffeeManager.coffeeBeanWeight = watchCoffeeStruct.coffeeBeanWeight
watchCoffeeManager.coffeeWater = watchCoffeeStruct.coffeeWater
watchCoffeeManager.coffeeKind = watchCoffeeStruct.coffeeKind
watchCoffeeManager.coffeeDripper = watchCoffeeStruct.coffeeDripper
watchCoffeeManager.waterTimeInput = watchCoffeeStruct.waterTimeInput
watchCoffeeManager.drinkPerson= watchCoffeeStruct.drinkPerson
print("watchCoffeeReload")
Task {
print("reloadActiveComplications")
await reloadActiveComplications()
}
}
}
var validSession: WCSession? {
#if os(iOS)
if let session = session, session.isPaired, session.isWatchAppInstalled {
return session
}
#elseif os(watchOS)
return session
#endif
return nil
}
override init() {
super.init()
registerWCSession()
}
private func registerWCSession() {
if let session = session {
session.delegate = self
session.activate()
} else {
print("error register WCSession")
}
}
func send(notificationStruct: WatchCoffeeStruct, didSend: @escaping (String?) -> Void) {
guard let session = session else {
didSend("session is nil")
return
}
guard session.activationState == .activated else {
didSend("activationState is not activated. \(session.activationState)")
return
}
#if os(iOS)
guard session.isWatchAppInstalled else {
didSend("watch app is not install")
return
}
#else
guard session.isCompanionAppInstalled else {
didSend("app is not install")
return
}
#endif
session.sendMessage(notificationStruct.messageToDic(), replyHandler: nil) { error in
print("did send message")
didSend(error.localizedDescription)
}
}
func send(_ communicationType: WatchCommunicationType) {
guard let session = session else {
return
}
guard session.activationState == .activated else {
return
}
#if os(iOS)
guard session.isWatchAppInstalled else {
return
}
#else
guard session.isCompanionAppInstalled else {
return
}
#endif
session.sendMessage(WatchCommunicationStruct(type: communicationType).communicationToDic(), replyHandler: nil) { error in
print("did send message")
}
}
func send(_ unit: String, didSend: @escaping (String?) -> Void) {
let sendUnitData = ["unit": unit]
guard let session = session else {
didSend("session is nil")
return
}
guard session.activationState == .activated else {
didSend("activationState is not activated.")
return
}
#if os(iOS)
guard session.isWatchAppInstalled else {
didSend("watch app is not install")
return
}
#else
guard session.isCompanionAppInstalled else {
didSend("app is not install")
return
}
#endif
session.sendMessage(sendUnitData, replyHandler: nil) { error in
print("did send message")
didSend(error.localizedDescription)
}
}
func updateApplication(notificationStruct: WatchCoffeeStruct, didSend: @escaping (String?) -> Void) {
guard let session = session else {
didSend("session is nil")
return
}
do {
try session.updateApplicationContext(notificationStruct.messageToDic())
print("did updateApplication message")
} catch {
didSend("updateApplicationContext error: \(error.localizedDescription)")
}
}
func updateApplication(unit: String, didSend: @escaping (String?) -> Void) {
let sendUnitData = ["unit": unit]
guard let session = session else {
didSend("session is nil")
return
}
do {
try session.updateApplicationContext(sendUnitData)
} catch {
didSend("updateApplicationContext error: \(error.localizedDescription)")
}
}
}
extension WatchConnectivityManager: WCSessionDelegate {
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
print("didReceiveApplicationContext")
DispatchQueue.main.async { [self] in
if let notificationStruct = applicationContext.dicToMessage() {
self.watchCoffeeStruct = notificationStruct
return
}
if let unit = applicationContext["unit"] as? String {
self.watchCoffeeStruct?.unit = unit
return
}
if let watchCommunicationStruct = applicationContext.dicToCommunication() {
if watchCommunicationStruct.type == .getHistoryData {
NotificationCenter.default.post(
name: .getHistoryData,
object: nil,
userInfo: nil
)
}
return
}
print("didReceiveMessage error")
}
}
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
DispatchQueue.main.async {
if let notificationStruct = message.dicToMessage() {
self.watchCoffeeStruct = notificationStruct
return
}
if let unit = message["unit"] as? String {
self.watchCoffeeStruct?.unit = unit
return
}
if let watchCommunicationStruct = message.dicToCommunication() {
if watchCommunicationStruct.type == .getHistoryData {
NotificationCenter.default.post(
name: .getHistoryData,
object: nil,
userInfo: nil
)
}
return
}
print("didReceiveMessage error")
}
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activationDidCompleteWith session = \(session.isReachable)")
print("activationDidCompleteWith error = \(error?.localizedDescription ?? "null")")
isReachable = session.isReachable
}
#if os(iOS)
func sessionReachabilityDidChange(_ session: WCSession) {
printD("sessionReachabilityDidChange = \(session.isReachable)")
isReachable = session.isReachable
}
func sessionWatchStateDidChange(_ session: WCSession) {
printD("sessionWatchStateDidChange = \(session.isReachable)")
isReachable = session.isReachable
}
func sessionDidBecomeInactive(_ session: WCSession) {
printD("sessionDidBecomeInactive = \(session.isReachable)")
}
func sessionDidDeactivate(_ session: WCSession) {
session.activate()
printD("sessionDidDeactivate = \(session.isReachable)")
}
#endif
}
- WatchManager
class WatchCoffeeManager: ObservableObject {
static let shared = WatchCoffeeManager()
@Published var coffeeBeanWeight: Double? = nil
@Published var coffeeWater: Double? = nil
@Published var coffeeKind: String? = nil
@Published var coffeeDripper: String? = nil
@Published var waterTimeInput: [Double]? = nil
@Published var drinkPerson: [String]? = nil
}
- ComplicationController
import ClockKit
import SwiftUI
enum Complications {
case Chart, BigIndicator, TrendBall, ModularCoffeeValue, UtilitarianLargeFlatCoffeeValue, GraphicCornerCoffee
var identifier: String {
switch self {
case .Chart: return "Chart"
case .BigIndicator: return "BigIndicator"
case .TrendBall: return "TrendBall"
case .ModularCoffeeValue: return "ModularCoffeeValue"
case .UtilitarianLargeFlatCoffeeValue: return "UtilitarianLargeFlatCoffeeValue"
case .GraphicCornerCoffee: return "GraphicCornerCoffee"
}
}
var displayName: String {
switch self {
case .Chart: return "Chart"
case .BigIndicator: return "BigIndicator"
case .TrendBall: return "TrendBall"
case .ModularCoffeeValue: return "CoffeeValue"
case .UtilitarianLargeFlatCoffeeValue: return "CoffeeValue"
case .GraphicCornerCoffee: return "CoffeeValue"
}
}
}
class ComplicationController: NSObject, CLKComplicationDataSource {
@ObservedObject private var watchManager = WatchCoffeeeManager.shared
func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
let coffeeBigIndicator = CLKComplicationDescriptor(identifier: Complications.BigIndicator.identifier, displayName: Complications.BigIndicator.displayName, supportedFamilies: [.circularSmall, .graphicCircular])
let coffeeChartView = CLKComplicationDescriptor(identifier: Complications.Chart.identifier, displayName: Complications.Chart.displayName, supportedFamilies: [.graphicRectangular])
let modularSmallCoffeeValue = CLKComplicationDescriptor(identifier: Complications.ModularCoffeeValue.identifier, displayName: Complications.ModularCoffeeValue.displayName, supportedFamilies: [.modularSmall])
let utilitarianLargeFlatCoffeeValue = CLKComplicationDescriptor(identifier: Complications.UtilitarianLargeFlatCoffeeValue.identifier, displayName: Complications.UtilitarianLargeFlatCoffeeValue.displayName, supportedFamilies: [.utilitarianLarge])
let graphicCornerCoffeeValue = CLKComplicationDescriptor(identifier: Complications.GraphicCornerCoffee.identifier, displayName: Complications.GraphicCornerCoffee.displayName, supportedFamilies: [.graphicCorner])
handler([coffeeBigIndicator,coffeeChartView,modularSmallCoffeeValue,utilitarianLargeFlatCoffeeValue,graphicCornerCoffeeValue])
}
func handleSharedComplicationDescriptors(_ complicationDescriptors: [CLKComplicationDescriptor]) {
// Do any necessary work to support these newly shared complication descriptors
}
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
// Call the handler with the last entry date you can currently provide or nil if you can't support future timelines
handler(nil)
}
func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
// Call the handler with your desired behavior when the device is locked
handler(.showOnLockScreen)
}
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
var entry = CLKComplicationTimelineEntry()
switch (complication.family, complication.identifier) {
case (.graphicCircular, Complications.BigIndicator.identifier):
let template = Templates.makeCoffeeIndicator(coffee:
.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.graphicRectangular, Complications.Chart.identifier):
let template = Templates.makeGraghicRectangularFullView(coffeeBeanWeight: watchManager.coffeeBeanWeight ?? 0,
coffeeWater: watchManager.coffeeWater ?? "",
coffeeKind: watchManager.coffeeKind ?? 0,
coffeeDripper: watchManager.coffeeDripper ?? 0,
coffeeArray: watchManager.coffeeArray ?? [0],
drinkPerson watchManager.dateArray ?? ["", "", "", ""])
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.modularSmall, Complications.ModularCoffeeValue.identifier):
let template = Templates.makeModularCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.utilitarianLarge, Complications.UtilitarianLargeFlatCoffeeValue.identifier):
let template = Templates.makeUtilitarianLargeFlatCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.modularSmall, Complications.ModularCoffeeValue.identifier):
let template = Templates.makeModularCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.utilitarianLarge, Complications.UtilitarianLargeFlatCoffeeValue.identifier):
let template = Templates.makeUtilitarianLargeFlatCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
case (.graphicCorner, Complications.GraphicCornerCoffee.identifier):
let template = Templates.makeGraphicCornerCoffeeValue(coffeeValue: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
default:
print("Complication family not supported")
}
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
handler(nil)
}
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
switch (complication.family, complication.identifier) {
case (.graphicCircular, Complications.BigIndicator.identifier):
let template = Templates.makeCoffeeIndicator(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
handler(template)
case (.graphicCircular, Complications.TrendBall.identifier):
let template = Templates.makeTrendBallIndicator(coffee: Int(watchManager.coffeeValue ?? 0), trend: watchManager.trend ?? 0)
handler(template)
case (.modularSmall, Complications.ModularCoffeeValue.identifier):
let template = Templates.makeModularCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
handler(template)
case (.utilitarianSmall, nil):
handler(nil)
case (.utilitarianSmallFlat, nil):
handler(nil)
case (.utilitarianLarge, Complications.UtilitarianLargeFlatCoffeeValue.identifier):
let template = Templates.makeUtilitarianLargeFlatCoffeeValue(coffee: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
handler(template)
case (.circularSmall, nil):
handler(nil)
case (.extraLarge, nil):
handler(nil)
case (.graphicCorner, Complications.GraphicCornerCoffee.identifier):
let template = Templates.makeGraphicCornerCoffeeValue(coffeeValue: watchManager.coffeeValue ?? 0, unit: watchManager.coffeeUnit ?? Unit.mgdL.rawValue)
handler(template)
case (.graphicBezel, nil):
handler(nil)
case (.graphicCircular, nil):
handler(nil)
case (.graphicRectangular, Complications.Chart.identifier):
let template = Templates.makeGraghicRectangularFullView(coffeeBeanWeight: watchManager.coffeeBeanWeight ?? 0,
coffeeWater: watchManager.coffeeWater ?? "",
coffeeKind: watchManager.coffeeKind ?? 0,
coffeeDripper: watchManager.coffeeDripper ?? 0,
coffeeArray: watchManager.coffeeArray ?? [0],
drinkPerson watchManager.dateArray ?? ["", "", "", ""])
handler(template)
case (.graphicExtraLarge, nil):
handler(nil)
@unknown default:
handler(nil)
}
}
}
There is my research reference in the past time: