I have two @Observable manager classes, which share a reference to a third class. I initialize this setup using a custom init in my App struct, like so:
@main
struct MyApp: App {
private let managerA: ManagerA
private let managerB: ManagerB
init() {
let managerC = ManagerC()
self.managerA = ManagerA(managerC: managerC)
self.managerB = ManagerB(managerC: managerC)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(managerA)
.environment(managerB)
}
}
}
I've been using this pattern for some time and it has been working fine. However, I just today discovered that @Observable objects are supposed to be initialized as @State vars, as shown in Apple's documentation here. This means I shoud be doing the following:
@main
struct MyApp: App {
@State private var managerA: ManagerA
@State private var managerB: ManagerB
init() {
let managerC = ManagerC()
self.managerA = ManagerA(managerC: managerC)
self.managerB = ManagerB(managerC: managerC)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(managerA)
.environment(managerB)
}
}
}
I've also seen some examples where the @State vars are initialized manually like this:
@main
struct MyApp: App {
@State private var managerA: ManagerA
@State private var managerB: ManagerB
init() {
let managerC = ManagerC()
let managerA = ManagerA(managerC: managerC)
let managerB = ManagerB(managerC: managerC)
self._managerA = State(initialValue: managerA)
self._managerB = State(initialValue: managerB)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(managerA)
.environment(managerB)
}
}
}
ChatGPT tells me the third approach is the correct one, but I don't understand why and ChatGPT can't produce a convincing explanation. The compiler doesn't produce any errors or warnings under each approach, and as far as I can tell, they all behave identically with no discernible difference in performance.
Does it matter which pattern I use? Is there a "correct" way?
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
Hello all, my team and I are looking for some advice on updating our app from using NavigationView and NavigationLink to NavigationStack and .navigationDestination now that NavigationView is deprecated.
A little background about our situation, our app is a mix of SwiftUI views and UIKit view controllers. We are slowly migrating towards primarily SwiftUI and we are at the point now where our main app entry point is SwiftUI. UIKit is now mainly just used for some legacy screens inside the app, but majority of our navigation is using SwiftUI with NavigationLinks.
I have spent a couple weeks on trying to migrate to using NavigationStack + .navigationDestination, but every time I do I keep running into issues. From what I understand, there seems to be two competing approaches for modern navigation. Those two approaches are...
Having a more global navigationDestination modifier defined at the root of each tab that essentially supports navigating to all pages. I have seen this referred to as a 'router'.
Applying a navigationDestination modifier on each page that navigates somewhere. This seems to be more 1-to-1 port of how we are currently using NavigationLink.
However, I tried implementing both of these solutions in our app and with both of them I ran into countless issues which made me second guess the solution I was currently implementing in favor of the other. This has led to where I am now, where I am really unsure what the recommended approach is.
I would love to hear from you all, what you have had the most success with. I am interested to hear what approach you chose, why you chose that, and then also some downsides to the approach you chose. Thanks in advance.
Topic:
UI Frameworks
SubTopic:
SwiftUI
Let's say you have a NavigationModel that contains three NavigationPaths, one for each option in the sidebar for a NavigationSplitView.
This NavigationModel is created by the App and passed down to the root view for the scene in its environment.
Each option has a separate NavigationStack and is passed a binding to the appropriate NavigationPath from the NavigationModel.
Is it expected that when the user changes the selection in the sidebar, the NavigationPath for the newly selected view should be erased?
This is what's currently happening on macOS 26. It seems like the default action when creating a NavigationStack and passing it a binding to a NavigationPath is to clear that path and start from the root view.
Is this normal, intended behaviour or is it a bug? Or, perhaps, an option or modifier I am missing?
I'm working on an iOS document-based app. It uses ReferenceFileDocument and custom creation of documents via DocumentGroupLaunchScene + NewDocumentButton. It works fine when I use the plain NewDocumentButton("Whatever") (without any more arguments), but when I want to perform additional setup via preapreDocumentURL or even just add a contentType it gives such output in the console when I hit it:
Content serialization failed, document won't be saved.
UTType.replayable is correctly wired up in the plist.
It looks like a bug in the SDK, but maybe there is a chance that I'm doing something wrong?
Here's a code:
import SwiftUI
import UniformTypeIdentifiers
import Combine
@main
struct MyApp: App {
var body: some Scene {
DocumentGroup {
Document()
} editor: { documentConfiguration in
EmptyView()
}
DocumentGroupLaunchScene("Yoyo") {
NewDocumentButton(contentType: .replayable) {
return URL(string: "whatever, it doesnt even go there...")!
}
}
}
}
final class Document: ReferenceFileDocument {
static var readableContentTypes: [UTType] { [.replayable] }
@Published var x = 0
init() {}
init(configuration: ReadConfiguration) throws {}
func snapshot(contentType: UTType) throws -> Data {
Data()
}
func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper {
.init(regularFileWithContents: snapshot)
}
}
extension UTType {
static var replayable: UTType {
UTType(exportedAs: "com.whatever.yo")
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Tags:
Files and Storage
File Provider
SwiftUI
Uniform Type Identifiers
From what I understand, you’re meant to pass information like a view model to a content view through its configuration state. What about callbacks (e.g. something like didClickButton)? They aren’t “inputs” into the content configuration like a view model would be, but it feels odd to create and apply a content configuration only to specify callbacks and nothing else.
hello, I have been receiving crash reports on iPadOS 26.1, When UITrackingElementWindowController viewDidDisappear
The feedback associated with this post is: FB20986398 and Exception
Exception
'Cannot remove an observer <PKTextEffectsWindowObserver 0x10854cbe0> for the key path "frame" from <UITextEffectsWindow 0x10827ca00> because it is not registered as an observer.'
#1 0x0000000183529814 in objc_exception_throw ()
#2 0x00000001845065a4 in -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:] ()
#3 0x00000001845069c8 in -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] ()
#4 0x00000001845068e0 in -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:context:] ()
#5 0x00000001cb22e894 in -[PKTextEffectsWindowObserver dealloc] ()
#6 0x000000018beafb28 in _setInteractionView ()
#7 0x000000018d81e8b8 in -[UIView(Dragging) removeInteraction:] ()
#8 0x00000001cb216448 in -[PKTextInputInteraction willMoveToView:] ()
#9 0x000000018beafb1c in _setInteractionView ()
#10 0x000000018d81e8b8 in -[UIView(Dragging) removeInteraction:] ()
#11 0x000000018d5ab094 in -[UIEditingOverlayViewController _removeInteractions] ()
#12 0x000000018cb166a8 in -[UIViewController _setViewAppearState:isAnimating:] ()
#13 0x000000018cb16d70 in __52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke_2 ()
#14 0x000000018cb16c10 in __52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke ()
#15 0x000000018655ef78 in __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ ()
#16 0x00000001866b4a24 in -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] ()
#17 0x000000018cb16a44 in -[UIViewController _setViewAppearState:isAnimating:] ()
#18 0x000000018cb1753c in -[UIViewController __viewDidDisappear:] ()
#19 0x000000018cb17638 in -[UIViewController _endAppearanceTransition:] ()
#20 0x000000018ca2401c in __48-[UIPresentationController transitionDidFinish:]_block_invoke ()
#21 0x000000018ca23cd0 in -[UIPresentationController transitionDidFinish:] ()
#22 0x000000018ca2d720 in -[_UICurrentContextPresentationController transitionDidFinish:] ()
#23 0x000000018ca27608 in __77-[UIPresentationController runTransitionForCurrentStateAnimated:handoffData:]_block_invoke.106 ()
#24 0x000000018cb31fec in -[_UIViewControllerTransitionContext completeTransition:] ()
#25 0x000000018d7f09bc in -[UITransitionView notifyDidCompleteTransition:] ()
#26 0x000000018d7f0750 in -[UITransitionView _didCompleteTransition:] ()
#27 0x000000018bf1c2a4 in __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__ ()
#28 0x000000018d817960 in -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] ()
#29 0x000000018d7f7168 in -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] ()
#30 0x000000018d7f75cc in -[UIViewAnimationState animationDidStop:finished:] ()
#31 0x000000018d7f763c in -[UIViewAnimationState animationDidStop:finished:] ()
#32 0x0000000186fedda4 in run_animation_callbacks ()
#33 0x000000010365e2d0 in _dispatch_client_callout ()
#34 0x000000010367f4c0 in _dispatch_main_queue_drain.cold.5 ()
#35 0x0000000103654778 in _dispatch_main_queue_drain ()
#36 0x00000001036546b4 in _dispatch_main_queue_callback_4CF ()
#37 0x00000001865b42c8 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#38 0x0000000186567b3c in __CFRunLoopRun ()
#39 0x0000000186566a6c in _CFRunLoopRunSpecificWithOptions ()
#40 0x0000000226ee5498 in GSEventRunModal ()
#41 0x000000018bf2aba4 in -[UIApplication _run] ()
#42 0x000000018bed3a78 in UIApplicationMain ()
Description:
I’m encountering an issue where the Apple Watch’s watchOS version is lower than the deployment target specified in my Xcode project.
For example, my Watch device is running watchOS 10.6, but my app’s deployment target is set to watchOS 9.6 or 10.6, and Xcode shows an error stating:
Error: “watchOS version doesn’t match the app’s deployment target.”
Could someone clarify how to properly handle this version mismatch?
Environment:
Xcode 26
iPhone: iOS 18
Apple Watch: watchOS 10.6
Any guidance or best practices would be appreciated.
Crash in UIKeyboardStateManager when repeatedly switching text input focus in WKWebView (hybrid app)
We’re building a hybrid iOS app using Angular (web) rendered inside a WKWebView, hosted by a native Swift app.
Recently, we encountered a crash related to UIKeyboardStateManager in UIKit when switching between text inputs continuously within an Angular screen.
Scenario
The screen contains several text input fields.
A “Next” button focuses the next input field programmatically.
After about 61 continuous input field changes, the app crashes.
It seems like this may be related to UIKit’s internal keyboard management while switching focus rapidly inside a WebView.
crash stack:
Crashed: com.apple.main-thread
0 WebKit 0xfbdad0 <redacted> + 236
1 UIKitCore 0x10b0548 -[UITextInteractionSelectableInputDelegate _moveToStartOfLine:withHistory:] + 96
2 UIKitCore 0xd0fb38 -[UIKBInputDelegateManager _moveToStartOfLine:withHistory:] + 188
3 UIKitCore 0xa16174 __158-[_UIKeyboardStateManager handleMoveCursorToStartOfLine:beforePublicKeyCommands:testOnly:savedHistory:force:canHandleSelectableInputDelegateCommand:keyEvent:]_block_invoke + 52
4 UIKitCore 0xa36ae4 -[_UIKeyboardStateManager performBlockWithTextInputChangesIgnoredForNonMacOS:] + 48
5 UIKitCore 0xa160f0 -[_UIKeyboardStateManager handleMoveCursorToStartOfLine:beforePublicKeyCommands:testOnly:savedHistory:force:canHandleSelectableInputDelegateCommand:keyEvent:] + 440
6 UIKitCore 0xa07010 -[_UIKeyboardStateManager handleKeyCommand:repeatOkay:options:] + 5760
7 UIKitCore 0xa2fb64 -[_UIKeyboardStateManager _handleKeyCommandCommon:options:] + 76
8 UIKitCore 0xa2fb08 -[_UIKeyboardStateManager _handleKeyCommand:] + 20
9 UIKitCore 0xa30684 -[_UIKeyboardStateManager handleKeyEvent:executionContext:] + 2464
10 UIKitCore 0xa2f95c __42-[_UIKeyboardStateManager handleKeyEvent:]_block_invoke + 40
11 UIKitCore 0x4b9460 -[UIKeyboardTaskEntry execute:] + 208
12 UIKitCore 0x4b92f4 -[UIKeyboardTaskQueue continueExecutionOnMainThread] + 356
13 UIKitCore 0x4b8be0 -[UIKeyboardTaskQueue addTask:breadcrumb:] + 120
14 UIKitCore 0x4a9ed0 -[_UIKeyboardStateManager _setupDelegate:delegateSame:hardwareKeyboardStateChanged:endingInputSessionIdentifier:force:delayEndInputSession:] + 3388
15 UIKitCore 0xfa290 -[_UIKeyboardStateManager setDelegate:force:delayEndInputSession:] + 628
16 UIKitCore 0xf617c -[UIKeyboardSceneDelegate _reloadInputViewsForKeyWindowSceneResponder:force:fromBecomeFirstResponder:] + 1140
17 UIKitCore 0xf5c88 -[UIKeyboardSceneDelegate _reloadInputViewsForResponder:force:fromBecomeFirstResponder:] + 88
18 UIKitCore 0x4fe4ac -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] + 84
19 WebKit 0xfbe708 <redacted> + 100
20 WebKit 0xfbf594 <redacted> + 340
21 WebKit 0x8a33d8 <redacted> + 32
22 WebKit 0x8cee04 <redacted> + 144
23 WebKit 0x1c83f0 <redacted> + 22692
24 WebKit 0x73f40 <redacted> + 264
25 WebKit 0x162c7c <redacted> + 40
26 WebKit 0x1623b4 <redacted> + 1608
27 WebKit 0x73298 <redacted> + 268
28 WebKit 0x72e48 <redacted> + 660
29 JavaScriptCore 0xdb00 WTF::RunLoop::performWork() + 524
30 JavaScriptCore 0xd744 WTF::RunLoop::performWork(void*) + 36
31 CoreFoundation 0xf92c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
32 CoreFoundation 0xf744 __CFRunLoopDoSource0 + 172
33 CoreFoundation 0xf5a0 __CFRunLoopDoSources0 + 232
34 CoreFoundation 0xff20 __CFRunLoopRun + 840
35 CoreFoundation 0x11adc CFRunLoopRunSpecific + 572
36 GraphicsServices 0x1454 GSEventRunModal + 168
37 UIKitCore 0x135274 -[UIApplication _run] + 816
38 UIKitCore 0x100a28 UIApplicationMain + 336
39 APP1 0xa2ed0 main + 21 (AppDelegate.swift:21)
40 ??? 0x1aa889f08 (シンボルが不足しています)
From reviewing the crash log, it appears that the crash occurs inside UIKeyboardStateManager while handling keyboard or cursor updates.
Questions
Has anyone seen this specific crash pattern involving UIKeyboardStateManager?
Are there known UIKit or WebKit bugs related to UIKeyboardStateManager when continuously changing focus between text fields (especially in WKWebView)?
Any insights or workarounds would be greatly appreciated.
Thanks!
With iOS 26.1 and beta 26.2, there is a crash seen for _UIButtonBarItemLayout update. Has anyone experienced this crash or has any information on this
Thread 0 name:
Thread 0 Crashed:
0 libobjc.A.dylib 0x000000019daa144c objc_retain + 16
1 UIKitCore 0x00000001a680b184 -[_UIButtonBarItemLayout _updateItemView] + 360
2 UIKitCore 0x00000001a6d5ddcc -[_UIButtonBarItemLayout minimumLayoutWidthGivenMinimumSpaceWidth:] + 28
3 UIKitCore 0x00000001a6d5eeec -[_UIButtonBarItemGroupLayout recalculateLayoutWidthsGivenItemSpaceWidth:] + 304
4 UIKitCore 0x00000001a6d4ca28 -[_UIButtonBar _widthInfoForLayout:] + 188
5 UIKitCore 0x00000001a68093b4 -[_UIButtonBar _layoutBar] + 108
6 UIKitCore 0x00000001a6809328 __42-[_UIButtonBarStackView updateConstraints]_block_invoke + 44
7 UIKitCore 0x00000001a7dbea8c +[UIView(Animation) performWithoutAnimation:] + 76
8 UIKitCore 0x00000001a68092d0 -[_UIButtonBarStackView updateConstraints] + 112
9 UIKitCore 0x00000001a64707e8 -[UIView(AdditionalLayoutSupport) _previousFittingSizeInfo] + 784
10 UIKitCore 0x00000001a6470508 -[UIView(AdditionalLayoutSupport) _previousFittingSizeInfo] + 48
Topic:
UI Frameworks
SubTopic:
UIKit
I have a UIViewController that uses MKMapview to display the motion history trajectory. Repeatedly entering and exiting UIViewController will cause a crash, and the crash stack is as follows:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x000000014bfc0fc8
Exception Codes: 0x0000000000000001, 0x000000014bfc0fc8
VM Region Info: 0x14bfc0fc8 is not in any region. Bytes after previous region: 217033 Bytes before following region: 61496
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
VM_ALLOCATE 14bf88000-14bf8c000 [ 16K] rw-/rwx SM=PRV
---> GAP OF 0x44000 BYTES
VM_ALLOCATE 14bfd0000-14bfd4000 [ 16K] rw-/rwx SM=PRV
Termination Reason: SIGNAL 11 Segmentation fault: 11
Terminating Process: exc handler [1881]
Triggered by Thread: 8
Thread 8 name: Dispatch queue: com.apple.root.background-qos
Thread 8 Crashed:
0 CoreFoundation 0x19e36ac40 CFRelease + 44
1 VectorKit 0x1ce16af6c md::TileGroupNotificationManager::~TileGroupNotificationManager() + 132
2 VectorKit 0x1cd6f7178 <deduplicated_symbol> + 76
3 VectorKit 0x1cdba8d74 -[VKSharedResources .cxx_destruct] + 32
4 libobjc.A.dylib 0x19b3321f8 object_cxxDestructFromClass(objc_object*, objc_class*) + 116
5 libobjc.A.dylib 0x19b32df20 objc_destructInstance_nonnull_realized(objc_object*) + 76
6 libobjc.A.dylib 0x19b32d4a4 _objc_rootDealloc + 72
7 VectorKit 0x1cdba93fc -[VKSharedResources dealloc] + 476
8 VectorKit 0x1cdafa3fc -[VKSharedResourcesManager _removeResourceUser] + 68
9 VectorKit 0x1cdafa380 +[VKSharedResourcesManager removeResourceUser] + 44
10 VectorKit 0x1cdafa2fc __37-[VKIconManager _internalIconManager]_block_invoke + 168
11 libdispatch.dylib 0x1d645b7ec _dispatch_client_callout + 16
12 libdispatch.dylib 0x1d6446664 _dispatch_continuation_pop + 596
13 libdispatch.dylib 0x1d6459528 _dispatch_source_latch_and_call + 396
14 libdispatch.dylib 0x1d64581fc _dispatch_source_invoke + 844
15 libdispatch.dylib 0x1d6453f48 _dispatch_root_queue_drain + 364
16 libdispatch.dylib 0x1d64546fc _dispatch_worker_thread2 + 180
17 libsystem_pthread.dylib 0x1f9b7e37c _pthread_wqthread + 232
18 libsystem_pthread.dylib 0x1f9b7d8c0 start_wqthread + 8
I have checked the code and did not find any issues. I have also tested on iOS 15, 16, and 18 without any issues. Could this be an error in the iOS 26 system? Have you ever met any friends? I hope to receive an answer. Thank you.
I'm experiencing an issue where my List selection (using EditButton) gets cleared when a confirmationDialog is presented, making it impossible to delete the selected items.
Environment:
Xcode 16.0.1
Swift 5
iOS 18 (targeting iOS 17+)
Issue Description:
When I select items in a List using EditButton and tap a delete button that shows a confirmationDialog, the selection is cleared as soon as the dialog appears. This prevents me from accessing the selected items to delete them.
Code:
State variables:
@State var itemsSelection = Set<Item>()
@State private var showDeleteConfirmation = false
List with selection:
List(currentItems, id: \.self, selection: $itemsSelection) { item in
NavigationLink(value: item) {
ItemListView(item: item)
}
}
.navigationDestination(for: Item.self) { item in
ItemViewDetail(item: item)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
EditButton()
}
}
Delete button with confirmation:
Button {
if itemsSelection.count > 1 {
showDeleteConfirmation = true
} else {
deleteItemsSelected()
}
} label: {
Image(systemName: "trash.fill")
.font(.system(size: 12))
.foregroundStyle(Color.red)
}
.padding(8)
.confirmationDialog(
"Delete?",
isPresented: $showDeleteConfirmation,
titleVisibility: .visible
) {
Button("Delete", role: .destructive) {
deleteItemsSelected()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Going to delete: \(itemsSelection.count) items?")
}
Expected Behavior:
The selected items should remain selected when the confirmationDialog appears, allowing me to delete them after confirmation.
Actual Behavior:
As soon as showDeleteConfirmation becomes true and the dialog appears, itemsSelection becomes empty (count = 0), making it impossible to delete the selected items.
What I've Tried:
Moving the confirmationDialog to different view levels
Checking if this is related to the NavigationLink interaction
Has anyone encountered this issue? Is there a workaround to preserve the selection when showing a confirmation dialog?
Hi Everyone,
I’m encountering a layout issue in SwiftUI starting with iOS 26 / Xcode 26 (also reproduced on iPadOS 26). I would like to see whether anyone else has seen the same behaviour, and if there’s any known workaround or fix from Apple.
Reproduction
Minimal code:
struct ContentView: View {
@State private var inputText: String = ""
var body: some View {
TextField("", text: $inputText)
.font(.system(size: 14, weight: .semibold))
.background(Capsule().foregroundStyle(.gray))
}
}
Environment:
Xcode: 26.1 (17B55)
Devices / Simulators: iOS 26.0, iOS 26.0.1, iOS 26.1 on iPhone 17 Pro Max; iPadOS 26 on iPad.
OS: iOS 26 / iPadOS 26
Observed behaviour
When the TextField is tapped for the first time (focus enters), its height suddenly decreases compared to the unfocused state.
After this initial focus-tap, subsequent unfocus/focus cycles preserve the height (i.e., height remains “normal” and stable).
Prior to iOS 26 (i.e., in older iOS versions) I did not observe this behaviour — same code worked fine.
I have not explicitly set .frame(height:) or extra padding, and I’m relying on the default intrinsic height + the Capsule background.
Work-arounds
Adding .frame(height: X) (fixed height) removes the issue (height stable).
Thanks in advance for any feedback!
[Submitted as FB20978913]
When using .navigationTransition(.zoom) with a fullScreenCover, deleting the source item from the destination view causes a brief black flash during the dismiss animation. This is only visible in Light mode.
REPRO STEPS
Build and run the sample code below.
Set the device to Light mode.
Tap any row to open its detail view.
In the detail view, tap Delete.
Watch the dismiss animation as the list updates.
EXPECTED
The zoom transition should return smoothly to the list with no dark or black flash.
ACTUAL
A visible black flash appears over the deleted row during the collapse animation. It starts black, shortens, and fades out in sync with the row-collapse motion. The flash lasts about five frames and is consistently visible in Light mode.
NOTES
Occurs only when deleting from the presented detail view.
Does not occur when deleting directly from the list.
Does not occur or is not visible in Dark mode.
Reproducible on both simulator and device.
Removing .navigationTransition(.zoom) or using .sheet instead of .fullScreenCover avoids the issue.
SYSTEM INFO
Version 26.1 (17B55)
iOS 26.1
Devices: iPhone 17 Pro simulator, iPhone 13 Pro hardware
Appearance: Light
Reproducible 100% of the time
SAMPLE CODE
import SwiftUI
struct ContentView: View {
@State private var items = (0..<20).map { Item(id: $0, title: "Item \($0)") }
@State private var selectedItem: Item?
@Namespace private var ns
var body: some View {
NavigationStack {
List {
ForEach(items) { item in
Button {
selectedItem = item
} label: {
HStack {
Text(item.title)
Spacer()
}
.padding(.vertical, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.matchedTransitionSource(id: item.id, in: ns)
.swipeActions {
Button(role: .destructive) {
delete(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
.listStyle(.plain)
.navigationTitle("Row Delete Issue")
.navigationSubtitle("In Light mode, tap item then tap Delete to see black flash")
.fullScreenCover(item: $selectedItem) { item in
DetailView(item: item, ns: ns) {
delete(item)
selectedItem = nil
}
}
}
}
private func delete(_ item: Item) {
withAnimation {
items.removeAll { $0.id == item.id }
}
}
}
struct DetailView: View {
let item: Item
let ns: Namespace.ID
let onDelete: () -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
VStack(spacing: 30) {
Text(item.title)
Button("Delete", role: .destructive, action: onDelete)
}
.navigationTitle("Detail")
.toolbar {
Button("Close") { dismiss() }
}
}
.navigationTransition(.zoom(sourceID: item.id, in: ns))
}
}
struct Item: Identifiable, Hashable {
let id: Int
let title: String
}
SCREEN RECORDING
In my AppKit app I have a an outline view where each cell has a title (NSTextField), image view (NSImageView), and 4 NSButtons that are essentially static badges (their .bezelStyle is set to .badge). I’ve seeing very low performance when doing quick reloads in successions, for example when filtering data. My app is built on macOS Tahoe.
Profiling the app in instruments I can see that each initialization takes more than 70ms. Does this make sense?
Thanks!
Topic:
UI Frameworks
SubTopic:
AppKit
The code below is a test to trigger UI updates every 30 seconds. I'm trying to keep most work off main and only push to main once I have the string (which is cached). Why is updating SwiftUI 30 times per second so expensive? This code causes 10% CPU on my M4 Mac, but comment out the following line:
Text(model.timeString)
and it's 0% CPU. The reason why I think I have too much work on main is because of this from instruments. But I'm no instruments expert.
import SwiftUI
import UniformTypeIdentifiers
@main
struct RapidUIUpdateTestApp: App {
var body: some Scene {
DocumentGroup(newDocument: RapidUIUpdateTestDocument()) { file in
ContentView(document: file.$document)
}
}
}
struct ContentView: View {
@Binding var document: RapidUIUpdateTestDocument
@State private var model = PlayerModel()
var body: some View {
VStack(spacing: 16) {
Text(model.timeString) // only this changes
.font(.system(size: 44, weight: .semibold, design: .monospaced))
.transaction { $0.animation = nil } // no implicit animations
HStack {
Button(model.running ? "Pause" : "Play") {
model.running ? model.pause() : model.start()
}
Button("Reset") { model.seek(0) }
Stepper("FPS: \(Int(model.fps))", value: $model.fps, in: 10...120, step: 1)
.onChange(of: model.fps) { _, _ in model.applyFPS() }
}
}
.padding()
.onAppear { model.start() }
.onDisappear { model.stop() }
}
}
@Observable
final class PlayerModel {
// Publish ONE value to minimize invalidations
var timeString: String = "0.000 s"
var fps: Double = 30
var running = false
private var formatter: NumberFormatter = {
let f = NumberFormatter()
f.minimumFractionDigits = 3
f.maximumFractionDigits = 3
return f
}()
@ObservationIgnored private let q = DispatchQueue(label: "tc.timer", qos: .userInteractive)
@ObservationIgnored private var timer: DispatchSourceTimer?
@ObservationIgnored private var startHost: UInt64 = 0
@ObservationIgnored private var pausedAt: Double = 0
@ObservationIgnored private var lastFrame: Int = -1
// cache timebase once
private static let secsPerTick: Double = {
var info = mach_timebase_info_data_t()
mach_timebase_info(&info)
return Double(info.numer) / Double(info.denom) / 1_000_000_000.0
}()
func start() {
guard timer == nil else { running = true; return }
let desiredUIFPS: Double = 30 // or 60, 24, etc.
let periodNs = UInt64(1_000_000_000 / desiredUIFPS)
running = true
startHost = mach_absolute_time()
let t = DispatchSource.makeTimerSource(queue: q)
// ~30 fps, with leeway to let the kernel coalesce wakeups
t.schedule(
deadline: .now(),
repeating: .nanoseconds(Int(periodNs)), // 33_333_333 ns ≈ 30 fps
leeway: .milliseconds(30) // allow coalescing
)
t.setEventHandler { [weak self] in self?.tick() }
timer = t
t.resume()
}
func pause() {
guard running else { return }
pausedAt = now()
running = false
}
func stop() {
timer?.cancel()
timer = nil
running = false
pausedAt = 0
lastFrame = -1
}
func seek(_ seconds: Double) {
pausedAt = max(0, seconds)
startHost = mach_absolute_time()
lastFrame = -1 // force next UI update
}
func applyFPS() { lastFrame = -1 } // next tick will refresh string
// MARK: - Tick on background queue
private func tick() {
let s = now()
let str = formatter.string(from: s as NSNumber) ?? String(format: "%.3f", s)
let display = "\(str) s"
DispatchQueue.main.async { [weak self] in
self?.timeString = display
}
}
private func now() -> Double {
guard running else { return pausedAt }
let delta = mach_absolute_time() &- startHost
return pausedAt + Double(delta) * Self.secsPerTick
}
}
nonisolated struct RapidUIUpdateTestDocument: FileDocument {
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
static let readableContentTypes = [
UTType(importedAs: "com.example.plain-text")
]
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
In our app, there is a UIWindow makeKeyAndVisible crash, and for now, it appears once, crash stack:
the crash detail:
crash.txt
in the RCWindowSceneManager class's makeWindowKeyAndVisible method, we check and set a window's windowScene and makeKeyAndVisible:
public func makeWindowKeyAndVisible(_ window: UIWindow?) {
guard let window else {
return
}
if let currentWindowScene {
if window.windowScene == nil || window.windowScene != currentWindowScene {
window.windowScene = currentWindowScene
}
window.makeKeyAndVisible()
}
}
and I set a break point at a normal no crash flow, the stack is:
why it crash? and how we avoid this, thank you.
The WatchOS app and view lifecycles for WatchKit and SwiftUI are documented in https://developer.apple.com/documentation/watchkit/working-with-the-watchos-app-life-cycle and https://developer.apple.com/documentation/swiftui/migrating-to-the-swiftui-life-cycle.
WatchOS 26 appears to change the app & view lifecycle from the behavior in WatchOS 11, and no longer matches the documented lifecycles.
On WatchOS 11, with a @WKApplicationDelegateAdaptor set, the following sequence of events would occur on app launch:
WKApplicationDelegate applicationDidFinishLaunching in WKApplicationState .inactive.
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .inactive.
View .onAppear @Environment(.scenePhase) .inactive
App onChange(of: @Environment(.scenePhase)): .active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: .@Environment(.scenePhase) .active
In WatchOS 26, this is now:
WKApplicationDelegate applicationDidFinishLaunching in WKApplicationState .inactive.
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .inactive.
App onChange(of: @Environment(.scenePhase)): .active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
View .onAppear @Environment(.scenePhase) .active
When resuming from the background in WatchOS 11:
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .background.
App onReceive(.willEnterForegroundNotification): WKApplicationState(rawValue: 2)
View .onChange of: .@Environment(.scenePhase) inactive
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: .@Environment(.scenePhase) active
The resume from background process in WatchOS 26 is baffling and seems like it must be a bug:
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .background.
App onReceive(.willEnterForegroundNotification): WKApplicationState(rawValue: 2)
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) active
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillResignActive in WKApplicationState .active.
App onReceive(.willResignActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) inactive
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) active
The app becomes active, then inactive, then active again.
The issues with these undocumented changes are:
It is undocumented.
If you relied on the previous process, this change can break your app.
A view no longer receives .onChange of: .@Environment(.scenePhase) .active state change during the launch process.
This bizarre applicationWillEnterForeground - applicationDidBecomeActive - applicationWillResignActive - applicationDidBecomeActive process on app resume does not match the documented process and is just...strange.
Is this new process what is intended? Is it a bug? Can an Apple engineer explain this new App resume from background process and why the View is created slightly later in the App launch process, so it does not receive the .onChange of @Environment(.scenePhase) message?
In contrast, the iOS 26 app lifecycle has not changed, and the iOS 18/26 app lifecycle closely follows the watchOS 11 app lifecycle (or watchOS 11 closely mimics iOS 18/26 with the exception that watchOS does not have a SceneDelegate).
Hi there! I'm making an app that stores data for the user's profile in SwiftData. I was originally going to use UserDefaults but I thought SwiftData could save Images natively but this is not true so I really could switch back to UserDefaults and save images as Data but I'd like to try to get this to work first. So essentially I have textfields and I save the values of them through a class allProfileData. Here's the code for that:
import SwiftData
import SwiftUI
@Model
class allProfileData {
var profileImageData: Data?
var email: String
var bio: String
var username: String
var profileImage: Image {
if let data = profileImageData,
let uiImage = UIImage(data: data) {
return Image(uiImage: uiImage)
} else {
return Image("DefaultProfile")
}
}
init(email:String, profileImageData: Data?, bio: String, username:String) {
self.profileImageData = profileImageData
self.email = email
self.bio = bio
self.username = username
}
}
To save this I create a new class (I think, I'm new) and save it through ModelContext
import SwiftUI
import SwiftData
struct CreateAccountView: View {
@Query var profiledata: [allProfileData]
@Environment(\.modelContext) private var modelContext
let newData = allProfileData(email: "", profileImageData: nil, bio: "", username: "")
var body: some View {
Button("Button") {
newData.email = email
modelContext.insert(newData)
try? modelContext.save()
print(newData.email)
}
}
}
To fetch the data, I originally thought that @Query would fetch that data but I saw that it fetches it asynchronously so I attempted to manually fetch it, but they both fetched nothing
import SwiftData
import SwiftUI
@Query var profiledata: [allProfileData]
@Environment(\.modelContext) private var modelContext
let fetchRequest = FetchDescriptor<allProfileData>()
let fetchedData = try? modelContext.fetch(fetchRequest)
print("Fetched count: \(fetchedData?.count ?? 0)")
if let imageData = profiledata.first?.profileImageData,
let uiImage = UIImage(data: imageData) {
profileImage = Image(uiImage: uiImage)
} else {
profileImage = Image("DefaultProfile")
}
No errors. Thanks in advance
Hello
I'm trying to use a TabView inside of the Sidebar in a NavigationSplitView.
I'm wanting to use .listStyle(.sidebar) in order to get the Liquid Glass effect.
However I can not find a way to remove the background of the TabView without changing the behavior of the TabView itself to paging with .tabViewStyle(.page)
NavigationSplitView {
TabView(
selection: .constant("List")
) {
Tab(value: "List") {
List {
Text("List")
}
}
}
} detail: {
}
Note: I wanting change the background of the TabView container itself. Not the TabBar.
Hey there,
I have an app that allows picking any folder via UIDocumentPickerViewController. Up until iOS18 users were able to pick folders from connected servers (servers connected in the Files app) as well.
On iOS26, the picker allows for browsing into the connected servers, but the Select button is greyed out and does nothing when tapped.
Is this a known issue? This breaks the whole premise of my file syncronization application.