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

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.
0
0
57
1d
iOS 27: swipeActions outside List never re-evaluates its content closure (stale labels, if gating ignored) — intended?
We are adopting the iOS 27 support for swipeActions on rows inside a ScrollView { LazyVStack } (with swipeActionsContainer() on the scroll view). We expected the row modifier to behave like it does inside List, but on iOS 27.0 (24A437, iPhone 17; also 27.0 simulator) the content closure appears to be evaluated only once, when the row is first registered, and never again while the row stays on screen. Is this the intended behavior, or a bug? What we observe Stale button labels / actions. A button whose title depends on state (Button(isPinned ? "Unpin" : "Pin")) keeps its first title after the state changes. The row body itself re-renders with the new state, but the swipe actions do not, until the row is recycled by the lazy stack (scroll it off-screen and back) — then it shows the new value. Conditional content is ignored. swipeActions { if !editing { Button(...) } } keeps showing the buttons after editing becomes true, and the row can still be swiped. Views placed inside the content that observe state themselves (an @Observable/@ObservedObject child view) do update their text. But an if inside that child view does not remove the buttons, and emptying a ForEach inside it removes the buttons while the row still slides open (onPresentationChanged fires with true). onLongPressGesture(minimumDuration: 0.7) on the same row fires only on touch-up (after ~3 s of holding) when the row has swipe actions in a ScrollView; inside List, and in a ScrollView without swipe actions, it fires at ~0.7 s while still pressed. The same row code inside List behaves as expected for all four points. Attaching or omitting swipeActionsContainer() and using the old swipeActions(edge:allowsFullSwipe:content:) overload instead of the new onPresentationChanged overload makes no difference. Minimal reproduction (iOS 27 only, single file) import SwiftUI @Observable final class Model { var editing = false var pinned: Set<Int> = [] } @available(iOS 27, *) struct ContentView: View { @State private var model = Model() @State private var lastLongPress = "-" var body: some View { VStack(spacing: 8) { Toggle("Editing (swipe should be off)", isOn: $model.editing) Text("long press: \(lastLongPress)").font(.caption) ScrollView { LazyVStack(spacing: 0) { ForEach(1...30, id: \.self) { n in HStack { Text("Row \(n)") Spacer() Text(model.pinned.contains(n) ? "pinned" : "").foregroundStyle(.secondary) } .frame(height: 56) .contentShape(Rectangle()) .onLongPressGesture(minimumDuration: 0.7) { lastLongPress = "row \(n) @ \(Date().formatted(date: .omitted, time: .standard))" } .swipeActions(edge: .trailing, allowsFullSwipe: false) { if !model.editing { // (2) ignored after first registration Button(model.pinned.contains(n) ? "Unpin" : "Pin") { // (1) title stays "Pin" if model.pinned.contains(n) { model.pinned.remove(n) } else { model.pinned.insert(n) } } .tint(.blue) } } onPresentationChanged: { _ in } } } } .swipeActionsContainer() } .padding(.horizontal) } } Steps: Swipe row 3, tap Pin. The row shows "pinned". Swipe row 3 again → the button still says Pin (in List it says Unpin). Tapping it runs the closure with the current model, so it unpins, but the label was wrong. Turn Editing on, swipe any row → it still opens with the button (in List it does not open). Press and hold a row for 3 s → "long press" timestamp is written only when the finger lifts. Questions Is "evaluate the swipeActions content once per row registration" the intended contract for swipe actions outside List? The documentation for swipeActionsContainer() only describes coordination (single open row, dismiss on scroll / outside tap), and swipeActions(edge:allowsFullSwipe:content:) doesn't mention any difference between List and other containers. If it is intended, what is the recommended way to (a) disable swipe for a row temporarily (e.g. during an edit mode) and (b) update button titles from state, without recreating the row view? We currently work around it with a child view that observes the model for titles, and allowsHitTesting(false) on the row to disable swiping, but both feel like they rely on undocumented behavior. Is the delayed onLongPressGesture (fires on touch-up) expected when a row carries swipe actions in a ScrollView? simultaneousGesture(LongPressGesture(...)) fires while pressed, which suggests the swipe recognizer imposes a failure requirement on the row's gestures.
0
0
55
1d
Can a Notes note be dragged into another app on iPhone Duo?
I’m adding drag-and-drop support to my app for multitasking on iPhone Duo. My app supports text/note content. Currently, a user can move content from Apple Notes into my app using Share or Copy/Paste. ** Questions** Can a note from the Notes app be dragged directly into another app during multitasking, similar to dragging a photo? If so, what content type does Notes provide through the drag-and-drop session? For example, is the note exposed as plain text (public.plain-text / UTType.plainText), rich text, or another transferable type? My goal is for the user to drag a note from Notes into my app and have it behave essentially like Copy → Paste, without requiring the Share sheet. Thank you.
0
0
76
2d
Widget color looks dull / washed out
Hi, Problem I have a widget which displays a bright red. When I display the same view inside the app, the red is bright. However when I use the same view on the widget, the color looks a bit dull. Note: Widget Style: Default (Always) It is the same dull red when in focus and when not in focus This happens on iOS (device and simulator) and macOS Questions How can I fix it on the widget? Does widget support Display P3 colors? Any help on this would be much appreciated.
0
0
278
4d
Camera direction and rotation in the half-folded pose on iPhone Duo
"Build a great camera experience for iPhone Duo" covers the device open and the device closed, including reconfiguring the session from AVCaptureDeviceDirectionCoordinator's change handler when the forward-facing camera changes. It does not cover the partially open pose, which "Leverage multiple displays and scenes on iPhone Duo" treats as a first-class hinge state with a continuous angle. Consider a camera app that keeps a live preview on screen and adopts AVCaptureDevice.RotationCoordinator — the documented remedy for cases like the square front sensor on iPhone 17, where a hard-coded 90° leaves the preview tilted — applying videoRotationAngleForHorizonLevelPreview to both the preview and the capture connection. In the partially open pose the two halves are at different physical attitudes: one stands up, the other lies flat. Which camera does AVCaptureDeviceDirectionCoordinator report as forward-facing while the hinge status is partially open? Does the hinge angle affect that decision? RotationCoordinator derives its angle from device attitude. With the two halves at different attitudes, which one does videoRotationAngleForHorizonLevelPreview reflect — the half carrying the active camera, the half hosting the preview layer, or a single device-level reference? If an app deliberately keeps the rear camera on the scene while its preview is drawn on the half facing the user, is that a supported configuration, or is the coordinator expected to override that choice? Is any of this modelled in the iPhone Duo simulator, or does the partially open pose require real hardware to test? Question 4 matters for planning: building against a rotation value that only appears on device would be worth knowing about before the hardware ships.
4
0
127
5d
iPhone Duo lying fully open and face-up: interface orientation, and rotating one ArrangementView region 180°
We're designing a two-person layout for iPhone Duo. The device lies fully open and flat on a table between two people facing each other. The inner display is in portrait orientation and split with ArrangementView using the default .split style, which stacks the regions in a tall window. Each person gets the half nearest them, and the far region uses .rotationEffect(.degrees(180)) so it faces the second person. The hinge isn't used for layout. While iPhone Duo lies fully open and face-up, does the interface orientation stay stable if the device is nudged or turned on the table? Is there a recommended way to hold it steady for one screen without restricting orientation app-wide? Is rotating one arrangement region 180° a supported pattern? Specifically, do the reserved regions from GeometryProxy.reservedRegions(kind:options:) need to be converted into the rotated region's coordinate space by hand? And do VoiceOver hit-testing and the focus ring follow the rotation? In an immersive full-screen view in this posture, can the status bar be hidden? If not, what's the recommended handling of its occlusion region, which would appear upside down to the second person?
0
0
58
5d
Being notified of fold / unfold on iPhone Duo — what to observe, and when is the new size reliable?
When iPhone Duo is folded or unfolded while our app is running, what is the supported way to be notified, so the layout can re-evaluate? Our app currently computes its board size once when the game screen appears and never recalculates, so a fold or unfold mid-game leaves it sized for the previous display. What should we observe, and at what point in the transition is the new size reliable to read?
0
0
23
5d
Reading the size of the display the app is actually on — is the window scene's screen the supported way?
Our SwiftUI word game shows a "screen too small" warning on small iPhones, and the same size check gates one feature. We originally read UIScreen.main.bounds. On iPhone Duo the app runs on the inner display (669 x 871 pt), but UIScreen.main returned the cover display's size, so the warning fired on the largest iPhone screen there is. We now read the size from the active window scene's screen instead, which fixes it. Two questions: Is the active window scene's screen the supported way to get the size of the display the app is actually on, or is there a better API (e.g. reading the size from the SwiftUI view hierarchy instead)? For roughly the first second after launch, the Duo reports the scene's screen bounds transposed (871 x 669 instead of 669 x 871). Re-reads a second later are correct. Is that expected, and what is the right moment or signal to read a trustworthy size at launch?
0
0
20
5d
Adapting a portrait phone layout to iPhone Duo's inner display — side-by-side, and what to key it off?
Our SwiftUI game's board screen is one vertical stack: header, board, two tile racks, action buttons, message bar. Built with the iOS 27 SDK it renders correctly upright on iPhone Duo's inner display, but the content needs ~948 pt in an 837 pt canvas, so the racks clip and the buttons fall off the bottom. The canvas is unusually wide and short (669 x 871) compared with any iPhone. What is the recommended way to adapt a portrait phone layout to the inner display — is the expectation that apps restructure into a side-by-side arrangement at this size class, and is there guidance on what to key that off? Size class alone reports .regular width on both displays.
0
0
20
5d
A Summary of the iPhone Duo Group Lab
Group Labs are a unique opportunity for the community to submit questions directly to a panel of Apple engineers and designers. Here are the highlights from the iPhone Duo Group Labs: How should apps preserve navigation and UI state when switching between the inner and outer displays? Treat display transitions as size-class and trait changes, not a scene disconnect or app termination; your process stays alive. For more information, see Prepare your app for iPhone Duo. For state that must survive a scene disconnect/reconnect, implement stateRestorationActivity(for:) to save an NSUserActivity Restoring your app's state. Do multiple instances of the same app on iPhone Duo share UserDefaults/@AppStorage state? Multiple instances of your app's UI on iPhone Duo behave similarly to multi-window support on iPadOS. To learn more, see Leverage multiple displays and scenes on iPhone Duo. Both UserDefaults and AppStorage are app-wide, not per-window, stores. If a full-screen app on the inner display is closed, does it move to the outer display or get backgrounded? iPhone Duo honors UIRequiresFullScreen and apps adapt in place as the device opens and closes rather than backgrounding. To learn more, watch Prepare your app for iPhone Duo. How should apps preserve state — text input, scroll position, video playback, camera sessions — during hinge angle transitions? For example, when a LazyVGrid's column count changes because the device folds, does SwiftUI preserve scroll position automatically, or should you use scrollPosition(id:)? The system generally preserves text input and scroll position automatically since hinge angle changes are represented as size-class and trait updates, not a scene disconnect or app termination. This applies even to cases like a LazyVGrid column-count change triggered by opening or closing the device — apps typically don't need to manually manage scroll position with scrollPosition(id:anchor:) for this transition. For more info, see Prepare your app for iPhone Duo. How can apps preserve what someone is doing when switching displays or folding/unfolding? Treat this as a resize/trait-change event, not app teardown — your process keeps running as size classes change. See Prepare your app for iPhone Duo to learn more. For scenes that actually disconnect and reconnect, implement stateRestorationActivity(for:) to save an NSUserActivity Restoring your app's state. How does actively playing video behave through the hinge angle animation? The system generally preserves video playback and player position automatically since hinge angle changes are represented as size-class and trait updates, not a scene disconnect or app termination. AVKit will scale and resize the video automatically. If a user folds or unfolds the device mid-checkout, what does the system preserve automatically, and what should the app manage itself to avoid lost input or duplicate requests? When someone opens or closes an iPhone Duo, the system represents this as a size-class and trait collection change, not a scene disconnect or app teardown, so in-memory state like input fields typically persists automatically since the app’s process keeps running. For more info, see Prepare your app for iPhone Duo. How should apps handle the keyboard and text input when the device folds or unfolds while typing? As iPhone Duo folds or unfolds, the available screen geometry and framing change. Ensure your app adopts standard layout controls, containers, and size classes to handle resizability gracefully across all poses. When a text field becomes first responder, the system automatically shows the keyboard and binds its input to the text field. Because the appearance of the keyboard has the potential to obscure portions of your user interface, you should update your interface as needed to ensure that the text field being edited remains visible. Use keyboard notifications such as keyboardWillShowNotification, keyboardWillHideNotification, and keyboardWillChangeFrameNotification to detect the appearance and disappearance of the keyboard and to make necessary changes to your interface layout. To learn more, see UITextField. If someone is typing and closes iPhone Duo, does the keyboard/editing session survive the hinge transition, or does it get a new scene? When a user types and closes iPhone Duo, the active editing session and keyboard do not get a completely new scene. Instead, the app undergoes a dynamic resizing and transitions from the inner display to the compact outer display, maintaining the existing scene and application state. For more info, watch Prepare your app for iPhone Duo.
16
0
1.5k
5d
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.
23
13
4.6k
6d
iPhone Duo : Using Group { NavigationStack { ... } } ignores the vertical toolbar
Hi, I was testing my app on iPhone Duo simulator and couldn't understand why even though I am using native navigation stack and toolbars, the toolbar items are not placed on the vertical space. With some debugging I found that it's due to the Group {} I am using Group - because I have a switch inside it where I switch between different types of views, and I insert an environment object only once in the Group {}.environment... I recreated the issue in a clean project, here is full source code: SourceCode.swift And the result when nesting the NavigationStack inside a Group {}. If I remove the Group, toolbar items are pushed to the vertical space as needed
0
0
69
6d
iOS 26: Toolbar button background flashes black during NavigationStack transitions (dark mode)
I’m seeing a visual glitch with toolbar buttons when building with Xcode 26 for iOS 26. During transitions (both pushing in a NavigationStack and presenting a .sheet with its own NavigationStack), the toolbar button briefly flashes the wrong background colour (black in dark mode, white in light mode) before animating to the correct Liquid Glass appearance. This happens even in a minimal example and only seems to affect system toolbar buttons. A custom view with .glassEffect() doesn’t have the issue. I’ve tried: .tint(...), UINavigationBarAppearance/UIToolbarAppearance, and setting backgrounds on hosting/nav/window but none of those made any difference. Here’s a minimal reproducible example: import SwiftUI struct ContentView: View { @State private var showingSheet = false var body: some View { NavigationStack { List { NavigationLink("Push (same stack — morphs)") { DetailView() } Button("Sheet (separate stack — flashes)") { showingSheet = true } } .navigationTitle("Root") .scrollContentBackground(.hidden) .background(.gray) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } .sheet(isPresented: $showingSheet) { SheetView() } } } } struct DetailView: View { var body: some View { Text("Detail (same stack)") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.gray) .navigationTitle("Detail") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } } } struct SheetView: View { var body: some View { NavigationStack { Text("Sheet (separate stack)") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.gray) .navigationTitle("Sheet") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } } } } Has anyone else seen this or found a workaround outside of disabling this background completely with .sharedBackgroundVisibility(.hidden)? I have filed a bug report under FB22141183
2
0
1.1k
6d
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?
0
1
142
1w
Proposal: Add a System Readable Content Layout to SwiftUI SwiftUI currently does not provide a native equivalent of UIKit’s UIView.readableContentGuide. For content-heavy applications such as articles, documentation, settings, email, and media desc
Proposal: Add a System Readable Content Layout to SwiftUI SwiftUI currently does not provide a native equivalent of UIKit's UIView.readableContentGuide. For content-heavy applications such as articles, documentation, settings, email, and media descriptions, developers often need to prevent text from becoming excessively wide on iPad, Mac, and other large displays. UIKit already provides a system-level solution through: view.readableContentGuide However, there is currently no equivalent API in SwiftUI. Current Workarounds Developers currently have to choose between several approaches, none of which provides the same behavior as UIKit's readableContentGuide. 1. Hard-coded maximum width content .frame(maxWidth: 700) This is simple, but the value is arbitrary and does not adapt to the platform, window size, Dynamic Type, or system layout rules. 2. containerRelativeFrame A developer can approximate a readable width: content .containerRelativeFrame(.horizontal) { length, axis in length * 0.52 } However, this is only an approximation. Developers still have to determine the appropriate ratio themselves, and the result does not necessarily match the system's readable content width. 3. Bridging to UIKit Another workaround is to create a UIKit view/controller and obtain: view.readableContentGuide.layoutFrame The resulting width can then be passed back into SwiftUI. This works for iOS and iPadOS, but introduces UIKit-specific implementation details into an otherwise pure SwiftUI view and does not provide a natural cross-platform solution for macOS, tvOS, and other SwiftUI platforms. Proposed API I propose that SwiftUI provide a system-defined readable content layout. For example: .contentWidth(.readable) or a dedicated layout/container: ReadableContent { content } Another possibility would be a layout guide exposed through the SwiftUI environment: @Environment(\.readableContentGuide) private var readableContentGuide allowing: content .frame( maxWidth: readableContentGuide.width ) The exact API design is of course up to Apple, but the important part is that the readable width should be determined by the system rather than by application-specific constants. Expected Behavior The readable content width should be determined by the current platform and environment, taking into account factors such as: Available window/container size Platform-specific layout conventions Dynamic Type / accessibility text sizes Layout margins Safe areas Orientation Size classes where applicable Current window size on macOS Appropriate platform-specific readable widths For example, the same SwiftUI view could behave naturally across devices: iPhone ┌─────────────────────────┐ │ │ │ Readable content │ │ │ └─────────────────────────┘ iPad ┌─────────────────────────────────────────────┐ │ │ │ ┌───────────────────────┐ │ │ │ Readable content │ │ │ └───────────────────────┘ │ │ │ └─────────────────────────────────────────────┘ Mac ┌──────────────────────────────────────────────────────────────┐ │ │ │ ┌───────────────────────┐ │ │ │ Readable content │ │ │ └───────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ The developer should not need to know the exact width used by the system. Why This Belongs in SwiftUI SwiftUI already provides many environment-driven layout behaviors that automatically adapt to the current platform and device. Readable content width is similarly a semantic layout concept, rather than a fixed visual dimension. For example, developers generally should not need to write: #if os(iOS) let maxWidth = 700 #elseif os(macOS) let maxWidth = 800 #elseif os(tvOS) let maxWidth = 1000 #endif A system-provided readable layout would allow the application to express its intent instead: ReadableContent { ArticleView() } This would also make SwiftUI applications more resilient to future platform changes because Apple could adjust the underlying readable-content rules without requiring developers to update hard-coded constants. Use Cases This would be particularly useful for: Article and reading applications Documentation viewers News applications Email clients Settings and preference screens Legal/privacy documents Markdown viewers AI/chat applications Book and EPUB readers Media descriptions and metadata Forms containing large amounts of text Relationship to UIKit UIKit already establishes a precedent with: UIView.readableContentGuide SwiftUI developers should have access to the same semantic concept without needing to bridge through UIKit. The SwiftUI API does not necessarily need to expose the UIKit implementation. It could instead provide a platform-independent abstraction whose implementation is appropriate for each SwiftUI platform. Summary I would like to request a native SwiftUI API for system-defined readable content width. The ideal solution would allow developers to express: ReadableContent { ArticleView() } or: ArticleView() .contentWidth(.readable) while SwiftUI automatically determines the appropriate readable width for the current platform, window, Dynamic Type settings, and layout environment. This would eliminate the need for hard-coded maximum widths and platform-specific UIKit/AppKit workarounds, while bringing SwiftUI closer to the adaptive layout behavior already available in UIKit through readableContentGuide.
1
1
193
1w
Combining NavigationSplitView and TabView in iOS 18
Hi folks, I've used a NavigationSplitView within one of the tabs of my app since iOS 16, but with the new styling in iOS 18 the toolbar region looks odd. In other tabs using e.g. simple stacks, the toolbar buttons are horizontally in line with the new tab picker, but with NavigationSplitView, the toolbar leaves a lot of empty space at the top (see below). Is there anything I can do to adjust this, or alternatively, continue to use the old style? Thanks!
15
3
3.4k
1w
Using CABTMIDILocalPeripheralViewController with SwiftUI
I'm trying to make an app that can send MIDI data over Bluetooth from my iPhone to my Mac. I'm using AudioKit as a framework for the MIDI aspect, and it's working great over a wired connection. I can't figure out how to implement MIDI over Bluetooth though.I found the CABTMIDILocalPeripheralViewController page here: https://developer.apple.com/documentation/coreaudiokit/cabtmidilocalperipheralviewcontroller, which looks promising, but I'm confused about how to use it.I'd really like to implement it using SwiftUI as opposed to UIKit, because the rest of the app is made with SwiftUI. Could someone please show me how I could use this in SwiftUI to make my iPhone discoverable as a Bluetooth MIDI device?I found some sample code written with UIKit, but I'd like to translate this to SwiftUI:import UIKit import CoreAudioKit import CoreMIDI class ViewController: UIViewController { var localPeripheralViewController:CABTMIDILocalPeripheralViewController? override func viewDidLoad() { super.viewDidLoad() localPeripheralViewController = CABTMIDILocalPeripheralViewController() } @IBAction func someAction(sender: AnyObject) { self.navigationController?.pushViewController(localPeripheralViewController!, animated: true) } }Thank you,Jack
5
0
1.3k
1w
SwiftUI Navigation Flicker When Navigating Between Screens With and Without .searchable
I’m experiencing a UI flickering issue in a SwiftUI application related to navigation and the .searchable modifier. I have the following navigation flow: Case 1: Event Dashboard ↓ Attendee List ↓ Back to Event Dashboard The Event Dashboard contains an event image at the top. The Attendee List has a navigation bar with a .searchable search field. When I navigate back from the Attendee List to the Event Dashboard, the event image briefly becomes smaller and then returns to its original size after approximately one second. Case 2: Event Dashboard ↓ Attendee List ↓ Attendee Detail The Attendee List contains .searchable, while the Attendee Detail screen does not. When navigating from the Attendee List to the Attendee Detail screen, the attendee profile image similarly becomes slightly smaller and then enlarges back to its original size. The behavior appears to be related to the navigation bar/search bar layout changing between screens. For example, the Attendee List currently uses: .searchable( text: $model.searchQuery, placement: .navigationBarDrawer, prompt: "Search" ) If I completely remove .searchable from the Attendee List, the flickering does not occur. I would like to understand whether this is expected SwiftUI behavior or a known issue with .searchable and navigation transitions. I am considering testing the Attendee List with ScrollView + LazyVStack instead of List to determine whether List is contributing to the issue.
4
0
734
1w
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
0
Boosts
0
Views
57
Activity
1d
iOS 27: swipeActions outside List never re-evaluates its content closure (stale labels, if gating ignored) — intended?
We are adopting the iOS 27 support for swipeActions on rows inside a ScrollView { LazyVStack } (with swipeActionsContainer() on the scroll view). We expected the row modifier to behave like it does inside List, but on iOS 27.0 (24A437, iPhone 17; also 27.0 simulator) the content closure appears to be evaluated only once, when the row is first registered, and never again while the row stays on screen. Is this the intended behavior, or a bug? What we observe Stale button labels / actions. A button whose title depends on state (Button(isPinned ? "Unpin" : "Pin")) keeps its first title after the state changes. The row body itself re-renders with the new state, but the swipe actions do not, until the row is recycled by the lazy stack (scroll it off-screen and back) — then it shows the new value. Conditional content is ignored. swipeActions { if !editing { Button(...) } } keeps showing the buttons after editing becomes true, and the row can still be swiped. Views placed inside the content that observe state themselves (an @Observable/@ObservedObject child view) do update their text. But an if inside that child view does not remove the buttons, and emptying a ForEach inside it removes the buttons while the row still slides open (onPresentationChanged fires with true). onLongPressGesture(minimumDuration: 0.7) on the same row fires only on touch-up (after ~3 s of holding) when the row has swipe actions in a ScrollView; inside List, and in a ScrollView without swipe actions, it fires at ~0.7 s while still pressed. The same row code inside List behaves as expected for all four points. Attaching or omitting swipeActionsContainer() and using the old swipeActions(edge:allowsFullSwipe:content:) overload instead of the new onPresentationChanged overload makes no difference. Minimal reproduction (iOS 27 only, single file) import SwiftUI @Observable final class Model { var editing = false var pinned: Set<Int> = [] } @available(iOS 27, *) struct ContentView: View { @State private var model = Model() @State private var lastLongPress = "-" var body: some View { VStack(spacing: 8) { Toggle("Editing (swipe should be off)", isOn: $model.editing) Text("long press: \(lastLongPress)").font(.caption) ScrollView { LazyVStack(spacing: 0) { ForEach(1...30, id: \.self) { n in HStack { Text("Row \(n)") Spacer() Text(model.pinned.contains(n) ? "pinned" : "").foregroundStyle(.secondary) } .frame(height: 56) .contentShape(Rectangle()) .onLongPressGesture(minimumDuration: 0.7) { lastLongPress = "row \(n) @ \(Date().formatted(date: .omitted, time: .standard))" } .swipeActions(edge: .trailing, allowsFullSwipe: false) { if !model.editing { // (2) ignored after first registration Button(model.pinned.contains(n) ? "Unpin" : "Pin") { // (1) title stays "Pin" if model.pinned.contains(n) { model.pinned.remove(n) } else { model.pinned.insert(n) } } .tint(.blue) } } onPresentationChanged: { _ in } } } } .swipeActionsContainer() } .padding(.horizontal) } } Steps: Swipe row 3, tap Pin. The row shows "pinned". Swipe row 3 again → the button still says Pin (in List it says Unpin). Tapping it runs the closure with the current model, so it unpins, but the label was wrong. Turn Editing on, swipe any row → it still opens with the button (in List it does not open). Press and hold a row for 3 s → "long press" timestamp is written only when the finger lifts. Questions Is "evaluate the swipeActions content once per row registration" the intended contract for swipe actions outside List? The documentation for swipeActionsContainer() only describes coordination (single open row, dismiss on scroll / outside tap), and swipeActions(edge:allowsFullSwipe:content:) doesn't mention any difference between List and other containers. If it is intended, what is the recommended way to (a) disable swipe for a row temporarily (e.g. during an edit mode) and (b) update button titles from state, without recreating the row view? We currently work around it with a child view that observes the model for titles, and allowsHitTesting(false) on the row to disable swiping, but both feel like they rely on undocumented behavior. Is the delayed onLongPressGesture (fires on touch-up) expected when a row carries swipe actions in a ScrollView? simultaneousGesture(LongPressGesture(...)) fires while pressed, which suggests the swipe recognizer imposes a failure requirement on the row's gestures.
Replies
0
Boosts
0
Views
55
Activity
1d
How to Keep Out a Tinted navigationbar from the Combined Status Bar Region in iPhone Duo using Swiftui
I think the title says it all.
Replies
1
Boosts
0
Views
332
Activity
2d
Can a Notes note be dragged into another app on iPhone Duo?
I’m adding drag-and-drop support to my app for multitasking on iPhone Duo. My app supports text/note content. Currently, a user can move content from Apple Notes into my app using Share or Copy/Paste. ** Questions** Can a note from the Notes app be dragged directly into another app during multitasking, similar to dragging a photo? If so, what content type does Notes provide through the drag-and-drop session? For example, is the note exposed as plain text (public.plain-text / UTType.plainText), rich text, or another transferable type? My goal is for the user to drag a note from Notes into my app and have it behave essentially like Copy → Paste, without requiring the Share sheet. Thank you.
Replies
0
Boosts
0
Views
76
Activity
2d
Widget color looks dull / washed out
Hi, Problem I have a widget which displays a bright red. When I display the same view inside the app, the red is bright. However when I use the same view on the widget, the color looks a bit dull. Note: Widget Style: Default (Always) It is the same dull red when in focus and when not in focus This happens on iOS (device and simulator) and macOS Questions How can I fix it on the widget? Does widget support Display P3 colors? Any help on this would be much appreciated.
Replies
0
Boosts
0
Views
278
Activity
4d
Camera direction and rotation in the half-folded pose on iPhone Duo
"Build a great camera experience for iPhone Duo" covers the device open and the device closed, including reconfiguring the session from AVCaptureDeviceDirectionCoordinator's change handler when the forward-facing camera changes. It does not cover the partially open pose, which "Leverage multiple displays and scenes on iPhone Duo" treats as a first-class hinge state with a continuous angle. Consider a camera app that keeps a live preview on screen and adopts AVCaptureDevice.RotationCoordinator — the documented remedy for cases like the square front sensor on iPhone 17, where a hard-coded 90° leaves the preview tilted — applying videoRotationAngleForHorizonLevelPreview to both the preview and the capture connection. In the partially open pose the two halves are at different physical attitudes: one stands up, the other lies flat. Which camera does AVCaptureDeviceDirectionCoordinator report as forward-facing while the hinge status is partially open? Does the hinge angle affect that decision? RotationCoordinator derives its angle from device attitude. With the two halves at different attitudes, which one does videoRotationAngleForHorizonLevelPreview reflect — the half carrying the active camera, the half hosting the preview layer, or a single device-level reference? If an app deliberately keeps the rear camera on the scene while its preview is drawn on the half facing the user, is that a supported configuration, or is the coordinator expected to override that choice? Is any of this modelled in the iPhone Duo simulator, or does the partially open pose require real hardware to test? Question 4 matters for planning: building against a rotation value that only appears on device would be worth knowing about before the hardware ships.
Replies
4
Boosts
0
Views
127
Activity
5d
iPhone Duo lying fully open and face-up: interface orientation, and rotating one ArrangementView region 180°
We're designing a two-person layout for iPhone Duo. The device lies fully open and flat on a table between two people facing each other. The inner display is in portrait orientation and split with ArrangementView using the default .split style, which stacks the regions in a tall window. Each person gets the half nearest them, and the far region uses .rotationEffect(.degrees(180)) so it faces the second person. The hinge isn't used for layout. While iPhone Duo lies fully open and face-up, does the interface orientation stay stable if the device is nudged or turned on the table? Is there a recommended way to hold it steady for one screen without restricting orientation app-wide? Is rotating one arrangement region 180° a supported pattern? Specifically, do the reserved regions from GeometryProxy.reservedRegions(kind:options:) need to be converted into the rotated region's coordinate space by hand? And do VoiceOver hit-testing and the focus ring follow the rotation? In an immersive full-screen view in this posture, can the status bar be hidden? If not, what's the recommended handling of its occlusion region, which would appear upside down to the second person?
Replies
0
Boosts
0
Views
58
Activity
5d
Being notified of fold / unfold on iPhone Duo — what to observe, and when is the new size reliable?
When iPhone Duo is folded or unfolded while our app is running, what is the supported way to be notified, so the layout can re-evaluate? Our app currently computes its board size once when the game screen appears and never recalculates, so a fold or unfold mid-game leaves it sized for the previous display. What should we observe, and at what point in the transition is the new size reliable to read?
Replies
0
Boosts
0
Views
23
Activity
5d
Reading the size of the display the app is actually on — is the window scene's screen the supported way?
Our SwiftUI word game shows a "screen too small" warning on small iPhones, and the same size check gates one feature. We originally read UIScreen.main.bounds. On iPhone Duo the app runs on the inner display (669 x 871 pt), but UIScreen.main returned the cover display's size, so the warning fired on the largest iPhone screen there is. We now read the size from the active window scene's screen instead, which fixes it. Two questions: Is the active window scene's screen the supported way to get the size of the display the app is actually on, or is there a better API (e.g. reading the size from the SwiftUI view hierarchy instead)? For roughly the first second after launch, the Duo reports the scene's screen bounds transposed (871 x 669 instead of 669 x 871). Re-reads a second later are correct. Is that expected, and what is the right moment or signal to read a trustworthy size at launch?
Replies
0
Boosts
0
Views
20
Activity
5d
Adapting a portrait phone layout to iPhone Duo's inner display — side-by-side, and what to key it off?
Our SwiftUI game's board screen is one vertical stack: header, board, two tile racks, action buttons, message bar. Built with the iOS 27 SDK it renders correctly upright on iPhone Duo's inner display, but the content needs ~948 pt in an 837 pt canvas, so the racks clip and the buttons fall off the bottom. The canvas is unusually wide and short (669 x 871) compared with any iPhone. What is the recommended way to adapt a portrait phone layout to the inner display — is the expectation that apps restructure into a side-by-side arrangement at this size class, and is there guidance on what to key that off? Size class alone reports .regular width on both displays.
Replies
0
Boosts
0
Views
20
Activity
5d
A Summary of the iPhone Duo Group Lab
Group Labs are a unique opportunity for the community to submit questions directly to a panel of Apple engineers and designers. Here are the highlights from the iPhone Duo Group Labs: How should apps preserve navigation and UI state when switching between the inner and outer displays? Treat display transitions as size-class and trait changes, not a scene disconnect or app termination; your process stays alive. For more information, see Prepare your app for iPhone Duo. For state that must survive a scene disconnect/reconnect, implement stateRestorationActivity(for:) to save an NSUserActivity Restoring your app's state. Do multiple instances of the same app on iPhone Duo share UserDefaults/@AppStorage state? Multiple instances of your app's UI on iPhone Duo behave similarly to multi-window support on iPadOS. To learn more, see Leverage multiple displays and scenes on iPhone Duo. Both UserDefaults and AppStorage are app-wide, not per-window, stores. If a full-screen app on the inner display is closed, does it move to the outer display or get backgrounded? iPhone Duo honors UIRequiresFullScreen and apps adapt in place as the device opens and closes rather than backgrounding. To learn more, watch Prepare your app for iPhone Duo. How should apps preserve state — text input, scroll position, video playback, camera sessions — during hinge angle transitions? For example, when a LazyVGrid's column count changes because the device folds, does SwiftUI preserve scroll position automatically, or should you use scrollPosition(id:)? The system generally preserves text input and scroll position automatically since hinge angle changes are represented as size-class and trait updates, not a scene disconnect or app termination. This applies even to cases like a LazyVGrid column-count change triggered by opening or closing the device — apps typically don't need to manually manage scroll position with scrollPosition(id:anchor:) for this transition. For more info, see Prepare your app for iPhone Duo. How can apps preserve what someone is doing when switching displays or folding/unfolding? Treat this as a resize/trait-change event, not app teardown — your process keeps running as size classes change. See Prepare your app for iPhone Duo to learn more. For scenes that actually disconnect and reconnect, implement stateRestorationActivity(for:) to save an NSUserActivity Restoring your app's state. How does actively playing video behave through the hinge angle animation? The system generally preserves video playback and player position automatically since hinge angle changes are represented as size-class and trait updates, not a scene disconnect or app termination. AVKit will scale and resize the video automatically. If a user folds or unfolds the device mid-checkout, what does the system preserve automatically, and what should the app manage itself to avoid lost input or duplicate requests? When someone opens or closes an iPhone Duo, the system represents this as a size-class and trait collection change, not a scene disconnect or app teardown, so in-memory state like input fields typically persists automatically since the app’s process keeps running. For more info, see Prepare your app for iPhone Duo. How should apps handle the keyboard and text input when the device folds or unfolds while typing? As iPhone Duo folds or unfolds, the available screen geometry and framing change. Ensure your app adopts standard layout controls, containers, and size classes to handle resizability gracefully across all poses. When a text field becomes first responder, the system automatically shows the keyboard and binds its input to the text field. Because the appearance of the keyboard has the potential to obscure portions of your user interface, you should update your interface as needed to ensure that the text field being edited remains visible. Use keyboard notifications such as keyboardWillShowNotification, keyboardWillHideNotification, and keyboardWillChangeFrameNotification to detect the appearance and disappearance of the keyboard and to make necessary changes to your interface layout. To learn more, see UITextField. If someone is typing and closes iPhone Duo, does the keyboard/editing session survive the hinge transition, or does it get a new scene? When a user types and closes iPhone Duo, the active editing session and keyboard do not get a completely new scene. Instead, the app undergoes a dynamic resizing and transitions from the inner display to the compact outer display, maintaining the existing scene and application state. For more info, watch Prepare your app for iPhone Duo.
Replies
16
Boosts
0
Views
1.5k
Activity
5d
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
23
Boosts
13
Views
4.6k
Activity
6d
iPhone Duo : Using Group { NavigationStack { ... } } ignores the vertical toolbar
Hi, I was testing my app on iPhone Duo simulator and couldn't understand why even though I am using native navigation stack and toolbars, the toolbar items are not placed on the vertical space. With some debugging I found that it's due to the Group {} I am using Group - because I have a switch inside it where I switch between different types of views, and I insert an environment object only once in the Group {}.environment... I recreated the issue in a clean project, here is full source code: SourceCode.swift And the result when nesting the NavigationStack inside a Group {}. If I remove the Group, toolbar items are pushed to the vertical space as needed
Replies
0
Boosts
0
Views
69
Activity
6d
iOS 26: Toolbar button background flashes black during NavigationStack transitions (dark mode)
I’m seeing a visual glitch with toolbar buttons when building with Xcode 26 for iOS 26. During transitions (both pushing in a NavigationStack and presenting a .sheet with its own NavigationStack), the toolbar button briefly flashes the wrong background colour (black in dark mode, white in light mode) before animating to the correct Liquid Glass appearance. This happens even in a minimal example and only seems to affect system toolbar buttons. A custom view with .glassEffect() doesn’t have the issue. I’ve tried: .tint(...), UINavigationBarAppearance/UIToolbarAppearance, and setting backgrounds on hosting/nav/window but none of those made any difference. Here’s a minimal reproducible example: import SwiftUI struct ContentView: View { @State private var showingSheet = false var body: some View { NavigationStack { List { NavigationLink("Push (same stack — morphs)") { DetailView() } Button("Sheet (separate stack — flashes)") { showingSheet = true } } .navigationTitle("Root") .scrollContentBackground(.hidden) .background(.gray) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } .sheet(isPresented: $showingSheet) { SheetView() } } } } struct DetailView: View { var body: some View { Text("Detail (same stack)") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.gray) .navigationTitle("Detail") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } } } struct SheetView: View { var body: some View { NavigationStack { Text("Sheet (separate stack)") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.gray) .navigationTitle("Sheet") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Action") {} } } } } } Has anyone else seen this or found a workaround outside of disabling this background completely with .sharedBackgroundVisibility(.hidden)? I have filed a bug report under FB22141183
Replies
2
Boosts
0
Views
1.1k
Activity
6d
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
0
Boosts
1
Views
142
Activity
1w
Proposal: Add a System Readable Content Layout to SwiftUI SwiftUI currently does not provide a native equivalent of UIKit’s UIView.readableContentGuide. For content-heavy applications such as articles, documentation, settings, email, and media desc
Proposal: Add a System Readable Content Layout to SwiftUI SwiftUI currently does not provide a native equivalent of UIKit's UIView.readableContentGuide. For content-heavy applications such as articles, documentation, settings, email, and media descriptions, developers often need to prevent text from becoming excessively wide on iPad, Mac, and other large displays. UIKit already provides a system-level solution through: view.readableContentGuide However, there is currently no equivalent API in SwiftUI. Current Workarounds Developers currently have to choose between several approaches, none of which provides the same behavior as UIKit's readableContentGuide. 1. Hard-coded maximum width content .frame(maxWidth: 700) This is simple, but the value is arbitrary and does not adapt to the platform, window size, Dynamic Type, or system layout rules. 2. containerRelativeFrame A developer can approximate a readable width: content .containerRelativeFrame(.horizontal) { length, axis in length * 0.52 } However, this is only an approximation. Developers still have to determine the appropriate ratio themselves, and the result does not necessarily match the system's readable content width. 3. Bridging to UIKit Another workaround is to create a UIKit view/controller and obtain: view.readableContentGuide.layoutFrame The resulting width can then be passed back into SwiftUI. This works for iOS and iPadOS, but introduces UIKit-specific implementation details into an otherwise pure SwiftUI view and does not provide a natural cross-platform solution for macOS, tvOS, and other SwiftUI platforms. Proposed API I propose that SwiftUI provide a system-defined readable content layout. For example: .contentWidth(.readable) or a dedicated layout/container: ReadableContent { content } Another possibility would be a layout guide exposed through the SwiftUI environment: @Environment(\.readableContentGuide) private var readableContentGuide allowing: content .frame( maxWidth: readableContentGuide.width ) The exact API design is of course up to Apple, but the important part is that the readable width should be determined by the system rather than by application-specific constants. Expected Behavior The readable content width should be determined by the current platform and environment, taking into account factors such as: Available window/container size Platform-specific layout conventions Dynamic Type / accessibility text sizes Layout margins Safe areas Orientation Size classes where applicable Current window size on macOS Appropriate platform-specific readable widths For example, the same SwiftUI view could behave naturally across devices: iPhone ┌─────────────────────────┐ │ │ │ Readable content │ │ │ └─────────────────────────┘ iPad ┌─────────────────────────────────────────────┐ │ │ │ ┌───────────────────────┐ │ │ │ Readable content │ │ │ └───────────────────────┘ │ │ │ └─────────────────────────────────────────────┘ Mac ┌──────────────────────────────────────────────────────────────┐ │ │ │ ┌───────────────────────┐ │ │ │ Readable content │ │ │ └───────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ The developer should not need to know the exact width used by the system. Why This Belongs in SwiftUI SwiftUI already provides many environment-driven layout behaviors that automatically adapt to the current platform and device. Readable content width is similarly a semantic layout concept, rather than a fixed visual dimension. For example, developers generally should not need to write: #if os(iOS) let maxWidth = 700 #elseif os(macOS) let maxWidth = 800 #elseif os(tvOS) let maxWidth = 1000 #endif A system-provided readable layout would allow the application to express its intent instead: ReadableContent { ArticleView() } This would also make SwiftUI applications more resilient to future platform changes because Apple could adjust the underlying readable-content rules without requiring developers to update hard-coded constants. Use Cases This would be particularly useful for: Article and reading applications Documentation viewers News applications Email clients Settings and preference screens Legal/privacy documents Markdown viewers AI/chat applications Book and EPUB readers Media descriptions and metadata Forms containing large amounts of text Relationship to UIKit UIKit already establishes a precedent with: UIView.readableContentGuide SwiftUI developers should have access to the same semantic concept without needing to bridge through UIKit. The SwiftUI API does not necessarily need to expose the UIKit implementation. It could instead provide a platform-independent abstraction whose implementation is appropriate for each SwiftUI platform. Summary I would like to request a native SwiftUI API for system-defined readable content width. The ideal solution would allow developers to express: ReadableContent { ArticleView() } or: ArticleView() .contentWidth(.readable) while SwiftUI automatically determines the appropriate readable width for the current platform, window, Dynamic Type settings, and layout environment. This would eliminate the need for hard-coded maximum widths and platform-specific UIKit/AppKit workarounds, while bringing SwiftUI closer to the adaptive layout behavior already available in UIKit through readableContentGuide.
Replies
1
Boosts
1
Views
193
Activity
1w
Combining NavigationSplitView and TabView in iOS 18
Hi folks, I've used a NavigationSplitView within one of the tabs of my app since iOS 16, but with the new styling in iOS 18 the toolbar region looks odd. In other tabs using e.g. simple stacks, the toolbar buttons are horizontally in line with the new tab picker, but with NavigationSplitView, the toolbar leaves a lot of empty space at the top (see below). Is there anything I can do to adjust this, or alternatively, continue to use the old style? Thanks!
Replies
15
Boosts
3
Views
3.4k
Activity
1w
Unable to display bluetooth paired device on iOS app
Hi there, I am working on bluetooth functionality of iOS and I have a feature that display all bluetooth paired devices on list view. Is there a way to get the list of paired device using swift programming. Many Thanks!
Replies
1
Boosts
0
Views
175
Activity
1w
Using CABTMIDILocalPeripheralViewController with SwiftUI
I'm trying to make an app that can send MIDI data over Bluetooth from my iPhone to my Mac. I'm using AudioKit as a framework for the MIDI aspect, and it's working great over a wired connection. I can't figure out how to implement MIDI over Bluetooth though.I found the CABTMIDILocalPeripheralViewController page here: https://developer.apple.com/documentation/coreaudiokit/cabtmidilocalperipheralviewcontroller, which looks promising, but I'm confused about how to use it.I'd really like to implement it using SwiftUI as opposed to UIKit, because the rest of the app is made with SwiftUI. Could someone please show me how I could use this in SwiftUI to make my iPhone discoverable as a Bluetooth MIDI device?I found some sample code written with UIKit, but I'd like to translate this to SwiftUI:import UIKit import CoreAudioKit import CoreMIDI class ViewController: UIViewController { var localPeripheralViewController:CABTMIDILocalPeripheralViewController? override func viewDidLoad() { super.viewDidLoad() localPeripheralViewController = CABTMIDILocalPeripheralViewController() } @IBAction func someAction(sender: AnyObject) { self.navigationController?.pushViewController(localPeripheralViewController!, animated: true) } }Thank you,Jack
Replies
5
Boosts
0
Views
1.3k
Activity
1w
SwiftUI Navigation Flicker When Navigating Between Screens With and Without .searchable
I’m experiencing a UI flickering issue in a SwiftUI application related to navigation and the .searchable modifier. I have the following navigation flow: Case 1: Event Dashboard ↓ Attendee List ↓ Back to Event Dashboard The Event Dashboard contains an event image at the top. The Attendee List has a navigation bar with a .searchable search field. When I navigate back from the Attendee List to the Event Dashboard, the event image briefly becomes smaller and then returns to its original size after approximately one second. Case 2: Event Dashboard ↓ Attendee List ↓ Attendee Detail The Attendee List contains .searchable, while the Attendee Detail screen does not. When navigating from the Attendee List to the Attendee Detail screen, the attendee profile image similarly becomes slightly smaller and then enlarges back to its original size. The behavior appears to be related to the navigation bar/search bar layout changing between screens. For example, the Attendee List currently uses: .searchable( text: $model.searchQuery, placement: .navigationBarDrawer, prompt: "Search" ) If I completely remove .searchable from the Attendee List, the flickering does not occur. I would like to understand whether this is expected SwiftUI behavior or a known issue with .searchable and navigation transitions. I am considering testing the Attendee List with ScrollView + LazyVStack instead of List to determine whether List is contributing to the issue.
Replies
4
Boosts
0
Views
734
Activity
1w