Provide views, controls, and layout structures for declaring your app's user interface using SwiftUI.

Posts under SwiftUI tag

200 Posts

Post

Replies

Boosts

Views

Activity

Best practice Replacement for Edit Button on iPhone Duo
On the iPhone Duo the standard "Edit" button for navigation bars is a text-based button, and text-based buttons are not pushed to the vertical toolbars on the Duo. This looks sometimes silly, if it is the only remaining button that can not be placed in the vertical toolbar and which has the result that an almost empty horizontal navigation bar must be kept visible as well. So I guess it would be good idea to have an icon-based replacement for the Editor button to avoid having two toolbars on the Duo. Is there a best practice for the icon to pick for such an edit button? Would be "square.and.pencil" (SF Symbols) a good choice? Or better the "pencil .circle", because the "square" one is often used for text-based input? Anything better?
1
0
571
1d
How to set custom height for keyboard extension without resize flicker?
Description I'm developing a custom keyboard extension using UIInputViewController and need to set a specific height of 268 points. The keyboard functions correctly, but there's a visible flicker and resize animation during launch that I cannot eliminate. The Problem When the keyboard launches, iOS provides incorrect heights before settling on the correct one. At launch, the view starts at 0×0. Around 295ms later, iOS sets the frame to 440×956 which is full screen height and wrong. Around 373ms, iOS changes it to 440×452 which is still wrong. Finally around 390ms, iOS settles at 440×268 which matches our constraint. This causes visible flicker as the view resizes three times rapidly. The keyboard appears to shrink from full screen down to the correct height, and users can clearly see this animation happening. What I've Tried I've tried adding a height constraint on self.view which gives me the correct height but causes the visible flicker. I created a custom UIInputView subclass and overrode intrinsicContentSize to return my desired height. iOS completely ignores this and gives random heights like 471pt, 680pt, or 956pt instead. I set allowsSelfSizing to true on my UIInputView subclass. iOS ignores this property. I set preferredContentSize on the view controller. iOS ignores this as well. I tried adding the constraint in viewDidAppear instead of viewDidLoad, thinking iOS might have settled by then. It still causes flicker. I overrode the frame and bounds setters on my UIInputView to clamp the height to my desired value. iOS bypasses these overrides somehow. I overrode layoutSubviews to force the correct height after the super call. iOS still applies its own height. Specific Question What is the correct API or technique to specify a keyboard extension's height that iOS will respect immediately upon launch, without triggering the resize animation sequence? Other third-party keyboards like Grammarly and SwiftKey appear to have solved this problem. Their keyboards appear at the correct height without any visible flicker. How do they achieve this? Expected Outcome The keyboard should appear at 268pt height on the first frame with no visible resize animation. Steps to Reproduce Create a new iOS App project in Xcode and add a Keyboard Extension target. In KeyboardViewController.swift, add a height constraint in viewDidLoad: override func viewDidLoad() { super.viewDidLoad() let heightConstraint = view.heightAnchor.constraint(equalToConstant: 268) heightConstraint.priority = .defaultHigh heightConstraint.isActive = true let label = UILabel() label.text = "Demo Keyboard" label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false view.addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), label.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } Build and run on a physical device. Enable the keyboard in Settings, then General, then Keyboard, then Keyboards. Open any app with a text field and switch to the custom keyboard using the globe button. Observe the height changing from around 956pt to 452pt to 268pt with visible animation. Environment iOS 17 and iOS 18 and 26.2, Xcode 16 and Xcode 26, affects all iPhone models tested, reproducible on both simulator and physical device.
2
5
1k
1d
visionOS : any non-nil preferredSurroundingsEffect enables a light-spill halo around VideoPlayerComponent video
Hi all — sharing a platform behavior I isolated while building a custom immersive video player, in case others hit it (and to ask whether it's intended). Finding On visionOS 27 (Xcode 27), an entity playing video via VideoPlayerComponent(videoRenderer:) gets a light-spill "screen glow" halo around its edges whenever the immersive space declares any non-nil preferredSurroundingsEffect. The halo follows the video's colors frame by frame. With no surroundings effect (nil), there is no halo. Quick repro Progressive immersive space + VideoPlayerComponent playing. Add .preferredSurroundingsEffect(.dim(intensity: 0.0)) → halo appears (even though the effect is visually a no-op). Remove it → halo disappears. Same with .colorMultiply(_:) and preset effects. Things I ruled out on device (Vision Pro) ImmersiveContentBrightness (.custom(1.0) vs .automatic): no difference. VideoPlayerComponent configuration (.stereo, spatial video mode, isPassthroughTintingEnabled) and component attach timing: no difference. Presenting the same video via VideoMaterial: no halo. Bright white SwiftUI content: no halo. Questions Is this coupling intended? The preferredSurroundingsEffect docs don't mention any effect on video playback. Is there a public way to keep a surroundings effect (e.g. room dimming) while avoiding the video light spill? I found none on VideoPlayerComponent. Current workaround: don't declare any surroundings effect in the modes where the halo is undesirable. Also filed via Feedback Assistant(FB25130748). Curious if anyone else has seen this or knows a switch.
0
1
458
1d
iOS 27: ScrollViewProxy.scrollTo no longer reaches unrealized LazyVStack rows; how to preserve position when prepending?
I have a chat screen built with ScrollView { LazyVStack } and ScrollViewReader. Two things that worked through iOS 26 stopped working on iOS 27 (Xcode 27, iOS 27.0 simulator and device; unchanged code still works on the iOS 26.x simulator): Open at the newest message. On appear I call proxy.scrollTo(lastId, anchor: .bottom). On iOS 27 the list stops two or three rows above the bottom when the rows have variable heights (text bubbles mixed with 200pt images). Load older messages at the top without the list jumping. When the user reaches the top, I prepend 25 older messages and call proxy.scrollTo(previousTopId, anchor: .top) so the row they were reading stays put. On iOS 27 the call does nothing: the list stays at the top of the newly inserted page (offset stays at 0), which immediately re-triggers the load. Re-issuing scrollTo on every layout change for a short window (which is what made this reliable on iOS 26) has no effect on iOS 27. Minimal reproduction import SwiftUI struct Message: Identifiable, Hashable { let id: Int let height: CGFloat // simulates text vs. image bubbles } @MainActor final class ChatModel: ObservableObject { @Published var messages: [Message] = [] @Published var previousTopId: Int? // set when a page is prepended private var nextOldId = 1_000 init() { messages = (0..<25).map { Message(id: nextOldId - $0, height: Self.randomHeight()) }.reversed() nextOldId -= 25 } func loadOlder() { let top = messages.first!.id let page = (0..<25).map { Message(id: nextOldId - $0, height: Self.randomHeight()) }.reversed() nextOldId -= 25 messages.insert(contentsOf: page, at: 0) previousTopId = top // "keep this row at the top" } static func randomHeight() -> CGFloat { [44, 60, 90, 200, 260].randomElement()! } } struct ChatView: View { @StateObject private var model = ChatModel() var body: some View { ScrollViewReader { proxy in ScrollView { LazyVStack(spacing: 8) { // top sentinel: load older when it becomes visible Color.clear.frame(height: 1) .onAppear { model.loadOlder() } ForEach(model.messages) { m in RoundedRectangle(cornerRadius: 12) .fill(m.height >= 200 ? .orange.opacity(0.4) : .blue.opacity(0.3)) .frame(height: m.height) .overlay(Text("\(m.id)")) .padding(.horizontal) .id(m.id) } } } .onAppear { // (1) open at the newest message DispatchQueue.main.async { proxy.scrollTo(model.messages.last!.id, anchor: .bottom) } } .onChange(of: model.previousTopId) { _, id in // (2) restore the row that was at the top before the prepend guard let id else { return } DispatchQueue.main.async { var t = Transaction(); t.disablesAnimations = true withTransaction(t) { proxy.scrollTo(id, anchor: .top) } } } } } } Observed (1) Open at the newest message, scrollTo(lastId, anchor: .bottom) iOS 26.x: lands on the last row. iOS 27.0: stops 2–3 rows above the bottom. (2) Prepend 25 rows, then scrollTo(previousTopId, anchor: .top) iOS 26.x: the previous top row is at the top of the viewport. iOS 27.0: the offset stays at 0 and the new page's first row is at the top. What I have tried on iOS 27 Re-issuing scrollTo for 0.5s on every content-height or offset change (via GeometryReader preferences). No effect; the target row is not realized, and the visible rows are kept stable instead. .defaultScrollAnchor(.bottom) and .defaultScrollAnchor(.bottom, for: .sizeChanges): fixes the initial open, but the stored anchor is re-applied once on the first content change (the prepend), which snaps the list to the newest message. .sizeChanges did not preserve the prepend. .scrollPosition(id: $topId, anchor: .top) with .scrollTargetLayout() on the LazyVStack: the binding tracks the top row correctly, but after the prepend SwiftUI re-targets the binding to the new page's first row. Writing the previous id back, in the same update or on later run-loop turns, does not restore the position. Replacing LazyVStack with VStack fixes both cases, but the list holds hundreds of image rows and needs the lazy container for memory. What does work: reaching the backing UIScrollView (SwiftUIIntrospect), recording the visible rows' frames before the insert and adjusting contentOffset after layout. It works but is a lot of code for something that used to be one scrollTo. Questions Is it intended on iOS 27 that ScrollViewProxy.scrollTo does not scroll to a LazyVStack row that is not currently realized, or that the lazy stack keeps the currently visible rows stable in preference to the requested target? The WWDC26 lazy-stacks session describes the stack and scroll view coordinating the offset as estimates update; is scrollTo to an unrealized row now unsupported? Is there a supported SwiftUI way on iOS 27 to keep the visible rows in place when items are prepended to a LazyVStack, or to scroll reliably to an unrealized row? For example a ScrollPosition usage or an anchor role I am missing. If not, is adjusting the UIScrollView offset the expected approach, or is List now the recommended container for chat-style lists with bidirectional paging? I have filed this as FB24968838 with the sample project attached.
1
0
189
2d
NavigationSplitView, endless loop detail<->content
Hi, I already had a problem with the NavigationSplitView before, and I'm still working on it. The NavigationSplitView issue on the phone occurs when trying to navigate back from the detail view to the content view; the app automatically navigates straight back to the detail view. I reproduced the issue in a small program and identified the critical section. However, I do not know why the problem is occurring. I did the same thing again, but this time with @Query, and there was no problem. The categories, recipes and single recipe, all loaded by @Query and no side effects. Requirements for the content: A state variable used in the view, e.g., isLoading A task that loads the data from a ModelActor in a ViewModel, setting isLoading to true beforehand and back to false upon completion Subsequently, it is only possible to exit the detail view after many attempts, because the app immediately navigates back to the detail view automatically upon trying to leave it. One issue is changing the isLoading status variable from within the task. That in itself isn't the problem; the issue only arises when reading data via a ModelActor. If the status variable is changed and, for example, the list is modified manually, there is no problem. It only occurs when a ModelActor is used. I based it on the NavigationCookbook: This is the view I use for the content section (list): @State private var vm: RecipeListViewModel = .init() @State var isLoading: Bool = false if isLoading { ProgressView("Loading...") } else { List(selection: $selectedRecipe) { ForEach(vm.recipes) { recipe in NavigationLink(recipe.name, value: recipe) } } } .task { // bug, when used in UI, because of ModelActor isLoading = true await vm.list(context, categoryName: selectedCategory.name) isLoading = false } .task { // works, because manually modifying the list isLoading = true await vm.addRecipe(context, categoryName: selectedCategory.name) isLoading = false } inside ViewModel // bug func list(_ modelContext: ModelContext, categoryName: String) async { recipes.removeAll() let dh: RecipeDataHandler = .init(modelContainer: modelContext.container) let values = await dh.listByName(categoryName: categoryName) for v in values { recipes.append(.init(recipeId: v.recipeId, name: v.name)) } } // works func addRecipe(_ modelContext: ModelContext, categoryName: String) async { isLoading = true defer { isLoading = false } recipes.removeAll() recipes.append(.init(recipeId: "a", name: "Pambazo")) } ModelActor @ModelActor actor RecipeDataHandler { func listByName(categoryName: String) async -> [RecipeData] { var list: [RecipeData] = [] do { let fd = FetchDescriptor<Recipe>(predicate: #Predicate { $0.category.name == categoryName }, sortBy: [SortDescriptor(\Recipe.name)]) let data = try modelContext.fetch(fd) for item in data { let rec: RecipeData = await .init(recipeId: item.recipeId, name: item.name) list.append(rec) } } catch { print("error: \(error.localizedDescription)") } return list } } Recipe is a @Model and RecipeData just a struct. In principle, everything should run on the MainActor, but the inclusion of the ModelActor still leads to this strange behavior. Any idea what the problem might be? Christian
0
0
66
2d
Toolbar background behaviour
Say i have a scrollview: I find it’s dumb how if you have the safe area inset or any kind of element below the toolbar, the background of those will extend to the top edge of the screen (by default), yet if the scrollview scrolls to the toolbar the toolbar background just “appears”, covering the existing background— its even worse cuz the toolbar background is there by default even when you didn’t set it in code. It seems more like a bug; I feel like this is something the SwiftUI team should improve upon. How about disabling toolbar background by default? Just wanted to get some more perspectives.
0
0
282
3d
iOS 27 beta 1: .scrollEdgeEffectStyle(.soft) renders fully transparent above safeAreaBar
Feedback ID: FB23086400 On iOS 27 beta 1, .scrollEdgeEffectStyle(.soft, for: .top) on a List underneath a custom .safeAreaBar(edge: .top) no longer renders the progressive fade-blur. The top edge is fully transparent — scrolled rows pass under the bar with no visual treatment at all, as if scrollEdgeEffectDisabled() had been applied. What I've verified so far: .hard renders correctly in the exact same hierarchy; only .soft is affected. The same binary works correctly on iOS 26.x Xcode preview. I'm building with Xcode 26.3 (iOS 26 SDK). Minimal reproduction: import SwiftUI struct EdgeEffectRepro: View { enum Style: String, CaseIterable, Identifiable { case automatic, soft, hard var id: Self { self } var value: ScrollEdgeEffectStyle { switch self { case .automatic: .automatic case .soft: .soft case .hard: .hard } } } @State private var style: Style = .soft @State private var useSystemBarOnly = false var body: some View { NavigationStack { List(0..<60, id: \.self) { i in Text("Row \(i)") .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground( i.isMultiple(of: 2) ? Color.orange.opacity(0.45) : Color.teal.opacity(0.45) ) } .scrollIndicators(.hidden) .scrollEdgeEffectStyle(style.value, for: .top) .safeAreaBar(edge: .top) { if !useSystemBarOnly { VStack(spacing: 8) { HStack { Text("Custom Top Bar") .font(.system(size: 28, weight: .bold)) Spacer() } HStack { Text("Second row (e.g. date range picker)") .font(.caption) .foregroundStyle(.secondary) Spacer() } } .padding(.horizontal) } } .safeAreaInset(edge: .bottom) { VStack(spacing: 8) { Picker("Edge effect style", selection: $style) { ForEach(Style.allCases) { Text($0.rawValue).tag($0) } } .pickerStyle(.segmented) Toggle("System bar only (control group)", isOn: $useSystemBarOnly) .font(.caption) } .padding() .background(.regularMaterial) } .navigationTitle("EdgeEffect Repro") .navigationBarTitleDisplayMode(.inline) } } } Steps: run on iOS 27 beta 1, set the picker to soft, scroll rows under the bar. Expected: fade-blur as on iOS 26. Actual: fully transparent. Switch to hard: renders fine.
24
13
5k
3d
Hard scroll edge effect
The example they gave for the hard scroll edge effect is the calendar app, but Idk if this is a bad example or if i am missing something, because the calendar app theres a date picker row below the toolbar, and from my testing, whenever theres some element between the toolbar and the scrollview, the fade and blur effect does not happen -- so in fact, the hard scroll edge effect is completely unnecessary.
0
0
75
3d
NSToolbarItemViewer's min/max size is nan
When attempting to use Toolbar(id: "") this crash occurs immediately upon showing the window. FAULT: NSInternalInconsistencyException: NSToolbarItemViewer's min/max size is nan. This indicates an item's size has been changed during layout which is illegal; { NSAssertFile = "NSToolbarItemViewer.m"; NSAssertLine = 942; } If I comment out the toolbars, re-run show the window once, then re-add id: the window properly displays, allows customization and does not error unless I clear data within ~/Library/Containers.
1
0
351
3d
Button or onTapGesture grows its hit region by a fixed amount past the view’s bounds.
SwiftUI Button hit area extends ~16pt beyond its visual frame — expected OS behavior? Hi everyone, I’m working on a custom SwiftUI segmented control and noticed some unexpected hit-testing behavior with Button. I have a button structured roughly like this: Button { selectItem(at: index) } label: { ZStack { if selectedIndex == index { selectedBackground } SegmentCellView( tab: tab, isSelected: selectedIndex == index ) } .frame(maxWidth: .infinity) .contentShape(Rectangle()) } .accessibilityLabel(Text(tab.labelText)) The visual frame of the button has a certain height, but when testing on a real device, I can tap approximately 16pt outside the button's visible frame and the button still receives the tap. This seems to happen even though I explicitly use: .contentShape(Rectangle()) to define the tappable shape. My questions are: Is this ~16pt expansion of the interactive area expected SwiftUI/OS behavior? Is this related to Apple's minimum hit-target/accessibility guidelines being automatically applied to controls such as Button? If so, is there an official API/documentation describing this behavior? Is there a recommended way to make the Button's hit-testing area exactly match the bounds defined by .contentShape(Rectangle()), if that is actually possible? I'm particularly interested in understanding whether this is intentional system behavior or something caused by the ButtonStyle / layout hierarchy. Thanks!
0
0
44
4d
How can I set per-window titles in the Window menu for an iPad app running on Apple silicon Mac?
I’m developing a SwiftUI iPad app that runs on an Apple silicon Mac as an iPad app—not as a Mac Catalyst app. The app supports multiple windows, with each Live Screen window associated with a different virtual machine. In the macOS Window menu, all child windows appear as “VirtualProg” rather than showing their content or VM name. The Live Screen window is created from a data-driven  WindowGroup , roughly like this: WindowGroup(id: "vm-screen", for: String.self) { $vmName in VMScreenWindow(vmName: vmName) .navigationTitle(vmName.map { "Live ($0)" } ?? "Live Screen") } I’ve tried setting the title with  .navigationTitle  both on the  WindowGroup  content and inside the window’s  NavigationStack . I also tried setting  windowScene.title  on the specific scene obtained from  view.window?.windowScene  after the view controller appeared. Neither changed the Window-menu entries. Is there a supported way to set a per-window title for the macOS Window menu when running an iPad app on Mac? Does  UIScene.title  affect that menu, or only the window title bar and app switcher? Is this a limitation of the iPad-app-on-Mac runtime, or is there a recommended SwiftUI/UIKit lifecycle hook for this? I’m looking for a public API solution rather than private AppKit/window introspection.
0
0
64
4d
toolbarMinimizationBehavior(_:for:) crashes on some iOS 27.0.0 devices: "missing weak symbol" despite #available(iOS 27.0, *)
We are seeing a production crash that only affects devices reporting iOS 27.0.0. Devices on 27.0.1 and later, and on iOS 26, are unaffected. The app is built with Xcode 27.0 (the same binary shape appears with the Xcode 27.1 RC toolchain), deployment target iOS 26.0. The crashing code is a plain availability-gated call: extension View { @ViewBuilder func disableNavigationBarMinimization() -> some View { if #available(iOS 27.0, *) { toolbarMinimizationBehavior(.never, for: .navigationBar) } else { self } } } Crash report excerpt (addresses and app symbols removed): crash_info_entry_0: Failed to look up symbolic reference at <addr> - offset <n> - symbol <nearest stripped symbol> in <app binary> - pointer at <addr> is likely a reference to a missing weak symbol Crashed: com.apple.main-thread 0 libsystem_kernel.dylib __pthread_kill 2 libsystem_c.dylib abort 3 libswiftCore.dylib <redacted> 9 libswiftCore.dylib swift_getTypeByMangledName + 908 10 libswiftCore.dylib swift_getTypeByMangledNameInContext2 + 248 11 <app> __swift_instantiateConcreteTypeFromMangledNameV2 12 <app> specialized closure #1 in View.disableNavigationBarMinimization() 13 <app> closure #1 in SomeView.body.getter What we established: Because the deployment target is 26.0 and the API is @available(iOS 27.0, *), the app weak-imports both the function and its opaque result type descriptor. dyld_info -imports on the binary shows: _$s7SwiftUI4ViewPAAE27toolbarMinimizationBehavior_3forQrAA07ToolbareF0V_AA0H9PlacementVdtF [weak-import] (from SwiftUI) _$s7SwiftUI4ViewPAAE27toolbarMinimizationBehavior_3forQrAA07ToolbareF0V_AA0H9PlacementVdtFQOMQ [weak-import] (from SwiftUI) The call site has to instantiate the opaque return type's metadata before calling the function. That is the frame that aborts. So when dyld binds the descriptor to null, #available(iOS 27.0, *) passes and the process still dies. The symbol is present and exported on the iOS 27.0 build we can inspect locally (24A437, via Xcode's DeviceSupport symbols), and the same code runs fine on the iOS 27.0 simulator runtime (24A434). We cannot reproduce the crash on any device we own. The SwiftUI binary in 24A437 exports both toolbarMinimizeBehavior(_:for:) / ToolbarMinimizeBehavior and toolbarMinimizationBehavior(_:for:) / ToolbarMinimizationBehavior. The iOS 27.1 SDK only declares the latter, still annotated @available(iOS 27.0, *). This looks like a rename late in the 27.0 cycle. Our working theory is that some iOS 27.0.0 builds in the field (earlier 27.0 builds, possibly the ones preinstalled on new devices) predate the rename and only contain toolbarMinimizeBehavior, so the renamed symbol is absent and the weak import resolves to null. Questions: Is the @available(iOS 27.0, *) annotation on toolbarMinimizationBehavior(_:for:) accurate for every shipped 27.0.0 build? Should it be 27.0.1 or 27.1? Is there a supported way to guard against a missing opaque type descriptor at runtime, short of raising the availability check? Feedback filed as FB25095054.
0
0
85
4d
SwiftUI views lock up after background and sleep for “Designed for iPad” apps
There's an easily reproducible SwiftUI bug on macOS where an app's UI state no longer updates/re-renders for "Designed for iPad" apps (i.e. ProcessInfo.processInfo.isiOSAppOnMac == true). The bug occurs in Xcode and also if the app is running independent of Xcode. The bug occurs when: the user Hides the app (i.e. it goes into the background) the user puts the Mac to sleep (e.g. Apple menu > Sleep) a total of ~60 seconds transpires (i.e. macOS puts the app into the "suspended state") when the app is brought back into the foreground the UI no longer updates properly The only way I have found to fix this is to manually open a new actual full app window via File > New, in which case the app works fine again in the new window. The following extremely simple code in a default Xcode project illustrates the issue: import SwiftUI @main struct staleApp: App { @State private var isBright = true var body: some Scene { WindowGroup() { ZStack { (isBright ? Color.white : Color.black).ignoresSafeArea() Button("TOGGLE") { isBright.toggle(); print("TAPPED") } } .onAppear { print("\(isBright ? "light" : "dark") view appeared") } } } } For the code above, after Hiding the app and putting the computer to sleep for 60 seconds or more, the button no longer swaps views, although the print statements still appear in the console upon tapping the button. Also, while in this buggy state, i can get the view to update to the current state (i.e. the view triggered by the last tap) by manually dragging the corner of the app window to resize the window. But after resizing, the view again does not update upon button tapping until I resize the window again. so it appears the diff engine is mucked or that the Scene or WindowGroup are no longer correctly running on the main thread I have tried rebuilding the entire view hierarchy by updating .id() on views but this approach does NOT work. I have tried many other options/hacks but have not been able to reset the 'view engine' other than opening a new window manually or by using: @Environment(.openWindow) private var openWindow openWindow could be a viable solution except there's no way to programmatically close the old window for isiOSAppOnMac (@Environment(.dismissWindow) private var dismissWindow doesn't work for iOS)
2
1
316
5d
iOS 27: SwiftUI keyboard safe area is not restored after returning to a UIHostingController
I have a SwiftUI view embedded in a UIHostingController. It contains a numeric text field at the top and a .borderedProminent button at the bottom. The text field is automatically focused when the view appears. The bottom button pushes another instance of the same hosting controller. On the initial appearance, SwiftUI correctly positions the button above the keyboard. However, after navigating back from VC2 to VC1, the text field is focused and the keyboard is visible, but the button remains behind the keyboard. This occurs regardless of whether VC2 is closed using the navigation bar back button or the interactive back gesture. This appears to be an iOS 27 regression involving SwiftUI keyboard avoidance inside a UIHostingController. The issue is especially problematic with a numeric keyboard because it has no Return or Done key. If the covered button is the primary way to continue or dismiss the keyboard, the user can no longer access it. Interestingly, the button is repositioned correctly as soon as I begin moving the app into the background. This suggests that SwiftUI still knows about the keyboard safe area but does not update the layout correctly when the hosting controller reappears after being popped to. Minimal reproducible example import SwiftUI import UIKit final class NumericInputHostingController: UIHostingController<NumericInputView> { init() { super.init( rootView: NumericInputView(onContinue: {}) ) rootView = NumericInputView { [weak self] in self?.navigationController?.pushViewController( NumericInputHostingController(), animated: true ) } } @available(*, unavailable) @MainActor required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } struct NumericInputView: View { @State private var value = "" @FocusState private var isTextFieldFocused: Bool let onContinue: () -> Void var body: some View { VStack(spacing: 24) { TextField("Enter a number", text: $value) .keyboardType(.numberPad) .focused($isTextFieldFocused) .padding(.horizontal, 16) .frame(height: 52) .background { RoundedRectangle(cornerRadius: 10) .stroke( Color.accentColor, lineWidth: 1.5 ) } Spacer() Button("Open Another", action: onContinue) .buttonStyle(.borderedProminent) .controlSize(.large) .frame(maxWidth: .infinity) } .padding(20) .background(Color(.systemBackground)) .onAppear { DispatchQueue.main.async { isTextFieldFocused = true } } .onDisappear { isTextFieldFocused = false } } } The initial controller is embedded in a navigation controller: let controller = NumericInputHostingController() let navigationController = UINavigationController( rootViewController: controller ) Steps to reproduce Present VC1. VC1 automatically focuses the numeric text field. Confirm that the bottom button is above the keyboard. Tap the button to push VC2. Navigate back to VC1. VC1 focuses its text field and displays the numeric keyboard. Observe that the bottom button is now behind the keyboard. Begin putting the app into the background. Observe that the button suddenly moves to the correct position above the keyboard. UIKit’s keyboardLayoutGuide handles the equivalent UIKit layout correctly. Is this a known issue with keyboard safe-area updates when a UIHostingController reappears after navigation? Is there a supported SwiftUI solution that does not require observing keyboard notifications manually?
3
1
341
5d
.contactAccessPicker shows blank sheet on iOS 26.2.1 on device
Calling contactAccessPicker results in a blank sheet and a jetsam error, rather than the expected contact picker, using Apple’s sample code, only on device with iOS 26.2.1. This is happening on a iPhone 17 Pro Max running 26.2.1, and not on a simulator. I’m running Apple's sample project Accessing a person’s contact data using Contacts and ContactsUI Steps: Run the sample app on device running iOS 26.2.1. Use the flow to authorize .limited access with 1 contact: Tap request access, Continue, Select Contacts. Select a contact, Continue, Allow Selected Contact. This all works as expected. Tap the add contact button in the toolbar to add a second contact. Expected: This should show the Contact Access Picker UI. Actual: Sheet is shown with no contents. See screenshot of actual results on iOS device running 26.2.1. Reported as FB21812568 I see a similar (same?) error reported for 26.1. It seems strange that the feature is completely broken for multiple point releases. Is anyone else seeing this or are the two of us running into the same rare edge case? Expected Outcome, seen on simulator running 26.2 Actual outcome, seen on device running 26.2.1
10
3
1.6k
5d
Dismiss/Close button - leading or trailing edge?
What is the recommended position of the "X" button dismissing a modal sheet on iOS? When we have "X" and "✓" the case is easy, but what if we just have "X" button? For example, on a "What's New" screen. I found guidelines here: https://developer.apple.com/design/human-interface-guidelines/sheets Based on resizable sheets presented there, the "X" button should be on leading edge. However, iOS is inconsistent and sometimes it presents the button on the leading edge and sometimes on the trailing edge. How do you approach it? Do you prefer having it on the leading or trailing edge? SwiftUI provides ToolbarItem(placement: .cancellationAction) , but it doesn't feel like the right placement. cancellationAction sounds like something for actionable sheets when we have something to be cancelled. I'm missing placement: .dismiss :).
1
0
1.2k
5d
NavigationSplitView without detail information
Hi, I have a working program with categories, lists and detail views. All connected via NavigationLink inside a NavigationStack. It looks a bit strange on an iPad, so I replaced the NavigationStack with a NavigationSplitView. The categories went into the sidebar and the lists into the content section. The detail section of the NavigationSplitView is empty and it still works. Is that the correct behavior? I don't mind it, because it works. example code: var body: some View { NavigationSplitView { List(selection: $listSelection) { Section("Section 1") { NavigationLink(value: EnumValue.V1) { CardView("Card 1") } NavigationLink(value: EnumValue.V2) { CardView("Card 2") } NavigationLink(value: EnumValue.V3) { CardView("Card 3") } } Section("Section 2") { NavigationLink(value: EnumValue.V4) { CardView("Card 4") } NavigationLink(value: EnumValue.V5) { CardView("Card 5") } } } .scrollIndicators(.hidden) .navigationTitle("Detail Empty") } content: { if let mt = listSelection { switch mt { case .V1: ListView1() case .V2: ListView2() case .V3: ListView3() case .V4: ListView4() case .V5: ListView5() } } } detail: { // empty } } In each ListView, I assign the ID to the EditorView so that the EditorView is updated. .navigationDestination(for: ViewListTransfer.self) { vl in switch vl { case .trend(bp: let bp): EditorView(value: bp) .id(bp.Id) case .trendNew: EditorView(val: nil) } } It seems to work because everything is linked via NavigationLinks, but it does look strange that the detail section is empty. The details section is mandatory; therefore, it cannot be omitted. Is this behavior something for a bug report? Christian
2
0
373
5d
Using .sidebarAdaptable style breaks NavigationStack's transition animation in tvOS
When a NavigationStack is nested inside a TabView that's using the .sidebarAdaptable style, it completely overwrites the transition animations of the NavigationStack. In tvOS 26, the transition for push and pop instantly snaps in place. in tvOS 27, it uses its own transition animation, which has strange behaviors, such as slow crossfade, and items rearranging if the frame of the child view is different. Here is a sample code that shows what I'm talking about. struct ContentView: View { @State private var selectedTab = 0 var body: some View { TabView(selection: $selectedTab) { Tab("Home", systemImage: "house", value: 0) { NavigationStack { VStack(spacing: 30) { Text("Root Home View") .font(.title) NavigationLink("Push Detail View") { Text("Child View") .font(.largeTitle) } } } } } .tabViewStyle(.sidebarAdaptable) } } Note: Placing NavigationStack over a TabView will retain the NavigationStack's transition animations, but you lose navigation paths per tab, and child views a rendered over the tabview.
0
0
288
5d
Best practice Replacement for Edit Button on iPhone Duo
On the iPhone Duo the standard "Edit" button for navigation bars is a text-based button, and text-based buttons are not pushed to the vertical toolbars on the Duo. This looks sometimes silly, if it is the only remaining button that can not be placed in the vertical toolbar and which has the result that an almost empty horizontal navigation bar must be kept visible as well. So I guess it would be good idea to have an icon-based replacement for the Editor button to avoid having two toolbars on the Duo. Is there a best practice for the icon to pick for such an edit button? Would be "square.and.pencil" (SF Symbols) a good choice? Or better the "pencil .circle", because the "square" one is often used for text-based input? Anything better?
Replies
1
Boosts
0
Views
571
Activity
1d
How to set custom height for keyboard extension without resize flicker?
Description I'm developing a custom keyboard extension using UIInputViewController and need to set a specific height of 268 points. The keyboard functions correctly, but there's a visible flicker and resize animation during launch that I cannot eliminate. The Problem When the keyboard launches, iOS provides incorrect heights before settling on the correct one. At launch, the view starts at 0×0. Around 295ms later, iOS sets the frame to 440×956 which is full screen height and wrong. Around 373ms, iOS changes it to 440×452 which is still wrong. Finally around 390ms, iOS settles at 440×268 which matches our constraint. This causes visible flicker as the view resizes three times rapidly. The keyboard appears to shrink from full screen down to the correct height, and users can clearly see this animation happening. What I've Tried I've tried adding a height constraint on self.view which gives me the correct height but causes the visible flicker. I created a custom UIInputView subclass and overrode intrinsicContentSize to return my desired height. iOS completely ignores this and gives random heights like 471pt, 680pt, or 956pt instead. I set allowsSelfSizing to true on my UIInputView subclass. iOS ignores this property. I set preferredContentSize on the view controller. iOS ignores this as well. I tried adding the constraint in viewDidAppear instead of viewDidLoad, thinking iOS might have settled by then. It still causes flicker. I overrode the frame and bounds setters on my UIInputView to clamp the height to my desired value. iOS bypasses these overrides somehow. I overrode layoutSubviews to force the correct height after the super call. iOS still applies its own height. Specific Question What is the correct API or technique to specify a keyboard extension's height that iOS will respect immediately upon launch, without triggering the resize animation sequence? Other third-party keyboards like Grammarly and SwiftKey appear to have solved this problem. Their keyboards appear at the correct height without any visible flicker. How do they achieve this? Expected Outcome The keyboard should appear at 268pt height on the first frame with no visible resize animation. Steps to Reproduce Create a new iOS App project in Xcode and add a Keyboard Extension target. In KeyboardViewController.swift, add a height constraint in viewDidLoad: override func viewDidLoad() { super.viewDidLoad() let heightConstraint = view.heightAnchor.constraint(equalToConstant: 268) heightConstraint.priority = .defaultHigh heightConstraint.isActive = true let label = UILabel() label.text = "Demo Keyboard" label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false view.addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), label.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } Build and run on a physical device. Enable the keyboard in Settings, then General, then Keyboard, then Keyboards. Open any app with a text field and switch to the custom keyboard using the globe button. Observe the height changing from around 956pt to 452pt to 268pt with visible animation. Environment iOS 17 and iOS 18 and 26.2, Xcode 16 and Xcode 26, affects all iPhone models tested, reproducible on both simulator and physical device.
Replies
2
Boosts
5
Views
1k
Activity
1d
visionOS : any non-nil preferredSurroundingsEffect enables a light-spill halo around VideoPlayerComponent video
Hi all — sharing a platform behavior I isolated while building a custom immersive video player, in case others hit it (and to ask whether it's intended). Finding On visionOS 27 (Xcode 27), an entity playing video via VideoPlayerComponent(videoRenderer:) gets a light-spill "screen glow" halo around its edges whenever the immersive space declares any non-nil preferredSurroundingsEffect. The halo follows the video's colors frame by frame. With no surroundings effect (nil), there is no halo. Quick repro Progressive immersive space + VideoPlayerComponent playing. Add .preferredSurroundingsEffect(.dim(intensity: 0.0)) → halo appears (even though the effect is visually a no-op). Remove it → halo disappears. Same with .colorMultiply(_:) and preset effects. Things I ruled out on device (Vision Pro) ImmersiveContentBrightness (.custom(1.0) vs .automatic): no difference. VideoPlayerComponent configuration (.stereo, spatial video mode, isPassthroughTintingEnabled) and component attach timing: no difference. Presenting the same video via VideoMaterial: no halo. Bright white SwiftUI content: no halo. Questions Is this coupling intended? The preferredSurroundingsEffect docs don't mention any effect on video playback. Is there a public way to keep a surroundings effect (e.g. room dimming) while avoiding the video light spill? I found none on VideoPlayerComponent. Current workaround: don't declare any surroundings effect in the modes where the halo is undesirable. Also filed via Feedback Assistant(FB25130748). Curious if anyone else has seen this or knows a switch.
Replies
0
Boosts
1
Views
458
Activity
1d
iOS 27: ScrollViewProxy.scrollTo no longer reaches unrealized LazyVStack rows; how to preserve position when prepending?
I have a chat screen built with ScrollView { LazyVStack } and ScrollViewReader. Two things that worked through iOS 26 stopped working on iOS 27 (Xcode 27, iOS 27.0 simulator and device; unchanged code still works on the iOS 26.x simulator): Open at the newest message. On appear I call proxy.scrollTo(lastId, anchor: .bottom). On iOS 27 the list stops two or three rows above the bottom when the rows have variable heights (text bubbles mixed with 200pt images). Load older messages at the top without the list jumping. When the user reaches the top, I prepend 25 older messages and call proxy.scrollTo(previousTopId, anchor: .top) so the row they were reading stays put. On iOS 27 the call does nothing: the list stays at the top of the newly inserted page (offset stays at 0), which immediately re-triggers the load. Re-issuing scrollTo on every layout change for a short window (which is what made this reliable on iOS 26) has no effect on iOS 27. Minimal reproduction import SwiftUI struct Message: Identifiable, Hashable { let id: Int let height: CGFloat // simulates text vs. image bubbles } @MainActor final class ChatModel: ObservableObject { @Published var messages: [Message] = [] @Published var previousTopId: Int? // set when a page is prepended private var nextOldId = 1_000 init() { messages = (0..<25).map { Message(id: nextOldId - $0, height: Self.randomHeight()) }.reversed() nextOldId -= 25 } func loadOlder() { let top = messages.first!.id let page = (0..<25).map { Message(id: nextOldId - $0, height: Self.randomHeight()) }.reversed() nextOldId -= 25 messages.insert(contentsOf: page, at: 0) previousTopId = top // "keep this row at the top" } static func randomHeight() -> CGFloat { [44, 60, 90, 200, 260].randomElement()! } } struct ChatView: View { @StateObject private var model = ChatModel() var body: some View { ScrollViewReader { proxy in ScrollView { LazyVStack(spacing: 8) { // top sentinel: load older when it becomes visible Color.clear.frame(height: 1) .onAppear { model.loadOlder() } ForEach(model.messages) { m in RoundedRectangle(cornerRadius: 12) .fill(m.height >= 200 ? .orange.opacity(0.4) : .blue.opacity(0.3)) .frame(height: m.height) .overlay(Text("\(m.id)")) .padding(.horizontal) .id(m.id) } } } .onAppear { // (1) open at the newest message DispatchQueue.main.async { proxy.scrollTo(model.messages.last!.id, anchor: .bottom) } } .onChange(of: model.previousTopId) { _, id in // (2) restore the row that was at the top before the prepend guard let id else { return } DispatchQueue.main.async { var t = Transaction(); t.disablesAnimations = true withTransaction(t) { proxy.scrollTo(id, anchor: .top) } } } } } } Observed (1) Open at the newest message, scrollTo(lastId, anchor: .bottom) iOS 26.x: lands on the last row. iOS 27.0: stops 2–3 rows above the bottom. (2) Prepend 25 rows, then scrollTo(previousTopId, anchor: .top) iOS 26.x: the previous top row is at the top of the viewport. iOS 27.0: the offset stays at 0 and the new page's first row is at the top. What I have tried on iOS 27 Re-issuing scrollTo for 0.5s on every content-height or offset change (via GeometryReader preferences). No effect; the target row is not realized, and the visible rows are kept stable instead. .defaultScrollAnchor(.bottom) and .defaultScrollAnchor(.bottom, for: .sizeChanges): fixes the initial open, but the stored anchor is re-applied once on the first content change (the prepend), which snaps the list to the newest message. .sizeChanges did not preserve the prepend. .scrollPosition(id: $topId, anchor: .top) with .scrollTargetLayout() on the LazyVStack: the binding tracks the top row correctly, but after the prepend SwiftUI re-targets the binding to the new page's first row. Writing the previous id back, in the same update or on later run-loop turns, does not restore the position. Replacing LazyVStack with VStack fixes both cases, but the list holds hundreds of image rows and needs the lazy container for memory. What does work: reaching the backing UIScrollView (SwiftUIIntrospect), recording the visible rows' frames before the insert and adjusting contentOffset after layout. It works but is a lot of code for something that used to be one scrollTo. Questions Is it intended on iOS 27 that ScrollViewProxy.scrollTo does not scroll to a LazyVStack row that is not currently realized, or that the lazy stack keeps the currently visible rows stable in preference to the requested target? The WWDC26 lazy-stacks session describes the stack and scroll view coordinating the offset as estimates update; is scrollTo to an unrealized row now unsupported? Is there a supported SwiftUI way on iOS 27 to keep the visible rows in place when items are prepended to a LazyVStack, or to scroll reliably to an unrealized row? For example a ScrollPosition usage or an anchor role I am missing. If not, is adjusting the UIScrollView offset the expected approach, or is List now the recommended container for chat-style lists with bidirectional paging? I have filed this as FB24968838 with the sample project attached.
Replies
1
Boosts
0
Views
189
Activity
2d
NavigationSplitView, endless loop detail<->content
Hi, I already had a problem with the NavigationSplitView before, and I'm still working on it. The NavigationSplitView issue on the phone occurs when trying to navigate back from the detail view to the content view; the app automatically navigates straight back to the detail view. I reproduced the issue in a small program and identified the critical section. However, I do not know why the problem is occurring. I did the same thing again, but this time with @Query, and there was no problem. The categories, recipes and single recipe, all loaded by @Query and no side effects. Requirements for the content: A state variable used in the view, e.g., isLoading A task that loads the data from a ModelActor in a ViewModel, setting isLoading to true beforehand and back to false upon completion Subsequently, it is only possible to exit the detail view after many attempts, because the app immediately navigates back to the detail view automatically upon trying to leave it. One issue is changing the isLoading status variable from within the task. That in itself isn't the problem; the issue only arises when reading data via a ModelActor. If the status variable is changed and, for example, the list is modified manually, there is no problem. It only occurs when a ModelActor is used. I based it on the NavigationCookbook: This is the view I use for the content section (list): @State private var vm: RecipeListViewModel = .init() @State var isLoading: Bool = false if isLoading { ProgressView("Loading...") } else { List(selection: $selectedRecipe) { ForEach(vm.recipes) { recipe in NavigationLink(recipe.name, value: recipe) } } } .task { // bug, when used in UI, because of ModelActor isLoading = true await vm.list(context, categoryName: selectedCategory.name) isLoading = false } .task { // works, because manually modifying the list isLoading = true await vm.addRecipe(context, categoryName: selectedCategory.name) isLoading = false } inside ViewModel // bug func list(_ modelContext: ModelContext, categoryName: String) async { recipes.removeAll() let dh: RecipeDataHandler = .init(modelContainer: modelContext.container) let values = await dh.listByName(categoryName: categoryName) for v in values { recipes.append(.init(recipeId: v.recipeId, name: v.name)) } } // works func addRecipe(_ modelContext: ModelContext, categoryName: String) async { isLoading = true defer { isLoading = false } recipes.removeAll() recipes.append(.init(recipeId: "a", name: "Pambazo")) } ModelActor @ModelActor actor RecipeDataHandler { func listByName(categoryName: String) async -> [RecipeData] { var list: [RecipeData] = [] do { let fd = FetchDescriptor<Recipe>(predicate: #Predicate { $0.category.name == categoryName }, sortBy: [SortDescriptor(\Recipe.name)]) let data = try modelContext.fetch(fd) for item in data { let rec: RecipeData = await .init(recipeId: item.recipeId, name: item.name) list.append(rec) } } catch { print("error: \(error.localizedDescription)") } return list } } Recipe is a @Model and RecipeData just a struct. In principle, everything should run on the MainActor, but the inclusion of the ModelActor still leads to this strange behavior. Any idea what the problem might be? Christian
Replies
0
Boosts
0
Views
66
Activity
2d
Toolbar background behaviour
Say i have a scrollview: I find it’s dumb how if you have the safe area inset or any kind of element below the toolbar, the background of those will extend to the top edge of the screen (by default), yet if the scrollview scrolls to the toolbar the toolbar background just “appears”, covering the existing background— its even worse cuz the toolbar background is there by default even when you didn’t set it in code. It seems more like a bug; I feel like this is something the SwiftUI team should improve upon. How about disabling toolbar background by default? Just wanted to get some more perspectives.
Replies
0
Boosts
0
Views
282
Activity
3d
iOS 27 beta 1: .scrollEdgeEffectStyle(.soft) renders fully transparent above safeAreaBar
Feedback ID: FB23086400 On iOS 27 beta 1, .scrollEdgeEffectStyle(.soft, for: .top) on a List underneath a custom .safeAreaBar(edge: .top) no longer renders the progressive fade-blur. The top edge is fully transparent — scrolled rows pass under the bar with no visual treatment at all, as if scrollEdgeEffectDisabled() had been applied. What I've verified so far: .hard renders correctly in the exact same hierarchy; only .soft is affected. The same binary works correctly on iOS 26.x Xcode preview. I'm building with Xcode 26.3 (iOS 26 SDK). Minimal reproduction: import SwiftUI struct EdgeEffectRepro: View { enum Style: String, CaseIterable, Identifiable { case automatic, soft, hard var id: Self { self } var value: ScrollEdgeEffectStyle { switch self { case .automatic: .automatic case .soft: .soft case .hard: .hard } } } @State private var style: Style = .soft @State private var useSystemBarOnly = false var body: some View { NavigationStack { List(0..<60, id: \.self) { i in Text("Row \(i)") .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground( i.isMultiple(of: 2) ? Color.orange.opacity(0.45) : Color.teal.opacity(0.45) ) } .scrollIndicators(.hidden) .scrollEdgeEffectStyle(style.value, for: .top) .safeAreaBar(edge: .top) { if !useSystemBarOnly { VStack(spacing: 8) { HStack { Text("Custom Top Bar") .font(.system(size: 28, weight: .bold)) Spacer() } HStack { Text("Second row (e.g. date range picker)") .font(.caption) .foregroundStyle(.secondary) Spacer() } } .padding(.horizontal) } } .safeAreaInset(edge: .bottom) { VStack(spacing: 8) { Picker("Edge effect style", selection: $style) { ForEach(Style.allCases) { Text($0.rawValue).tag($0) } } .pickerStyle(.segmented) Toggle("System bar only (control group)", isOn: $useSystemBarOnly) .font(.caption) } .padding() .background(.regularMaterial) } .navigationTitle("EdgeEffect Repro") .navigationBarTitleDisplayMode(.inline) } } } Steps: run on iOS 27 beta 1, set the picker to soft, scroll rows under the bar. Expected: fade-blur as on iOS 26. Actual: fully transparent. Switch to hard: renders fine.
Replies
24
Boosts
13
Views
5k
Activity
3d
Hard scroll edge effect
The example they gave for the hard scroll edge effect is the calendar app, but Idk if this is a bad example or if i am missing something, because the calendar app theres a date picker row below the toolbar, and from my testing, whenever theres some element between the toolbar and the scrollview, the fade and blur effect does not happen -- so in fact, the hard scroll edge effect is completely unnecessary.
Replies
0
Boosts
0
Views
75
Activity
3d
NSToolbarItemViewer's min/max size is nan
When attempting to use Toolbar(id: "") this crash occurs immediately upon showing the window. FAULT: NSInternalInconsistencyException: NSToolbarItemViewer's min/max size is nan. This indicates an item's size has been changed during layout which is illegal; { NSAssertFile = "NSToolbarItemViewer.m"; NSAssertLine = 942; } If I comment out the toolbars, re-run show the window once, then re-add id: the window properly displays, allows customization and does not error unless I clear data within ~/Library/Containers.
Replies
1
Boosts
0
Views
351
Activity
3d
Ghost Padding on NavigationBarPlatterContainer
Hello, I have a SwiftUI View sitting in the UIHosting view controller. On rotation to landscape, the system would add padding to the toolbar. I presume it's the nav bar. Anyone experienced this? What would be this space? padding? content margin?
Replies
4
Boosts
0
Views
476
Activity
4d
Button or onTapGesture grows its hit region by a fixed amount past the view’s bounds.
SwiftUI Button hit area extends ~16pt beyond its visual frame — expected OS behavior? Hi everyone, I’m working on a custom SwiftUI segmented control and noticed some unexpected hit-testing behavior with Button. I have a button structured roughly like this: Button { selectItem(at: index) } label: { ZStack { if selectedIndex == index { selectedBackground } SegmentCellView( tab: tab, isSelected: selectedIndex == index ) } .frame(maxWidth: .infinity) .contentShape(Rectangle()) } .accessibilityLabel(Text(tab.labelText)) The visual frame of the button has a certain height, but when testing on a real device, I can tap approximately 16pt outside the button's visible frame and the button still receives the tap. This seems to happen even though I explicitly use: .contentShape(Rectangle()) to define the tappable shape. My questions are: Is this ~16pt expansion of the interactive area expected SwiftUI/OS behavior? Is this related to Apple's minimum hit-target/accessibility guidelines being automatically applied to controls such as Button? If so, is there an official API/documentation describing this behavior? Is there a recommended way to make the Button's hit-testing area exactly match the bounds defined by .contentShape(Rectangle()), if that is actually possible? I'm particularly interested in understanding whether this is intentional system behavior or something caused by the ButtonStyle / layout hierarchy. Thanks!
Replies
0
Boosts
0
Views
44
Activity
4d
How can I set per-window titles in the Window menu for an iPad app running on Apple silicon Mac?
I’m developing a SwiftUI iPad app that runs on an Apple silicon Mac as an iPad app—not as a Mac Catalyst app. The app supports multiple windows, with each Live Screen window associated with a different virtual machine. In the macOS Window menu, all child windows appear as “VirtualProg” rather than showing their content or VM name. The Live Screen window is created from a data-driven  WindowGroup , roughly like this: WindowGroup(id: "vm-screen", for: String.self) { $vmName in VMScreenWindow(vmName: vmName) .navigationTitle(vmName.map { "Live ($0)" } ?? "Live Screen") } I’ve tried setting the title with  .navigationTitle  both on the  WindowGroup  content and inside the window’s  NavigationStack . I also tried setting  windowScene.title  on the specific scene obtained from  view.window?.windowScene  after the view controller appeared. Neither changed the Window-menu entries. Is there a supported way to set a per-window title for the macOS Window menu when running an iPad app on Mac? Does  UIScene.title  affect that menu, or only the window title bar and app switcher? Is this a limitation of the iPad-app-on-Mac runtime, or is there a recommended SwiftUI/UIKit lifecycle hook for this? I’m looking for a public API solution rather than private AppKit/window introspection.
Replies
0
Boosts
0
Views
64
Activity
4d
toolbarMinimizationBehavior(_:for:) crashes on some iOS 27.0.0 devices: "missing weak symbol" despite #available(iOS 27.0, *)
We are seeing a production crash that only affects devices reporting iOS 27.0.0. Devices on 27.0.1 and later, and on iOS 26, are unaffected. The app is built with Xcode 27.0 (the same binary shape appears with the Xcode 27.1 RC toolchain), deployment target iOS 26.0. The crashing code is a plain availability-gated call: extension View { @ViewBuilder func disableNavigationBarMinimization() -> some View { if #available(iOS 27.0, *) { toolbarMinimizationBehavior(.never, for: .navigationBar) } else { self } } } Crash report excerpt (addresses and app symbols removed): crash_info_entry_0: Failed to look up symbolic reference at <addr> - offset <n> - symbol <nearest stripped symbol> in <app binary> - pointer at <addr> is likely a reference to a missing weak symbol Crashed: com.apple.main-thread 0 libsystem_kernel.dylib __pthread_kill 2 libsystem_c.dylib abort 3 libswiftCore.dylib <redacted> 9 libswiftCore.dylib swift_getTypeByMangledName + 908 10 libswiftCore.dylib swift_getTypeByMangledNameInContext2 + 248 11 <app> __swift_instantiateConcreteTypeFromMangledNameV2 12 <app> specialized closure #1 in View.disableNavigationBarMinimization() 13 <app> closure #1 in SomeView.body.getter What we established: Because the deployment target is 26.0 and the API is @available(iOS 27.0, *), the app weak-imports both the function and its opaque result type descriptor. dyld_info -imports on the binary shows: _$s7SwiftUI4ViewPAAE27toolbarMinimizationBehavior_3forQrAA07ToolbareF0V_AA0H9PlacementVdtF [weak-import] (from SwiftUI) _$s7SwiftUI4ViewPAAE27toolbarMinimizationBehavior_3forQrAA07ToolbareF0V_AA0H9PlacementVdtFQOMQ [weak-import] (from SwiftUI) The call site has to instantiate the opaque return type's metadata before calling the function. That is the frame that aborts. So when dyld binds the descriptor to null, #available(iOS 27.0, *) passes and the process still dies. The symbol is present and exported on the iOS 27.0 build we can inspect locally (24A437, via Xcode's DeviceSupport symbols), and the same code runs fine on the iOS 27.0 simulator runtime (24A434). We cannot reproduce the crash on any device we own. The SwiftUI binary in 24A437 exports both toolbarMinimizeBehavior(_:for:) / ToolbarMinimizeBehavior and toolbarMinimizationBehavior(_:for:) / ToolbarMinimizationBehavior. The iOS 27.1 SDK only declares the latter, still annotated @available(iOS 27.0, *). This looks like a rename late in the 27.0 cycle. Our working theory is that some iOS 27.0.0 builds in the field (earlier 27.0 builds, possibly the ones preinstalled on new devices) predate the rename and only contain toolbarMinimizeBehavior, so the renamed symbol is absent and the weak import resolves to null. Questions: Is the @available(iOS 27.0, *) annotation on toolbarMinimizationBehavior(_:for:) accurate for every shipped 27.0.0 build? Should it be 27.0.1 or 27.1? Is there a supported way to guard against a missing opaque type descriptor at runtime, short of raising the availability check? Feedback filed as FB25095054.
Replies
0
Boosts
0
Views
85
Activity
4d
Accent Color is broken in Xcode 27.1 RC 1
In Xcode 27.1 RC 1 there is a regression bug displaying incorrect accent color across the app. It worked correctly in Xcode 27.1 Beta 1. It affects borderPromintent top bar buttons and tab bars: https://x.com/kulik_wojciech/status/2107252543709692166?s=20 I already reported it as a bug: FB25074768
Replies
4
Boosts
0
Views
151
Activity
5d
SwiftUI views lock up after background and sleep for “Designed for iPad” apps
There's an easily reproducible SwiftUI bug on macOS where an app's UI state no longer updates/re-renders for "Designed for iPad" apps (i.e. ProcessInfo.processInfo.isiOSAppOnMac == true). The bug occurs in Xcode and also if the app is running independent of Xcode. The bug occurs when: the user Hides the app (i.e. it goes into the background) the user puts the Mac to sleep (e.g. Apple menu > Sleep) a total of ~60 seconds transpires (i.e. macOS puts the app into the "suspended state") when the app is brought back into the foreground the UI no longer updates properly The only way I have found to fix this is to manually open a new actual full app window via File > New, in which case the app works fine again in the new window. The following extremely simple code in a default Xcode project illustrates the issue: import SwiftUI @main struct staleApp: App { @State private var isBright = true var body: some Scene { WindowGroup() { ZStack { (isBright ? Color.white : Color.black).ignoresSafeArea() Button("TOGGLE") { isBright.toggle(); print("TAPPED") } } .onAppear { print("\(isBright ? "light" : "dark") view appeared") } } } } For the code above, after Hiding the app and putting the computer to sleep for 60 seconds or more, the button no longer swaps views, although the print statements still appear in the console upon tapping the button. Also, while in this buggy state, i can get the view to update to the current state (i.e. the view triggered by the last tap) by manually dragging the corner of the app window to resize the window. But after resizing, the view again does not update upon button tapping until I resize the window again. so it appears the diff engine is mucked or that the Scene or WindowGroup are no longer correctly running on the main thread I have tried rebuilding the entire view hierarchy by updating .id() on views but this approach does NOT work. I have tried many other options/hacks but have not been able to reset the 'view engine' other than opening a new window manually or by using: @Environment(.openWindow) private var openWindow openWindow could be a viable solution except there's no way to programmatically close the old window for isiOSAppOnMac (@Environment(.dismissWindow) private var dismissWindow doesn't work for iOS)
Replies
2
Boosts
1
Views
316
Activity
5d
iOS 27: SwiftUI keyboard safe area is not restored after returning to a UIHostingController
I have a SwiftUI view embedded in a UIHostingController. It contains a numeric text field at the top and a .borderedProminent button at the bottom. The text field is automatically focused when the view appears. The bottom button pushes another instance of the same hosting controller. On the initial appearance, SwiftUI correctly positions the button above the keyboard. However, after navigating back from VC2 to VC1, the text field is focused and the keyboard is visible, but the button remains behind the keyboard. This occurs regardless of whether VC2 is closed using the navigation bar back button or the interactive back gesture. This appears to be an iOS 27 regression involving SwiftUI keyboard avoidance inside a UIHostingController. The issue is especially problematic with a numeric keyboard because it has no Return or Done key. If the covered button is the primary way to continue or dismiss the keyboard, the user can no longer access it. Interestingly, the button is repositioned correctly as soon as I begin moving the app into the background. This suggests that SwiftUI still knows about the keyboard safe area but does not update the layout correctly when the hosting controller reappears after being popped to. Minimal reproducible example import SwiftUI import UIKit final class NumericInputHostingController: UIHostingController<NumericInputView> { init() { super.init( rootView: NumericInputView(onContinue: {}) ) rootView = NumericInputView { [weak self] in self?.navigationController?.pushViewController( NumericInputHostingController(), animated: true ) } } @available(*, unavailable) @MainActor required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } struct NumericInputView: View { @State private var value = "" @FocusState private var isTextFieldFocused: Bool let onContinue: () -> Void var body: some View { VStack(spacing: 24) { TextField("Enter a number", text: $value) .keyboardType(.numberPad) .focused($isTextFieldFocused) .padding(.horizontal, 16) .frame(height: 52) .background { RoundedRectangle(cornerRadius: 10) .stroke( Color.accentColor, lineWidth: 1.5 ) } Spacer() Button("Open Another", action: onContinue) .buttonStyle(.borderedProminent) .controlSize(.large) .frame(maxWidth: .infinity) } .padding(20) .background(Color(.systemBackground)) .onAppear { DispatchQueue.main.async { isTextFieldFocused = true } } .onDisappear { isTextFieldFocused = false } } } The initial controller is embedded in a navigation controller: let controller = NumericInputHostingController() let navigationController = UINavigationController( rootViewController: controller ) Steps to reproduce Present VC1. VC1 automatically focuses the numeric text field. Confirm that the bottom button is above the keyboard. Tap the button to push VC2. Navigate back to VC1. VC1 focuses its text field and displays the numeric keyboard. Observe that the bottom button is now behind the keyboard. Begin putting the app into the background. Observe that the button suddenly moves to the correct position above the keyboard. UIKit’s keyboardLayoutGuide handles the equivalent UIKit layout correctly. Is this a known issue with keyboard safe-area updates when a UIHostingController reappears after navigation? Is there a supported SwiftUI solution that does not require observing keyboard notifications manually?
Replies
3
Boosts
1
Views
341
Activity
5d
.contactAccessPicker shows blank sheet on iOS 26.2.1 on device
Calling contactAccessPicker results in a blank sheet and a jetsam error, rather than the expected contact picker, using Apple’s sample code, only on device with iOS 26.2.1. This is happening on a iPhone 17 Pro Max running 26.2.1, and not on a simulator. I’m running Apple's sample project Accessing a person’s contact data using Contacts and ContactsUI Steps: Run the sample app on device running iOS 26.2.1. Use the flow to authorize .limited access with 1 contact: Tap request access, Continue, Select Contacts. Select a contact, Continue, Allow Selected Contact. This all works as expected. Tap the add contact button in the toolbar to add a second contact. Expected: This should show the Contact Access Picker UI. Actual: Sheet is shown with no contents. See screenshot of actual results on iOS device running 26.2.1. Reported as FB21812568 I see a similar (same?) error reported for 26.1. It seems strange that the feature is completely broken for multiple point releases. Is anyone else seeing this or are the two of us running into the same rare edge case? Expected Outcome, seen on simulator running 26.2 Actual outcome, seen on device running 26.2.1
Replies
10
Boosts
3
Views
1.6k
Activity
5d
Dismiss/Close button - leading or trailing edge?
What is the recommended position of the "X" button dismissing a modal sheet on iOS? When we have "X" and "✓" the case is easy, but what if we just have "X" button? For example, on a "What's New" screen. I found guidelines here: https://developer.apple.com/design/human-interface-guidelines/sheets Based on resizable sheets presented there, the "X" button should be on leading edge. However, iOS is inconsistent and sometimes it presents the button on the leading edge and sometimes on the trailing edge. How do you approach it? Do you prefer having it on the leading or trailing edge? SwiftUI provides ToolbarItem(placement: .cancellationAction) , but it doesn't feel like the right placement. cancellationAction sounds like something for actionable sheets when we have something to be cancelled. I'm missing placement: .dismiss :).
Replies
1
Boosts
0
Views
1.2k
Activity
5d
NavigationSplitView without detail information
Hi, I have a working program with categories, lists and detail views. All connected via NavigationLink inside a NavigationStack. It looks a bit strange on an iPad, so I replaced the NavigationStack with a NavigationSplitView. The categories went into the sidebar and the lists into the content section. The detail section of the NavigationSplitView is empty and it still works. Is that the correct behavior? I don't mind it, because it works. example code: var body: some View { NavigationSplitView { List(selection: $listSelection) { Section("Section 1") { NavigationLink(value: EnumValue.V1) { CardView("Card 1") } NavigationLink(value: EnumValue.V2) { CardView("Card 2") } NavigationLink(value: EnumValue.V3) { CardView("Card 3") } } Section("Section 2") { NavigationLink(value: EnumValue.V4) { CardView("Card 4") } NavigationLink(value: EnumValue.V5) { CardView("Card 5") } } } .scrollIndicators(.hidden) .navigationTitle("Detail Empty") } content: { if let mt = listSelection { switch mt { case .V1: ListView1() case .V2: ListView2() case .V3: ListView3() case .V4: ListView4() case .V5: ListView5() } } } detail: { // empty } } In each ListView, I assign the ID to the EditorView so that the EditorView is updated. .navigationDestination(for: ViewListTransfer.self) { vl in switch vl { case .trend(bp: let bp): EditorView(value: bp) .id(bp.Id) case .trendNew: EditorView(val: nil) } } It seems to work because everything is linked via NavigationLinks, but it does look strange that the detail section is empty. The details section is mandatory; therefore, it cannot be omitted. Is this behavior something for a bug report? Christian
Replies
2
Boosts
0
Views
373
Activity
5d
Using .sidebarAdaptable style breaks NavigationStack's transition animation in tvOS
When a NavigationStack is nested inside a TabView that's using the .sidebarAdaptable style, it completely overwrites the transition animations of the NavigationStack. In tvOS 26, the transition for push and pop instantly snaps in place. in tvOS 27, it uses its own transition animation, which has strange behaviors, such as slow crossfade, and items rearranging if the frame of the child view is different. Here is a sample code that shows what I'm talking about. struct ContentView: View { @State private var selectedTab = 0 var body: some View { TabView(selection: $selectedTab) { Tab("Home", systemImage: "house", value: 0) { NavigationStack { VStack(spacing: 30) { Text("Root Home View") .font(.title) NavigationLink("Push Detail View") { Text("Child View") .font(.largeTitle) } } } } } .tabViewStyle(.sidebarAdaptable) } } Note: Placing NavigationStack over a TabView will retain the NavigationStack's transition animations, but you lose navigation paths per tab, and child views a rendered over the tabview.
Replies
0
Boosts
0
Views
288
Activity
5d