Been at this for ages now, getting nowhere, thanks to Swift and the betas...
The iOS app schedules a local notification that has a userInfo dictionary, and one small JPEG image attachment.
Objective-C in iOS app:
content.attachments = @[[UNNotificationAttachment attachmentWithIdentifier:myIdentifier URL:[imageURL filePathURL] options:@{UNNotificationAttachmentOptionsTypeHintKey : UTTypeJPEG} error:&error]];
This works fine. The notification is correctly scheduled.
If I ignore the Watch and let the notification appear on my phone's Lock Screen, the image is there.
Going back to the Watch. The Watch app receives the notification, and the didReceive method is called in the NotificationController.
No matter what I try, I can't get the image from the notification.
NotificationController.swift: (image is sent to the NotificationView to use as the background.)
guard let attachment = notification.request.content.attachments.first
else {
print("Couldn't get the first attachment, using default")
image = Image.init(kDefaultImage)
return
}
// We get here, so we know there's an attachment
if attachment.url.startAccessingSecurityScopedResource() {
let imageData = try? Data.init(contentsOf: attachment.url)
if let imageData = imageData {
image = Image(uiImage: UIImage(data: imageData) ?? UIImage.init(imageLiteralResourceName: kDefaultImageMasked))
}
attachment.url.stopAccessingSecurityScopedResource()
} else {
// << I ALWAYS HIT THIS BIT >>
print("Couldn't access the file, using default")
image = Image.init(kDefaultImageMasked)
}
I always get told I can't access the file in the security scoped bit.
If I take out that check I get Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value because the code doesn't put anything into image. Obviously I would put the default image in there, but once the Watch app crashes the notification that's shown on the Watch shows the correct image, but it's obviously not using my NotificationView because that crashed.
How the hell do I get the image from the attachment? This was simple to do in the old WatchKit extension stuff, look:
NSArray *attachments = notification.request.content.attachments;
if(attachments.count == 1) {
[_groupAll setBackgroundImage:attachments[0]];
}
No problems there; it always worked.
Thanks.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
... all talking about customer care numbers.
Maybe Apple should make these Dev Forums just for signed-in Developers? If a Developer spams, they can be blocked. But if just anyone can register and post, they can spam, get banned, then start a new account.
Or, Apple should implement some way of blocking the spam before it gets posted.
I really don't understand what these spammers think they're getting out of doing this? Who is going to look through the forums and think, "Oh, a customer care number for something I've never heard of? I should phone that immediately!" Really does prove that spammers are dumb.
I have an app with Home Screen widgets, and new Lock Screen widgets, and I'm trying to get my complications to work on the Watch.
The widgets are running off dynamic intents, taking a list of items from user defaults and providing that list for the user to choose when they add a Home Screen or Lock Screen widget. This works fine.
In the "Complications and widgets: Reloaded" video from WWDC2022 at about 7:00 the guy tells you to duplicate the widget extension, rename it, and set it to run on watchOS. I ended up with these targets:
Main App (embeds Widget, Intents Handler and Watch App)
Widget
Intents Handler
Watch App (embeds Complications)
Complications (new, copy of Widget)
At about 8:30 he adds the supported families for the Watch to his EmojiRangersWidget WidgetConfiguration, so it looks like the code for the accessory widgets is used for both Lock Screen widgets and complications?
Should I be putting all my complications views in the Widget target, something like this?
@main
struct WidgetEntry: Widget
{
public var body: some WidgetConfiguration {
IntentConfiguration(kind: kWidgetKind,
intent: DynamicItemSelectionIntent.self,
provider: Provider()
) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("abc")
.description("def")
#if os(watchOS)
.supportedFamilies([.accessoryCircular, .accessoryInline, .accessoryRectangular, .accessoryCorner])
#else
.supportedFamilies([.accessoryCircular, .accessoryInline, .accessoryRectangular, .systemSmall, .systemMedium])
#endif
}
}
struct WidgetEntryView: View
{
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
@ViewBuilder
var body: some View {
#if os(watchOS)
switch family {
case .accessoryCircular, .accessoryCorner, .accessoryInline, .accessoryRectangular:
ComplicationView(item: entry.item)
@unknown default:
UnknownComplicationView(item: entry.item)
}
#else
switch family {
case .accessoryCircular, .accessoryInline, .accessoryRectangular:
LockScreenWidgetView(item: entry.item)
case .systemSmall:
SmallWidgetView(item: entry.item)
case .systemMedium, .systemLarge, .systemExtraLarge:
MediumWidgetView(item: entry.item)
@unknown default:
UnknownWidgetView(item: entry.item)
}
#endif
}
}
I've previously been told by an Apple engineer on these forums: "The complication code should be in the watch app target. That's where watchOS 7 and 8 will look for complications, and where watchOS 9 will look for old ClockKit complications for migration to their WidgetKit counterparts." I'm no longer supporting watchOS 7/8, but does this mean that watchOS 7/8 will look for old complications in there to migrate to the new stuff into the Widget target? I asked a couple of follow-up questions but they never got answered ¯\_(ツ)_/¯
Apple make this stuff so unnecessarily complex, even though their videos make it look so easy. How many times have we all paused their videos to see exactly what code they're writing and where they're putting it? There's practically zero help out there - these forums are full of questions and few answers. Xcode should have much better documentation and help to guide you through this. It takes so long to get anything done because there just isn't the information we need.
My app lets you create a list of items and pick one as the main item. For Home Screen widgets there are two bits of text you can use in the panel that appears when you want to add the widget:
public var body: some WidgetConfiguration {
IntentConfiguration(kind: "myWidgetKind", intent: DynamicSelectionIntent.self, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("Title")
.description("Description")
So, for a Home Screen widget the panel displays the title and description, and the preview shows the main item. Once you've added the widget you can edit it and pick a different item, so the title is "Display an Item" and the description is quite general, telling you what the widget can display.
The title and description are also displayed in the panel when you want to add a Lock Screen widget:
For the inline Lock Screen widget, you see "Title".
For rectangular and circular widgets below the clock, you see the usual Home Screen panel, so both "Title" and "Description".
There's no way of editing the item that sits behind the Lock Screen widget once you've added it, so the general text needs to be more specific and refer to the main item.
How do you give different title and description text when you're adding a Lock Screen widget?
Let's say you have a Text.init(Date().advanced(by: 5), style: .timer). It's set to countdown to a date 5 seconds from now (just for simplicity's sake).
It goes like this:
Countdown starts, and shows 0:05.
After 1 second it shows 0:04.
After 2 seconds it shows 0:03.
After 3 seconds it shows 0:02.
After 4 seconds it shows 0:01.
After 5 seconds it shows 0:00. Countdown done.
After 6 seconds it should show 0:01, right? It doesn't; it shows 0:00.
After 7 seconds it shows 0:01, even though only 6 seconds have passed.
It's almost as though the timer is being restarted once it hits zero, but that isn't right. There aren't "two seconds at 0:00" (that's not how time works...).
Anyone seeing this, or is just me?
In the first image you can see my app at the top of the list, and Apple's Weather app widget. Both are showing the SF Symbol rendered correctly.
In the second image the inline widget chosen is Apple's Weather app. The SF Symbol is correctly shown.
In the third image my app's inline widget has been chosen, but the image is not rendered correctly; it's just a block.
Is there something I have to do to my image to have it rendered correctly?
Hi all. I have a timer working in a view on the Watch app, but I just can't get them working in widgets. Can you use timers in Home Screen & Lock Screen widgets? I can't find anything that says you can't...
Take this code:
struct ScratchPadView: View {
@State var backgroundGradient: LinearGradient = gradientOne
let gradientTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
let date: Date = getDateFromString("20220828 10:00")
ZStack {
backgroundGradient
.onReceive(gradientTimer) { _ in
self.backgroundGradient = (date >= Date()) ? gradientOne : gradientTwo
}
}
}
}
All this is supposed to do is change the gradient in the background from gradientOne to gradientTwo when the current date is after the date supplied. Doesn't work. The date can come and go and the gradient never changes from what it was initially set up to use: gradientOne.
Like I say, this works fine in a Watch app View, but just doesn't work in a widget.
Is the only alternative to provide a timeline entry for that date so the widget refreshes and notices that the date has now passed?
In my Watch app, the WatchDelegate class is the WCSession delegate and handles transferring data with the iOS app.
When the delegate receives some data it stores it in the UserDefaults.
When the Watch app is launched it reads the existing data stored in the defaults and creates the view.
I have a WatchApp file which contains this:
@main
struct WatchApp: App {
var body: some Scene {
WindowGroup {
if(getItems().count == 0) {
NoEventsView()
} else {
ItemsListView(available: getItems())
}
}
}
}
As you can see, if there are no events in the defaults it shows the NoEventsView; and if there are some it shows the EventsListView.
When the Watch delegate receives a change in the events, I need to refresh this view. The delegate can receive zero or more events.
How on Earth do I do that?
In iOS I could call a method to reload a table of data, or post a notification to another view controller to do that, but in the Watch and with SwiftUI there doesn't seem to be any obvious way of refreshing a view.
Is there any way of telling the App struct to refresh, or a particular view? For example, if I extracted the if statement into its own "struct WhichView: View", could I tell that to refresh?
I've read a LOT on the net these past few days on @State vars, @ObservedObject, @Published etc, but nothing I've seen works, or is far too weird and complex for my situation.
I literally just want WatchApp or WhichView to redraw when I tell it to. How do I do that?
Thanks.