Has anyone been able to create a Control Center widget that opens a snippet view? There are stock Control Center widgets that do this, but I haven't been able to get it to work.
Here's what I tried:
struct SnippetButton: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: "***.***.snippetWidget"
) {
ControlWidgetButton(action: SnippetIntent()) {
Label("Show Snippet", systemImage: "map.fill")
}
}
.displayName(LocalizedStringResource("Show Snippet"))
.description("Show a snippet.")
}
}
struct SnippetIntent: ControlConfigurationIntent {
static var title: LocalizedStringResource = "Show a snippet"
static var description = IntentDescription("Show a snippet with some text.")
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
return .result(dialog: IntentDialog("Hello!"), view: SnippetView())
}
}
struct SnippetView: View {
var body: some View {
Text("Hello!")
}
}
WidgetKit
RSS for tagShow relevant, glanceable content from your app on iOS and iPadOS Home Screen and Lock Screen, macOS Desktop, Apple Watch Smart Stack and Complications, and in StandBy mode on iPhone.
Posts under WidgetKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
My SwiftData model doesn't have time to update, and the widget gets old data on the timeline
I create simple SwiftData app with widget extension. To share swiftdata between widget and app I use the same approach as in sample code "Adopting SwiftData for a Core Data app":
let modelContext = ModelContext(DataModel.shared.modelContainer)
if let models = try? modelContext.fetch(fetchDescriptor) {
// use
}
After I create/delete/updated model I call:
WidgetCenter.shared.reloadAllTimelines()
The problem is that the widget receives outdated data as a result of modelContext.fetch. To get the new data I need to call WidgetCenter.shared.reloadAllTimelines() twice or with some delay:
// MyModelEditorView
private func save() {
if let myModel {
myModel.title = title
} else {
let myModel = MyModel(
title: title
)
modelContext.insert(word)
}
WidgetCenter.shared.reloadAllTimelines()
// i have to call it twice
WidgetCenter.shared.reloadAllTimelines()
}
I couldn't test SampleCode for similar update behavior, because somehow I couldn't get the models into the widget and I only have "No Trip" displayed there.
If I remove the second call, the UI is updated immediately and the new title is displayed, but the widget in the timeline receive the old title.
I don't understand whether this is the case and I need to update the widget with a delay or am I doing something wrong?
BTW:
Why is sample code in init we don't use shared container?
private let modelContainer: ModelContainer
init() {
do {
// why we don't use DataModel.shared.modelContainer
modelContainer = try ModelContainer(for: Trip.self)
} catch {
fatalError("Failed to create the model container: \(error)")
}
}
I have success with starting live activity from the server but I have some questions and problem
first I get pushToStartTokenUpdates only once what if my user missed it? so I could not start from the server
pushToStartTokenUpdates how long I can use? will it refresh the new token? for start live activity via push notification?
does token I get from pushToStartTokenUpdates use for only start live activity event from server and can't use for update ?
so if token get from pushToStartTokenUpdates for only start live activity via remote so how can I obtain the token for update ?
I'm trying to start a live activity that allows a user to see and control a recording from their lock screen. I have an AppIntent that uses a class to start recording the user. The intent is used in a home screen widget. Upon pressing the button in the widget, the intent is called, which then calls the startRecording function within its perform.
This function then tries to start the live activity, but it is currently failing with a "Failed to start live activity: The operation couldn’t be completed. Target does not include NSSupportsLiveActivities plist key" error.
The relevant code block is this:
func startRecording() {
print("[RecordingManager] start recording called")
isRecording = true
let activityAttributes = RecordingControlWidgetAttributes(name: "RecordingManagerActivity")
let initialContentState = RecordingControlWidgetAttributes.ContentState(isRecording: true, startTime: Date())
let initialContent = ActivityContent(state: initialContentState, staleDate: nil)
if ActivityAuthorizationInfo().areActivitiesEnabled {
do {
liveActivity = try Activity<RecordingControlWidgetAttributes>.request(
attributes: activityAttributes,
content: initialContent,
pushType: nil
)
} catch {
print("Failed to start live activity: \(error.localizedDescription)")
}
} else {
print("Live activities are not available")
}
// TODO: actually start the recording
}
When this function is run/the button is pressed, the following messages are printed:
"[RecordingManager] start recording called
Failed to start live activity: The operation couldn’t be completed. Target does not include NSSupportsLiveActivities plist key"
However, I have included the NSSupportsLiveActivities key, with value YES, in the target Info in XCode for both the main app target and the WidgetExtension.
The following line exists in both the Release and Debug parts of the project.pbxproj file:
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
I also tried including this key and value directly in the Info.plist file of both targets, but that also didn't work.
This issue is occurring both on device and in simulator. I also checked that both my device and the simulator has LiveActivities turned on in Settings for the app.
What could be going wrong? Are there any other situations where this error may print?
Hi everyone,
Like so many before me, i tried to install my app to test widgets, live activities & dynamic island implementation on a real device or on the simulator (neither works). The problem is that when I try to run the project I get this message :
"SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8 "Failed to show Widget 'fr.example.LiveActivities.LiveWidget' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace).
[......]".
The first thing I tried was looking on Apple's forum and StackOverflow for a solution but after 1hour trying almost eveyrthing, nothing seemed to resolve my problem.
Does anyone has an idea of how I could try to fix it ? I would love to show this project in my portfolio in order to find an apprenticeship, but I can't until I find a way to fix this.
Thanks a lot!
I've created a control centre widget modelled on the talk in "Extend your app's controls across the system".
In the talk, the user can select which timer they want to start ("work" or "violin").
The specific timer is passed to ToggleTimerIntent as follows:
ControlWidgetToggle(
timerState.timer.name,
isOn: timerState.isRunning
action: ToggleTimerIntent(timer: timerState.timer)
) { isOn in
...
}
However, I can't find a way to access the timer value from inside my ToggleTimerIntent, since this code isn't provided in the talk.
An AppIntent must provide an empty init(), so I've specified timer as an option String:
@available(iOS 18, *)
struct ToggleTimerIntent: SetValueIntent {
static let title: LocalizedStringResource = "Toggle Timer"
// This value isn't never populated.
var timer: String?
@Parameter(title: "Running")
var value: Bool
func perform() async throws -> some IntentResult {
if let timer {
}
else {
// Always here
}
}
}
When I backtrace the code, ToggleTimerIntent is instantiated several times, however perform() is only called on an instance where the timer value isn't provided.
I can't select my Companion App Target in the Selection Many for "Embed in" in the add Target File. It's a Companion App for a Flutter App.
I add the target via File -> New -> Target -> WatchOS -> WidgetExtension
The minimum version for the flutter project is iOS 14 and the watch watchOS 10.
I tried readding my WatchTarget but it didn't work that time either. I made a dummy project with a default iOS App (No Flutter) and default WatchOS App and there I had the option to select my Companion Target.
checking Configuration Intent also changes nothing for the outcome.
I also tried adding one into another Runner (Flutter) Project with an Companion App and I run into the same Issue there.
I was adding WidgetExtension target for my old project.
The widget target is running fine in iOS 17, In my case, widget need to support iOS 14 and above, so I updated my widget code to old style, To removing WidgetConfigurationIntent and AppIntentTimelineProvider.
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry()
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
completion(Timeline(entries: [SimpleEntry()], policy: .never))
}
typealias Entry = SimpleEntry
}
struct SimpleEntry: TimelineEntry {
let date: Date = Date()
}
struct NCWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .time)
}
}
}
struct NCWidget: Widget {
let kind: String = "NCWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
NCWidgetEntryView(entry: entry)
}
.configurationDisplayName("DisplayName")
.description("description")
}
}
In my case, the code was working fine in iOS 17 simulator, But if I try to run in iOS 15, it returns the below error
code-SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8 "Failed to show Widget 'com.name-pprd.NCWidgetExtension' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=5 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedDescription=The request to open "com.apple.springboard" failed., NSLocalizedFailureReason=Unexpected error type., NSUnderlyingError=0x600003570b40 {Error Domain=BSServiceConnectionErrorDomain Code=3 "XPC error received on message reply handler" UserInfo={BSErrorCodeDescription=OperationFailed, NSLocalizedFailureReason=XPC error received on message reply handler}}, BSErrorCodeDescription=InvalidResponse}." UserInfo={NSLocalizedDescription=Failed to show Widget 'com.name-pprd.NCWidgetExtension' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=5 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedDescription=The request to open "com.apple.springboard" failed., NSLocalizedFailureReason=Unexpected error type., NSUnderlyingError=0x600003570b40 {Error Domain=BSServiceConnectionErrorDomain Code=3 "XPC error received on message reply handler" UserInfo={BSErrorCodeDescription=OperationFailed, NSLocalizedFailureReason=XPC error received on message reply handler}}, BSErrorCodeDescription=InvalidResponse}., NSUnderlyingError=0x600003570bd0 {Error Domain=FBSOpenApplicationServiceErrorDomain Code=5 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedDescription=The request to open "com.apple.springboard" failed., NSLocalizedFailureReason=Unexpected error type., NSUnderlyingError=0x600003570b40 {Error Domain=BSServiceConnectionErrorDomain Code=3 "XPC error received on message reply handler" UserInfo={BSErrorCodeDescription=OperationFailed, NSLocalizedFailureReason=XPC error received on message reply handler}}, BSErrorCodeDescription=InvalidResponse}}}
Domain: DTXMessage
Code: 1
User Info: {
DVTErrorCreationDateKey = "2024-07-20 17:30:58 +0000";
}
--
System Information
macOS Version 14.5 (Build 23F79)
Xcode 15.0.1 (22266) (Build 15A507)
Timestamp: 2024-07-20T23:00:58+05:30
In widget target Minimum target is 14.0
In App target Minimum target is 13.0
HI,
I'm trying to deeplink from my widget to a view in my app.
I'm using the "SwiftUI app lifecycle".
This the code I'm currently using:
var body: some Scene {
WindowGroup {
RootView()
.onContinueUserActivity("NextDeparturesWidgetConfigurationIntent") { userActivity in
guard let configuration: NextDeparturesWidgetConfigurationIntent = userActivity
.widgetConfigurationIntent(),
let stationEntity = configuration.stationEntity else { return }
NotificationCenter.default.post(name: .onOpenStation, object: stationEntity.id)
}
}
}
It is working fine when the app is already running (in the background).
However when the app is "cold starting" (ie. not in memory) onContinueUserActivity is never called.
I tried adding a UIApplicationDelegateAdaptor:
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
if let userActivity = options.userActivities.first {
let configuration: NextDeparturesWidgetConfigurationIntent? = userActivity.widgetConfigurationIntent()
// casting fails
}
return UISceneConfiguration(
name: nil,
sessionRole: connectingSceneSession.role
)
}
I can see that the activity is received but it is never casted to NextDeparturesWidgetConfigurationIntent.
What am I missing ?
Controls' actions use App Intents in iOS 18.
However, when executing App Intents from Controls, alert dialogs such as IntentDialog can't be used.
This makes it unclear how to display errors when trying to create Controls that do not launch the app.
Is there any way to handle this?
I am working on building Control widgets for our app and have noticed that openAppWhenRun doesn't seem to work for any ControlConfigurationIntent. When attaching the debugger to the widget extension in a sample project, I see the following error:
Unknown NSError The operation couldn’t be completed. (LNActionExecutorErrorDomain error 2018.)
This is reproducible as of Xcode 16.0 Beta 2 and Beta 3.
I have noted that using an OpenIntent, with a parameter called target that conforms to AppEnum seems to open the app properly, but if I use that workaround, adding any additional parameters to the OpenIntent seems to break things again.
Are others seeing this issue? I have feedback FB14357691.
Here's some sample code below to reproduce:
var body: some ControlWidgetConfiguration {
AppIntentControlConfiguration(kind: "Open Any Screen", intent: OpenAppScreenIntent.self) { template in
ControlWidgetButton(action: template) {
Label {
Text("Open App")
} icon: {
Image(systemName: "calendar")
}
}.tint(.red)
}
}
}
enum AppScreen: CaseIterable {
case calendar
case campus
case search
var title: String {
switch self {
case .calendar:
"Calendar"
case .campus:
"Campus"
case .search:
"Search"
}
}
}
struct OpenAppScreenIntent: ControlConfigurationIntent {
static var title: LocalizedStringResource = "Open app to a screen"
static var description = IntentDescription("Opens the app.")
/// The app should open regardless of what happens here
static let openAppWhenRun: Bool = true
@Parameter(title: "Screen", optionsProvider: OsuScreenOptionsProvider())
var screen: String?
struct OsuScreenOptionsProvider: DynamicOptionsProvider {
func results() async throws -> ItemCollection<String> {
var screenTitles: [String] = []
for screen in AppScreen.allCases {
async let title = screen.title
await screenTitles.append(title)
}
return ItemCollection {
ItemSection(items: screenTitles.map { IntentItem($0)})
}
}
func defaultResult() async -> String? {
return "Campus"
}
}
@MainActor
func perform() async throws -> some IntentResult {
#warning("The app should open regardless of what happens in this method, but it doesn't")
return .result()
}
}
Hello.
In iOS 17, after updating the app, when trying to "Edit Widget" from a long press on the widget, "Unknown extension process" is displayed and the widget cannot be edited. At this time, the widget becomes completely white (or completely black), and it cannot be fixed without restarting the iPhone. This issue occurs sporadically on some devices. The implementation uses AppIntentTimelineProvider. Does anyone know a solution, workaround, or the cause of this problem?
Thank you.
ControlWidget in iOS 18 Beta not showing and the widget created prior to iOS18 Beta not showing too.
I tried creating a ControlWidget following this Apple document - https://developer.apple.com/documentation/widgetkit/creating-controls-to-perform-actions-across-the-system, but for some reason the Control is not showing while I am trying to add and on top of that the widget which we created prior to iOS18 is also not showing, while trying to add. Here is the gist of code :
struct WidgetLauncher{
static func main() {
if #available(iOSApplicationExtension 18.0, *) {
appWidgetsFor18.main()
} else {
appWidgets.main()
}
struct apptWorkWidgets: WidgetBundle {
var body: some Widget {
WidgetPriorToiOS18()
}
}
@available(iOSApplicationExtension 18.0, *)
struct appWidgetsFor18: WidgetBundle {
var body: some Widget {
WidgetPriorToiOS18()
PerformActionButton() //This from the apple's document.
}
}
@available(iOSApplicationExtension 18.0, *)
struct PerformActionButton: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: "com.example.myApp.performActionButton"
) {
ControlWidgetButton(action: PerformAction()) {
Label("Perform Action", systemImage: "checkmark.circle")
}
}
.displayName("Perform Action")
.description("An example control that performs an action.")
}
}
struct PerformAction: AppIntent {
static let title: LocalizedStringResource = "Perform action"
func perform() async throws -> some IntentResult {
// Code that performs the action...
return .result()
}
}
I'm trying to add a ControlWidget to my WidgetBundle like this:
struct MyWidgets: WidgetBundle {
var body: some Widget {
if #available(iOSApplicationExtension 18.0, *) {
LauncherControl()
}
MyLiveActivityWidget()
HomeScreenWidget()
LockScreenWidget()
}
This works exactly as expected on iOS 18. However on iOS 17 my app seems to have no widgets at all.
The workaround described here (https://www.avanderlee.com/swiftui/variable-widgetbundle-configuration/) does not work either since WidgetBundleBuilder.buildBlock does not accept ControlWidget as an argument.
What is the correct way to include a Control widget conditionally on iOS 18?
I create an app that allow user tap on button in widget but when app is running it's got an error when tap: Could not find an intent with identifier AppIntentsIdentifier , mangled TypeName: Optional("Somethings")
If I have an interactive widget, with button that triggers an AudioPlaybackIntent, how should the intent communicate with the main app's audio player to pause/play?
I was thinking about instantiating the player in the Widget explicitly in its lifecycle. But, if the audio player gets instantiated separately in the app and in the widget, wouldn't I have two players running? Or am I misunderstanding the setup?
I'm making an app where there are two widgets. Both widgets are supposed to get their timelines once per day, as all data for the day is known at midnight. I'm having an issue where when put on a development device, the widgets' timelines work correctly, but do not refresh the next day. I've tried both .atEnd and .after(Date) refresh policies and neither seems to work. Does anyone know why the widget isn't refreshing properly? I'm almost certain that I'm under the daily limit of refreshes (one timeline refresh and ~12 timeline entries per day). Thank you for any help! Dev device iPhone 15 Pro on 17.5.1 (21F90) with Xcode Version 15.4 (15F31d). Below is the timeline code for one of the widgets:
struct EndTimeProvider: TimelineProvider {
var currentHour: Int {
Calendar.current.component(.hour, from: .now)
}
var currentMinute : Int {
Calendar.current.component(.minute, from: .now)
}
var placeholderSixthPeriod: Period {
var endMinute: Int = 0
var endHour: Int = 0
if currentMinute > 60-14 {
endHour = currentHour + 1
endMinute = (currentMinute + 14) % 60
} else {
endHour = currentHour
endMinute = currentMinute + 14
}
return Period(name: "Period 6", start: "00:00", end: "\(endHour):\(endMinute)")
}
func placeholder(in context: Context) -> EndTimeEntry {
return EndTimeEntry(date: .now, displayPeriod: placeholderSixthPeriod, scheduleName: "Regular Day")
}
func getSnapshot(in context: Context, completion: @escaping (EndTimeEntry) -> ()) {
if context.isPreview {
completion(placeholder(in: context))
return
}
let entry = EndTimeEntry(date: .now, displayPeriod: placeholderSixthPeriod, scheduleName: "Regular Day")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [EndTimeEntry] = []
let context = PersistenceController.shared.backgroundContext
let scheduleFetch = StoredScheduleOnDate.fetchRequest()
do {
let storedSchedules = try context.fetch(scheduleFetch)
let currentDate = Date()
if let todaySchedule = storedSchedules.first(where: {
Calendar.current.isDate($0.date!, equalTo: currentDate, toGranularity: .day)
})?.schedule?.asDayType() {
// Have an entry at midnight when schedules are needed
let morningStart = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: .now)!
let morningPeriod = Period(name: "Good morning", start: "00:00", end: todaySchedule.periods.first!.start)
let morningEntry = EndTimeEntry(date: morningStart, displayPeriod: morningPeriod, scheduleName: todaySchedule.name)
entries.append(morningEntry)
// Passing periods should show the next full period's end time.
// This means that an entry's date should be the past period's end, or the start in the first period's case.
let firstPeriod = todaySchedule.periods.first!
let firstPeriodEntry = EndTimeEntry(date: firstPeriod.getStartAsDate(), displayPeriod: firstPeriod, scheduleName: todaySchedule.name)
entries.append(firstPeriodEntry)
for index in 1..<todaySchedule.periods.count {
let entry = EndTimeEntry(date: todaySchedule.periods[index-1].getEndAsDate(), displayPeriod: todaySchedule.periods[index], scheduleName: todaySchedule.name)
entries.append(entry)
}
// Have an entry at the end of the day to have the start time of the next day shown
if let tomorrowSchedule = storedSchedules.first(where: {
Calendar.current.isDate($0.date!, equalTo: Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!, toGranularity: .day)
})?.schedule?.asDayType() {
// At EOD, show tomorrow's start
let endOfDay: Date = todaySchedule.periods.last!.getEndAsDate()
let overnightPeriod: Period = Period(name: "Good night", start: todaySchedule.periods.last!.end, end: "00:00")
let overnightEntry = EndTimeEntry(date: endOfDay, displayPeriod: overnightPeriod, scheduleName: tomorrowSchedule.name, overrideDisplayDate: tomorrowSchedule.periods.first!.getStartAsDate())
entries.append(overnightEntry)
}
}
} catch {
fatalError("Could not fetch from Core Data for widget timeline. \(error)")
}
let tomorrowMorning = Calendar.current.date(bySettingHour: 0, minute: 1, second: 0, of: Calendar.current.date(byAdding: .day, value: 1, to: .now)!)!
let timeline = Timeline(entries: entries, policy: .after(tomorrowMorning))
completion(timeline)
}
}
struct EndTimeEntry: TimelineEntry {
let date: Date
let displayPeriod: Period
let scheduleName: String
let overrideDisplayDate: Date?
init(date: Date, displayPeriod: Period, scheduleName: String, overrideDisplayDate: Date) {
self.date = date
self.displayPeriod = displayPeriod
self.scheduleName = scheduleName
self.overrideDisplayDate = overrideDisplayDate
}
init(date: Date, displayPeriod: Period, scheduleName: String) {
self.date = date
self.displayPeriod = displayPeriod
self.scheduleName = scheduleName
self.overrideDisplayDate = nil
}
}
...
Hello,
I'm facing problems when attempting to update my watchOS complication when relevant data on the iPhone app changes.
From what I gather reading the documentation I have to use the Watch Connectivity Framework to send said new data from the phone to the watch:
use transferCurrentComplicationUserInfo() to send a dictionary of data from the phone to the watch
implement the didReceiveUserInfo delegate method to handle incoming data on the watch
in said handler, save the incoming data to UserDefaults using an App Group so the widget-extension can read that data
after saving the data to UserDefaults, call WidgetCenter.shared.reloadAllTimelines() so watchOS can request fresh timelines for my complications
change the getTimeline() method of my TimelineProvider so it uses the received data from UserDefaults OR async fetch fresh data if received data from phone is too old
If I understand correctly, transferCurrentComplicationUserInfo() is limited to be used a maximum of 50 times a day. I'm running the apps in debug mode, so this should be no problem.
Here is my current implementation:
1 : Setup of my WC class:
final class Connectivity: NSObject
{
// singleton approach
static let shared = Connectivity()
// used to rate limit transmissions from phone → watch
private var lastSentBalanceContext: Date? = nil
private override init()
{
super.init()
// no need to check availability on watchOS
#if !os(watchOS)
guard WCSession.isSupported() else { return }
#endif
WCSession.default.delegate = self
WCSession.default.activate()
}
}
2 : The method enabling transmission from phone to watch:
#if os(iOS)
extension Connectivity: WCSessionDelegate
{
func sendBalanceContext(sample: HealthData)
{
guard WCSession.default.activationState == .activated else { return }
guard WCSession.default.isWatchAppInstalled else { return }
// rate limitat transmissions
guard self.lastSentBalanceContext == nil || abs(Date.now.timeIntervalSince(self.lastSentBalanceContext!)) > 10
else { return }
if WCSession.default.remainingComplicationUserInfoTransfers > 0
{
WCSession.default.transferCurrentComplicationUserInfo([
"context": "balance",
"date": sample.date,
"burnedActive": sample.burnedActive,
// more data...
])
self.lastSentBalanceContext = .now
}
}
// boilerplate handlers here
}
#endif
3 : Delegete method that handles incoming data on the watch:
#if os(watchOS)
extension Connectivity: WCSessionDelegate
{
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:])
{
guard let context = userInfo["context"] as? String,
context == "balance"
else { return }
guard let date = userInfo["date"] as? Date,
let burnedActive = userInfo["burnedActive"] as? Int
/* more data... */
else { return }
guard let SharedDefaults = UserDefaults(suiteName: "group.4DXABR577J.com.count.kcal.app")
else { return }
// TimelineProvider uses this to determine wether to use this data or fetch data on its own
SharedDefaults.set(Date.now, forKey: "lastReceivedBalanceContext")
SharedDefaults.set(date, forKey: "date")
SharedDefaults.set(burnedActive, forKey: "burnedActive")
// more data...
WidgetCenter.shared.reloadAllTimelines()
}
// boilerplate handlers
}
#endif
4 : Finally, the TimelineProvider:
struct HealthDataEntry: TimelineEntry
{
let date: Date
let data: HealthData
}
struct HealthDataTimelineProvider: TimelineProvider
{
// other callbacks here
func getTimeline(in context: Context, completion: @escaping (Timeline<HealthDataEntry>) -> Void)
{
let SharedDefaults: UserDefaults = UserDefaults(suiteName: "group.4DXABR577J.com.count.kcal.app")!
let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
// use data from phone if it is less than 60 seconds old
if let lastReceivedBalanceContext = SharedDefaults.object(forKey: "lastReceivedBalanceContext") as? Date
{
let interval = lastReceivedBalanceContext.timeIntervalSince(.now)
if interval > -60 && interval <= 0
{
let data = HealthData(date: SharedDefaults.object(forKey: "date") as? Date ?? Date(timeIntervalSinceReferenceDate: 0),
burnedActive: SharedDefaults.integer(forKey: "burnedActive"),
burnedActive7: SharedDefaults.integer(forKey: "burnedActive7") /* other data ... */)
let timeline = Timeline(
entries: [HealthDataEntry(date: .now, data: data)],
policy: .after(nextUpdateDate)
)
completion(timeline)
return
}
}
// default: fetch from HealthKit (if received data from phone is > 60s)
Task
{
let timeline = Timeline(
entries: [HealthDataEntry(date: .now, data: try! await asyncFetchData())],
policy: .after(nextUpdateDate)
)
completion(timeline)
}
}
}
The issue I am facing is that the watchOS complication only gets refreshed when I acitvely build and run the watchOS app in Xcode and then initiate a transmission of data to the watch. This works even if I do it back to back to back. As soon as I stop the watchOS app from within Xcode, my complications won't update anymore.
I noticed this behavior when I used print() statements throughout my code to see whether it is beeing executed as expected. The iPhone sends data, the watch receives it but then the watch fails to update the complications ONLY when not running from Xcode.
Can you spot any flaws in my implementation or in my understanding?
Maybe transferCurrentComplicationUserInfo() just isn't as reliable as I think it should be? I interpreted it as being practically guaranteed to refresh the complications 50 times a day, pretty much instantly?
Any help or guidance would be greatly appreciated!
Hello!
I'm building a Countdown Timer for the Dynamic Island using a Live Activity.
I have two issues for which I can't find any solution:
We want to display the time in the "X minutes" format, like in this example. I went through the forum but all the answers were wrong, because they were using a Text(:format) which never updates in the live activity, or a Text(:timerInterval) which we can't format.
I want the Live Activity to end once the timer gets to zero. I found this staleDate parameter that I thought would helped, but is actually only adding loaders on my design once the date is reached. I tried to implement a solution like the answer of this post, but the if (context.isStale) {...} part is never being rendered. It also looks like the stale sate gets activated only when the app is focus again.
I tried several fixes, went through a lot of forum and posts, but I can't find any solution.
Thanks!
I have a watchOS app where a user can select a picture. I’d like the picture to be displayed in a complication. I’m using WidgetKit, and I found out that for some watch faces (rendering mode = accented), the image gets tinted. Instead of the picture, the user sees only a colored box.
It appears that using the old framework, ClockKit, it was possible to display an image that gets slightly colored with the tint color on tinted watch faces. I believe this is to ensure backward compatibility of old complications.
My question is: can I do the same using WidgetKit? I tried using the widgetAccentable() modifier, but it doesn’t work.
Here's an example of what I mean. In the middle complication, instead of the pink square, I'd like to display the user picture.