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

SwiftUI Documentation

Posts under SwiftUI subtopic

Post

Replies

Boosts

Views

Activity

TextKit 2 + SwiftUI (NSViewRepresentable): NSTextLayoutManager rendering attributes don’t reliably draw/update
I’m embedding an NSTextView (TextKit 2) inside a SwiftUI app using NSViewRepresentable. I’m trying to highlight dynamic subranges (changing as the user types) by providing per-range rendering attributes via NSTextLayoutManager’s rendering-attributes mechanism. The issue: the highlight is unreliable. Often, the highlight doesn’t appear at all even though the delegate/data source is returning attributes for the expected range. Sometimes it appears once, but then it stops updating even when the underlying “highlight range” changes. This feels related to SwiftUI - AppKit layout issue when using NSViewRepresentable (as said in https://developer.apple.com/documentation/swiftui/nsviewrepresentable). What I’ve tried Updating the state that drives the highlight range and invalidating layout fragments / asking for relayout Ensuring all updates happen on the main thread. Calling setNeedsDisplay(_:) on the NSViewRepresentable’s underlying view. Toggling the SwiftUI view identity (e.g. .id(...)) to force reconstruction (works, but too expensive / loses state). Question In a SwiftUI + NSViewRepresentable setup with TextKit 2, what is the correct way to make NSTextLayoutManager re-query and redraw rendering attributes when my highlight ranges change? Is there a recommended invalidation call for TextKit 2 to trigger re-rendering of rendering attributes? Or is this a known limitation when hosting NSTextView inside SwiftUI, where rendering attributes aren’t reliably invalidated? If this approach is fragile, is there a better pattern for dynamic highlights that avoids mutating the attributed string (to prevent layout/scroll jitter)?
4
0
374
3h
Handling View Creation for Heterogeneous Data
In my project (an Package), I have created an Manager (can be classified as an ViewModel) that will handle state updates throughout the Package Component view: Note: The code is simplified for better understanding and to focus on principles behind things I did. The manager does complex things during state updates. public class ComponentManager: ObservedObject { @Published var rows: [any RowProtocol] = [] func updateState(_ newState: any RowProtocolData, id: String) { guard let index = rows.firstIndex(where: { $0.id == id }) else { return } rows[index].updateState(newState) } func getState(id: String) -> any RowProtocolData? { guard let index = rows.firstIndex(where: { $0.id == id }) else { return nil } return rows[index].state } } The RowProtocol is defined as follows: public protocol RowStateProtocol {} public protocol RowProtocol: Identifiable { associatedtype State: RowStateProtocol associatedtype RowView: View var id: String { get } var state: State { get } func updateState(_ newState: State) @MainActor @ViewBuilder func renderRow() -> RowView } extension RowProtocol { func updateState(_ newState: any RowProtocolData) { guard let newState = newState as? State else { return } self.updateState(newState) } } Then in Component View, I need to render the rows based on the underlying type of the row, this where the renderRow() comes in: struct ComponentView: View { @ObservedObject var manager: ComponentManager var body: some View { List { ForEach(manager.rows, id: \.id) { row in HStack { // This HStack prevent List from initing all rows due to AnyView. AnyView(row.renderRow()) } } } } } The row views will be accepting binding to the state of the row and update their state, let says we have a TextRow and a ToggleRow: struct TextRow: RowProtocol { var id: String var state: TextRowState func updateState(_ newState: TextRowState) { self.state = newState } } struct ToggleRow: RowProtocol { var id: String var state: ToggleRowState func updateState(_ newState: ToggleRowState) { self.state = newState } } In this, offcourse we cannot create an binding directly to the state of the row, since the state are through the manager and the row data won't have access to the manager. So I created an property wrapped that use the closures passed by the manager into environment to create the binding and an view that will give the binding to the content view: extenstion EnvironmentValues { @Entry internal var getState: (String) -> any RowStateProtocol? @Entry internal var updateState: (any RowStateProtocol, String) -> Void } @propertyWrapper struct RowStateBinding<State: RowStateProtocol & Equatable>: DynamicProperty { @Environment(\.getState) private var getState @Environment(\.updateState) private var updateState private let id: String init(id: String) { self.id = id } var wrappedValue: State { get { getState(id) as! State } nonmutating set { if wrappedValue != newValue { // only update for an new change, since set can be triggered for any number of reasons. updateState(newValue, id) } } } var projectedValue: Binding<State> { Binding( get: { self.wrappedValue }, set: { newValue in self.wrappedValue = newValue } ) } } struct RowStateBindingView<Content: View, State: RowStateProtocol & Equatable>: View { @RowStateBinding<State> private var state: State private let content: (Binding<State>) -> Content init(id: String, @ViewBuilder content: @escaping (Binding<State>) -> Content) { self._state = RowStateBinding(id: id) self.content = content } var body: some View { content($state) } } and in the renderRows: struct TextRowView: View { @Binding var text: TextRowState var body: some View { TextField("Enter text", text: $text.text) } } extension TextRow { func renderRow() -> some View { RowStateBindingView(id: id) { state in TextField("Enter text", text: state.text) } } } struct ToggleRowView: View { @Binding var state: ToggleRowState var body: some View { Toggle("Toggle", isOn: $state.isOn) } } extension ToggleRow { func renderRow() -> some View { RowStateBindingView(id: id) { state in Toggle("Toggle", isOn: state.isOn) } } } This way, I can adopt any view as an row view and most importantly, the view can be completely independent of the manager and used as an standalone view. Also clients of the library can create their own custom rows by just conforming to the RowProtocol and creating the view for it, without worrying about how the state management works. The manager will handle all the state updates. I prefer using stucts over classes for rows and states, since its easier to manage state updates. What do you think about this approach? Do you see any potential issues with this? Is there a better way to achieve this?
0
0
15
14h
SwiftUI ​Charts: In iOS 27, annotation overlays exceed the bounds of an annotation
I'm seeing a regression in SwiftUI Charts on iOS 27 beta 1. Any view placed inside a BarMark's overlay annotation no longer receives the size of the parent BarMark. It collapses to zero, so any content sized from geo.size (e.g. a Rectangle meant to fill the bar) renders empty or incorrectly. Expected: The GeometryReader reports the BarMark's rendered width/height, and the Rectangle fills the BarMark (this is the behavior in iOS 26 and earlier). Actual: On iOS 27 beta 1, geo.size is effectively zero, so the overlay content has an extremely small size. I suspect this could be a small bug with the new ContentBuilder / ViewBuilder changes but that's just a hunch. Here's a code sample which reproduces the issue. // MARK: - Mock Data Models struct ScheduleSeries: Identifiable { let id = UUID() let data: [ScheduleItem] } struct ScheduleItem: Identifiable { let id = UUID() let startDate: Date let startHour: Double let endHour: Double let secondaryText: String? } // MARK: - Minimal Reproducible Example struct ContentView: View { // Generate two consecutive days for the mock data let mockSchedule: [ScheduleSeries] = [ ScheduleSeries(data: [ ScheduleItem( startDate: Date(), startHour: 9.0, endHour: 11.5, secondaryText: "Morning Event" ), ScheduleItem( startDate: Calendar.current.date(byAdding: .day, value: 1, to: Date())!, startHour: 13.0, endHour: 16.0, secondaryText: "Afternoon Event" ) ]) ] var body: some View { VStack(alignment: .leading) { Text("FB: Annotation Sizing Bug") .font(.headline) .padding(.bottom, 8) Text("Expected: The gray Rectangle should stretch to fill the BarMark.\nActual: GeometryReader/Annotation fails to size to the parent BarMark.") .font(.caption) .foregroundColor(.secondary) .padding(.bottom) Chart(mockSchedule) { series in ForEach(series.data, id: \.startDate) { element in BarMark( x: .value("Day", element.startDate, unit: .day, calendar: .current), yStart: .value("Start", element.startHour), yEnd: .value("End", element.endHour), width: .ratio(0.99) ) .annotation(position: .overlay, alignment: .topLeading) { item in ZStack { VStack(alignment: .leading, spacing: 0) { // BUG DEMONSTRATION: // This GeometryReader and Rectangle previously filled the BarMark, but in Xcode 27 it does not GeometryReader { geo in Rectangle() .fill(Color.black.opacity(0.15)) .frame(width: geo.size.width, height: geo.size.height) } } .foregroundColor(.white) .font(.caption2) } } } } .chartYScale(domain: 0...24) // Lock the Y-axis to a 24-hour scale } .padding() } } Environment: Xcode 27 beta 1 / iOS 27 beta 1 Reproduces on device and Simulator Worked as expected on iOS 26 and earlier Here's what the issue looks like in our app with zero code changes: iOS 26 iOS 27 I've filed a feedback report (FB23016343) with a sample project attached. Has anyone else hit this, or found a workaround for sizing overlay annotation content to a BarMark in iOS 27? Thanks!
0
0
15
17h
How to detect backspace in SwiftUI TextField without falling back to UIViewRepresentable?
I'm building a multi-box PIN/OTP input in SwiftUI. In UIKit, I used UITextFieldDelegate to detect backspace presses on an empty field to move focus backward. SwiftUI’s .onChange(of: text) only triggers when text is actually deleted, completely missing backspaces on an already empty field. Is there a pure SwiftUI way to handle this now, or are we still forced to wrap UITextField via UIViewRepresentable?
1
0
41
22h
Source view disappearing when interrupting a zoom navigation transition
When I use the .zoom transition in a navigation stack, I get a glitch when interrupting the animation by swiping back before it completes. When doing this, the source view disappears. I can still tap it to trigger the navigation again, but its not visible on screen. This seems to be a regression in iOS 26, as it works as expected when testing on iOS 18. Has someone else seen this issue and found a workaround? Is it possible to disable interrupting the transition? Filed a feedback on the issue FB19601591 Screen recording: https://share.icloud.com/photos/04cio3fEcbR6u64PAgxuS2CLQ Example code @State var showDetail = false @Namespace var namespace var body: some View { NavigationStack { ScrollView { showDetailButton } .navigationTitle("Title") .navigationBarTitleDisplayMode(.inline) .navigationDestination(isPresented: $showDetail) { Text("Detail") .navigationTransition(.zoom(sourceID: "zoom", in: namespace)) } } } var showDetailButton: some View { Button { showDetail = true } label: { Text("Show detail") .padding() .background(.green) .matchedTransitionSource(id: "zoom", in: namespace) } } }
Topic: UI Frameworks SubTopic: SwiftUI
20
25
2.3k
1d
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.
0
1
46
1d
Is there a better way to hide a view in a custom Layout other than placing it off-screen?
I have a custom Layout that places a number of labels for a cell footer in a certain way based on the available width that needs to conditionally hide those views that do not entirely fit anymore (based on some priorities I specify). Currently I simply move the subviews that do not fit anymore off-screen and use clipping to hide them outside the layout, as I did not find an "official" way to hide / exclude a subview from a Layout. Does anyone know a better / nicer way to do this in SwiftUI?
Topic: UI Frameworks SubTopic: SwiftUI
0
0
15
1d
Pass data to an @Observable model
Overview I have a navigation split view. The detail view contains a model now this model depends on id from the parent view. Questions How can I pass data from the parent view and yet create the view in the detail view? Or should I be pass the model from the parent view, but the problem is the parent view needs to persist model. Or is there a better approach?
0
0
26
1d
AsyncRenderer stack limit
We've been getting stack overflows in code we don't control, in the background AsyncRenderer thread in a chain of calls to updateInheritedViewAsync. But the stack is less than 200 calls deep, presumably because it's a background thread with a smaller stack limit. Is it possible to adjust AsyncRenderer's stack limit? Or otherwise, what limits should we be aware of to prevent running into this issue? com.apple.SwiftUI.AsyncRenderer: EXC_BAD_ACCESS (code=2, address=0x16f5ebe30) #0 0x000000019c6b4460 in function signature specialization <Arg[3] = Dead> of static SwiftUI.DisplayList.ViewUpdater.Model.merge(item: inout SwiftUI.DisplayList.Item, index: SwiftUI.DisplayList.Index, into: inout SwiftUI.DisplayList.ViewUpdater.Model.State) -> SwiftUI.DisplayList.ViewUpdater.Model.MergedViewRequirements () #1 0x000000019c7c2850 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #2 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #3 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #4 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () ... #147 0x000000019c7c364c in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #148 0x000000019c7c2074 in SwiftUI.DisplayList.ViewUpdater.updateAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldList: SwiftUI.DisplayList, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newList: SwiftUI.DisplayList, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #149 0x000000019c7c1a78 in renderAsync () #150 0x000000019c60fc68 in renderDisplayList () #151 0x000000019c612094 in protocol witness for SwiftUI.ViewGraphRenderHost.renderDisplayList(_: SwiftUI.DisplayList, asynchronously: Swift.Bool, time: SwiftUI.Time, nextTime: SwiftUI.Time, targetTimestamp: Swift.Optional<SwiftUI.Time>, version: SwiftUI.DisplayList.Version, maxVersion: SwiftUI.DisplayList.Version) -> SwiftUI.Time in conformance SwiftUI.ViewGraph : SwiftUI.ViewGraphRenderHost in SwiftUI () #152 0x000000019c7c0dd0 in renderAsync () #153 0x000000019c7be6c8 in SwiftUI.ViewGraphHost.displayLinkTimer(timestamp: SwiftUI.Time, targetTimestamp: SwiftUI.Time, isAsyncThread: Swift.Bool) -> () () #154 0x000000019c7beab8 in SwiftUI.ViewGraphDisplayLink.displayLinkTimer(__C.CADisplayLink) -> () () #155 0x000000019c7be5a8 in @objc SwiftUI.ViewGraphDisplayLink.displayLinkTimer(__C.CADisplayLink) -> () () #156 0x0000000192fdbb24 in CA::Display::DisplayLinkItem::dispatch_ () #157 0x0000000192fb9164 in CA::Display::DisplayLink::dispatch_items () #158 0x0000000192f91870 in display_timer_callback () #159 0x000000019256d4cc in __CFMachPortPerform () #160 0x000000019259d0b0 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ () #161 0x000000019259cfd8 in __CFRunLoopDoSource1 () #162 0x0000000192574c1c in __CFRunLoopRun () #163 0x0000000192573a6c in _CFRunLoopRunSpecificWithOptions () #164 0x0000000190533f54 in -[NSRunLoop(NSRunLoop) runMode:beforeDate:] () #165 0x000000018fb9a51c in -[NSRunLoop(NSRunLoop) run] () #166 0x000000019c7cd5b0 in function signature specialization <Arg[1] = Dead> of static SwiftUI.ViewGraphDisplayLink.asyncThread(arg: Swift.Optional<Any>) -> () () #167 0x000000019c7cd288 in @objc static SwiftUI.ViewGraphDisplayLink.asyncThread(arg: Swift.Optional<Any>) -> () () #168 0x000000018fbf321c in __NSThread__start__ () #169 0x00000001ef0d044c in _pthread_start ()
Topic: UI Frameworks SubTopic: SwiftUI
2
1
83
1d
Making View's init nonisolated with environment variables
Views with custom nonisolated init fail to compile when using @MainActor-isolated @Environment properties (e.g., \.openURL, \.dismiss). From the Swift 6 migration documentation, it seems encouraged to define inits nonisolated, and it seems base SwiftUI components also have their inits defined as nonisolated (e.g. TimelineView, LazyHStack, etc), which makes sense. How do you achieve marking a SwiftUI View's init as nonisolated when it uses environment variables, without producing the following build error "Main actor-isolated default value of 'self.openURL' cannot be used in a nonisolated initalizer" ? It seems @State variable defined in a view has the ability to be set in a nonisolated init but not @Environment. What mechanism prevents this under the hood ?
Topic: UI Frameworks SubTopic: SwiftUI
7
0
92
1d
How to get the glassy picker showed at WWD27
In the session "What’s new in SwiftUI", at 02:38 Steven shows various Liquid Glass components, among them, a very pretty interactive glassy Picker. At first I thought this was built into the system and tried everything possible, until to rewatch the presentation and notice this: "On macOS, like on iOS, you can mark Liquid Glass custom elements as "interactive" so they respond more fluidly to user's clicks. And this is optimized to work great with the mouse pointer, so it feels right at home on the Mac." Does anyone have pointers to where one can find a Picker that implements all these system behaviors that folks have come to expect? I have my own version, which is deeply lacking, and so is every other implementation out there that I saw, and I thought sprinkling some "interactive" as instructed would help, but it barely made a dent.
Topic: UI Frameworks SubTopic: SwiftUI
2
0
104
1d
Dynamic layouts with multi-line Text views
For a dynamic layout reacting to width changes using ViewThatFits, I need a 2-line Text view where content gets wrapped exactly once, but doesn't get abbreviated. If the text is too long to fit in two lines, ViewThatFits should choose the next layout. I tried to use .lineLimit(2, reservesSpace: true), but then ViewThatFits always takes this layout even when the text content gets too long to fit, so it gets abbreviated with "...", instead of choosing the next layout. How else can I build a layout with a two-line Text, which does signal to ViewThatFits if the space doesn't fit the entire text so it can choose the next layout?
Topic: UI Frameworks SubTopic: SwiftUI
2
0
59
1d
How to avoid trailing toolbar item re-rendering on child navigation
I have a NavigationStack where every child view except the root has a trailing close button. On each child view transition, the button re-renders itself. Is there a way to fix the button? I saw in the Apple Developer app the behavior I want when registering a lab and the confirmation screen, but I think the implementation is just changing the center content of the view and it isn't pushing a new view to the stack path that has different toolbar items. Root: VStack { Content Next button --> Page 2 } Cancel button leading edge Page 2: VStack { Copy Next button --> page 3 } built in back button leading edge cancel button trailing edge Page 3: VStack { Copy Finish button --> dismisses whole workflow } built in back button leading edge cancel button trailing edge So on page 3, the cancel button is new. I can't figure out how to not have the glass effect animate it in new. I want the 'same' cancel button to be there. This is an oversimplification of a resumable form where the user can cancel (save and resume) at any time. Is there a built in way to have the trailing edge button be fixed? Would moving where the button is defined make a different and expose a way for each child view to propagate upwards if the cancel button should be shown or not? Updated with sample, assumes iOS 18 + 26 code so using .topTrailing not new 'action' placement: import SwiftUI struct Demo: View { @State private var path: [String] = [] var body: some View { NavigationStack(path: $path) { Button("Go") { path.append("Second") } .navigationDestination(for: String.self) { destination in switch destination { case "Second": VStack { Text("Second") Button("Next") { path.append("Third") } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", role: .cancel) { path = [] } } } case "Third": VStack { Text("Third") Button("Finish") { path = [] } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", role: .cancel) { path = [] } } } default: Text("Undefined") } } } } }
Topic: UI Frameworks SubTopic: SwiftUI
2
0
43
1d
NSViewRepresentable updates triggered by .onChange ignore SwiftUI Transactions on macOS
I am encountering a systemic issue on macOS where NSViewRepresentable (and some native container views like Table) completely discard explicit SwiftUI animations when the state change is handled via an .onChange modifier. While the exact same reactive architecture produces fluid animations on iOS, the AppKit bridge on macOS snaps the frame updates instantly. I have filed a formal bug report for this behavior, but I want to open this up to the community to see if anyone has found a cleaner architectural workaround. The Problem When observing a state change (e.g., via @AppStorage, @SceneStorage, or local state) using .onChange, applying a withAnimation block fails to animate the underlying layer changes in an AppKit representable view. // The Reactive Pattern that breaks on macOS .onChange(of: toggle) { newValue in withAnimation(.easeInOut(duration: 0.5)) { self.targetColor = newValue ? .systemBlue : .systemRed } } The Diagnostic Anomaly If you inspect context.transaction inside the updateNSView(_:context:) method during this lifecycle pass, SwiftUI reports that the transaction is animated: func updateNSView(_ nsView: NSView, context: Context) { // Prints 'true', indicating SwiftUI thinks it's animating print("Is Animated: \(context.transaction.animation != nil)") // Result: Snaps instantly. No animation occurs. nsView.layer?.backgroundColor = targetColor.cgColor } Why It Happens (The Double-Commit) It appears that on macOS, .onChange flushes a static layout transaction to the window layer immediately upon the state mutating. By the time the withAnimation block evaluates inside the closure, the AppKit backing layer has already processed a implicit setDisableActions(true) directive. The GPU pipeline for that transaction frame is effectively closed, despite what the context.transaction metadata claims. The Low-Level Workaround To force the AppKit bridge to respect the animation intent, I have to manually drop into Core Animation inside updateNSView and explicitly veto SwiftUI's action-disabling behavior: func updateNSView(_ nsView: NSView, context: Context) { CATransaction.begin() if context.transaction.animation != nil { // Explicitly override SwiftUI's implicit frame lock CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.5) // Hardcoded fallback match } else { CATransaction.setDisableActions(true) } nsView.layer?.backgroundColor = targetColor.cgColor CATransaction.commit() } My Questions: Is this intentional behavior due to how AppKit's layer-backed architectures handle frame integrity vs. iOS's fluid layout engine? Has anyone found a way to bridge SwiftUI's Animation type curves (like .spring()) cleanly down into the CATransaction or NSAnimationContext layer without hardcoding durations inside updateNSView? Is there a purely "Reactive" paradigm that avoids mutating state at the primary action source (e.g., forcing a Button to own the animation logic) while maintaining fluid transitions on macOS?
Topic: UI Frameworks SubTopic: SwiftUI
1
0
45
1d
ForEach with calculations between each cell
I'm attempting to add a TimeInterval calculated at each step of a ForEach to a Date (hr & min) of the previous cell. For each cell this works: Start Time = 12:55 Delta interval = 3600 seconds (1hr) Next time = 1:55 I want to use that Next time as the start one for the next cell - instead what I have working is Delta interval = 30 (5 min) Next time = 1:00 but what I want is 2:00 (12:55 + 1hr + 5min) I'm just having trouble thinking through how to do that. Thanks!
Topic: UI Frameworks SubTopic: SwiftUI
2
0
40
1d
Custom PinnedScrollableViews
In a simple scenario, it's possible to have a ScrollView that contains a LazyVStack where the headers or footers of Section gets pinned to the top safe area. In a more complicated scenario, where The ScrollView needs to ignore the top safe area The navigation bar is hidden until a certain view, call it Foo, has gone behind what used to be the top safe area, i.e., only about 100p of the view is visible which would be invisible if the navigation bar wasn't transparent/hidden. The navigation bar becomes visible once Foo is scrolled up to a threshold that was explained earlier A second view, call is Bar, that's positioned below Foo, should never go behind the navigation bar, i.e., it should be pinned to the bottom of the navigation bar I managed to achieve this behaviour by having a State for the ignored safe area edges that gets updated based on how far Foo is scrolled. However, I get a jumpy behaviour because the ScrollView tries to adjust its contentOffset/contentInset based on the new top safe area. This jumpy behaviour is very hard to compensate especially when user scrolls very fast. In UIKit, I could achieve this behaviour by overriding viewSafeAreaInsetsDidChangeand tweaking the contentInset, and contentOffset on a collection/table view. Maybe I need to rethink my approach in a declarative way that aligns better with SwiftUI. Any thoughts on how to achieve this?
Topic: UI Frameworks SubTopic: SwiftUI
4
1
55
1d
TextKit 2 + SwiftUI (NSViewRepresentable): NSTextLayoutManager rendering attributes don’t reliably draw/update
I’m embedding an NSTextView (TextKit 2) inside a SwiftUI app using NSViewRepresentable. I’m trying to highlight dynamic subranges (changing as the user types) by providing per-range rendering attributes via NSTextLayoutManager’s rendering-attributes mechanism. The issue: the highlight is unreliable. Often, the highlight doesn’t appear at all even though the delegate/data source is returning attributes for the expected range. Sometimes it appears once, but then it stops updating even when the underlying “highlight range” changes. This feels related to SwiftUI - AppKit layout issue when using NSViewRepresentable (as said in https://developer.apple.com/documentation/swiftui/nsviewrepresentable). What I’ve tried Updating the state that drives the highlight range and invalidating layout fragments / asking for relayout Ensuring all updates happen on the main thread. Calling setNeedsDisplay(_:) on the NSViewRepresentable’s underlying view. Toggling the SwiftUI view identity (e.g. .id(...)) to force reconstruction (works, but too expensive / loses state). Question In a SwiftUI + NSViewRepresentable setup with TextKit 2, what is the correct way to make NSTextLayoutManager re-query and redraw rendering attributes when my highlight ranges change? Is there a recommended invalidation call for TextKit 2 to trigger re-rendering of rendering attributes? Or is this a known limitation when hosting NSTextView inside SwiftUI, where rendering attributes aren’t reliably invalidated? If this approach is fragile, is there a better pattern for dynamic highlights that avoids mutating the attributed string (to prevent layout/scroll jitter)?
Replies
4
Boosts
0
Views
374
Activity
3h
Handling View Creation for Heterogeneous Data
In my project (an Package), I have created an Manager (can be classified as an ViewModel) that will handle state updates throughout the Package Component view: Note: The code is simplified for better understanding and to focus on principles behind things I did. The manager does complex things during state updates. public class ComponentManager: ObservedObject { @Published var rows: [any RowProtocol] = [] func updateState(_ newState: any RowProtocolData, id: String) { guard let index = rows.firstIndex(where: { $0.id == id }) else { return } rows[index].updateState(newState) } func getState(id: String) -> any RowProtocolData? { guard let index = rows.firstIndex(where: { $0.id == id }) else { return nil } return rows[index].state } } The RowProtocol is defined as follows: public protocol RowStateProtocol {} public protocol RowProtocol: Identifiable { associatedtype State: RowStateProtocol associatedtype RowView: View var id: String { get } var state: State { get } func updateState(_ newState: State) @MainActor @ViewBuilder func renderRow() -> RowView } extension RowProtocol { func updateState(_ newState: any RowProtocolData) { guard let newState = newState as? State else { return } self.updateState(newState) } } Then in Component View, I need to render the rows based on the underlying type of the row, this where the renderRow() comes in: struct ComponentView: View { @ObservedObject var manager: ComponentManager var body: some View { List { ForEach(manager.rows, id: \.id) { row in HStack { // This HStack prevent List from initing all rows due to AnyView. AnyView(row.renderRow()) } } } } } The row views will be accepting binding to the state of the row and update their state, let says we have a TextRow and a ToggleRow: struct TextRow: RowProtocol { var id: String var state: TextRowState func updateState(_ newState: TextRowState) { self.state = newState } } struct ToggleRow: RowProtocol { var id: String var state: ToggleRowState func updateState(_ newState: ToggleRowState) { self.state = newState } } In this, offcourse we cannot create an binding directly to the state of the row, since the state are through the manager and the row data won't have access to the manager. So I created an property wrapped that use the closures passed by the manager into environment to create the binding and an view that will give the binding to the content view: extenstion EnvironmentValues { @Entry internal var getState: (String) -> any RowStateProtocol? @Entry internal var updateState: (any RowStateProtocol, String) -> Void } @propertyWrapper struct RowStateBinding<State: RowStateProtocol & Equatable>: DynamicProperty { @Environment(\.getState) private var getState @Environment(\.updateState) private var updateState private let id: String init(id: String) { self.id = id } var wrappedValue: State { get { getState(id) as! State } nonmutating set { if wrappedValue != newValue { // only update for an new change, since set can be triggered for any number of reasons. updateState(newValue, id) } } } var projectedValue: Binding<State> { Binding( get: { self.wrappedValue }, set: { newValue in self.wrappedValue = newValue } ) } } struct RowStateBindingView<Content: View, State: RowStateProtocol & Equatable>: View { @RowStateBinding<State> private var state: State private let content: (Binding<State>) -> Content init(id: String, @ViewBuilder content: @escaping (Binding<State>) -> Content) { self._state = RowStateBinding(id: id) self.content = content } var body: some View { content($state) } } and in the renderRows: struct TextRowView: View { @Binding var text: TextRowState var body: some View { TextField("Enter text", text: $text.text) } } extension TextRow { func renderRow() -> some View { RowStateBindingView(id: id) { state in TextField("Enter text", text: state.text) } } } struct ToggleRowView: View { @Binding var state: ToggleRowState var body: some View { Toggle("Toggle", isOn: $state.isOn) } } extension ToggleRow { func renderRow() -> some View { RowStateBindingView(id: id) { state in Toggle("Toggle", isOn: state.isOn) } } } This way, I can adopt any view as an row view and most importantly, the view can be completely independent of the manager and used as an standalone view. Also clients of the library can create their own custom rows by just conforming to the RowProtocol and creating the view for it, without worrying about how the state management works. The manager will handle all the state updates. I prefer using stucts over classes for rows and states, since its easier to manage state updates. What do you think about this approach? Do you see any potential issues with this? Is there a better way to achieve this?
Replies
0
Boosts
0
Views
15
Activity
14h
Dynamic Property inplace of onChange, task.
In the recent SwiftUI Group Lab, they mentioned using Dynamic Property instead of onChange, How to do it? Could it used as an actual property type instead of just using in combination with @propertyWrapper
Replies
0
Boosts
0
Views
20
Activity
15h
SwiftUI ​Charts: In iOS 27, annotation overlays exceed the bounds of an annotation
I'm seeing a regression in SwiftUI Charts on iOS 27 beta 1. Any view placed inside a BarMark's overlay annotation no longer receives the size of the parent BarMark. It collapses to zero, so any content sized from geo.size (e.g. a Rectangle meant to fill the bar) renders empty or incorrectly. Expected: The GeometryReader reports the BarMark's rendered width/height, and the Rectangle fills the BarMark (this is the behavior in iOS 26 and earlier). Actual: On iOS 27 beta 1, geo.size is effectively zero, so the overlay content has an extremely small size. I suspect this could be a small bug with the new ContentBuilder / ViewBuilder changes but that's just a hunch. Here's a code sample which reproduces the issue. // MARK: - Mock Data Models struct ScheduleSeries: Identifiable { let id = UUID() let data: [ScheduleItem] } struct ScheduleItem: Identifiable { let id = UUID() let startDate: Date let startHour: Double let endHour: Double let secondaryText: String? } // MARK: - Minimal Reproducible Example struct ContentView: View { // Generate two consecutive days for the mock data let mockSchedule: [ScheduleSeries] = [ ScheduleSeries(data: [ ScheduleItem( startDate: Date(), startHour: 9.0, endHour: 11.5, secondaryText: "Morning Event" ), ScheduleItem( startDate: Calendar.current.date(byAdding: .day, value: 1, to: Date())!, startHour: 13.0, endHour: 16.0, secondaryText: "Afternoon Event" ) ]) ] var body: some View { VStack(alignment: .leading) { Text("FB: Annotation Sizing Bug") .font(.headline) .padding(.bottom, 8) Text("Expected: The gray Rectangle should stretch to fill the BarMark.\nActual: GeometryReader/Annotation fails to size to the parent BarMark.") .font(.caption) .foregroundColor(.secondary) .padding(.bottom) Chart(mockSchedule) { series in ForEach(series.data, id: \.startDate) { element in BarMark( x: .value("Day", element.startDate, unit: .day, calendar: .current), yStart: .value("Start", element.startHour), yEnd: .value("End", element.endHour), width: .ratio(0.99) ) .annotation(position: .overlay, alignment: .topLeading) { item in ZStack { VStack(alignment: .leading, spacing: 0) { // BUG DEMONSTRATION: // This GeometryReader and Rectangle previously filled the BarMark, but in Xcode 27 it does not GeometryReader { geo in Rectangle() .fill(Color.black.opacity(0.15)) .frame(width: geo.size.width, height: geo.size.height) } } .foregroundColor(.white) .font(.caption2) } } } } .chartYScale(domain: 0...24) // Lock the Y-axis to a 24-hour scale } .padding() } } Environment: Xcode 27 beta 1 / iOS 27 beta 1 Reproduces on device and Simulator Worked as expected on iOS 26 and earlier Here's what the issue looks like in our app with zero code changes: iOS 26 iOS 27 I've filed a feedback report (FB23016343) with a sample project attached. Has anyone else hit this, or found a workaround for sizing overlay annotation content to a BarMark in iOS 27? Thanks!
Replies
0
Boosts
0
Views
15
Activity
17h
How to detect backspace in SwiftUI TextField without falling back to UIViewRepresentable?
I'm building a multi-box PIN/OTP input in SwiftUI. In UIKit, I used UITextFieldDelegate to detect backspace presses on an empty field to move focus backward. SwiftUI’s .onChange(of: text) only triggers when text is actually deleted, completely missing backspaces on an already empty field. Is there a pure SwiftUI way to handle this now, or are we still forced to wrap UITextField via UIViewRepresentable?
Replies
1
Boosts
0
Views
41
Activity
22h
Source view disappearing when interrupting a zoom navigation transition
When I use the .zoom transition in a navigation stack, I get a glitch when interrupting the animation by swiping back before it completes. When doing this, the source view disappears. I can still tap it to trigger the navigation again, but its not visible on screen. This seems to be a regression in iOS 26, as it works as expected when testing on iOS 18. Has someone else seen this issue and found a workaround? Is it possible to disable interrupting the transition? Filed a feedback on the issue FB19601591 Screen recording: https://share.icloud.com/photos/04cio3fEcbR6u64PAgxuS2CLQ Example code @State var showDetail = false @Namespace var namespace var body: some View { NavigationStack { ScrollView { showDetailButton } .navigationTitle("Title") .navigationBarTitleDisplayMode(.inline) .navigationDestination(isPresented: $showDetail) { Text("Detail") .navigationTransition(.zoom(sourceID: "zoom", in: namespace)) } } } var showDetailButton: some View { Button { showDetail = true } label: { Text("Show detail") .padding() .background(.green) .matchedTransitionSource(id: "zoom", in: namespace) } } }
Topic: UI Frameworks SubTopic: SwiftUI
Replies
20
Boosts
25
Views
2.3k
Activity
1d
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
0
Boosts
1
Views
46
Activity
1d
Is there a better way to hide a view in a custom Layout other than placing it off-screen?
I have a custom Layout that places a number of labels for a cell footer in a certain way based on the available width that needs to conditionally hide those views that do not entirely fit anymore (based on some priorities I specify). Currently I simply move the subviews that do not fit anymore off-screen and use clipping to hide them outside the layout, as I did not find an "official" way to hide / exclude a subview from a Layout. Does anyone know a better / nicer way to do this in SwiftUI?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
0
Boosts
0
Views
15
Activity
1d
onChange(of:initial:_:) changes when same value assigned
Overview When calling onChange(of:initial:_:) with initial as true, closure called even when the value assigned is the same as previous value (not just the first time, subsequently too). However when initial value is false it is called only when value changes. Questions Is this a bug? Am I missing something?
Replies
0
Boosts
0
Views
19
Activity
1d
Pass data to an @Observable model
Overview I have a navigation split view. The detail view contains a model now this model depends on id from the parent view. Questions How can I pass data from the parent view and yet create the view in the detail view? Or should I be pass the model from the parent view, but the problem is the parent view needs to persist model. Or is there a better approach?
Replies
0
Boosts
0
Views
26
Activity
1d
about presentationDetents modifier
How do I make the sheet occupy the full screen width and bottom when I customize the height of the sheet using presentationDetents?
Replies
1
Boosts
0
Views
34
Activity
1d
AsyncRenderer stack limit
We've been getting stack overflows in code we don't control, in the background AsyncRenderer thread in a chain of calls to updateInheritedViewAsync. But the stack is less than 200 calls deep, presumably because it's a background thread with a smaller stack limit. Is it possible to adjust AsyncRenderer's stack limit? Or otherwise, what limits should we be aware of to prevent running into this issue? com.apple.SwiftUI.AsyncRenderer: EXC_BAD_ACCESS (code=2, address=0x16f5ebe30) #0 0x000000019c6b4460 in function signature specialization <Arg[3] = Dead> of static SwiftUI.DisplayList.ViewUpdater.Model.merge(item: inout SwiftUI.DisplayList.Item, index: SwiftUI.DisplayList.Index, into: inout SwiftUI.DisplayList.ViewUpdater.Model.State) -> SwiftUI.DisplayList.ViewUpdater.Model.MergedViewRequirements () #1 0x000000019c7c2850 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #2 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #3 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #4 0x000000019c7c3ef0 in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () ... #147 0x000000019c7c364c in SwiftUI.DisplayList.ViewUpdater.updateInheritedViewAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldItem: SwiftUI.DisplayList.Item, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newItem: SwiftUI.DisplayList.Item, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #148 0x000000019c7c2074 in SwiftUI.DisplayList.ViewUpdater.updateAsync(platform: SwiftUI.DisplayList.ViewUpdater.Platform, oldList: SwiftUI.DisplayList, oldParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>, newList: SwiftUI.DisplayList, newParentState: Swift.UnsafePointer<SwiftUI.DisplayList.ViewUpdater.Model.State>) -> Swift.Optional<SwiftUI.Time> () #149 0x000000019c7c1a78 in renderAsync () #150 0x000000019c60fc68 in renderDisplayList () #151 0x000000019c612094 in protocol witness for SwiftUI.ViewGraphRenderHost.renderDisplayList(_: SwiftUI.DisplayList, asynchronously: Swift.Bool, time: SwiftUI.Time, nextTime: SwiftUI.Time, targetTimestamp: Swift.Optional<SwiftUI.Time>, version: SwiftUI.DisplayList.Version, maxVersion: SwiftUI.DisplayList.Version) -> SwiftUI.Time in conformance SwiftUI.ViewGraph : SwiftUI.ViewGraphRenderHost in SwiftUI () #152 0x000000019c7c0dd0 in renderAsync () #153 0x000000019c7be6c8 in SwiftUI.ViewGraphHost.displayLinkTimer(timestamp: SwiftUI.Time, targetTimestamp: SwiftUI.Time, isAsyncThread: Swift.Bool) -> () () #154 0x000000019c7beab8 in SwiftUI.ViewGraphDisplayLink.displayLinkTimer(__C.CADisplayLink) -> () () #155 0x000000019c7be5a8 in @objc SwiftUI.ViewGraphDisplayLink.displayLinkTimer(__C.CADisplayLink) -> () () #156 0x0000000192fdbb24 in CA::Display::DisplayLinkItem::dispatch_ () #157 0x0000000192fb9164 in CA::Display::DisplayLink::dispatch_items () #158 0x0000000192f91870 in display_timer_callback () #159 0x000000019256d4cc in __CFMachPortPerform () #160 0x000000019259d0b0 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ () #161 0x000000019259cfd8 in __CFRunLoopDoSource1 () #162 0x0000000192574c1c in __CFRunLoopRun () #163 0x0000000192573a6c in _CFRunLoopRunSpecificWithOptions () #164 0x0000000190533f54 in -[NSRunLoop(NSRunLoop) runMode:beforeDate:] () #165 0x000000018fb9a51c in -[NSRunLoop(NSRunLoop) run] () #166 0x000000019c7cd5b0 in function signature specialization <Arg[1] = Dead> of static SwiftUI.ViewGraphDisplayLink.asyncThread(arg: Swift.Optional<Any>) -> () () #167 0x000000019c7cd288 in @objc static SwiftUI.ViewGraphDisplayLink.asyncThread(arg: Swift.Optional<Any>) -> () () #168 0x000000018fbf321c in __NSThread__start__ () #169 0x00000001ef0d044c in _pthread_start ()
Topic: UI Frameworks SubTopic: SwiftUI
Replies
2
Boosts
1
Views
83
Activity
1d
Making View's init nonisolated with environment variables
Views with custom nonisolated init fail to compile when using @MainActor-isolated @Environment properties (e.g., \.openURL, \.dismiss). From the Swift 6 migration documentation, it seems encouraged to define inits nonisolated, and it seems base SwiftUI components also have their inits defined as nonisolated (e.g. TimelineView, LazyHStack, etc), which makes sense. How do you achieve marking a SwiftUI View's init as nonisolated when it uses environment variables, without producing the following build error "Main actor-isolated default value of 'self.openURL' cannot be used in a nonisolated initalizer" ? It seems @State variable defined in a view has the ability to be set in a nonisolated init but not @Environment. What mechanism prevents this under the hood ?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
7
Boosts
0
Views
92
Activity
1d
How to get the glassy picker showed at WWD27
In the session "What’s new in SwiftUI", at 02:38 Steven shows various Liquid Glass components, among them, a very pretty interactive glassy Picker. At first I thought this was built into the system and tried everything possible, until to rewatch the presentation and notice this: "On macOS, like on iOS, you can mark Liquid Glass custom elements as "interactive" so they respond more fluidly to user's clicks. And this is optimized to work great with the mouse pointer, so it feels right at home on the Mac." Does anyone have pointers to where one can find a Picker that implements all these system behaviors that folks have come to expect? I have my own version, which is deeply lacking, and so is every other implementation out there that I saw, and I thought sprinkling some "interactive" as instructed would help, but it barely made a dent.
Topic: UI Frameworks SubTopic: SwiftUI
Replies
2
Boosts
0
Views
104
Activity
1d
iOS 27 Swift Charts issues
I get lots of glitches in display of Swift Charts on my iPhone running iOS 27. Works fine on simulator though. Am I the only one?
Replies
2
Boosts
0
Views
48
Activity
1d
Dynamic layouts with multi-line Text views
For a dynamic layout reacting to width changes using ViewThatFits, I need a 2-line Text view where content gets wrapped exactly once, but doesn't get abbreviated. If the text is too long to fit in two lines, ViewThatFits should choose the next layout. I tried to use .lineLimit(2, reservesSpace: true), but then ViewThatFits always takes this layout even when the text content gets too long to fit, so it gets abbreviated with "...", instead of choosing the next layout. How else can I build a layout with a two-line Text, which does signal to ViewThatFits if the space doesn't fit the entire text so it can choose the next layout?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
2
Boosts
0
Views
59
Activity
1d
How to avoid trailing toolbar item re-rendering on child navigation
I have a NavigationStack where every child view except the root has a trailing close button. On each child view transition, the button re-renders itself. Is there a way to fix the button? I saw in the Apple Developer app the behavior I want when registering a lab and the confirmation screen, but I think the implementation is just changing the center content of the view and it isn't pushing a new view to the stack path that has different toolbar items. Root: VStack { Content Next button --> Page 2 } Cancel button leading edge Page 2: VStack { Copy Next button --> page 3 } built in back button leading edge cancel button trailing edge Page 3: VStack { Copy Finish button --> dismisses whole workflow } built in back button leading edge cancel button trailing edge So on page 3, the cancel button is new. I can't figure out how to not have the glass effect animate it in new. I want the 'same' cancel button to be there. This is an oversimplification of a resumable form where the user can cancel (save and resume) at any time. Is there a built in way to have the trailing edge button be fixed? Would moving where the button is defined make a different and expose a way for each child view to propagate upwards if the cancel button should be shown or not? Updated with sample, assumes iOS 18 + 26 code so using .topTrailing not new 'action' placement: import SwiftUI struct Demo: View { @State private var path: [String] = [] var body: some View { NavigationStack(path: $path) { Button("Go") { path.append("Second") } .navigationDestination(for: String.self) { destination in switch destination { case "Second": VStack { Text("Second") Button("Next") { path.append("Third") } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", role: .cancel) { path = [] } } } case "Third": VStack { Text("Third") Button("Finish") { path = [] } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", role: .cancel) { path = [] } } } default: Text("Undefined") } } } } }
Topic: UI Frameworks SubTopic: SwiftUI
Replies
2
Boosts
0
Views
43
Activity
1d
NSViewRepresentable updates triggered by .onChange ignore SwiftUI Transactions on macOS
I am encountering a systemic issue on macOS where NSViewRepresentable (and some native container views like Table) completely discard explicit SwiftUI animations when the state change is handled via an .onChange modifier. While the exact same reactive architecture produces fluid animations on iOS, the AppKit bridge on macOS snaps the frame updates instantly. I have filed a formal bug report for this behavior, but I want to open this up to the community to see if anyone has found a cleaner architectural workaround. The Problem When observing a state change (e.g., via @AppStorage, @SceneStorage, or local state) using .onChange, applying a withAnimation block fails to animate the underlying layer changes in an AppKit representable view. // The Reactive Pattern that breaks on macOS .onChange(of: toggle) { newValue in withAnimation(.easeInOut(duration: 0.5)) { self.targetColor = newValue ? .systemBlue : .systemRed } } The Diagnostic Anomaly If you inspect context.transaction inside the updateNSView(_:context:) method during this lifecycle pass, SwiftUI reports that the transaction is animated: func updateNSView(_ nsView: NSView, context: Context) { // Prints 'true', indicating SwiftUI thinks it's animating print("Is Animated: \(context.transaction.animation != nil)") // Result: Snaps instantly. No animation occurs. nsView.layer?.backgroundColor = targetColor.cgColor } Why It Happens (The Double-Commit) It appears that on macOS, .onChange flushes a static layout transaction to the window layer immediately upon the state mutating. By the time the withAnimation block evaluates inside the closure, the AppKit backing layer has already processed a implicit setDisableActions(true) directive. The GPU pipeline for that transaction frame is effectively closed, despite what the context.transaction metadata claims. The Low-Level Workaround To force the AppKit bridge to respect the animation intent, I have to manually drop into Core Animation inside updateNSView and explicitly veto SwiftUI's action-disabling behavior: func updateNSView(_ nsView: NSView, context: Context) { CATransaction.begin() if context.transaction.animation != nil { // Explicitly override SwiftUI's implicit frame lock CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.5) // Hardcoded fallback match } else { CATransaction.setDisableActions(true) } nsView.layer?.backgroundColor = targetColor.cgColor CATransaction.commit() } My Questions: Is this intentional behavior due to how AppKit's layer-backed architectures handle frame integrity vs. iOS's fluid layout engine? Has anyone found a way to bridge SwiftUI's Animation type curves (like .spring()) cleanly down into the CATransaction or NSAnimationContext layer without hardcoding durations inside updateNSView? Is there a purely "Reactive" paradigm that avoids mutating state at the primary action source (e.g., forcing a Button to own the animation logic) while maintaining fluid transitions on macOS?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
1
Boosts
0
Views
45
Activity
1d
ForEach with calculations between each cell
I'm attempting to add a TimeInterval calculated at each step of a ForEach to a Date (hr & min) of the previous cell. For each cell this works: Start Time = 12:55 Delta interval = 3600 seconds (1hr) Next time = 1:55 I want to use that Next time as the start one for the next cell - instead what I have working is Delta interval = 30 (5 min) Next time = 1:00 but what I want is 2:00 (12:55 + 1hr + 5min) I'm just having trouble thinking through how to do that. Thanks!
Topic: UI Frameworks SubTopic: SwiftUI
Replies
2
Boosts
0
Views
40
Activity
1d
Custom PinnedScrollableViews
In a simple scenario, it's possible to have a ScrollView that contains a LazyVStack where the headers or footers of Section gets pinned to the top safe area. In a more complicated scenario, where The ScrollView needs to ignore the top safe area The navigation bar is hidden until a certain view, call it Foo, has gone behind what used to be the top safe area, i.e., only about 100p of the view is visible which would be invisible if the navigation bar wasn't transparent/hidden. The navigation bar becomes visible once Foo is scrolled up to a threshold that was explained earlier A second view, call is Bar, that's positioned below Foo, should never go behind the navigation bar, i.e., it should be pinned to the bottom of the navigation bar I managed to achieve this behaviour by having a State for the ignored safe area edges that gets updated based on how far Foo is scrolled. However, I get a jumpy behaviour because the ScrollView tries to adjust its contentOffset/contentInset based on the new top safe area. This jumpy behaviour is very hard to compensate especially when user scrolls very fast. In UIKit, I could achieve this behaviour by overriding viewSafeAreaInsetsDidChangeand tweaking the contentInset, and contentOffset on a collection/table view. Maybe I need to rethink my approach in a declarative way that aligns better with SwiftUI. Any thoughts on how to achieve this?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
4
Boosts
1
Views
55
Activity
1d