I’ve spent two days trying to solve a problem without success. Despite searching through numerous forums, online resources, and YouTube tutorials, I haven’t been able to resolve the issue.
The problem arises when I add a widget for both the Lock/Home Screen and my Apple Watch. When I open the app on my iPhone, the widget updates smoothly, as expected. However, the widget on my Apple Watch doesn’t update.
My goal is to update the widget by simply opening the app on my iPhone, making some changes, and then updating the entire widget, including those on the Lock/Home Screen and the Apple Watch. However, only the Apple Watch widget remains unaffected. When I check the debug, it indicates that ‘pair/app installed’ are true, but ‘reachable’ is false.
Here’s the relevant code snippet when I try to update the data for the entire widget:
var todayMood: [Int] = [-1, -1, -1, -1]
var calendarCurrentMood: [Int: [Int]] = [:]
func updateWidgetInfo() {
let validValues = todayMood.filter { $0 >= 0 && $0 <= 100 }
let sum = validValues.reduce(0, +)
// Calculate the average
let average: Double
var widgetString = ""
if validValues.isEmpty {
average = 0
widgetString = "--%"
} else {
average = Double(sum) / Double(validValues.count)
widgetString = "(Int(average))%"
}
let finalMoodRatingPercent = average / 100
if let sharedDefaults = UserDefaults(suiteName: "group.com.travgalax.Mood-Shine.widget") {
sharedDefaults.set(finalMoodRatingPercent, forKey: "widgetValue")
sharedDefaults.set(widgetString, forKey: "widgetString")
sharedDefaults.set(todayMood, forKey: "widgetFourMood")
sharedDefaults.set(Date().convertToString(type: .longDate), forKey: "widgetDate")
WidgetCenter.shared.reloadAllTimelines()
print("Your today mood is (widgetString), value is (finalMoodRatingPercent). Widget should be updated...")
updateWidgetColors()
} else {
print("Something wrong while updating the widget.")
}
}
func updateWidgetColors() {
var intToUIColor: [Int: [UIColor]] = [:]
for (key, value) in calendarCurrentMood {
var colorGrabbed: [UIColor] = []
for insertColors in value { colorGrabbed.append(getColorFromPer(Double(insertColors), colorArray: neonColor, minPer: 0, maxPer: 100)) }
intToUIColor[key] = colorGrabbed
}
let colorDataArray = neonColor.compactMap { color -> Data? in
return try? NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false)
}
let colorData: [Int: [[CGFloat]]] = intToUIColor.mapValues { $0.map { color in
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return [red, green, blue, alpha]
}}
// Convert colorData to Data
let colorDataEncoded = try? JSONEncoder().encode(colorData)
if let sharedDefaults = UserDefaults(suiteName: "group.com.travgalax.Mood-Shine.widget") {
sharedDefaults.set(colorDataArray, forKey: "widgetColors")
sharedDefaults.set(colorDataEncoded, forKey: "widgetCalendar")
WidgetCenter.shared.reloadAllTimelines()
notifyWatchAboutUpdate()
} else {
print("Something wrong while updating the widget.")
}
}
And here’s the Watch Connectivity (WCSession) manager:
func notifyWatchAboutUpdate() {
print("Is supported: (WCSession.isSupported())")
switch WCSession.default.activationState {
case .notActivated:
print("Activation State: Not activated")
case .inactive:
print("Activation State: Inactive")
case .activated:
print("Activation State: Activated")
print("Is paired: (WCSession.default.isPaired)")
print("Is watch app installed: (WCSession.default.isWatchAppInstalled)")
print("Is reachable: (WCSession.default.isReachable)")
print("Is complation enabled: (WCSession.default.isComplicationEnabled)")
@unknown default:
print("Activation State: Unknown")
}
let message = ["command": "updateData"]
WCSession.default.sendMessage(message, replyHandler: nil) { error in
print("Failed to send message to watch: (error.localizedDescription)")
}
}
class MyWCSessionManager: NSObject, WCSessionDelegate {
func sessionDidBecomeInactive(_ session: WCSession) {
session.activate()
}
func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
static let shared = MyWCSessionManager()
private override init() { super.init() }
func setupSession() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
// Handle activation completion
print("WCSession activated with state: (activationState)")
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("Reachability changed to: (session.isReachable)")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
// Handle incoming messages
if let command = message["command"] as? String, command == "updateData" {
// Handle the update command
print("Received update command from watch")
}
}
}
My SceneDelegate:
let watchSessionManager = MyWCSessionManager.shared
watchSessionManager.setupSession()
My Watch App:
import SwiftUI
import WatchConnectivity
class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject {
static let shared = WatchSessionManager()
@Published var widgetValue: Double = 0.0
@Published var widgetString: String = "--%"
@Published var widgetFourMood: [Int] = [-1, -1, -1, -1]
@Published var widgetDate: String = ""
private override init() {
super.init()
setupSession()
}
func setupSession() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// Required WCSessionDelegate methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if activationState == .activated {
print("Watch session activated successfully.")
} else {
print("Error activating watch session: (error?.localizedDescription ?? "Unknown error")")
}
}
// Handling incoming user info
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
DispatchQueue.main.async {
self.widgetValue = userInfo["widgetValue"] as? Double ?? 0.0
self.widgetString = userInfo["widgetString"] as? String ?? "--%"
self.widgetFourMood = userInfo["widgetFourMood"] as? [Int] ?? [-1, -1, -1, -1]
self.widgetDate = userInfo["widgetDate"] as? String ?? ""
// Update your watch's interface or data store
self.updateWatchWidget()
}
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
if let command = message["command"] as? String, command == "updateData" {
// Handle the update command
self.updateWatchWidget()
}
}
func updateWatchWidget() {
// Code to update your watch's widget
}
}
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
WatchSessionManager.shared.setupSession()
}
// other extension delegate methods...
}
@main
struct Mood_App_Watch_AppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
And final:
import WidgetKit
import SwiftUI
import Combine
class SharedUserDefaults: ObservableObject {
static let shared = SharedUserDefaults()
@Published var widgetValue: Double = 0.0
@Published var widgetString: String = "--%"
@Published var widgetFourMood: [Int] = [-1, -1, -1, -1]
@Published var widgetColors: [UIColor] = [.red, .blue]
@Published var widgetDate: String = ""
private let sharedDefaults = UserDefaults(suiteName: "group.com.travgalax.Mood-Shine.widget")
init() { loadData() }
func loadData() {
widgetValue = sharedDefaults?.double(forKey: "widgetValue") ?? 0.0
widgetString = sharedDefaults?.string(forKey: "widgetString") ?? "--%"
widgetFourMood = sharedDefaults?.array(forKey: "widgetFourMood") as? [Int] ?? [-1, -1, -1, -1]
widgetDate = sharedDefaults?.string(forKey: "widgetDate") ?? ""
if widgetDate != Date().convertToString(dateFormat: "MMMM yyyy") {
// MARK: Reset goes here
widgetValue = 0.0
widgetString = "--%"
widgetFourMood = [-1, -1, -1, -1]
}
// Retrieve the Data array from UserDefaults
if let colorDataArray = sharedDefaults?.array(forKey: "widgetColors") as? [Data] {
// Convert Data back to UIColors
widgetColors = colorDataArray.compactMap { data -> UIColor? in
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data)
}
}
}
}
struct TheWidgetEntry: TimelineEntry {
let date: Date
}
struct Provider: TimelineProvider {
typealias Entry = TheWidgetEntry
func placeholder(in context: Context) -> TheWidgetEntry {
TheWidgetEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (TheWidgetEntry) -> ()) {
let entry = TheWidgetEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [TheWidgetEntry] = []
// Get the current date
let currentDate = Date()
// Create an entry for the current date
let currentEntry = TheWidgetEntry(date: currentDate)
entries.append(currentEntry)
// Calculate the date for the next midnight
let calendar = Calendar.current
var nextMidnight = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: currentDate)!
nextMidnight = calendar.date(byAdding: .day, value: 1, to: nextMidnight)!
// Create an entry for the next midnight
let midnightEntry = TheWidgetEntry(date: nextMidnight)
entries.append(midnightEntry)
// Create the timeline
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct Watch_WidgetEntryView : View {
@Environment(.widgetFamily) var widgetFamily
@ObservedObject var sharedUserDefaults = SharedUserDefaults.shared
var entry: TheWidgetEntry
init(entry: Provider.Entry) {
self.entry = entry
sharedUserDefaults.loadData()
}
var body: some View {
let widgetValue = sharedUserDefaults.widgetValue
let widgetString = sharedUserDefaults.widgetString
let widgetFourMood = sharedUserDefaults.widgetFourMood
let widgetColors = sharedUserDefaults.widgetColors
switch widgetFamily {
case .accessoryInline:
Watch_Inline_View(widgetString: widgetString)
case .accessoryCorner:
Watch_Corner_View(widgetValue: widgetValue, widgetColors: Gradient(colors: [Color(uiColor: widgetColors[0]), Color(uiColor: widgetColors[1])]))
case .accessoryCircular:
Watch_Circular_View(widgetValue: widgetValue, widgetString: widgetString, widgetColors: Gradient(colors: [Color(uiColor: widgetColors[0]), Color(uiColor: widgetColors[1])]))
case .accessoryRectangular:
Watch_Rectangular_View(widgetValue: widgetFourMood, widgetString: widgetString, widgetColors: Gradient(colors: [Color(uiColor: widgetColors[0]), Color(uiColor: widgetColors[1])]))
default:
Text("Not available at this time: (widgetFamily)")
}
}
}
#Preview("Inline", as: .accessoryInline) {
Watch_Widget_Inline()
} timeline: {
TheWidgetEntry(date: .now)
}
#Preview("Corner", as: .accessoryCorner) {
Watch_Widget_Corner()
} timeline: {
TheWidgetEntry(date: .now)
}
#Preview("Circular", as: .accessoryCircular) {
Watch_Widget_Circular()
} timeline: {
TheWidgetEntry(date: .now)
}
#Preview("Rectangular", as: .accessoryRectangular) {
Watch_Widget_Rectangular()
} timeline: {
TheWidgetEntry(date: .now)
}
I’ve ensured that Bluetooth Always Usage is set in my Info.plist, and I’ve checked physical connectivity as well. Despite these efforts, the issue persists. If you have any insights into why my Apple Watch widget is not updating and why the watch app remains unreachable, I’d greatly appreciate your help. Thank you!