Post

Replies

Boosts

Views

Activity

How do you get the attachment image from a local notification on the Watch?
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.
2
0
1.5k
Sep ’22
Content blocking a specific class?
I have a content blocker working fine, but have noticed some websites are putting a class in the main body tag, and removing it when the user taps to accept/decline cookies. For example: <body class="homepage ContentPage language-en modal-open no-overflow"> ... <div id="cookieNotice">...</div> My content blocker can remove the cookieNotice div completely using: div[id="cookieNotice"], but the page doesn't scroll because the body tag includes modal-open no-overflow. Can a content blocker remove values/classes from a tag? If so, what would the XPath look like? Thanks.
0
0
612
Apr ’23
Complications only allow you to select from 15 items?
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?
2
0
909
Oct ’23
Timers in widgets, how to calculate correct time
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.
2
0
1k
Nov ’23
How to calculate differences between dates for a widget timer
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!
0
0
772
Jan ’24
Convert ObjC+CoreData to Swift/SwiftUI+SwiftData?
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!
0
0
773
Feb ’24
How to handle long press on a Text() in iOS26
In iOS 18 the following code works to set a state variable when you hold your finger on the Text() field (well, the ScrollView()), but it doesn't work in iOS 26: @State private var pressed: Bool = false ... ScrollView { VStack { Text("Some text goes here") }.frame(maxWidth: .infinity) } .onTapGesture {} // This is required to allow the long press gesture to be recognised .gesture( DragGesture(minimumDistance: 0) .onChanged({ _ in pressed = true }) .onEnded({ _ in pressed = false }) ) .background(pressed ? .black.opacity(0.4) : .clear) I've tried changing this to: var dragGesture: some Gesture { DragGesture(minimumDistance: 0) .onChanged({ _ in self.pressed = true }) .onEnded({ _ in self.pressed = false }) } ... ScrollView { VStack { Text("Some text goes here") }.frame(maxWidth: .infinity) } .gesture(dragGesture) .background(pressed ? .black.opacity(0.4) : .clear) And this: var longPress: some Gesture { LongPressGesture(minimumDuration: 0.25) .onChanged({ _ in self.pressed = true }) .onEnded({ _ in self.pressed = false }) } ... ScrollView { VStack { Text("Some text goes here") }.frame(maxWidth: .infinity) } .gesture(longPress) .background(pressed ? .black.opacity(0.4) : .clear) Neither works. Any ideas? Thanks.
1
0
85
Sep ’25
SwiftUI state is maddening
I honestly thought I was getting somewhere with this, but alas, no. Every time I do anything in my List of ItemRows it jumps back to the top. Here's the setup: DataService.swift: final class DataService { static let shared = DataService() private init() {} let coreData: CoreData = CoreData() let modelData: ModelData = ModelData() } ModelData.swift: @Observable class ModelData: ObservableObject { var allItems: [ItemDetails] var standardItems: [ItemDetails] var archivedItems: [ItemDetails] init() { allItems = [] standardItems = [] archivedItems = [] } func getInitialData() { // Get all items, then split them into archived and non-archived sets, because you can't use `.filter` in a view... allItems = dataService.coreData.getAllItems() standardItems.append(contentsOf: allItems.filter { !$0.archived }) archivedItems.append(contentsOf: allItems.filter { $0.archived }) } } MainApp.swift: // Get access to the data; this singleton is a global as non-view-based functions, including the `Scene`, need to access the model data let dataService: DataService = DataService.shared @main struct MainApp: App { // Should this be @ObservedObject or @StateObject? @ObservedObject private var modelData: ModelData = dataService.modelData // I would use @StateObject if the line was... //@StateObject private var modelData: ModelData = ModelData() // right? // But then I couldn't use modelData outside of the view hierarchy var body: some Scene { WindowGroup { ZStack { MainView() .environment(modelData) } } .onAppear { modelData.getInitialData() } } } MainView.swift: struct MainView: View { @Environment(ModelData.self) private var modelData: ModelData var body: some View { ... ForEach(modelData.standardItems) { item in ItemRow(item) } ForEach(modelData.archivedItems) { item in ItemRow(item) } } } ItemRow.swift: struct ItemRow: View { @Environment(\.accessibilityDifferentiateWithoutColor) private var accessibilityDifferentiateWithoutColor var item: ItemDetails @State private var showDeleteConfirmation: Bool = false var body: some View { // Construct the row view // `accessibilityDifferentiateWithoutColor` is used within the row to change colours if DWC is enabled, e.g. use different symbols instead of different colours for button images. // Add the .leftSwipeButtons, .rightSwipeButtons, and .contextMenu // Add the .confirmationDialog for when I want to ask for confirmation before deleting an item } } Now, the problems: Swipe an item row, tap one of the buttons, e.g. edit, and the list refreshes and jumps back to the top. In the console I see: ItemRow: @self, @identity, _accessibilityDifferentiateWithoutColor changed. Why did accessibilityDifferentiateWithoutColor change? The setting in Settings > Accessibility > Display & Text Size has not been changed, so why does the row's view think it changed? With a .confirmationDialog attached to the end of the ItemRow (as seen in the code above), if I swipe and tap the delete button the list refreshes and jumps back to the top again. In the console I see: ItemRow: @self, @identity, _accessibilityDifferentiateWithoutColor, _showDeleteConfirmation changed. Right, it changed for the one row that I tapped the button for. Why does every row get redrawn? I already had to shift from using the colorScheme environment variable to add new asset colours with light and dark variants to cover this, but you can't do that with DWC. Honestly, managing state in SwiftUI is a nightmare. I had zero problems until iOS 26 started removing one or two rows when I scrolled, and the fix for that - using @Statebject/@ObservedObject - has introduced multiple further annoying, mind-bending problems, and necessitated massive daily refactorings. And, of course, plenty of my time islost trying to figure out where a problem is in the code because "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"...
7
0
233
Oct ’25