I have a Home Screen widget that contains a timer counting down to a specific date. In this image you can see the date calculations:
eventDate: 25th Dec 2023 at 09:00:00
entryDate(Date.now): This is just showing you the date in the first SimpleEntry for the widget.
getTimeRemaining(entryDate): This shows the number of seconds from the entryDate to the eventDate, figures out how many days there are ("43 days"), and how many hours:mins:secs to the event time, so "10:48:52".
Then there's a second entry, entryDate2, that's one hour later, and the values are appropriately calculated.
When I create the timeline entries, I add them for:
Now (1 entry)
Now plus one hour to a week away (one entry per hour = 167 entries)
Event date (1 entry)
Event date plus one hour to a week later (one entry per hour = 167 entries)
Each SimpleEntry entry contains a dictionary with the relevant timer details, and that's what the widget uses to determine what to display in the timer.
SwiftUI lacks any useful formatting for a timer. Even the developer docs state: "Example output: 36:59:01". Who wants to see a timer with 36 hours on it? I want it to say "1 day 12:59:01", so I munge the numbers about and grab the separate parts, converting 36:59:01 into "1 day" and "12:59:01". You can see that in the image above.
When the entry date of the timeline is reached and the widget is redrawn, it uses the entry containing the dictionary saying it should display "43 days" and the countdown timer should be 10:48:52, then an hour later the dictionary says it's 43 days 9:48:52, etc.
The issue is that the widgets, even though they're supposed to have entries at each hour, will always end up displaying something like "29:17:09". The timeline has an entry at every hour, so it should be using the right values. I've checked, and the right values are in the dictionary, so why does the timer keep getting out of sync?
I could cut out a massive amount of my widget code if only Text.init(date, style: .timer) would allow some proper formatting.
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
I have some apps that work fine under the iOS16 simulator, but they stop working in iOS17. It's the same code other than a minor tweak (*), and doesn't work under iOS17.
(*) When building, for iOS17 I needed to add a call to
.containerBackground(.fill.tertiary, for: .widget) for the View in the WidgetConfiguration for it to not throw an error.
Are there any other changes needed, because now my lock screen widgets no longer work.
thank you!
My widgets come up blank on physical iPhone 15 Pros. They work fine on every other iPhone regardless of their iOS versions, but are always blank on the 15 Pros specifically. What can be the issue here? I am honestly clueless about this strange behaviour. Shot in the dark but could it be because of the new chip?
I have also made additional arrangements in the ui to make sure to support .containerBackground API while also supporting earlier supporting iOS 16 which does not have this API.
Thanks
Hi there,
We've built a lock screen widget for our app, which displays sensitive information which we would love to hide when the device is locked. The widget contains a VStack with in there a Text with the subject name and another Text with the actual value of the subject. The last Text component with the value should be redacted.
During development, we've tried already some things like fully hiding the content on locked (setting Data protection to "Complete protection" on the App ID), but that was not really the result we were looking for.
We also tried to set .redacted(reason: .privacy) on the Text component, but that does not result in masking the value when locked. Same goes for setting the component with privacySensitive(). What can I do to make this behaviour work as expected?
The view:
VStack(alignment: .leading) {
Text("Name of Subject")
.font(.system(size: 15.0))
Text(entry.dataIsOutdated ? "-.--" : entry.valueAsString!)
.font(.system(size: 28.0))
.fontWeight(.bold)
.minimumScaleFactor(0.5)
.redacted(reason: .privacy)
}
.frame(maxWidth: .infinity, alignment: .leading)
.widgetURL(URL(string: "app-scheme://deeplink"))
Thanks for helping!
Calling reloadTimelines() on WidgetCenter.shared does not reload the timeline on my Apple Watch with watchOS 10.2 beta 2. Used to work in watchOS 10.1 and before. It does not work in iOS 17.2 beta 2 either. Do others see the same problem?
I have edited the default widget with Intent, but am being hit with the following errors… it runs perfectly fine if I don’t use an Intent in a static widget
Could not find an intent with identifier ConfigurationAppIntent, mangledTypeName: Optional("27trainWidgetsConfigExtension22ConfigurationAppIntentV")
associateAppIntent(forUserActivity:) Error converting INIntent to App Intent: AppIntents.PerformIntentError.intentNotFound
I think it may be something to do with Info.plist?
I have developed an interactive widget which looks as the following
It is using CoreData.
The view is implemented as the following
struct widget_extensionEntryView : View {
var body: some View {
if let nsTodoList = nsTodoList {
VStack(alignment: .leading, spacing: 1) {
...
ForEach(0..<prefixGoodNSTodoArray.count, id: \.self) { index in
...
let todoIntent = TodoIntent(objectIDAsString: objectIDAsString, parentOrder: parentOrder)
Button(intent: todoIntent) {
}
}
}
}
}
}
The AppIntent is implemented as the following
import Foundation
import AppIntents
import WidgetKit
import CoreData
struct TodoIntent: AppIntent {
static var title: LocalizedStringResource = "Complete Task"
static var description: IntentDescription = IntentDescription("Complete selected task")
@Parameter(title: "objectIDAsString")
var objectIDAsString: String
@Parameter(title: "parentOrder")
var parentOrder: Int
init() { }
init(objectIDAsString: String, parentOrder: Int) {
self.objectIDAsString = objectIDAsString
self.parentOrder = parentOrder
}
func perform() async throws -> some IntentResult {
guard let objectID = NSManagedObjectID.from(objectIDAsString) else { return .result() }
guard let nsTodoList = NSTodoListRepository.INSTANCE.getNSTodoList(objectID) else { return .result() }
nsTodoList.toggleChecked(context: CoreDataStack.INSTANCE.viewContext, parentOrder: Int64(parentOrder))
RepositoryUtils.saveContextIfPossible(CoreDataStack.INSTANCE.viewContext)
TodoWidgetOptions.isWrittenByWidgetExtension = true
// Refresh all home widgets.
// TODO: https://developer.apple.com/forums/thread/721937
WidgetCenter.shared.reloadAllTimelines()
return .result()
}
}
The interactive widget works pretty well.
However, tapping on the widget has no response in the following situations:
After an overnight, we turn on the iPhone's screen and tap on the widget.
After a few hours of idle time, we turn on the iPhone's screen and tap on the widget.
One of the steps below will make the widget workable again:
Launch and close the main app again. The main app will call WidgetCenter.shared.reloadAllTimelines() during sceneDidEnterBackground.
Press and hold on the widget, choose 'Edit widget', and select the desired Todo list.
One thing to take note of is that I am using IntentTimelineProvider instead of AppIntentTimelineProvider. The reason I am using 'older tech' is due to the limitation mentioned in https://developer.apple.com/forums/thread/741053
However, I am not sure whether this is the root cause of the problem.
Does anyone have any idea why such a problem occurs?
Thanks.
Hello, I am getting issue while installing widget build on device. I worked on widget and for that added different provisioning profile apart from main app profiles. I added profiles in ExportOptions.plist file also.
I checked with project settings also, On my projet target and Widget Target i have ONLY_ACTIVE_ARCH = YES and EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm 64.
My build is successful through Jenkins but when i install same build on device, getting issue as"The developer of this app needs to update it to work with this version of iOS: Keep or Delete"
Xcode: 14.3.1
MacOS: Ventura 13.5.1
Device iOS version: 16.6.1
Hello,
I created a lock screen widget for my app last year, as a widget app extension. It has worked fine, but I am working on a new release and it has stopped updating:
It is not called anymore when I call WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget"), and the preview screen is now blank when I add it on the lockscreen.
I haven't touched anything related to the widget from what I know. The only things I can think of is that Xcode has updated my project files automagically, and I have updated cocoapods.
I looked at the device logs, and found this:
[...MyWidget] Failed to launch extension with error: Error Domain=com.apple.extensionKit.errorDomain Code=2 UserInfo={NSUnderlyingError=0x84b4d4410 {Error Domain=RBSRequestErrorDomain Code=5 UserInfo={NSLocalizedFailureReason=, NSUnderlyingError=0x84b45ea30 {Error Domain=NSPOSIXErrorDomain Code=111 UserInfo={NSLocalizedDescription=}}}}}.
I have no idea what this is, hoping someone can come up with suggestions on where to look. I have looked at my git history but I can't find anything changed.
I had an occasional problem with the widget on iOS 17.1.1. The widgets work fine on systems like iOS 16 and 17.0.3, but last night, the widgets are not working on iOS 17.1.1 with an iPhone 15 Pro Max, both small and medium widgets occasional are not working. They display a gray background, and the system hasn't generated an IPS file. It seems like the widget has stopped working. Is this a bug in iOS 17.1.1? I also use WidgetCenter.shared.reloadAllTimelines(), the widgets are still not working.
When I add AppEnity to my model, I receive this error that is still repeated for each attribute in the model. The models are already marked for Widget Extension in Target Membership. I have already cleaned and restarted, nothing works. Will anyone know what I'm doing wrong?
Unable to find matching source file for path "@_swiftmacro_21HabitWidgetsExtension0A05ModelfMm.swift"
import SwiftData
import AppIntents
enum FrecuenciaCumplimiento: String, Codable {
case diario
case semanal
case mensual
}
@Model
final class Habit: AppEntity {
@Attribute(.unique) var id: UUID
var nombre: String
var descripcion: String
var icono: String
var color: String
var esHabitoPositivo: Bool
var valorObjetivo: Double
var unidadObjetivo: String
var frecuenciaCumplimiento: FrecuenciaCumplimiento
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Hábito"
static var defaultQuery = HabitQuery()
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(nombre)")
}
static var allHabits: [Habit] = [
Habit(id: UUID(), nombre: "uno", descripcion: "", icono: "circle", color: "#BF0000", esHabitoPositivo: true, valorObjetivo: 1.0, unidadObjetivo: "", frecuenciaCumplimiento: .mensual),
Habit(id: UUID(), nombre: "dos", descripcion: "", icono: "circle", color: "#BF0000", esHabitoPositivo: true, valorObjetivo: 1.0, unidadObjetivo: "", frecuenciaCumplimiento: .mensual)
]
/*
static func loadAllHabits() async throws {
do {
let modelContainer = try ModelContainer(for: Habit.self)
let descriptor = FetchDescriptor<Habit>()
allHabits = try await modelContainer.mainContext.fetch(descriptor)
} catch {
// Manejo de errores si es necesario
print("Error al cargar hábitos: \(error)")
throw error
}
}
*/
init(id: UUID = UUID(), nombre: String, descripcion: String, icono: String, color: String, esHabitoPositivo: Bool, valorObjetivo: Double, unidadObjetivo: String, frecuenciaCumplimiento: FrecuenciaCumplimiento) {
self.id = id
self.nombre = nombre
self.descripcion = descripcion
self.icono = icono
self.color = color
self.esHabitoPositivo = esHabitoPositivo
self.valorObjetivo = valorObjetivo
self.unidadObjetivo = unidadObjetivo
self.frecuenciaCumplimiento = frecuenciaCumplimiento
}
@Relationship(deleteRule: .cascade)
var habitRecords: [HabitRecord] = []
}
struct HabitQuery: EntityQuery {
func entities(for identifiers: [Habit.ID]) async throws -> [Habit] {
//try await Habit.loadAllHabits()
return Habit.allHabits.filter { identifiers.contains($0.id) }
}
func suggestedEntities() async throws -> [Habit] {
//try await Habit.loadAllHabits()
return Habit.allHabits// .filter { $0.isAvailable }
}
func defaultResult() async -> Habit? {
try? await suggestedEntities().first
}
}
I'am developing an iOS widget for my weather app, where the user can set the widget to "My location". This means the widget needs to be refreshed on location changes. Since a widget can't run a location manager in the background, apple tech support wrote that you have to setup a location manager in the main app and share the updated location data over App groups to the widget. This part works fine. I also managed to setup a location manager running in the background, but it uses too much battery and shows always the location indicator on top (blue bar) if the app is running, but I don't need this since its not a navigation app or something similar. How to configure a lightweight location manager running in the background?
class WidgetLocationManager: NSObject, CLLocationManagerDelegate {
static let shared: WidgetLocationManager = WidgetLocationManager()
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyKilometer
manager.distanceFilter = 1000
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false
manager.activityType = .other
manager.showsBackgroundLocationIndicator = false
}
func setupWidgetLocationManager() {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
manager.startMonitoringSignificantLocationChanges()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager.authorizationStatus == .notDetermined || manager.authorizationStatus == .denied || manager.authorizationStatus == .restricted {
manager.stopUpdatingLocation()
manager.stopMonitoringSignificantLocationChanges()
}
if manager.authorizationStatus == .authorizedAlways || manager.authorizationStatus == .authorizedWhenInUse {
manager.startUpdatingLocation()
manager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let newestLocation = locations.last {
UserDefaults(suiteName: "group.com.***")?.set(Double(newestLocation.coordinate.latitude), forKey: "newest_location_latitude")
UserDefaults(suiteName: "group.com.***")?.set(Double(newestLocation.coordinate.longitude), forKey: "newest_location_longitude")
UserDefaults(suiteName: "group.com.***")?.set(Double(newestLocation.altitude), forKey: "newest_location_altitude")
WidgetCenter.shared.reloadAllTimelines()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
}
Capability for background modes location is set, also mandatory strings in info.plist for location privacy info.
Hello!
We have issue with updating multiply widgets of one kind.
For my case we have widget with one setting (period), and it will be common case for user to use two widgets of one kind to see data for two periods. Also user need to sign in our app to see info on widget.
After installing app we add 2 or 3 widgets of one kind on home screen. Now widgets saying what we need to sign in. We tap on widget, signing in, app sends WidgetCenter.shared.reloadAllTimelines(). When we going to home screen.
One widget updated and showing data and another non-updated and saying what we need to sign in
Hi team, I see there is a new protocol PlayVideoIntent, how to use it in iOS17.2. Is that I mean I can play video on a widget? Thank you for your reply
My live activity is working perfectly on iOS 16.4 simulator but when I run the same code on iOS 17 or 17.2, the live activity does not show up anymore. There are no errors thrown and it says that the live activity is active.
Users want to be able to launch my app from their watch face. I'm not interested in any dynamic updates etc., I literally just want to display my app's icon and be done with it.
Using WidgetKit I have written a class that resizes my app's icon to the TimelineProviderContext.displaySize and that works for 50% of the Watch faces. However, some watch faces require the icon to be black and white whilst on other watch faces you can use colors.
If you use a color icon on a watch face that expected a black-and-white (or alpha channel) image, it will just display a gray circle.
How am I supposed to handle the 2 different color schemes in the watch faces? I have checked for TimelineProviderContext.family.description now to see if I can differentiate it based on that however this leads to contradictions as well; it says accessoryCircular for the "Activity Digital" face (which supports color) but also for for "California" face (which does not support color). What other value can one check for in order to know whether the watch face allows a color icon?
I always hated setting up complications in watchOS, they couldn't have done it worse for developers who just want the icon to show up on the Watch face, not even providing a simple sample (I'm happy to get corrected on this) but with WidgetKit it seems impossible to achieve anything at all
I can't get the simplest Widget to work. I simply want to present the app's icon. It does what it's supposed to do in the simulator but on the actual device it only says "Please adopt containerBackground API" at the place where the Widget would have shown otherwise.
I found out that I am supposed to add .containerBackground(Color.clear, for: .widget) for no reason at all to make this work. However, when I do that, I just get a gray circle where the app icon was supposed to show. The png files should be fine because again on the simulator it works so I have no idea what I am doing wrong.
If someone can tell me what I'm doing wrong or if someone can share their Widget class that would be awesome - I literally only want to show my app's icon on the Watch face but I already fail at that, no idea why they made WidgetKit impossible to work with.
import SwiftUI
import WidgetKit
struct IconEntry: TimelineEntry {
let date = Date()
let icon: Image
}
struct IconProvider: TimelineProvider {
func placeholder(in context: Context) -> IconEntry {
return IconEntry(icon: getImageForContext(context, color: false))
}
func getSnapshot(in context: Context, completion: @escaping (IconEntry) -> ()) {
let entry = IconEntry(icon: getImageForContext(context, color: false))
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let entry = IconEntry(icon: getImageForContext(context, color: false))
let timeline = Timeline(entries: [entry], policy: .never)
completion(timeline)
}
}
struct IconWidget: Widget {
let kind: String = "IconWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: IconProvider()) { entry in
IconView()
.containerBackground(Color.clear, for: .widget)
}
.supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular])
.description(Text("Show the app's icon on a watch face."))
}
}
struct InlineWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "Inline", provider: IconProvider()) { entry in
Text("App Title")
.containerBackground(Color.clear, for: .widget)
}
.configurationDisplayName("App Title")
.description("Description")
.supportedFamilies([.accessoryInline])
}
}
func getImageForContext(_ context: TimelineProviderContext, color: Bool) -> Image {
var fileNameToUse = "iconColor.png"
if(!color) {
fileNameToUse = "iconAlpha.png"
}
let displaySize = context.displaySize
if(displaySize.width == 0 || displaySize.height == 0) {
return Image(uiImage: .init())
}
let uiImage = UIImage(imageLiteralResourceName: fileNameToUse).resizeImage(width: displaySize.width, height: displaySize.height)
let image = Image(uiImage: uiImage)
return image
}
struct IconView: View {
@Environment(\.widgetRenderingMode) var widgetRenderingMode
var body: some View {
imageFor(widgetRenderingMode)
.resizable()
.onAppear {
print("ICON VIEW \(widgetRenderingMode == .fullColor ? "COLOR" : "ALPHA")")
}
.containerBackground(Color.clear, for: .widget)
}
}
private func imageFor(_ widgetRenderingMode: WidgetRenderingMode) -> Image {
switch widgetRenderingMode {
case .fullColor:
return Image(uiImage: UIImage(imageLiteralResourceName: "iconColor.png"))
default:
return Image(uiImage: UIImage(imageLiteralResourceName: "iconAlpha.png"))
}
}
@main
struct IconBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
IconWidget()
InlineWidget()
}
}
extension UIImage {
func resizeImage(width: CGFloat, height: CGFloat) -> UIImage {
let edgeLength = min(width, height)
let newSize = CGSize(width: edgeLength, height: edgeLength)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
if let resizedImage = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
return resizedImage
} else {
return UIImage()
}
}
}
I have a widget that has timeline entries for Monday, Tuesday, Wednesday, etc.
For example, today is Tuesday, but the widget displays the timeline entry for Monday. When I delete the Monday entry, it does display the Tuesday entry. So it's like it's selecting Monday even though today is Tuesday.
Is there a trick I'm missing?
thanks
I have a requirement where I have enabled the widget for my app but I don't want to show it to some specific user. And on click of any button on my app, I can enable the widget for that user.
Basically I can pass the value to supportedFamilies(_:) on runtime?
Any help will be appreciated
After updating our app some of our users are seeing that their widget has gone blank, the whole widget it just white. The only way to make the widget render again is to reboot the phone. This seems to be happening randomly and mainly on iOS17.
Looking on the web, seems like other people are also having this issue so hopefully Apple will make a fix at some point but I'm wondering if other people have had this issue and figured out a workaround to stop it happening?
Thanks.