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

Question: Task modifier with id called on initial
Hello, I have a question regarding the task(id:) modifier. It is possible, of course, that I am using the modifier for the wrong purpose. Consider a multi-field search scenario where any change to a field triggers a loading process via the task(id:) modifier. The problem is that every task(id:) modifier executes when the view is first loaded, causing the same data to be loaded multiple times—which makes no sense. I do not want to add a separate search button or implement debouncing; those would merely be workarounds to bypass the actual issue. The onChange modifier has an initial parameter that defaults to false, preventing the action from running when the view is first loaded. However, the task(id:) modifier does not have this parameter. How can I prevent all task(id:) modifiers from executing when the view loads? After all, the standard task modifier should handle the initial load. struct ContentView: View { @State private var field1: String = "" @State private var field2: String = "" @State private var field3: String = "" var body: some View { VStack { TextField("val1", text: $field1) TextField("val2", text: $field2) TextField("val3", text: $field3) } .padding() .task { print("task") } .task(id: field1) { print("task 1") } .task(id: field2) { print("task 2") } .task(id: field3) { print("task 3") } } } Christian
1
0
239
10m
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?
2
1
230
6h
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
1
0
53
6h
Can we opt out of the Horizontal Bar on the iPhone Duo?
We are aware that we can opt out of the vertical bar to make it horizontal for folded and unfolded landscape: https://developer.apple.com/documentation/uikit/uiviewcontroller/preferredverticalbarbehavior As per the title, we would like to know if if the reverse is possible (i.e. can we have the unfolded portrait display a vertical bar instead of a horizontal one)
1
0
53
7h
[VoiceOver] Dynamic fractions (e.g., ½, 1/2) are completely omitted/skipped in iOS 27
Platform & OS Version: iOS 27 Screen Reader: VoiceOver Framework: SwiftUI / UIKit / WebKit (WKWebView) Issue Summary: When updating dynamic text or UI elements containing fraction values—whether using Unicode fraction glyphs (e.g., ½, ¾) VoiceOver in iOS 27 completely omits and skips reading the fraction value entirely. Steps to Reproduce: Enable VoiceOver on iOS 27 (Settings > Accessibility > VoiceOver). Open an app or web view where fraction values, tap to focus on the element or trigger voice over Expected Result: VoiceOver should speak the accessible label or text content (e.g., "one half") Actual Result: VoiceOver skips the fraction entirely, reading only surrounding text or remaining silent over the fraction value.
0
0
29
11h
SwiftUI interactive Liquid Glass highlight boundary for a custom L shape
Full code following thread 826485. Feedback: FB25055820. Earlier calibrated Lab recordings on an iPhone 17 Pro Simulator (iOS 27.2 Beta 1, 24B5084k; Xcode 27.2, 27B5019j) showed a round press highlight inside the L, including on its empty cutout where the Button action did not fire. Interactive off removed it; a wider host widened it without stretching the L. These are Simulator observations. This extracted English project compiled, but has not been newly run or optically recorded. The baseline already uses the SAME L contentShape after glassEffect. Other matching-L placements are not established failures. Which public API controls the highlight boundary, and where should contentShape go? Are nonconvex shapes supported? Hold each arm, then the cutout; compare the highlight and action counter. Default uses the recorded baseline order. import SwiftUI @main struct CustomGlassHighlightApp: App { var body: some Scene { WindowGroup { GlassHighlightStudy() } } } private enum Experiment: String, CaseIterable, Identifiable { case baseline = "Baseline — L after glass" case interactiveOff = "Interactive off" case wideHost = "Wide host (180 pt)" case labelAndAfter = "L on label + after glass" case rectangleBefore = "Rectangle before glass" case rectangleAfter = "Rectangle after glass" var id: String { rawValue } var hostWidth: CGFloat { self == .wideHost ? 180 : 120 } var isInteractive: Bool { self != .interactiveOff } var explanation: String { switch self { case .baseline: return "120 × 120 pt host. The same L Shape is used by glassEffect and by contentShape after glassEffect." case .interactiveOff: return "Same L, frame, and modifier order as baseline; only interactive is false." case .wideHost: return "180 × 120 pt host. The L remains 120 × 120 pt, centered in the wider host." case .labelAndAfter: return "Baseline plus the same L contentShape on the Button label, before the Button is constructed." case .rectangleBefore: return "Diagnostic mismatch: Rectangle contentShape before glassEffect; the glass remains an L." case .rectangleAfter: return "Diagnostic mismatch: Rectangle contentShape after glassEffect; the glass remains an L." } } } // A fixed-size nonconvex path. Widening its host does not stretch the L. private struct Fixed120GlassL: Shape { func path(in rect: CGRect) -> Path { let origin = CGPoint(x: rect.midX - 60, y: rect.midY - 60) let points: [CGPoint] = [ .zero, CGPoint(x: 120, y: 0), CGPoint(x: 120, y: 42), CGPoint(x: 42, y: 42), CGPoint(x: 42, y: 120), CGPoint(x: 0, y: 120) ] var path = Path() for (index, point) in points.enumerated() { let target = CGPoint(x: origin.x + point.x, y: origin.y + point.y) if index == 0 { path.move(to: target) } else { path.addLine(to: target) } } path.closeSubpath() return path } } private enum HintPosition { case label, before, after } private struct HitShapeHint: ViewModifier { let experiment: Experiment let position: HintPosition @ViewBuilder func body(content: Content) -> some View { switch (experiment, position) { case (.labelAndAfter, .label): content.contentShape(.interaction, Fixed120GlassL()) case (.rectangleBefore, .before), (.rectangleAfter, .after): content.contentShape(.interaction, Rectangle()) case (.baseline, .after), (.interactiveOff, .after), (.wideHost, .after), (.labelAndAfter, .after): content.contentShape(.interaction, Fixed120GlassL()) default: content } } } private struct GlassHighlightStudy: View { @State private var experiment: Experiment = .baseline @State private var actionCount = 0 var body: some View { ZStack { LinearGradient( colors: [Color(red: 0.06, green: 0.14, blue: 0.26), Color(red: 0.38, green: 0.00, blue: 0.25), .black], startPoint: .topLeading, endPoint: .bottomTrailing ).ignoresSafeArea() VStack(spacing: 24) { Text("Custom Glass Highlight") .font(.title2.bold()) Picker("Experiment", selection: $experiment) { ForEach(Experiment.allCases) { item in Text(item.rawValue).tag(item) } } .pickerStyle(.menu) Text(experiment.explanation) .font(.callout) .frame(minHeight: 76, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) // This modifier order is extracted from the earlier causal probe. Button { actionCount += 1 } label: { Fixed120GlassL().fill(Color.white.opacity(0.001)) .frame(width: experiment.hostWidth, height: 120) .modifier(HitShapeHint(experiment: experiment, position: .label)) } .buttonStyle(.plain) .modifier(HitShapeHint(experiment: experiment, position: .before)) .glassEffect(.regular.interactive(experiment.isInteractive), in: Fixed120GlassL()) .modifier(HitShapeHint(experiment: experiment, position: .after)) .overlay { Fixed120GlassL() .stroke(Color.white.opacity(0.35), lineWidth: 0.5) .allowsHitTesting(false) .accessibilityHidden(true) } .frame(width: experiment.hostWidth, height: 120) .frame(width: 220, height: 220) .id(experiment) .accessibilityLabel("L-shaped glass button") Text("Button actions: \(actionCount)") .monospacedDigit() Text("Press and hold the top arm, then the lower arm. Try the empty lower-right cutout. Compare the highlight with the L contour and the action counter.") .font(.callout) .frame(maxWidth: .infinity, alignment: .leading) Spacer(minLength: 0) } .foregroundStyle(.white) .padding(24) } .preferredColorScheme(.dark) .onChange(of: experiment) { _, _ in actionCount = 0 } } }
0
0
82
1d
TimelineView's custom schedule termination
Hello, How should I terminate my custom implementation of TimelineSchedule? Should I return nil or .distantFuture? Is there actual difference? Please find my sample implementation below: struct FixedNumberSchedule: TimelineSchedule { let count: Int func entries( from startDate: Date, mode: Mode ) -> Entries { Entries(startDate: startDate, count: count) } struct Entries: Sequence, IteratorProtocol { var currentDate: Date? let initialCount: Int var count: Int init(startDate: Date,count: Int) { self.currentDate = startDate self.initialCount = count self.count = count } mutating func next() -> Date? { defer { if count > 1 { currentDate?.addTimeInterval(1) count -= 1 } else { self.currentDate = nil } } print("Seq \(initialCount), \(currentDate, default: "none")") return currentDate } } } Here's the catch: For FixedNumberSchedule(count: 1) I get logs in console: Seq 1, 2026-10-03 10:04:33 +0000 Seq 1, none For FixedNumberSchedule(count: 2) I get infinite logs in console: Seq 2, 2026-10-03 10:04:33 +0000 Seq 2, 2026-10-03 10:04:34 +0000 Seq 2, none Seq 2, none ... Side note: the displayed result is the same for both sequences (the last valid element from second sequence is never displayed). So, If the sequence has more than a single entry to return, it actually never ends. Is this a bug? Of course, if the sequence has ended, I could keep the code as simple as possible. But the loop still troubles me, thus my initial question: Should I actually return nil or .distantFuture? BR, Krystian
0
0
258
2d
Live Activity background tint and action colour use light appearance on a dark Lock Screen (iOS 27)
On iOS 27, a Live Activity's activityBackgroundTint and activitySystemActionForegroundColor are sometimes resolved with their light-appearance variants while the Lock Screen is dark. The activity's own content correctly uses dark at the same time. This is corrected the next time the activity is updated. If the tint's light variant is near-white, the system replaces it with its default blue. The Live Activity background and the "Allow Live Activities" prompt turn bright blue. The "Allow"/"Don't Allow" labels also use the action colour's light variant (black instead of white). Observed on the following: iPhone Duo, iOS 27.1: start an activity on a dark wallpaper when the extension's first render is slow. Adding Thread.sleep(forTimeInterval: 1) to the body reproduces it reliably. In a real app, a heavier first render (custom font registration, larger layouts) triggers it some of the time. iPhone 18 Pro, iOS 27.0: start an activity on a light wallpaper, then switch to a dark wallpaper. I have tried an asset catalogue colour, a dynamic UIColor, switching on colorScheme, and an identical update straight after requesting the activity, but all still show blue. Only a fixed dark tint with a fixed white action colour avoids it. I've filed FB25019357 with a sample project attached. Are there any known workarounds?
0
0
138
4d
Revealed keyboard drifts in progressive immersion, and no immersion style gives windows-in-front + Crown + passthrough cutouts
Environment: Apple Vision Pro, visionOS 27.0.1 (24M372). Magic Keyboard, with Settings › Awareness & Safety › Reveal Keyboards set to Always. Our app puts the user in an opaque virtual room while they work at their real desk, using app windows and Mac Virtual Display. A virtual desk is placed on the real desk. It is world-locked: positioned once from a PlaneAnchor (PlaneDetectionProvider), then a plain entity at fixed ImmersiveSpace coordinates. No AnchorEntity or WorldAnchor is involved. We offer two modes: .mixed, and .progressive for Digital Crown control. Each gives us only half of what desk work needs. 1. The revealed keyboard drifts in progressive immersion (FB25006789) In .progressive, the physical keyboard shown by keyboard awareness does not stay registered with the virtual desk. After the keyboard has been out of view for a few seconds (for example, while looking at a Mac Virtual Display window), it looks "floaty" when you look back: as the head turns, it slides against the desk. Turning right, the keyboard moves left farther than the desk does. What we checked: It happens in .progressive at any Crown amount, and in .full. With identical content in .mixed (no app passthrough cutout, so again only keyboard awareness shows the keyboard), it is much steadier. On 27.0.1, PlaneAnchor and DeviceAnchor coordinateSpace(correction: .rendered) are exactly equal to their .none transforms: 0 mm, 0°, scale 1. That held over 835 per-frame samples with ±50° yaw and up to 36 cm of head movement. So the rendered correction gives the app nothing to compensate with. The app's desk transform is constant while the drift is visible. upperLimbVisibility(.automatic) vs .visible and a .portrait progressive aspect ratio make no difference. Possibly related: https://developer.apple.com/forums/thread/796557 (FB19666209). 2. No immersion style combines what desk work needs (FB25006837) We need, at the same time: windows (our own and Mac Virtual Display) never covered by the room's walls, furniture or trees; the Digital Crown to bring the real surroundings back in; a passthrough cutout over the real desk, and a well-registered keyboard. .mixed: ✓ OcclusionMaterial cutouts reveal passthrough. ✓ The keyboard stays registered. ✗ RealityKit entities can render in front of windows, including Mac Virtual Display and window chrome. breakthroughEffect doesn't cover whole windows or Mac Virtual Display. ✗ The Crown can't adjust immersion. .progressive: ✓ Windows always stay in front of immersive content (as an Apple engineer confirmed in https://developer.apple.com/forums/thread/764556). ✓ The Crown adjusts immersion. ✗ OcclusionMaterial reveals nothing inside the portal, so there is no desk cutout. ✗ The keyboard drifts (issue 1). Because of issue 1 we have switched our default back to .mixed, and lost windows-in-front. Questions Is keyboard awareness in .progressive/.full expected to be registered less accurately than in .mixed? Is there a supported way to keep it registered with world-locked content, or to get the pose at which the system draws it? In .mixed, is there a supported way to keep windows (including Mac Virtual Display) in front of an app's immersive content? Or, in .progressive, a supported way to show passthrough in an app-defined region inside the portal? Any pointers or workarounds are appreciated.
0
0
1k
5d
navigationTransition in NavigationStack does not work in sheets in iOS 27
navigationTransition(_ style: some NavigationTransition) in NavigationStack does not work in sheets in iOS 27 and iPadOS 27. NavigationTransitions allow a smooth transition from one view to another. An app perfectly working on iOS/iPadOS 26.x does not work correctly on iOS/iPadOS 27.x We have made a simple sample project to show the issue. On 26 OSes there are smooth transitions, on 27.0 when a navigationTransition should occur after a push in the NavigationStack in a sheet, the sheet becomes invisible (the main screen reappears) and suddenly the new view appears without transition. Sample project in Feedback FB24996035 Sample line with the issue SecondPathView(namespace: namespace, path: $path) .navigationTransition(.zoom(sourceID: transitionFromFirstPathId, in: namespace))
0
0
324
6d
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
123
1w
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
87
1w
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
116
1w
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
376
1w
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
217
1w
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
114
1w
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
50
1w
Question: Task modifier with id called on initial
Hello, I have a question regarding the task(id:) modifier. It is possible, of course, that I am using the modifier for the wrong purpose. Consider a multi-field search scenario where any change to a field triggers a loading process via the task(id:) modifier. The problem is that every task(id:) modifier executes when the view is first loaded, causing the same data to be loaded multiple times—which makes no sense. I do not want to add a separate search button or implement debouncing; those would merely be workarounds to bypass the actual issue. The onChange modifier has an initial parameter that defaults to false, preventing the action from running when the view is first loaded. However, the task(id:) modifier does not have this parameter. How can I prevent all task(id:) modifiers from executing when the view loads? After all, the standard task modifier should handle the initial load. struct ContentView: View { @State private var field1: String = "" @State private var field2: String = "" @State private var field3: String = "" var body: some View { VStack { TextField("val1", text: $field1) TextField("val2", text: $field2) TextField("val3", text: $field3) } .padding() .task { print("task") } .task(id: field1) { print("task 1") } .task(id: field2) { print("task 2") } .task(id: field3) { print("task 3") } } } Christian
Replies
1
Boosts
0
Views
239
Activity
10m
Is it possible to apply the glass effect to the top toolbar?
when I tried ToolbarItemGroup(placement: .title) { VStack { header() }.glassEffect() } the glass effect is only applied to the element itself. And the ToolbarItemGroup does seem to have the glass effect apis either. What I want is for the overall toolbar to have the glass effect, not just the individual elements in the toolbar.
Replies
0
Boosts
0
Views
34
Activity
5h
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
2
Boosts
1
Views
230
Activity
6h
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
1
Boosts
0
Views
53
Activity
6h
Can we opt out of the Horizontal Bar on the iPhone Duo?
We are aware that we can opt out of the vertical bar to make it horizontal for folded and unfolded landscape: https://developer.apple.com/documentation/uikit/uiviewcontroller/preferredverticalbarbehavior As per the title, we would like to know if if the reverse is possible (i.e. can we have the unfolded portrait display a vertical bar instead of a horizontal one)
Replies
1
Boosts
0
Views
53
Activity
7h
[VoiceOver] Dynamic fractions (e.g., ½, 1/2) are completely omitted/skipped in iOS 27
Platform & OS Version: iOS 27 Screen Reader: VoiceOver Framework: SwiftUI / UIKit / WebKit (WKWebView) Issue Summary: When updating dynamic text or UI elements containing fraction values—whether using Unicode fraction glyphs (e.g., ½, ¾) VoiceOver in iOS 27 completely omits and skips reading the fraction value entirely. Steps to Reproduce: Enable VoiceOver on iOS 27 (Settings > Accessibility > VoiceOver). Open an app or web view where fraction values, tap to focus on the element or trigger voice over Expected Result: VoiceOver should speak the accessible label or text content (e.g., "one half") Actual Result: VoiceOver skips the fraction entirely, reading only surrounding text or remaining silent over the fraction value.
Replies
0
Boosts
0
Views
29
Activity
11h
SwiftUI interactive Liquid Glass highlight boundary for a custom L shape
Full code following thread 826485. Feedback: FB25055820. Earlier calibrated Lab recordings on an iPhone 17 Pro Simulator (iOS 27.2 Beta 1, 24B5084k; Xcode 27.2, 27B5019j) showed a round press highlight inside the L, including on its empty cutout where the Button action did not fire. Interactive off removed it; a wider host widened it without stretching the L. These are Simulator observations. This extracted English project compiled, but has not been newly run or optically recorded. The baseline already uses the SAME L contentShape after glassEffect. Other matching-L placements are not established failures. Which public API controls the highlight boundary, and where should contentShape go? Are nonconvex shapes supported? Hold each arm, then the cutout; compare the highlight and action counter. Default uses the recorded baseline order. import SwiftUI @main struct CustomGlassHighlightApp: App { var body: some Scene { WindowGroup { GlassHighlightStudy() } } } private enum Experiment: String, CaseIterable, Identifiable { case baseline = "Baseline — L after glass" case interactiveOff = "Interactive off" case wideHost = "Wide host (180 pt)" case labelAndAfter = "L on label + after glass" case rectangleBefore = "Rectangle before glass" case rectangleAfter = "Rectangle after glass" var id: String { rawValue } var hostWidth: CGFloat { self == .wideHost ? 180 : 120 } var isInteractive: Bool { self != .interactiveOff } var explanation: String { switch self { case .baseline: return "120 × 120 pt host. The same L Shape is used by glassEffect and by contentShape after glassEffect." case .interactiveOff: return "Same L, frame, and modifier order as baseline; only interactive is false." case .wideHost: return "180 × 120 pt host. The L remains 120 × 120 pt, centered in the wider host." case .labelAndAfter: return "Baseline plus the same L contentShape on the Button label, before the Button is constructed." case .rectangleBefore: return "Diagnostic mismatch: Rectangle contentShape before glassEffect; the glass remains an L." case .rectangleAfter: return "Diagnostic mismatch: Rectangle contentShape after glassEffect; the glass remains an L." } } } // A fixed-size nonconvex path. Widening its host does not stretch the L. private struct Fixed120GlassL: Shape { func path(in rect: CGRect) -> Path { let origin = CGPoint(x: rect.midX - 60, y: rect.midY - 60) let points: [CGPoint] = [ .zero, CGPoint(x: 120, y: 0), CGPoint(x: 120, y: 42), CGPoint(x: 42, y: 42), CGPoint(x: 42, y: 120), CGPoint(x: 0, y: 120) ] var path = Path() for (index, point) in points.enumerated() { let target = CGPoint(x: origin.x + point.x, y: origin.y + point.y) if index == 0 { path.move(to: target) } else { path.addLine(to: target) } } path.closeSubpath() return path } } private enum HintPosition { case label, before, after } private struct HitShapeHint: ViewModifier { let experiment: Experiment let position: HintPosition @ViewBuilder func body(content: Content) -> some View { switch (experiment, position) { case (.labelAndAfter, .label): content.contentShape(.interaction, Fixed120GlassL()) case (.rectangleBefore, .before), (.rectangleAfter, .after): content.contentShape(.interaction, Rectangle()) case (.baseline, .after), (.interactiveOff, .after), (.wideHost, .after), (.labelAndAfter, .after): content.contentShape(.interaction, Fixed120GlassL()) default: content } } } private struct GlassHighlightStudy: View { @State private var experiment: Experiment = .baseline @State private var actionCount = 0 var body: some View { ZStack { LinearGradient( colors: [Color(red: 0.06, green: 0.14, blue: 0.26), Color(red: 0.38, green: 0.00, blue: 0.25), .black], startPoint: .topLeading, endPoint: .bottomTrailing ).ignoresSafeArea() VStack(spacing: 24) { Text("Custom Glass Highlight") .font(.title2.bold()) Picker("Experiment", selection: $experiment) { ForEach(Experiment.allCases) { item in Text(item.rawValue).tag(item) } } .pickerStyle(.menu) Text(experiment.explanation) .font(.callout) .frame(minHeight: 76, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) // This modifier order is extracted from the earlier causal probe. Button { actionCount += 1 } label: { Fixed120GlassL().fill(Color.white.opacity(0.001)) .frame(width: experiment.hostWidth, height: 120) .modifier(HitShapeHint(experiment: experiment, position: .label)) } .buttonStyle(.plain) .modifier(HitShapeHint(experiment: experiment, position: .before)) .glassEffect(.regular.interactive(experiment.isInteractive), in: Fixed120GlassL()) .modifier(HitShapeHint(experiment: experiment, position: .after)) .overlay { Fixed120GlassL() .stroke(Color.white.opacity(0.35), lineWidth: 0.5) .allowsHitTesting(false) .accessibilityHidden(true) } .frame(width: experiment.hostWidth, height: 120) .frame(width: 220, height: 220) .id(experiment) .accessibilityLabel("L-shaped glass button") Text("Button actions: \(actionCount)") .monospacedDigit() Text("Press and hold the top arm, then the lower arm. Try the empty lower-right cutout. Compare the highlight with the L contour and the action counter.") .font(.callout) .frame(maxWidth: .infinity, alignment: .leading) Spacer(minLength: 0) } .foregroundStyle(.white) .padding(24) } .preferredColorScheme(.dark) .onChange(of: experiment) { _, _ in actionCount = 0 } } }
Replies
0
Boosts
0
Views
82
Activity
1d
TimelineView's custom schedule termination
Hello, How should I terminate my custom implementation of TimelineSchedule? Should I return nil or .distantFuture? Is there actual difference? Please find my sample implementation below: struct FixedNumberSchedule: TimelineSchedule { let count: Int func entries( from startDate: Date, mode: Mode ) -> Entries { Entries(startDate: startDate, count: count) } struct Entries: Sequence, IteratorProtocol { var currentDate: Date? let initialCount: Int var count: Int init(startDate: Date,count: Int) { self.currentDate = startDate self.initialCount = count self.count = count } mutating func next() -> Date? { defer { if count > 1 { currentDate?.addTimeInterval(1) count -= 1 } else { self.currentDate = nil } } print("Seq \(initialCount), \(currentDate, default: "none")") return currentDate } } } Here's the catch: For FixedNumberSchedule(count: 1) I get logs in console: Seq 1, 2026-10-03 10:04:33 +0000 Seq 1, none For FixedNumberSchedule(count: 2) I get infinite logs in console: Seq 2, 2026-10-03 10:04:33 +0000 Seq 2, 2026-10-03 10:04:34 +0000 Seq 2, none Seq 2, none ... Side note: the displayed result is the same for both sequences (the last valid element from second sequence is never displayed). So, If the sequence has more than a single entry to return, it actually never ends. Is this a bug? Of course, if the sequence has ended, I could keep the code as simple as possible. But the loop still troubles me, thus my initial question: Should I actually return nil or .distantFuture? BR, Krystian
Replies
0
Boosts
0
Views
258
Activity
2d
in iPadOS remove New window from Window top menu bar in Swift UI
Hi, I have an univesal iOS app that supports multiple windows but i dont want the user to create the new window from WIndow top menu bar in iPad. I will create the windows from the main app using openWindow. Is there a way i can hide the New Window in the Window Menu top bar? Thanks in advance
Replies
0
Boosts
0
Views
286
Activity
4d
Live Activity background tint and action colour use light appearance on a dark Lock Screen (iOS 27)
On iOS 27, a Live Activity's activityBackgroundTint and activitySystemActionForegroundColor are sometimes resolved with their light-appearance variants while the Lock Screen is dark. The activity's own content correctly uses dark at the same time. This is corrected the next time the activity is updated. If the tint's light variant is near-white, the system replaces it with its default blue. The Live Activity background and the "Allow Live Activities" prompt turn bright blue. The "Allow"/"Don't Allow" labels also use the action colour's light variant (black instead of white). Observed on the following: iPhone Duo, iOS 27.1: start an activity on a dark wallpaper when the extension's first render is slow. Adding Thread.sleep(forTimeInterval: 1) to the body reproduces it reliably. In a real app, a heavier first render (custom font registration, larger layouts) triggers it some of the time. iPhone 18 Pro, iOS 27.0: start an activity on a light wallpaper, then switch to a dark wallpaper. I have tried an asset catalogue colour, a dynamic UIColor, switching on colorScheme, and an identical update straight after requesting the activity, but all still show blue. Only a fixed dark tint with a fixed white action colour avoids it. I've filed FB25019357 with a sample project attached. Are there any known workarounds?
Replies
0
Boosts
0
Views
138
Activity
4d
Revealed keyboard drifts in progressive immersion, and no immersion style gives windows-in-front + Crown + passthrough cutouts
Environment: Apple Vision Pro, visionOS 27.0.1 (24M372). Magic Keyboard, with Settings › Awareness & Safety › Reveal Keyboards set to Always. Our app puts the user in an opaque virtual room while they work at their real desk, using app windows and Mac Virtual Display. A virtual desk is placed on the real desk. It is world-locked: positioned once from a PlaneAnchor (PlaneDetectionProvider), then a plain entity at fixed ImmersiveSpace coordinates. No AnchorEntity or WorldAnchor is involved. We offer two modes: .mixed, and .progressive for Digital Crown control. Each gives us only half of what desk work needs. 1. The revealed keyboard drifts in progressive immersion (FB25006789) In .progressive, the physical keyboard shown by keyboard awareness does not stay registered with the virtual desk. After the keyboard has been out of view for a few seconds (for example, while looking at a Mac Virtual Display window), it looks "floaty" when you look back: as the head turns, it slides against the desk. Turning right, the keyboard moves left farther than the desk does. What we checked: It happens in .progressive at any Crown amount, and in .full. With identical content in .mixed (no app passthrough cutout, so again only keyboard awareness shows the keyboard), it is much steadier. On 27.0.1, PlaneAnchor and DeviceAnchor coordinateSpace(correction: .rendered) are exactly equal to their .none transforms: 0 mm, 0°, scale 1. That held over 835 per-frame samples with ±50° yaw and up to 36 cm of head movement. So the rendered correction gives the app nothing to compensate with. The app's desk transform is constant while the drift is visible. upperLimbVisibility(.automatic) vs .visible and a .portrait progressive aspect ratio make no difference. Possibly related: https://developer.apple.com/forums/thread/796557 (FB19666209). 2. No immersion style combines what desk work needs (FB25006837) We need, at the same time: windows (our own and Mac Virtual Display) never covered by the room's walls, furniture or trees; the Digital Crown to bring the real surroundings back in; a passthrough cutout over the real desk, and a well-registered keyboard. .mixed: ✓ OcclusionMaterial cutouts reveal passthrough. ✓ The keyboard stays registered. ✗ RealityKit entities can render in front of windows, including Mac Virtual Display and window chrome. breakthroughEffect doesn't cover whole windows or Mac Virtual Display. ✗ The Crown can't adjust immersion. .progressive: ✓ Windows always stay in front of immersive content (as an Apple engineer confirmed in https://developer.apple.com/forums/thread/764556). ✓ The Crown adjusts immersion. ✗ OcclusionMaterial reveals nothing inside the portal, so there is no desk cutout. ✗ The keyboard drifts (issue 1). Because of issue 1 we have switched our default back to .mixed, and lost windows-in-front. Questions Is keyboard awareness in .progressive/.full expected to be registered less accurately than in .mixed? Is there a supported way to keep it registered with world-locked content, or to get the pose at which the system draws it? In .mixed, is there a supported way to keep windows (including Mac Virtual Display) in front of an app's immersive content? Or, in .progressive, a supported way to show passthrough in an app-defined region inside the portal? Any pointers or workarounds are appreciated.
Replies
0
Boosts
0
Views
1k
Activity
5d
navigationTransition in NavigationStack does not work in sheets in iOS 27
navigationTransition(_ style: some NavigationTransition) in NavigationStack does not work in sheets in iOS 27 and iPadOS 27. NavigationTransitions allow a smooth transition from one view to another. An app perfectly working on iOS/iPadOS 26.x does not work correctly on iOS/iPadOS 27.x We have made a simple sample project to show the issue. On 26 OSes there are smooth transitions, on 27.0 when a navigationTransition should occur after a push in the NavigationStack in a sheet, the sheet becomes invisible (the main screen reappears) and suddenly the new view appears without transition. Sample project in Feedback FB24996035 Sample line with the issue SecondPathView(namespace: namespace, path: $path) .navigationTransition(.zoom(sourceID: transitionFromFirstPathId, in: namespace))
Replies
0
Boosts
0
Views
324
Activity
6d
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
123
Activity
1w
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
87
Activity
1w
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
393
Activity
1w
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
116
Activity
1w
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
376
Activity
1w
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
217
Activity
1w
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
114
Activity
1w
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
50
Activity
1w