Post

Replies

Boosts

Views

Activity

Unable to install iOS & watchOS app to iPhone, because of intents change
I've been happily building and deploying my app to my iPhone and Watch S8, and the app was ready to submit to App Store Connect last night. However, when archiving it I got an error saying that my DynamicEventSelectionIntent was in multiple extensions. It was, kind of. When I started working on the complications I copied the Widgets intents into the complications, and left the name the same, but they were not in multiple targets. It looks like the info plist only had one item in the IntentsSupported (because they're the same name), so I decided to rename them so I had a widget one and a complications one. The problem I have now is that I can't deploy to my iPhone and Watch anymore because I'm getting this error: This app contains a WatchKit app with one or more Siri Intents app extensions that declare IntentsSupported that are not declared in any of the companion app's Siri Intents app extensions. WatchKit Siri Intents extensions' IntentsSupported values must be a subset of the companion app's Siri Intents extensions' IntentsSupported values. All I've done is rename one intent, and locate every instance of it in the info plist files, and add the appropriate new one into the right places. Here's what I've got. Main App contains Widget and WidgetIntentHandler, plus Watch App, which contains Complications and ComplicationsIntentHandler. Target: Main app: (I've removed everything that has no bearing on extensions.) Target: Widget: Target: WidgetIntentHandler: Target: Watch App: Target: Complications: Target: ComplicationsIntentHandler: Please, can someone tell me what should and should not be in the various parts, as I've tried for 12 hours now and I cannot get this to deploy to my iPhone anymore :( Thanks.
12
1
2.3k
May ’25
Can't pin down how to show complications properly at different stages
I understand that complications in WidgetKit and watchOS 9 are different to the old ClockKit way, and I'm trying to populate the getSnapshot, getPlaceholder and getTimeline functions appropriately for the new method. I'm using dynamic intents, and I've got that working and providing a set of items I want the user to choose from, but at the various stages of selecting and adding a complication to the watch face, nothing I do seems to work properly on a device, and sometimes in the Simulator. On the Simulator: 1: The previews all use the same data, but getSnapshot() is supposed to return the data specific to that event from the configuration, i.e. if let theId = configuration.event?.identifier. "Christmas" is correct, but "Gallery Opening" is using Christmas's data. Are you only supposed to show ONE particular bit of data, like some dummy preview data or something? I can do that, but the complication picker then just looks really boring, with just the text below being different. I note that Apple's weather complications show different data. How did they do that? 2: You can see that once I pick an event it's displayed correctly in the Watch face edit screen: The "72d" circular complication is the "New York" event, and the rectangular one is correctly using the data for the "London Party!". 3: Once I've selected the events I want to use in those complications the edit screen shows them as totally blank, not even a placeholder. Which method gives us that preview? 4: Once I confirm the edits and return to the Watch face, the complications appear correctly: Note: This is all from the Simulator (apart from the image of Apple's Weather complications). This stuff barely works on a device. Half the time the complications are all placeholders, and half the time they're using the wrong data! If it works in the Simulator, it should work on the phone, or there is no point in giving us the Simulator if the results are different. So basically: Q1. How do I use different data in the complications picker? Q2. How do you get the previews to show when you're in edit mode? Q3. Does anyone know how to get print() statements to work when I'm running the Complications scheme? I could answer all these questions myself if I could output some debug info to the console, but all I see is the output from the Watch App target...
11
2
3.9k
Mar ’23
[WC] WCSession counterpart app not installed BUT IT IS!
Right, this is getting on my nerves now. iOS app installed on iPhone via Xcode. Watch app installed on Watch via Xcode. Both apps are running and are in the foreground. iOS app launches on iPhone and reports: WCSession.isSupported = YES theDelegate.session.isPaired = YES theDelegate.session.watchAppInstalled = NO theDelegate.session.activationState = Activated I press a button in the Watch app. It reports: session == activated and reachable iOS app delegate receives a message from the Watch app: didReceiveMessage (from Watch): message = {     action = giveMeUpdatedItems; } The apps must be installed on the devices in order for the Watch app to have used sendMessage (which is only available if the session is reachable, which it is). iOS app delegate passes that through as a notification to another bit of code that collates the info and sends it back to the Watch app. watchNotificationUpdateData; userInfo = {     action = giveMeUpdatedItems; } That bit of code in the iOS app checks whether we can send data to the Watch app, and doesn't send the data because: WCSession.isSupported = YES theDelegate.session.isPaired = YES theDelegate.session.watchAppInstalled = NO theDelegate.session.activationState = Activated If I remove the check for watchAppInstalled, I get this: Error sending Watch application context: Watch app is not installed. {     NSLocalizedDescription = "Watch app is not installed.";     NSLocalizedRecoverySuggestion = "Install the Watch app."; } I've deleted and reinstalled the app on both devices countless times. I've rebooted the devices, plus the Mac. I've reinstalled Xcode. I've cleaned builds. I've deleted DerivedData. And still it says the companion app isn't installed.
10
3
4.9k
Feb ’24
List jumps back to the top
Following on from this thread: https://developer.apple.com/forums/thread/805037 my list of items is now correctly maintaining state (no more disappearing rows), but I'm now hitting a really annoying issue: Every time something changes - even just changing the dark mode of the device - the entire list of items is refreshed, and the list jumps back to the top. A simple representation: // modelData.filteredItems is either all items or some items, depending on whether the user is searching List { ForEach(modelData.filteredItems) { item in ItemRow(item: item) } } When the user isn't searching, filteredItems has everything in it. When they turn on search, I filter and sort the data in place: // Called when the user turns on search, or when the searchString or searchType changes func sortAndFilterItemsInModelData() { modelData.filteredItems.removeAll() // Remove all items from the filtered array modelData.filteredItems.append(contentsOf: modelData.allItems) // Add all items back in let searchString: String = modelData.searchString.lowercased() switch(modelData.searchType) { case 1: // Remove all items from the filtered array that don't match the search string modelData.filteredItems.removeAll(where: { !$0.name.lowercased().contains(searchString) }) ... } // Sorting switch(modelData.sortKey) { case sortKeyDate: modelData.sortAscending ? modelData.filteredItems.sort { $0.date < $1.date } : modelData.filteredItems.sort { $0.date > $1.date } // Sorts in place ... } } The method doesn't return anything because all the actions are done in place on the data, and the view should display the contents of modelData.filteredItems. If you're searching and there are, say 10 items in the list and you're at the bottom of the list, then you change the search so there are now 11 items, it jumps back to the top rather than just adding the extra ItemRow to the bottom. Yes, the data is different, but it hasn't been replaced; it has been altered in place. The biggest issue here is that you can simply change the device to/from Dark Mode - which can happen automatically at a certain time of day - and you're thrown back to the top of the list. The array of data hasn't changed, but SwiftUI treats it as though it has. There's also a section in the List that can be expanded and contracted. It shows or hides items of a certain type. When I expand it, I expect the list to stay in the same place and just show the extra rows, but again, it jumps to the top. It's a really poor user experience. Am I doing something wrong (probably, yes), or is there some other way to retain the scroll position in a List? The internet suggests switching to a LazyVStack, but I lose left/right swipe buttons and the platform-specific styling. Thanks.
9
0
212
Oct ’25
watchOS 9.1 beta 2 crashing Watch faces?
I just got an Apple Watch Series 8, and I've updated it to the latest beta of watchOS 9.1. About 6 times a day I have to hard reboot the Watch because the Watch faces stop working. The screen goes black. I can access the app list, run apps etc., but the Watch face just isn't there anymore. Notification Centre and swipe up settings work fine; it's just the Watch faces. As I said, a reboot fixes it, but it just dies randomly afterwards. Is anyone else experiencing this with this beta? Given that I've got a new device and updated to new software, there are two variables to consider. I don't see how it can be a hardware issue because the screen works fine for everything else. If it's just this beta, then I can wait for the next one. Thanks.
7
0
1.7k
Oct ’22
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
232
Oct ’25
App Store Review rejecting app because it references upcoming software
I updated one of my apps with the released version of Xcode 14. The minimum deployment version in the project and target are both iOS 13 (iPadOS 13). I built and tested it with the Simulator from the released version of Xcode 14. I built and tested it on some real devices with the released version of Xcode 14. I archived it, validated it, and uploaded it to the App Store for review. My "What's New in the Version" text says, "Supports all iPads compatible with iPadOS 13-15." App Store Review are rejecting it because: "Your app or its metadata contains references to a pre-release version of Apple software, products, or hardware. Apps with compatibility references to a pre-release candidate version of an Apple operating system or pre-released Apple products or hardware are not in compliance with the Apple Developer Program License Agreement." or: "We are unable to approve your app because it was built for iPadOS only using a pre-release version of iPadOS 16. While your app may not support new features in iPadOS 16, we are only accepting iOS 16, iPadOS 15.7, and universal apps at this time. To resolve this issue, resubmit your app built for a released version of iOS or iPadOS." I checked, and there was a random provisioning profile that seems to have been put in by having Xcode 14 update a copy of the original project to the recommended project settings. That seems to be a bug. So, I removed that profile and built again. App Store Review are still rejecting it. How do I resolve this? I've searched the app, and there is nothing in there targeting iPadOS 16. Nothing at all. I'm minded to think that because I have the latest Xcode-beta installed on my Mac, that it's using/writing data to a shared location, and when building with the released version of Xcode 14, something is being contaminated in that way? Any ideas?
5
0
3.4k
Sep ’22
Refreshing widgets - policy and background tasks?
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!
5
0
1.1k
Dec ’24
I'm confused about WatchKit and WidgetKit and Watch Apps... Help!
Sorry for the length of this post, and all the questions. I have an iOS app written in Objective-C (too big to convert to Swift right now), and I successfully added a WatchKit app and WatchKit Extension some years ago. I also added Home Screen widgets when iOS 14 was released. With the iOS 16 betas I'd like to support Lock Screen widgets, and have also decided to move the WatchKit app/extension to SwiftUI. User journey: MyApp is launched and an item with an image is created. This item is stored in Core Data and its image is stored in a directory in the app's documents directory. A version of it is stored in NSUserDefaults in a shared app group. The user adds a Home Screen widget to show that item. Its data is pulled from the defaults, and the image is loaded from the document's directory. The user installs the Watch app and launches it. The Watch app looks inside the user defaults to retrieve the item. The iOS app sends the image to the Watch and the Watch app stores it locally (it's a small image, taking up barely a few Kb). This all works fine right now. Currently I have these targets: MyApp = main iOS app. MyApp WatchKit = Watch app storyboards and asset catalogs. MyApp WatchKit Extension = code to update the Watch interface. MyApp Widget = Home Screen widgets. MyApp IntentHandler = dynamic intents handler for the Home Screen widgets. Q1. Where do I put the code for the Lock Screen widgets? I figure these go into the My App Widget target because they're widgets and appear on the iPhone? In this video (https://developer.apple.com/videos/play/wwdc2022/10050) at 07:00 it tells you to duplicate the existing Widget target, change the bundle, change it to run on watchOS and embed it in your existing Watch App. As my original Watch App is written in Objective-C (MyApp WatchKit Extension, above) I can't/shouldn't do that, so... Q2. I think I have to create a new MyApp Watch App target, and perform the video steps on that target? I can create the views for that app, no problem. Most people update to the latest watchOS, and it's only now that watchOS 9 won't support Watch Series 3. Q3. Do I need to keep MyApp WatchKit and MyApp WatchKit Extension around? I can support older versions of watchOS if it helps my users, but they'll probably want to use the new version of watchOS, right? Can you install both versions of the app on your Watch (with watchOS 9), or does the new Swift app override the old WatchKit extension? Q4. Once I've designed the new Watch App's views in SwiftUI to replace the old WatchKit extension, where do I put the code for the complications that are being replaced? The WWDC 2022 videos (above, and a couple linked to on that page) have confused me a bit. Do I put complications views in the new MyApp Watch App target along with the other views that replace the old Watch app, or in MyApp Widget? Q5. The MyApp Widget target contains a bunch of code (WidgetUtils.swift) that populates the Home Screen widgets (and the new Lock Screen widgets), and it would fit right into the new MyApp Watch App target. Can I share that code between the two app targets just by adding WidgetUtils.swift to both target's membership? MyApp sends small images to the current MyApp WatchKit Extension. There's no code in the existing WidgetUtils.swift to handle file transfers because the images for the Home Screen widgets are pulled from the iOS app, so I need to write that in Swift for the new MyApp Watch App. The logic is already there in the old target, but I can't see any sort of equivalent to the WatchKit extension delegate where I currently handle the file transfers. Q6. Where does that go in MyApp Watch App? The existing MyApp WatchKit Extension occasionally asks MyApp for some new data. This is all done in the extension delegate which wakes up the iOS app and updates the NSUserDefaults which the extension then reads from. Q7. How do you do that in MyApp Watch App? Don't be afraid to be verbose in your responses. The more detail the better! Thank you in advance.
4
0
3.8k
Oct ’23
How to refresh a View?
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.
4
1
2.4k
Aug ’22
In-app purchases fail: "Password reuse" error?
I'm trying to implement my first in-app purchase, and I've created the IAP in App Store Connect, and created a sandbox account. When I open Settings > Developer in the iPhone Simulator, there is a "Sandbox Apple Account" option at the bottom. If I click the blue "Sign In" link I'm asked for the email and password, so I enter the correct credentials for the sandbox account. The "Sign In" text goes grey for a few seconds, then it goes blue again. It never changes to show that I'm signed in. Should it? (I think so.) Do I need to sign into the Simulator's Apple Account, too? (I don't think so.) Anyway, aside from that, in my app the IAP is listed and I have a button to purchase it. When I click it I'm asked for the email and password to sign into the Apple Account. I enter the correct sandbox email and password (they are definitely correct) and I see this in the Xcode console: Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=530 "(null)" UserInfo={client-environment-type=Sandbox, NSUnderlyingError=0x600000d0c7b0 {Error Domain=AMSErrorDomain Code=100 "Authentication Failed The authentication failed." UserInfo={NSMultipleUnderlyingErrorsKey=( "Error Domain=AMSErrorDomain Code=2 \"Password reuse not available for account The account state does not support password reuse.\" UserInfo={NSDebugDescription=Password reuse not available for account The account state does not support password reuse., AMSDescription=Password reuse not available for account, AMSFailureReason=The account state does not support password reuse.}", "Error Domain=AMSErrorDomain Code=0 \"Authentication Failed Encountered an unrecognized authentication failure.\" UserInfo={NSDebugDescription=Authentication Failed Encountered an unrecognized authentication failure., AMSDescription=Authentication Failed, AMSFailureReason=Encountered an unrecognized authentication failure.}" ), AMSDescription=Authentication Failed, NSDebugDescription=Authentication Failed The authentication failed., AMSFailureReason=The authentication failed.}}} Why is it talking about password reuse? AFAIK, I have only one sandbox account for this app (and none for any of my other apps), and this is the only one of my apps that has an IAP. Any ideas on how to get an IAP working? Thanks!
4
1
2.0k
Jan ’25
macOS 26 Tahoe beta 1 - Cannot login
A few days ago I installed beta 1 of macOS 26 Tahoe on a spare MacBook Pro 14-inch M4. Everything went well, and it looks okay (well, I don't like it at all, but hey-ho), and I shut it down. I started it up today, and it rejects my login password every time, then locks my account. When I click the link to "Restart and show password reset options" I'm asked for my Apple ID details, so I enter the correct email and password, then the MBP reboots and I'm back on the login screen with no indication that anything has changed. Guess I'm stuck now... FB18364657
4
1
642
Sep ’25
How to support foregroundColor (deprecated) and foregroundStyle in watchOS 9/10?
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?
3
2
3.5k
Feb ’25
How to resize a UIImage and get rid of the white line?
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.
3
0
1.6k
Mar ’24
How to use the new Text timer formats?
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
3
0
1.3k
Jul ’24