In my Watch app on watchOS 9 I was using .foregroundColor(myColour) to colour the text in a widgetLabel on a corner complication like this:
let myColour: Color = functionThatReturnsAColorObjectConstructedLike Color.init(...) // green
.widgetLabel {
Text(myText)
.foregroundColor(myColour)
}
It worked fine; the widget label was green.
Now, in watchOS 10, I see that foregroundColor() is being deprecated in favour of foregroundStyle(), and I can use .foregroundStyle(.green), and - importantly - foregroundStyle() is only available on watchOS 10 and newer.
myColour is calculated depending on some other info, so I can't just write .green, and when I use .foregroundStyle(myColour) the widget label comes out as white every time, even if I set myColour = .green.
I think I have to use some sort of extension to pick the right combination, something like:
extension View {
func foregroundType(colour: Colour, style: any ShapeStyle) -> some THING? {
if #available(watchOS 10.0, *) {
return foregroundStyle(style)
} else {
return foregroundColor(colour)
}
}
}
// Usage
let myStyle: any ShapeStyle = SOMETHING?
...
.widgetLabel {
Text(myText)
.foregroundType(colour: myColour, style: myStyle)
It doesn't work. I just can't figure out what should be returned, nor how to return it. Any ideas?
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
Hi everyone,
I have two questions regarding some issues with the new iOS 17 interactive widgets and live activities.
Question 1: How can I update other Widgets and/or Live Activities, based on an AppIntent interaction in either one?
I have several widgets that is displaying the same information, but in different sizes (Small, Medium, Large and Lockscreen). Most of our users are using many of them together on their Lockscreen and Home Screen. When they are interacting with the AppIntent button in either the Widget og LiveActivity it does not update and vice-versa.
Based on the documentation it's pretty clear that interacting with a Widget it will update the timeline for that specific widget after the .perform() in the AppIntent is returned. Unfortunately, I also need to update the LiveActivity and the other TimeLines for the rest of the widgets. I have tried to use the WidgetCenter.shared.reloadAllTimelines() but it seems like it does not update the LiveActivity or the LockScreen widgets correctly.
Question 2: How can I update and reschedule the local notifications based on the interaction in the Widget and Live Activity?
The app sends a local notification whenever an activity starts or stops (e.g like a pomodoro timer with intervals). Let's assume the user have setup a 30 minute activity in the app, but now decides to pause the activity from either the widget or live activity. I then want to remove the scheduled local notification that was supposed to be fired in 30 minutes. I haven't been able to read from the documentation if this is possible, but would like to know if there is any way this can be performed.
Looking forward to hear if anyone have encountered the same challenges :)
I used WidgetKit, AppIntents and AppIntentConfiguration to build some Widgets and Complications. The target version of the app is iOS 17 and WatchOS 10...
But for some Users the Complications are not shown and for other User (all on iOS 17 and WatchOS 10) everything is fine...
The Version is currently only available using TestFlight.
What can cause this problems for some users?
Hi!
I have an Apple Watch app that provide widgets (circular, corners and InLine) to my users.
I use the IntentTimelineProvider.recommendations method to provide the preconfigured widgets but only 16 first seems to be displayed.
Is there an implicit limit of 16 items or I am doing something wrong?
https://developer.apple.com/documentation/widgetkit/intenttimelineprovider/recommendations()-5ltr5
I am having some problem with accessing the CLLLocationManager location from my widget. It works fine with Xcode 15 running on a iOS17 simulator. But running it on a iOS17 device gives me an error in the delegate:
To access the location manager, I have this class:
class WidgetLocationManager: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
private var handler: ((CLLocation?) -> Void)?
override init() {
super.init()
DispatchQueue.main.async {
print("WidgetLocationManager: init")
self.locationManager = CLLocationManager()
if self.locationManager!.authorizationStatus == .notDetermined {
print("WidgetLocationManager: init - auth status is Undetermined")
} else {
print("WidgetLocationManager: init - auth status = \(self.locationManager!.authorizationStatus)")
}
}
}
func fetchLocation(handler: @escaping (CLLocation?) -> Void) {
self.handler = handler
self.locationManager = CLLocationManager()
self.locationManager!.delegate = self
self.locationManager!.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
if (CLLocationCoordinate2DIsValid(lastLocation.coordinate) == true && abs(lastLocation.timestamp.timeIntervalSinceNow) < 60 && lastLocation.horizontalAccuracy < 200.0 && lastLocation.horizontalAccuracy > 0.0) {
self.handler!(locations.last!)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("WidgetLocationManager: locationManager didFailWithError = \(error)")
self.handler!(nil)
}
}
When run on device, I get an error:
WidgetLocationManager: locationManager didFailWithError = Error Domain=kCLErrorDomain Code=1 "(null)"
Code = 1 in CLError is kCLErrorDenied ("Access to location or ranging has been denied by the user")
This is despite getting the following output in the init method:
WidgetLocationManager: init - auth status = CLAuthorizationStatus(rawValue: 4)
The weird thing is that it works fine in the simulator. On device, I've tried deleting and reinstalling the app a couple of times, restarting the device, making sure the privacy setting is correct etc.
Also, on the iOS17 device, when I am in the "Add Widget" page, the location manager runs fine in the preview screen, and shows the 'current location' to the user. But as soon as I add the widget to the home screen, it starts giving me this problem where the location can't be found, and I have to display an error message in the widget.
I have the following keys in the Info.plist for the widget:
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationWhenInUseUsageDescription
NSWidgetWantsLocation
The app target also has the following keys in the Info.plist:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
Any idea for what I can try to fix this problem?
Thanks.
I am not sure if is it watchOS 10 regression, but running my widget on Apple Watch I can see Label which is supposed to be rendered redacted when Apple Watch is not on my wrist. I use the .privacySensitive() modifier by it seems not to do anything. I can use the isLuminanceReduced environment in combination with .redacted(reason: .placeholder) which works when the wrist is not raised. But there's no environment AFAIK to know when the watch is locked.
BTW .redacted(reason: .privacy) don't do anything either, I have to use .placeholder.
Is anyone else having issues with privacy on Apple Watch when using the .accessoryRectangular widget family? Talking about regular widget, not the Smart Stack.
I am having trouble displaying widgets on iOS17.
In iOS16, it can be displayed in the full size of the widget, but in iOS17, it is displayed in an area 16 dots smaller in all direction than the original size.
I would like to have the same display on iOS17 as on iOS16, is there a better way?
struct widgetSmall : View {
var body: some View {
ZStack() {
Rectangle()
.foregroundColor(Color.yellow)
VStack() {
HStack() {
Text("Upper-left")
Spacer()
}
Spacer()
HStack() {
Spacer()
Text("Lower-right")
}
}
}
}
}
I've been working on an app that has lock screen and home screen widgets supports. Now we are rewriting the whole app -- and in a brand new workspace -- but only porting the existing widget to the new workspace with minimum tweaking.
We've made sure all the app bundle ID & app group ID are matching between the old and new, for the app, widget and intents (can't use app intents as that's still beta for our 16.0 requirement).
Now we are start testing in TestFlight builds we are seeing the home screen widgets that was installed in the original app turns blank/white when the new app is installed. It still handle tapping to the app but nothing visual; configuration pane is "unable to load". When my coworker installed the production app back onto the device the data in User Defaults is loaded, so we know data is not lost.
There's almost nothing online talking about widget upgrades but one article, that basically says it cannot be done -- and suggested a workaround to deprecate the production widget. :/
Anyone has experience on major app upgrade affecting widget upgrade? if yes did you have a solution to handle it that you can share?
Thanks!
I'm trying to get rid of the auto-padding added recently by iOS 17, and I found out that you can use the WidgetConfigurations properly with the new Preview Macro, but I'm having trouble getting it to work, and some help would be much appreciated.
Here's my code:
struct ChartEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
...
static func getSampleEntry() -> ChartEntry {
let sample = ChartEntry(date: Date(), configuration: WidgetUtils.getSampleConfiguration())
return sample
}
}
struct PreviewTimelineProvider: TimelineProvider {
typealias Entry = ChartEntry
func placeholder(in context: Context) -> ChartEntry {
ChartEntry.getSampleEntry()
}
func getSnapshot(in context: Context, completion: @escaping (ChartEntry) -> Void) {
completion(ChartEntry.getSampleEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<ChartEntry>) -> Void) {
var entries: [ChartEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = ChartEntry(date: entryDate, configuration: WidgetUtils.getSampleConfiguration())
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct DailyHoursOfSunshine: Identifiable {
let id = UUID()
var date: Date
var hoursOfSunshine: Double
init(date: Date, hoursOfSunshine: Double) {
self.date = date
self.hoursOfSunshine = hoursOfSunshine
}
}
var data = [
DailyHoursOfSunshine(date: Date(), hoursOfSunshine: 0), // Sunday
DailyHoursOfSunshine(date: Date().addingTimeInterval(86400), hoursOfSunshine: 60), // Monday
DailyHoursOfSunshine(date: Date().addingTimeInterval(2 * 86400), hoursOfSunshine: 50), // Tuesday
DailyHoursOfSunshine(date: Date().addingTimeInterval(3 * 86400), hoursOfSunshine: 72), // Wednesday
DailyHoursOfSunshine(date: Date().addingTimeInterval(4 * 86400), hoursOfSunshine: 88), // Thursday
DailyHoursOfSunshine(date: Date().addingTimeInterval(5 * 86400), hoursOfSunshine: 70), // Friday
DailyHoursOfSunshine(date: Date().addingTimeInterval(6 * 86400), hoursOfSunshine: 120) // Saturday
]
struct ChartSmallView: View {
var entry: ChartEntry
var body: some View {
VStack {
Spacer()
VStack(alignment: .leading, spacing: 0) {
Chart(data) {
AreaMark(
x: .value("Week", $0.date),
y: .value("Hours of Sunshine", $0.hoursOfSunshine)
)
.alignsMarkStylesWithPlotArea()
}
.foregroundStyle(
.linearGradient (
colors: [.blue, .clear],
startPoint: .top,
endPoint: .bottom
)
)
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.chartLegend(.hidden)
.padding([.horizontal], 3)
}
Spacer()
}
.frame(maxWidth: .infinity)
}
}
#Preview("Small", as: .systemSmall) {
ChartWidget()
} timelineProvider: {
PreviewTimelineProvider()
}
NOTE: There's nothing much on my ChartWidget struct, just the configuration to disable the margins and declare the preferred size and calling the ChartSmallView struct, I just didn't copy it here because it is in another file and I'm a little busy to go out excluding sensible information.
I would actually prefer to call just ChartSmallView on my preview, but then I wouldn't have access to the configuration that disables the awful contextualized margins.
The code compiles just fine, but when the preview tries to build it, the following error happens:
== PREVIEW UPDATE ERROR:
CompileDylibError: Failed to build ChartSmallView.swift
Compiling failed: cannot convert value of type 'PreviewTimelineProvider' to closure result type 'any View'
@__swiftmacro_63MyWidgetExtension_PreviewReplacement_ChartSmallView_133_77BD2C8074131F1FDF53BC08606762A7Ll0F0fMf_.swift
------------------------------
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, xrOS 1.0, *)
struct $s63MyWidgetExtension_PreviewReplacement_ChartSmallView_133_77BD2C8074131F1FDF53BC08606762A7Ll0F0fMf_15PreviewRegistryfMu_: DeveloperToolsSupport.PreviewRegistry {
static let fileID: String = "MyWidgetExtension_PreviewReplacement_ChartSmallView_1/ChartSmallView.1.preview-thunk.swift"
static let line: Int = 125
static let column: Int = 1
static func makePreview() throws -> DeveloperToolsSupport.Preview {
DeveloperToolsSupport.Preview("Small", as: .systemSmall) {
ChartWidget()
} timelineProvider: {
PreviewTimelineProvider()
}
}
}
------------------------------
/.../MyWidgetExtension.build/Objects-normal/x86_64/ChartSmallView.1.preview-thunk.swift:137:13: error: cannot convert value of type 'PreviewTimelineProvider' to closure result type 'any View'
PreviewTimelineProvider()
^~~~~~~~~~~~~~~~~~~~~~~~~
as! any View
I have also attempted to use the Preview Macro in the following way:
#Preview("Small", as: .systemSmall) {
ChartWidget()
} timeline: {
ChartEntry.getSampleEntry()
}
But achieved the same result.
If anyone has any idea of what I'm doing wrong, I would appreciate the help. I couldn't find much material online to help me here
how do you put this widget with my image on the map as in the photo thanks
I've created a fresh XCode project and have added a widget extension. Once I run the widgetExtension scheme on my device XCode crashes. I need to run the scheme so I can see my debug logs for the widget.
Device: iPhone 15 Pro Max (iOS 17.0.2)
XCode version: Version 15.0 (15A240d)
I just want to know what is maximum height of dynamic island(expanded). When I just go through the documentation, it is showing 160pt. Here is url:
https://developer.apple.com/design/human-interface-guidelines/live-activities#Specifications
But as I observed, some of applications have more than 160pt height of dynamic island (expanded). If this is possible, how we can increase height of dynamic island.
Even if we go through figma library, it is also showing more height
https://www.figma.com/file/YVWH9fn64y36***6jYdks5/Dynamic-Island-iPhone-14-(Community)?node-id=0%3A1&mode=dev
Hello,
With the advent of widget interactivity, in order to support state management, I'd like to differentiate one widget from another, even if they share the same configuration.
Is this possible? Many of my search results are turning up iOS 15 era information, and I am not sure if that's still valid.
Thank you
I'm currently implementing interactive widgets for my app. The widget calls an intent that is also used by Shortcuts and this interaction seems to work with no issues. Now I'm looking to get that running in Standby and on the lockscreen with the phone locked. I figured static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed would do the trick, but the phone still asks to be unlocked before running the intent. Is there any way to get this working? I've seen some examples for widgets that can be run with the phone locked, but they all seem to be audio intents that play some audio.
Example of the intent code:
Hello,
Is there a way for an app to identify whether the Standby feature is activated in iOS?
The objective is to offer users to enable Live Activities and monitor information during the Standby session.
PS: Currently, Apple's documentation provides only two available properties:
standBy: WidgetLocation
disfavoredLocations(_:for:)
Best Regards
Hey there,
Is there a way for an app to access the Standby settings listed below in iOS 17?
Standby
Night Mode
Show Notifications
Show Preview on Tap Only
The objective is determine whether the user has turned off the "Show Notifications" property, which could result in missing non-critical notifications.
Best Regards
Hi, I have an app that contains widgetkit elements. I have appgroup and everything used work fine before ios 17 and xcode 15
I was using suitname pattern like group.MY_TEAM_ID.com.xxxx.xxxxx
and there was no problem with it. now xcode 15 complains like Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd. when I change suitname group.com.xxxx.xxxxx or any other suitname. I see data saved to userdefaults but reading data from widget site returns nil all the time.
I have created a widget that has two configurable options, one is a boolean, and it works well, and the other one is an integer that makes problems. This is my current parameter's configuration:
Whenever I create this widget, “plus days” is not set to 0, but to -2. Or any value I put in the minimum field. If the default value is different from 0, everything works well.
I also tried to use Field instead of Stepper, but then both minimum and maximum values are ignored, regardless of what values I put in there.
I also noticed that setting a value in here to 0 removes tags from the XML, so I tried to keep them with value 0, but it also didn't do the trick.
Is it a bug or desired behavior? And is there a workaround for this?
Hi! I'm working on a widget using the new app intent & widgetkit features. Until now everything works, but I can't figure out how to give for example a line of text or a dummy data when the intent data is empty.
For example in the Widgetsmith app, you can put pictures on your homescreen using widgets. But from the moment that you delete the picture in the app, the widget changes to a text screen where they have placed information about changing the widget to another option.
Can you help me?
My code so far:
//
// TimersIntentWidget.swift
//
// Created by Mees Akveld on 25/09/2023.
//
import Foundation
import AppIntents
import WidgetKit
import SwiftUI
@available(iOS 17, *)
struct PresetIntentWidget: AppIntentTimelineProvider {
func timeline(for configuration: selectPresetIntent, in context: Context) async -> Timeline<PresetEntry> {
let entry = PresetEntry(date: Date(), presetModel: configuration.preset)
let timeline = Timeline(entries: [entry], policy: .never)
return timeline
}
func snapshot(for configuration: selectPresetIntent, in context: Context) async -> PresetEntry {
PresetEntry(date: Date(), presetModel: configuration.preset)
}
func placeholder(in context: Context) -> PresetEntry {
PresetEntry(date: Date(), presetModel: WidgetPreset.allPresets[0])
}
}
@available(iOS 17, *)
struct PresetEntry: TimelineEntry {
let date: Date
let presetModel: WidgetPreset
}
@available(iOS 17, *)
struct PresetWidgetView: View {
let entry: PresetEntry
private func isColorTheme() -> Int {
let defaults = UserDefaults(suiteName: "group.MeesAkveld.ShareDefaults")
let colorTheme = defaults?.integer(forKey: "ColorTheme") ?? 1
if colorTheme == 0 {
return 1
} else { return colorTheme }
}
var body: some View {
GeometryReader { geometry in
ZStack {
Image("bannerT\(isColorTheme())_Small")
.resizable()
.aspectRatio(contentMode: .fill)
.scaleEffect(1.11)
HStack {
Rectangle()
.cornerRadius(20)
.frame(width: geometry.size.width - 20, height: geometry.size.height - 20, alignment: .center)
.foregroundStyle(customBlue2Func())
Spacer()
}
.padding(.leading, 10)
HStack {
VStack(alignment: .leading, spacing: 10) {
Image(systemName: entry.presetModel.icon != "" ? entry.presetModel.icon : "timer")
.font(.system(size: 30))
.multilineTextAlignment(.center)
.frame(width: 18)
.offset(x: 10)
VStack(alignment: .leading){
Text(entry.presetModel.title != "" ? entry.presetModel.title : "")
.bold()
Text(timeToText(hours: entry.presetModel.hours, minutes: entry.presetModel.minutes, seconds: entry.presetModel.seconds))
}
.lineLimit(entry.presetModel.id == "noPresets" ? 2 : 1)
}
.foregroundColor(customBlueFunc())
Spacer()
}
.padding(25)
}
.widgetURL(URL(string:"preset:///\(entry.presetModel.id)"))
}
}
}
@available(iOS 17, *)
struct PresetWidgetConfiguration: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: "presetWidgetIntent",
intent: selectPresetIntent.self,
provider: PresetIntentWidget()
) { entry in
PresetWidgetView(entry: entry)
}
.configurationDisplayName("Presets")
.description("View and activate a preset of your choosing from your homescreen.")
.contentMarginsDisabled()
.supportedFamilies([.systemSmall])
}
}
@available(iOS 17, *)
struct PresetIntentWidget_Previews: PreviewProvider {
static var previews: some View {
PresetWidgetView(entry: PresetEntry(date: Date(), presetModel: WidgetPreset(id: "", title: "Title", icon: "", hours: 1, minutes: 1, seconds: 1, showEditView: false)))
}
}
@available(iOS 17, *)
struct WidgetPreset: AppEntity, Decodable {
var id: String
var title: String
var icon: String
var hours: Int
var minutes: Int
var seconds: Int
var showEditView: Bool
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Preset"
static var defaultQuery = WidgetPresetQuery()
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(title)", subtitle: "\(timeToText(hours: hours, minutes: minutes, seconds: seconds))")
}
static var allPresets: [WidgetPreset]{
get {
let defaults = UserDefaults(suiteName: "group.MeesAkveld.ShareDefaults")
let presetsData = defaults!.data(forKey: "presets")
if let data = presetsData,
let loadedPresets = try? JSONDecoder().decode([WidgetPreset].self, from: data) {
return loadedPresets
} else {
return []
}
}
}
}
@available(iOS 17, *)
struct WidgetPresetQuery: EntityQuery {
func entities(for identifiers: [WidgetPreset.ID]) async throws -> [WidgetPreset] {
WidgetPreset.allPresets.filter {
identifiers.contains($0.id)
}
}
func suggestedEntities() async throws -> [WidgetPreset] {
WidgetPreset.allPresets
}
func defaultResult() async -> WidgetPreset? {
WidgetPreset.allPresets.first
}
}
@available(iOS 17, *)
struct selectPresetIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Preset"
static var description: IntentDescription = IntentDescription("Select a preset")
@Parameter(title: "Selected preset:")
var preset: WidgetPreset
}
Hi there,
we're in the process of refactoring some portions of our widgets to add some new capabilities like interactivity.
In the mean time, we're wondering if there is any support for native MapKit within Widget on iOS17.
We tried a simple test, but seems that MapKit is still not available on Widgets. Is this correct? Am I missing something?
Many thanks for your answer!