I have widgets providing their timeline using the .atEnd reload policy, i.e.:
// AppIntentTimelineProvider:
return Timeline(entries: entries, policy: .atEnd)
// TimelineProvider
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
I can't seem to find any information on what happens after the end of the timeline. So, let's say I've got two days worth of entries, the dev docs for the reload policy say, "A policy that specifies that WidgetKit requests a new timeline after the last date in a timeline passes."
Great! But how does it request the new timeline? Does iOS launch my app in the background and simply re-run the timeline to generate another two days worth of entries? I doubt it.
I figure I need to implement some sort of background task, and the dev docs say how to do it with an Operation, but then I read that this is an old way of doing it? I've found some info online saying to use something like this, so this is what I've implemented:
let kBackgroundWidgetRefreshTask = "my.refresh.task.identifier" // This has been registered in the info.plist correctly
class SchedulingService {
static let shared = SchedulingService()
func registerBackgroundTasks() {
let isRegistered = BGTaskScheduler.shared.register(forTaskWithIdentifier: kBackgroundWidgetRefreshTask, using: nil) { task in
print("Background task is executing: \(task.identifier)") // This does print "true"
self.handleWidgetRefresh(task: task as! BGAppRefreshTask)
}
print("Is the background task registered? \(isRegistered)")
}
func scheduleWidgetRefresh() {
let request = BGAppRefreshTaskRequest(identifier: kBackgroundWidgetRefreshTask)
// Fetch no earlier than 1 hour from now - test, will be two days
request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
print("Scheduled widget refresh for one hour from now")
} catch {
print("Could not schedule widget refresh: \(error)")
}
}
private func handleWidgetRefresh(task: BGAppRefreshTask) {
// Schedule a new refresh task
scheduleWidgetRefresh()
// Start refresh of the widget data
let refreshTask = Task {
do {
print("Going to refresh widgets")
try await self.backgroundRefreshWidgets()
task.setTaskCompleted(success: true)
} catch {
print("Could not refresh widgets: \(error)")
task.setTaskCompleted(success: false)
}
}
// Provide the background task with an expiration handler that cancels the operation
task.expirationHandler = {
refreshTask.cancel()
}
}
func backgroundRefreshWidgets() async throws {
print("backgroundRefreshWidgets() called")
definitelyRefreshWidgets()
}
}
As I've commented above, the line print("Background task is executing: \(task.identifier)") does print true so the task has been registered correctly.
I've put the app into the background and left it for hours and nothing is printed to the console. I've implemented a logger that writes to a file in the app container, but that doesn't get anything either.
So, is there something I'm misunderstanding? Should I change the reload policy to .after(date)? But what makes the timeline reload?
As a second but linked issue, my widgets have countdown timers on them and the entire timeline shows that every entry is correct, but the widgets on the Home Screen simply fail to refresh correctly.
For example, with timeline entries for every hour for the next two days from 6pm today (so, 7pm, 8pm...) every entry in the preview in Xcode shows the right countdown timer. However, if you put the widget on the Home Screen, after about five hours the timer shows 25:12:34 (for example).
No entry in the timeline preview ever shows more than 24 hours because the entires are every hour, and the one that shows a timer starting at 23:00:00 should never get to 24:00:00 as the next entry would kick in from 0:00:00, so it should never show more than 23:59:59 on the timer. It's like the 23:00:00 timer is just left to run for hours instead of being replaced by the next entry.
It's as though the widget isn't refreshing correctly and entries aren't loaded? Given this is the Simulator - and my development device - and both are set to Developer Mode so widget refresh budgets aren't an issue, why is this happening? How do you get widgets to refresh properly? The dev docs are not very helpful (neither is the Backyard Birds example Apple keep pushing).
Thanks!
Post
Replies
Boosts
Views
Activity
On iOS you can create a new Lock Screen that contains a bunch of emoji, and they'll get put on the screen in a repeating pattern, like this:
When you have two or more emoji they're placed in alternating patterns, like this:
How do I write something like that? I need to handle up to three emoji, and I need the canvas as large as the device's screen, and it needs to be saved as an image. Thanks!
(I've already written an emojiToImage() extension on String, but it just plonks one massive emoji in the middle of an image, so I'm doing something wrong there.)
When a user swipes up to see the app switcher, I put a blocking view over my app so the data inside cannot be seen if you flick through the app switcher. I do this by checking if the scenePhase goes from .active to .inactive.
If the app goes into the background, scenePhase == .background so I trigger something that would force the user to authenticate with Face ID/Touch ID when the app is next brought to the foreground or launched.
However, this doesn't seem to work. The biometrics authentication is executed, but it just lets the user in without showing the Face ID animation. I put my finger over the sensors so it couldn't possibly be authenticating, but it just lets them in.
Here's a quick set of logs:
scenePhase == .inactive - User showed app switcher
scenePhase == .background - User swiped up fully, went to Home Screen
scenePhase == .inactive - User has tapped the app icon
scenePhase == .active - App is now active
authenticate() - Method called
authenticate(), authenticateViaBiometrics() == true - User is going to be authenticated via Face ID
// Face ID did not appear!
success = true - Result of calling `context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics` means user was authenticated successfully
error = nil - No error in the authentication policy
authenticate(), success - Method finished, user was authenticated
Here's the code:
print("authenticate(), authenticateViaBiometrics() == true - User is going to be authenticated via Face ID")
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
// Handle permission denied or error
print("authenticate(), no permission, or error")
authenticated = false
defaultsUpdateAuthenticated(false)
defaultsUpdateAuthenticateViaBiometrics(false)
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Authenticate with biometrics") { (success, error) in
DispatchQueue.main.async {
print("success = \(success)")
print("error = \(String(describing: error?.localizedDescription))")
if(success) {
print("authenticate(), success")
authenticated = true
} else {
print("authenticate(), failure")
authenticated = false
}
}
}
This happens with or without the DispatchQueue... call.
According to this Apple page, if you make any money from your apps in the EU you have to provide your email address, phone number and address, and they will be displayed on your App Store page for all and sundry to see, use, and likely, abuse.
I don't want anyone and everyone to know those details; they are private. I thought Apple was all about privacy? I understand they have to adhere to the DSA, but Apple hasn't raised a single objection to this.
Apple has consistently said that not sharing a user's email address with a developer is a part of being in the App Store, i.e. Spotify can't contact someone who downloaded their app; but a user can now contact the developer?
I barely make any money from my apps - not even enough to cover the annual developer program fee - but I keep developing to stay current. I cannot afford a PO Box or business address and phone number to shield me from this, so I'm likely to remove my apps from the EU market.
You might think I'm being overly-cautious, or having a knee-jerk reaction, but these are my personal, private details, and they should not be available publicly just because I barely clear £1.50 a month from my apps.
I'm trying to get a countdown timer to work, and the way I currently do it in my watchOS 10 app is a complicated load of nonsense to get two Strings that look like "1w 1d" and "12:34:56", i.e. a String that shows components like year, week and day, and another String showing hours, minutes and seconds.
The new Text formats seen here https://developer.apple.com/wwdc24/10144?time=645 look useful, but I can't get them to return the values I need.
If I use this:
let dateA = Date.now
let dateB = Date.advanced(by: /* value for 8 days, 12 hours, 34 minutes and 56 seconds */)
Text(dateA, format: .offset(to: dateB, allowedFields: [.year, .week, .day], maxFieldCount: 3))
I expect to see "1 week 1 day", but it always comes back as "8 days". I guess it's giving me the most concise result, but I don't want that. I'm not sure anyone would want that.
Imagine you have an event coming up in 3 days 6 hours, do you want to see "in 78 hours" or do you want "in 3 days and 6 hours"? Why must we make the user calculate the days and hours in their head when we have the ability to give them the right information?
While I'm on this, why does the resulting String have to have "in " at the beginning? I don't want that; it's not my use case, but it's forced on me.
I've raised this a hundred times with Apple. I just want to see a String that shows the difference between two dates in a format of my choosing, i.e. "1w 1d", but they never give me what I need, so I have to write extremely complex - and fragile - code that figures this stuff out, and I still can't get that to work properly.
Why can't we just have something like:
Text(from: dateA, to: dateB, format: "%yy %ww %dd") // for "1 year 2 weeks 3 days", show parts with a value > 0
Text(from: dateA, to: dateB, format: "%0yy %0ww %0dd") // for "0 years 2 weeks 3 days", show all parts regardless of value
Text(from: dateA, to: dateB, format: "%y %w %d") // for "1y 2w 3d", show parts with a value > 0
Text(from: dateA, to: dateB, format: "%0y %0w %0d") // for "0y 2w 3d", show all parts regardless of value
iOS app with Home Screen and Lock Screen widgets written in Swift/SwiftUI. I've never been able to get widgets to work properly. It's more pronounced on Lock Screen widgets, so let's try that method first...
The app stores data in Core Data as an Event. They're read into my model and stored as WidgetEventDetails structs:
struct WidgetEventDetails: AppEntity, Identifiable, Hashable {
public var eventId: String
public var name: String
public var date: Date
public var id: String {
eventId
}
This all works absolutely fine in the iOS app, and each one is unique based on the eventId.
When I go to add a Lock Screen widget, I customise the Lock Screen, tap in the section to add a widget, and my widgets appear correctly and are selectable: (bottom right, says "1y 28w 1d")
So, I tap it and it appears in the widgets section:
But it appears as "17w 6d", which is a different event entirely. Notice how the one in the selectable widgets has changed to "15w 5d", and the one I tapped (1y 28w 1d) is nowhere to be seen.
So, I tap the one in the top row (17w 6d) to select an event, and this appears, suggesting that the event is the "Edinburgh & Glasgow 2024-02" event:
But that event is actually only a day away (1d), so that's not the one I selected at all.
I tap the list and see these events:
I select "Las Vegas 2024", which is 17w 3d away, and this is shown:
17w 6d is a different event, not Las Vegas 2024.
So, I tap it again and see this. The "Loading" text appears for ages, but occasionally does show the full list, as before:
I select "Edinburgh & Glasgow 2024-02" which is 1d away, and I see this again:
So, I resign myself to hoping it'll just figure itself out, and I tap "Done":
"17w 6d" again :(
I finish customising, and exit the customisation screen. I show the Lock Screen, and I see this:
Why doesn't this work?
Here's the code:
@main
struct WidgetExtensionBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
WidgetExtension()
}
}
struct WidgetExtension: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kWidgetKind, intent: WidgetEventIntent.self, provider: WidgetEventTimelineProvider()) { entry in
WidgetEntry(entry: entry)
.environment(modelData)
}
.configurationDisplayName(NSLocalizedString("AddingWidget_Title", comment: "Adding the Widget"))
.description(NSLocalizedString("AddingWidget_Description", comment: "Adding the Widget"))
.supportedFamilies([.accessoryCircular, .accessoryInline, .accessoryRectangular, .systemSmall, .systemMedium])
.contentMarginsDisabled()
}
}
struct WidgetEventIntent: WidgetConfigurationIntent {
static let title: LocalizedStringResource = "AddingWidget_Title"
static let description = IntentDescription(LocalizedStringResource("AddingWidget_Description"))
@Parameter(title: LocalizedStringResource("Event"))
var event: WidgetEventDetails?
init(event: WidgetEventDetails? = nil) {
self.event = event
}
init() {}
static var parameterSummary: some ParameterSummary {
Summary {
\.$event
}
}
}
struct EventQuery: EntityQuery, Sendable {
func entities(for identifiers: [WidgetEventDetails.ID]) async throws -> [WidgetEventDetails] {
modelData.availableEvents.filter { identifiers.contains($0.id) } // availableEvents is just [WidgetEventDetails]
}
func suggestedEntities() async throws -> [WidgetEventDetails] {
return modelData.availableEvents.filter { $0.type == kEventTypeStandard }
}
}
If you think it's the TimelineProvider causing it, I can provide that code, too.
Hi all. I've spent six hours today trying to resize images from the Photos Library on the iPhone Simulator, but no matter what I try - and I've tried everything on StackOverflow - I always get a white line at the bottom of the image, even when trying the suggestions that specifically say "this one even gets rid of the white line!"
This is just one of the many attempts:
let image: UIImage = <whatever>
let size = CGSize(width: 800, height: 600)
guard let data = resizeImage(image: image, size: size, scale: 1).jpegData(compressionQuality: 0.75) ?? image.pngData() else {
return false
}
func resizeImage(image: UIImage, size: CGSize, scale: CGFloat) -> UIImage {
let availableRect = AVFoundation.AVMakeRect(aspectRatio: image.size, insideRect: .init(origin: .zero, size: maxSize))
let targetSize = availableRect.size
let format = UIGraphicsImageRendererFormat()
format.scale = scale
let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
let resized = renderer.image { (context) in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
return resized
}
I've read that image.draw(in:) adds that white line because it's using non-integer values and the background of a JPEG is white, so I floor()d and round()ed the size values, but I always get the white line.
I just can't seem to get it to work. Does anyone have a working function that will resize one of the JPEG images included in the Simulator without putting that white line down there?
Note: The pink/purple flowers image always resizes and comes back upside down, and the only difference I can see in the six images included in the Simulator is that that one is HEIF while all the others are JPEG. I'll also need any function to handle HEIF/HEIC, too, I guess...
This is on Xcode 15.0.1 (15A507) with Simulator iOS 17.0.1. I've also tried it on a real device (iPhone 15 Pro Max, iOS 17.4) and the same thing happens, so it's not like the Simulator images are banjaxed in some way.
Hey all. I've been rewriting my Objective-C app in SwiftUI (it's going well, thanks!) and I'm hitting this issue that I thought I'd solved ages ago.
I have version 5.1 of my original ObjC app using version 11 of the Core Data model. I've created a new version 12, added a couple of new attributes, and created a mapping model from 11 to 12.
The fields I've added are booleans, and I've set them to have a default value of NO so that new entries will automatically get NO, but there are existing records that don't have that field at all, and I'd like them to also get NO (or YES, depending on something in the other fields, but I haven't figured out how to do that yet).
This is how Core Data is set up in the new Swift target:
public extension URL {
// Returns a URL for the given app group and database pointing to the sqlite database
static func storeURL(for appGroup: String, databaseName: String) -> URL {
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?.appending(path: kCoreDataFolder) else {
logger.error("Shared file container could not be created.")
fatalError("CoreData: Shared file container could not be created.")
}
return fileContainer.appendingPathComponent("\(kCoreDataModel)")
}
}
// MARK: - Persistence Controller
struct CoreData {
static let shared = CoreData()
let container: NSPersistentContainer
init() {
let storeURL = URL.storeURL(for: kAppGroup, databaseName: kCoreDataModel)
let storeDescription = NSPersistentStoreDescription(url: storeURL)
container = NSPersistentContainer(name: kCoreDataModel)
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
logger.error("Failed to initialise Managed Object Model from url: \(storeURL), with error: \(error), \(error.userInfo)")
fatalError("CoreData: Failed to initialise Managed Object Model from url: \(storeURL), with error: \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
When I run the old app on the Simulator it adds some demo data (using model v11), and the app functions properly. If I then install the new version - using v12 - it fails with a Core Data error because it couldn't migrate the data. (Apologies, I cannot get the exact error at the moment as I'm part-way through redoing some other stuff and the app won't build.)
I've read somewhere that "When we use the NSPersistentContainer class to create and manage the Core Data stack, we don't need to do any additional setup work, the lightweight migration is automatically activated for us." But if that's the case, why does my app crash? I'll try and get the exact error for you...
How do I implement lightweight migration in Swift? This page suggests creating a persistent coordinator and adding a couple of options, but I can't quite figure out how to do that with the code I already have, and each time I try it seems to beat the original store so I keep going back to the above code because it works.
I have a List which acts like a form where you can enter values. It uses a custom View struct SectionArea to separate the various sections and cut down on code duplication, like this:
List {
SectionArea(title: "Section One", height: 176, horizontalPadding: 0, roundCorners: (0, 0, 0, 0)) {
VStack {
Button {
print("Pressed Button One")
fullScreenViewChoice = .optionOne
showSubView.toggle()
} label: {
HStack {
Text("Button One")
Spacer()
Image(systemName: "chevron.right")
}
}
.frame(height: 40)
Divider()
Button {
print("Pressed Button Two")
fullScreenViewChoice = .optionTwo
showSubView.toggle()
} label: {
HStack {
Text("Button Two")
Spacer()
Image(systemName: "chevron.right")
}
}
.frame(height: 40)
Divider()
}
}
}
.listStyle(.plain)
.listRowSpacing(0)
It works fine, but regardless of which button I press it always acts as though both buttons have been pressed. Say I press Button One, the console will display:
Pressed Button One
Pressed Button Two
Can someone tell me where I'm going wrong here, please? Thanks!
EDIT: Actually, it doesn't look like it's because of the SectionArea struct, because I've taken the code from there and wrapped it around the content directly, rather than using that struct, and it still does it.
I've removed everything and just put this:
List {
VStack {
ButtonOne
ButtonTwo
}
}
It still presses both buttons.
I have an iOS app that's written in Objective-C, uses Core Data, and has a number of SwiftUI targets (Widgets, Watch app). I'd like to convert the main app to SwiftUI and keep access to the data in the Core Data stack, but move to SwiftData immediately. Since I'm doing a lot of rewriting, it makes sense to leap ahead rather than have to rewrite it in a year or two. Effort isn't an issue; I'm a tenacious SOB ;)
But I have no idea how to do this, and can't find any examples on the net of this particular scenario. All I can find is how to start using SwiftData instead of Core Data in an app that's already written in Swift.
So, how do I go about the migration without losing data?
I guess I'll need to add a new Swift/SwiftUI target for the main app, but then how do I migrate the Core Data store over? In ObjC there's a lot of messing with stacks and the actual location of the model in the filesystem, but I doubt this is necessary in the new way of doing things?
Any help would be appreciated. Thanks!
I have a countdown/up timer in a widget, and I want to format the timer to something more readable than the default that Text.init(myDate, style: .timer) provides.
The default outputs a timer for just the hours in the date range. So, for example, a timer of 1 week, 5 hours, 12 minutes and 45 seconds will appear as 173:12:45 (which is 7 * 24 + 5 = 173) - not very user-friendly.
An ideal output would be 1 week 05:12:45. Is there any way of doing that?
I've tried a number of different ways using TimeInterval, DateInterval, and modding (%) values - like modding the hours count by 168 to get a number of weeks - but they're pretty much useless when there are never a set number of days or weeks in a year due to leap years.
It would be great if you would provide actual code examples rather than saying to use a certain API, as I could very easily go down a rabbit hole like I have with TimeInterval and DateInterval. Thanks!
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.
I want to add a large widget to my app, but it doesn't make sense to simply expand the information that's displayed in a small or medium widget to fill up the space of a large widget.
Let's say I have a bunch of events in my app, and the user can add a small/medium widget to their Home Screen and choose which one of their events the widget shows. This is fine and works well, but for a large widget I want to have a totally different experience.
I want to display the next four upcoming events in a list. These events are set by the order they're going to occur (date-wise) so you can't really pick which four you want to display, so I don't want to be able to edit the widget. However, if I add such a widget you can still hold down and edit the widget to select an event.
Also - and I'm not sure why it does this - when you hold down and edit the widget, it displays the title and description that are assigned to my small/medium AppIntentConfiguration widget, and not the title and description that are provided to the large StaticConfiguration widget, i.e.:
struct WidgetExtension: Widget // Small/medium dynamic widget
{
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: kWidgetKind,
intent: WidgetEventIntent.self,
provider: WidgetEventTimelineProvider()
) { entry in
WidgetEntry(entry: entry)
}
.configurationDisplayName(smallMediumTitle). // This and the line below...
.description(NSLocalizedString(smallMediumDescription) // are seen when editing the large widget
.supportedFamilies([.systemSmall, .systemMedium])
}
}
struct WidgetExtension_Large: Widget // Large static widget
{
var body: some WidgetConfiguration {
StaticConfiguration(
kind: kWidgetKind,
provider: WidgetEventTimelineProvider_Large()
) { entry in
WidgetEntry_Large(entry: entry)
}
.configurationDisplayName(largeTitle)
.description(largeDescription)
.supportedFamilies([.systemLarge])
}
}
If it's not possible to have a non-editable widget, it might be more fun to let the user select four events themselves, so I'd need to change the large widget to an AppIntentConfiguration widget, but what would the four parameters look like?
Currently, I have this for the small/medium widget that lets you select one event:
@Parameter(title: "Event")
var event: WidgetEventDetails?
init(event: WidgetEventDetails? = nil) {
self.event = event
}
init() {
}
static var parameterSummary: some ParameterSummary {
Summary {
\.$event
}
}
How would I change it to select four events? It might be helpful to only be able to select a second event if a first one has been chosen, and a third if a first & second have been chosen etc. Any ideas? Thanks.
Just finalising some work on my app update, and it seems that when you go to select an item to show in a complication, when you select your app in the list, the subsequent list only shows 15 of your items.
If a user of my app has transferred 20 items to their Watch, they can't select five of them to be shown in a complication. Is that right?
If that's a hard limit then I need to be able to separate them out into bunches of 15 items, or maybe have them display under A-E, F-J etc.
Does this have to be done as a separate Widget in the WidgetBundle? And how do I do that? Given that I currently have one widget in that bundle that should show everything (20 items), how would I split it out to show an "A-E" widget with those items beginning with A...E? Do I have to have an A-E widget with its own set of data?
Here's my WidgetConfigurationIntent:
WidgetEventDetails is a struct conforming to AppEntity, Identifiable, Hashable. It contains all the data needed to show in a widget.
Here's my placeholder, snapshot, timeline etc.:
When I go to add a widget to my iPhone Home Screen I choose my app, and the small and medium widgets are displayed, and they use the data from the placeholder function correctly, i.e. a random set of data (an 'event') is returned and seen in the widget picker.
I select a widget and it appears on the Home Screen, but it's now just a placeholder view, and it stays like that forever. If I hold down and edit the widget, there's no selected event, as in, I might've picked "Christmas" from the widget picker but when it gets added to the Home Screen that link is lost.
So, I edit the widget and go to choose any event, and this appears for ages:
When it finally displays the list of events I pick one and the widget looks this forever:
I can't see what I'm doing wrong. Any ideas? If you need any more of the code, please just ask. I'm getting really frustrated with this stuff and just want it to work.
I get so far with it, it all goes well, then something happens and it just breaks. And it's not my coding as I'm using git and can can go back to previous commits where stuff was working, only to find it doesn't. I'm glad iOS 17 now has this "State of Mind" logging 'cos it shows exactly how I feel developing for iOS! 🥸