Post

Replies

Boosts

Views

Activity

Reply to BGContinuedProcessingTask launchHandler invocation
Thanks Kevin! Moving performCountWork into the launchHandler (instead of before register) fixed it, simply because the call to set the totalUnitCount did not happen because currentBackgroundContinuedProcessingTask is nil at that time. Makes sense why it was indeterminate, it was never given a total! 😄 performCountWork still doesn't have to be called in the launchHandler, as long as I have the data to be able to set the total unit count when the task gets created at any time during the already in-flight operation, that approach works too. If I did that though, that brings up a question, what if the operation completes before the launchHandler is invoked (if I were to use the queue strategy)? A task would start for work that's already completed and thus wouldn't get progress updates and setTaskCompleted wouldn't be invoked. It seems much more straightforward to use the fail strategy and perform the work in the launchHandler (and if it the submit request fails perform the work without a BGContinuedProcessingTask as we do on iOS 18), at least for my simple use case. The only odd thing I'm seeing now is it animates indeterminately until completedUnitCount is set to a new value. In this example, that happens after just 1 second, so the switch from indeterminate to determinate looks janky. I set total to 100 and completed to 0 when the task starts, but it animates indeterminately until completedUnitCount is set to 1. If I set completed to 1 from the beginning, it animates indeterminately until completed is set to 2 (even if it gets set to 1 again). Any ideas to prevent the indeterminate state always show determinate progress?
Sep ’25
Reply to BGContinuedProcessingTask launchHandler invocation
I'm not sure what's going on, but I think this is a bug in your app, probably caused by this: [line with Task.sleep] Unless I've misread something, that means you're trying to update the UI and progress display 100x per second and the entire process completed in 1s The UILabel text and task's progress is updated once every 1 billion nanoseconds (1 second). This code sleeps for 1s to simulate a long running process that updates progress once per second and takes 100 seconds to complete. If you place a print statement after line 70, it is executed 100 times total after 1 minute and 40 seconds. I'd have to look at progress implementation, but it's possible/likely that it has some logic in it that's keeping it indeterminate because you're not on screen very long and the value is constantly changing. The indeterminate progress bar is visible (from the get-go and throughout the operation) even when the app stays in the foreground the whole time on iPad. There are details I could argue about in a "real" application I would be interested to hear! I do plan to put this in a real app with a slightly different long-running operation ha (notably you're manipulating UIKit from a background thread) I don't believe that's the case, setting breakpoints before the while block, in it, and after indicates it's on com.apple.main-thread.
Sep ’25
Reply to BGContinuedProcessingTask launchHandler invocation
the launchHandler is there to "tell" you the task has started and provide you the BGTask object you use to manage that work. No task type actually requires the work to occur inside the task. Thanks Kevin! So I need to think about this a little differently then. Instead of performing the work once you get a BGContinuedProcessingTask, just begin performing the work (the exact same way you do in iOS 18), if a BGContinuedProcessingTask starts persist it and update it as the work completes, and reset state when the work finishes or gets canceled. Basically, something like this: 1-6 I put together code to demonstrate this approach. Did I understand correctly, this should be all good with this simple counting demo? class ViewController: UIViewController { private let taskIdentifier = "\(Bundle.main.bundleIdentifier!).count_task" private let countToNumber: Int64 = 100 private var currentBackgroundContinuedProcessingTask: BGContinuedProcessingTask? private var shouldStopCounting = false @IBOutlet var progressLabel: UILabel! @IBAction func startCountingTapped(_ sender: UIButton) { performCountWork() // Register to support background continued processing // Ensure this is only registered once otherwise app crashes with exception: Launch handler for task with identifier has already been registered if !(UIApplication.shared.delegate as! AppDelegate).didRegisterCountBackgroundContinuedTask { (UIApplication.shared.delegate as! AppDelegate).didRegisterCountBackgroundContinuedTask = true BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: .main) { task in guard let task = task as? BGContinuedProcessingTask else { return } // The background continued processing task has started, use this task to manage the ongoing work self.currentBackgroundContinuedProcessingTask = task task.expirationHandler = { self.shouldStopCounting = true } } } // Request a background continued processing task start let request = BGContinuedProcessingTaskRequest( identifier: taskIdentifier, title: "Demo Task", subtitle: progressText(completedUnitCount: 0, totalUnitCount: countToNumber) ) request.strategy = .fail // Start the task immediately and fail if it cannot do { try BGTaskScheduler.shared.submit(request) } catch { // This should approximately never happen because the app doesn't submit any more tasks to exceed the system limit // But no big deal if it does not start, the user will just have to keep the app open to make progress print("Failed to submit task: \(error.localizedDescription)") } } @IBAction func stopCountingTapped(_ sender: UIButton) { shouldStopCounting = true } private func performCountWork() { Task { var countedToNumber: Int64 = 0 progressLabel.text = progressText(completedUnitCount: countedToNumber, totalUnitCount: countToNumber) currentBackgroundContinuedProcessingTask?.progress.completedUnitCount = countedToNumber currentBackgroundContinuedProcessingTask?.progress.totalUnitCount = Int64(countToNumber) while !shouldStopCounting && countedToNumber < countToNumber { try! await Task.sleep(nanoseconds: 1_000_000_000) countedToNumber += 1 progressLabel.text = progressText(completedUnitCount: countedToNumber, totalUnitCount: countToNumber) if let task = currentBackgroundContinuedProcessingTask { task.progress.completedUnitCount = countedToNumber task.updateTitle(task.title, subtitle: progressText(completedUnitCount: countedToNumber, totalUnitCount: countToNumber)) } } progressLabel.text = shouldStopCounting ? "Canceled" : "Success!" currentBackgroundContinuedProcessingTask?.setTaskCompleted(success: !shouldStopCounting) currentBackgroundContinuedProcessingTask = nil shouldStopCounting = false } } private func progressText(completedUnitCount: Int64, totalUnitCount: Int64) -> String { return "\(completedUnitCount) / \(totalUnitCount)" } } This seems to work, minus some behavior I did not expect: The Live Activity appears upon Start Counting while the app is in the foreground, I expected it to not appear until the user backgrounds the app. I'm using iPad Pro 26.0 (23A341). But on iPhone Air with 26.1 (23B5044l) the Live Activity does not appear until I close the app. Perhaps a bug that got resolved? The circular progress bar is spinning indeterminately in the Live Activity, not filling the circle as a percentage of completion (on both devices). the two strategies are "fail" (the default) and "queue" BGContinuedProcessingTaskRequest.strategy docs state queue is the default 😅
Sep ’25
Reply to SwiftUI's List backed by CoreData using @FetchRequest fails to update on iOS 26 when compiled with Xcode 26
I’m experiencing this issue with @FetchRequest in my app as well (using LazyVGrid instead of List). The above sample code replicates on iOS 26 beta 8 and 9 using the latest Xcode beta, though it seems to not replicate the first time you run the app only subsequent runs fail to update the UI upon changing a managed object a second time. I filed FB20029616 with a sysdiagnose and screen recording from when it replicated in my app.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’25
Reply to How to reduce cell height (vertical margins) when using UIListContentConfiguration
Thanks, I should note I want to reduce the vertical margins in the cell as opposed to setting an absolute fixed height dimension, in order to properly support dynamic type text sizes. This is the compositional layout I'm using: let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment) }
Topic: UI Frameworks SubTopic: UIKit Tags:
Aug ’25
Reply to MKReverseGeocodingRequest and CNPostalAddress from MKMapItem
I would try to see if you can make MKAddressRepresentations work for your needs, and if you could present the customization options to your customers based that line up with what that class offers I'll pursue this route, thanks! Existing customers will need to change their settings to choose one of the predetermined formats that most closely matches their use case. As folks reach out to report what they can't achieve anymore, I can file feedback reports requesting more formats be added to MKAddressRepresentations. The only thing I'm struggling to come up with is a friendly name to show in the UI for selecting the predetermined format of "city with context" both the short and full variants. Best I have is City, City and State, or City and State and Region - the word "state" is probably not appropriate everywhere. 🤔 it is very easy to naively construct geographically incorrect place descriptions ... we want to discourage folks from doing these string concatenation techniques, and instead rely on the framework to provide you with the correctly formatted information through MKAddressRepresentations Makes sense! With the approach to select specific date components, I took inspiration from DateFormatter.setLocalizedDateFormatFromTemplate(_:) where you specify which components you want and you'll get an appropriately formatted localized string. I thought I achieved that fairly well by preserving the order of components in the postal address just filtering out undesired ones, but can certainly imagine there could be scenarios where the resulting string doesn't really work for different locations around the world. I did file FB8648023 (Template API desired to format addresses with different components, similar to DateFormatter.setLocalizedDateFormatFromTemplate) hoping to replace my solution with a native API, but maybe that's infeasible given address complexity?
Aug ’25
Reply to MKReverseGeocodingRequest and CNPostalAddress from MKMapItem
Hi Ed! The use case in my app is to get the address for where a photo was taken. The user selects which address component(s) they want included. So I look at each attribute from the full attributed address to see if the CNPostalAddressPropertyAttribute is a component the user selected if so I add it to an array. Once enumeration is complete I join the array with commas to separate the components. For examples, if they selected zip it would be just 92101, or if they selected street and city and state it’d be 3835 N Harbor Dr, San Diego, CA. (The selected attributes aren’t guaranteed to be in the final string and that is acknowledged.)
Aug ’25