Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.

All subtopics
Posts under UI Frameworks topic

Post

Replies

Boosts

Views

Activity

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
124
1w
Liquid Glass tab bar doesn't update its background on tab switch until I scroll — expected? Workaround?
Hi, On iOS 26 I'm seeing a behavior with the new Liquid Glass tab bar in SwiftUI and I can't tell if it's expected or if there's a supported workaround. When I switch tabs in a TabView, the floating Liquid Glass tab bar keeps the glass appearance it derived from the previous tab's content. It only refreshes to match the newly selected tab once I scroll the new tab's scroll view — even by a single point. In Apple's own apps (e.g. Home) the bar updates immediately on tab change, which makes me think this isn't intended. I reduced it to a minimal example — no images, no glassEffect, no NavigationStack, just gradients: struct ContentView: View { enum TabID: Hashable { case blue, green, dark } @State private var selection: TabID = .blue var body: some View { TabView(selection: $selection) { Tab("Blue", systemImage: "drop.fill", value: .blue) { Page(colors: [.blue, .indigo]) } Tab("Green", systemImage: "leaf.fill", value: .green) { Page(colors: [.green, .teal]) } Tab("Dark", systemImage: "moon.fill", value: .dark) { Page(colors: [.gray, .black]) } } } } struct Page: View { let colors: [Color] var body: some View { ZStack { LinearGradient(colors: colors, startPoint: .top, endPoint: .bottom) .ignoresSafeArea() ScrollView { ForEach(0..<40, id: \.self) { Text("Row \($0)") .foregroundStyle(.white) .frame(maxWidth: .infinity, alignment: .leading) .padding() } } } } } Steps to reproduce: Run on a real device (iPhone 17 Pro Max, iOS 26.5.1, Xcode 26). Switch between the three tabs without scrolling. Expected: the tab bar's glass tint adapts to the destination tab's gradient immediately on switch. Actual: the bar keeps the previous tab's tint until I scroll the new view ~1pt. What I've already tried (no luck): Both the modern Tab API and the legacy .tabItem / .tag style — same behavior. Faking a scroll with setContentOffset (animated and non-animated) — doesn't reliably trigger the refresh; only a genuine user scroll does. toolbarBackgroundVisibility(...) and forcing a color scheme — no effect. Questions: Is this a known issue / expected behavior in iOS 26? Is there a supported way to make the tab bar re-sample its Liquid Glass backdrop when the selected tab changes (without requiring a user scroll)? I've also filed it via Feedback Assistant. Thanks! Configuration: iPhone 17 Pro Max, iOS 26.5.1, Xcode 26.
0
0
131
1w
Menu in the bottom bar flies to the top of the screen
I have a Menu in a Toolbar (specifically, the .bottomBar). If I open the menu quickly after it appears (within a few seconds), it flies to the top of the screen. I've created a minimum woking example below. This appears to be a pretty glaring iOS 26 bug that has been present since the early betas, but I can't seem to find much discussion about it (apart from this post from 8 months ago), so I'm wondering if I might be doing something wrong. Or maybe someone managed to figure out a workaround. If the Menu is very simple (just Text items), it seems to be okay. But if the Menu is even slightly complex (e.g. includes icons), then it exhibits the flying behavior. I've also been able to reproduce this bug under different types of navigation component (e.g. NavigationSplitView). I'm seeing this behavior in the current version of iOS (26.2.1), both on device and in the simulator. MWE struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Go to Detail") { DetailView() } } .navigationTitle("Root") } } } struct DetailView: View { var body: some View { VStack { Text("Detail View") } .navigationTitle("Detail") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .bottomBar) { Menu { Button { } label: { Label("Delete", systemImage: "trash") } } label: { Image(systemName: "ellipsis.circle") } } } } }
Topic: UI Frameworks SubTopic: SwiftUI
3
1
291
1w
NavigationSplitView no longer pops back to the root view when selection = nil in iOS 26.4 (with a nested TabView)
In iOS 26.4 (iPhone, not iPad), when a NavigationSplitView is combined with a nested TabView, it no longer pops back to the root sidebar view when the List selection is set to nil. This has been working fine for at least a few years, but has just stopped working in iOS 26.4. Here's a minimal working example: import SwiftUI struct ContentView: View { @State var articles: [Article] = [Article(articleTitle: "Dog"), Article(articleTitle: "Cat"), Article(articleTitle: "Mouse")] @State private var selectedArticle: Article? = nil var body: some View { NavigationSplitView { TabView { Tab { List(articles, selection: $selectedArticle) { article in Button { selectedArticle = article } label: { Text(article.title) } } } label: { Label("Explore", systemImage: "binoculars") } } } detail: { Group { if let selectedArticle { Text(selectedArticle.title) } else { Text("No selected article") } } .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", systemImage: "xmark") { selectedArticle = nil } } } } } } struct Article: Identifiable, Hashable { let id: String let title: String init(articleTitle: String) { self.id = articleTitle self.title = articleTitle } } First, I'm aware that nesting a TabView inside a NavigationSplitView is frowned upon: Apple seems to prefer NavigationSplitView nested inside a Tab. However, for my app, that leads to a very confusing user experience. Users quickly get lost because they end up with different articles open in different tabs and it doesn't align well with my core distinction between two "modes": article selection mode and article reading mode. When the user is in article selection mode (sidebar view), they can pick between different ways of selecting an article (Explore, Bookmarks, History, Search), which are implemented as "tabs". When they pick an article from any tab they jump into article reading mode (the detail view). Second, I'm using .navigationBarBackButtonHidden(true) to remove the auto back button that pops back to the sidebar view. This button does still work in iOS 26.4, even with the nested TabView. However, I can't use the auto back button because my detail view is actually a WebView with its own back/forward logic and UI. Therefore, I need a separate close button to exit from the detail view. My close button sets selectedArticle to nil, which (pre-iOS 26.4) would trigger the NavigationSplitView to pop back to the sidebar view. For some reason, in iOS 26.4 the NavigationSplitView doesn't seem to bind correctly to the List's selection parameter, specifically when there's a TabView nested between them. Or, rather, it binds, but fails to pop back when selection becomes nil. One option is to replace NavigationSplitView with NavigationStack (on iPhone). NavigationStack still works with a nested TabView, but it creates other downstream issues for me (as well as forcing me to branch for iPhone and iPad), so I'd prefer to continue using NavigationSplitView. Does anyone have any ideas about how to work around this problem? Is there some way of explicitly telling NavigationSplitView to pop back to the sidebar view on iPhone? (I've tried setting the column visibility but nothing seems to work). Thanks for any help!
2
1
276
1w
MapKit MapCamera
SwiftUI Map with MapCamera jerks on every GPS update instead of animating smoothly I'm trying to make camera to follow the user smoothly during navigation using MapCamera with heading and pitch, similar to Apple Maps or Google Maps. The camera updates on every GPS tick but instead of animating smoothly between positions it jerks , it snaps to the new position, pauses, snaps again, pauses...terrible UX. The blue user location (UserAnnotation) puck moves completely smoothly. Only the camera jerks I have tried all sort of animations and interpolation you may think of. Something is just not right, must be something missing from the puzzle. I have prepared a minimal reproducible example so you can copy and paste the only thing needed is to add the Privacy - Location When In Use Usage Description Run in Simulator, go to Features > Location > Freeway Drive and tap on Track then you'll notice how camera is following then stop then following and stops again Don't bother using AI, he has no clue what's this all about. I also went through docs to find anything useful like a magic modifier, but no joy Here is a video hosted online as well: [https://streamable.com/ear9cv] And a code snippet copy paste import MapKit import CoreLocation // You'll only need to add Privacy - Location When In Use Usage Description to the Info tab struct ContentView: View { @State private var locationManager = LocationManagerDelegate() @State private var cameraPosition: MapCameraPosition = .userLocation(followsHeading: false, fallback: .automatic) @State private var isTracking: Bool = false @State private var lastKnownHeading: Double = 0 var body: some View { Map(position: $cameraPosition) { UserAnnotation() } .onChange(of: locationManager.location) { _, location in guard isTracking, let location else { return } withAnimation(.linear(duration: 0.5)) { cameraPosition = .camera(MapCamera( centerCoordinate: location.coordinate, distance: 1000, heading: location.course, pitch: 60 )) } } .safeAreaInset(edge: .bottom) { // Added to the safeAreaInset to keep the Apple Logo visible Button("Track") { isTracking.toggle() locationManager.requestPermission() locationManager.startNavigating() } .buttonStyle(.glassProminent) .buttonSizing(.flexible) .controlSize(.extraLarge) .padding(.horizontal) } } } @MainActor @Observable final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate { var location: CLLocation? var authorizationStatus: CLAuthorizationStatus = .notDetermined let manager = CLLocationManager() private var liveUpdateTask: Task<Void, Never>? override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation authorizationStatus = manager.authorizationStatus } func requestPermission() { manager.requestWhenInUseAuthorization() } func startNavigating() { liveUpdateTask = Task { do { for try await update in CLLocationUpdate.liveUpdates(.automotiveNavigation) { guard let newLocation = update.location else { continue } self.location = newLocation } } catch { print("Live updates error: \(error)") } } } func stopNavigating() { liveUpdateTask?.cancel() liveUpdateTask = nil manager.requestLocation() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { location = locations.last } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { authorizationStatus = manager.authorizationStatus } }
0
0
56
1w
How to build a picker wheel similar as the Calendar App?
How to build the below UI using SwiftUI? I tried to use Picker with wheel style, but it is not the same as the screenshot. The screenshot came from the iOS built-in calendar app. Add a new calendar event Click "Repeat" Choose "Custom" Click "Every day" The required picker wheel will be displayed Picker("Every", selection: $interval) { ForEach(1..<366) { interval in Text("\(interval)").tag(interval) } } .pickerStyle(.wheel)
0
0
92
1w
NSTokenField - How To Tell If I'm Editing an Existing Token in -tokenField:representedObjectForEditingString: ?
I'm trying to use NSTokenField for the first time. So my custom 'representedObject' for a token has additional model data tied to it (not just the editing/display string). I noticed when I edit an existing token, type text, and hit the enter key I get the following delegate callback: - (nullable id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString; This same delegate method is called when I type a brand new token. Is there a way to distinguish if I'm editing a token vs. creating a new one? My expectation is to be able to do something like this (made up enhancement): - (nullable id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString atIndex:(NSUInteger)existingTokenIndex { if (existingTokenIndex == NSNotFound) { // Token is new, create a new instance MyTokenObject *newToken = //create and configure. return newToken; } else { // This would update the editing string but wouldn't discard existing data held by the token. MyTokenObject *tokenObj = [self existingTokenAtIndex:existingTokenIndex]; tokenObj.editingString = editingString; return tokenObj; } }
2
0
186
1w
How do I get SwiftUI to let me determine a custom frame size for NSTextField
I have a NSViewRepresentable that wraps an NSTextField subclass which is displayed as larger than your typical text field. SwiftUI doesn't seem to allow me to set the size of the view when the underlying is an NSTextField. It forces it as a single line field. I've tried both setting the frame on creation as well as using SwiftUI .frame(width:height:) on the represented view. I always end up with a single line field. struct BigTextField: NSViewRepresentable { @Binding var text: String class Coordinator: NSObject, NSTextFieldDelegate { var parent: BigTextField init(_ parent: BigTextField) { self.parent = parent } func controlTextDidChange(_ obj: Notification) { if let textField = obj.object as? NSTextField { parent.text = textField.stringValue } } } func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: Context) -> NSTextField { //let frame = NSRect(x: 0, y: 0, width: 350, height: 140) //let textField = NSTextField(frame: frame) let textField = NSTextField() textField.isEditable = true textField.isBordered = true textField.isBezeled = true textField.delegate = context.coordinator // Assign the coordinator return textField } func updateNSView(_ nsView: NSTextField, context: Context) { if nsView.stringValue != text { nsView.stringValue = text } } } I've also included the SwiftUI declaration which demonstrates the problem. struct ContentView: View { @State var text : String = "Test string" var body: some View { VStack { BigTextField(text: $text) .frame(width: 350, height: 140) } .padding() } } NSTextField can be any arbitrary frame size. I already do this from AppKit but am trying to adapt this custom field to work within SwiftUI. SwiftUI seems to override the sizing of this NSViewRepresentable that I give it. Am I missing something here? Is there some way to override SwiftUI's sizing behavior? Thank you.
0
0
97
2w
Creating NSStatusBar.system.statusItem generates console warning
Putting: let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) at the top of AppDelegate.swift and no other code at all generates the warning below in the console when the app runs. Looks benign but it sure would be great to not have that happen. Anyone figure out how to stop this other than muting the warning? I tried all kinds of exotic deferring but that just makes the code brittle and unreadable. Here's the warning: It's not legal to call -layoutSubtreeIfNeeded on a view which is already being laid out. If you are implementing the view's -layout method, you can call -[super layout] instead. Break on void _NSDetectedLayoutRecursion(void) to debug. This will be logged only once. This may break in the future. Xcode 26.5, 26.4 - macOS 26.5.1
Topic: UI Frameworks SubTopic: AppKit
2
0
110
2w
How to animate NSSegmentedControl on macOS 26?
I noticed that in Xcode 27 Beta 1 on macOS 27 Beta 1, changing the selection of the segmented control used to switch between the Sidebar and Inspector tabs is animated. However, the standard segmented controls used by NSTabView/NSTabViewController, as well as a regular NSSegmentedControl, do not appear to have any such animation on macOS 27. I also tried using the animator proxy: segmentedControl.animator().selectedSegment = index but this did not change the behavior. How can I implement the same kind of segmented control selection animation that Xcode uses? Is there a public API for this, or is it achieved through a different mechanism?
Topic: UI Frameworks SubTopic: AppKit
1
0
93
2w
Back gesture not disabled with navigationBarBackButtonHidden(true) when using .zoom transition
[Submitted as FB22226720] For a NavigationStack destination, applying .navigationBarBackButtonHidden(true) hides the back button and also disables the interactive left-edge back gesture when using the standard push navigation transition. However, when the destination uses .navigationTransition(.zoom), the back button is hidden but the left-edge back gesture is still available—it can still be dismissed even though back is intentionally suppressed. This creates inconsistent behavior between navigation transition styles. navigationBarBackButtonHidden(_:) works with a standard push transition, but not with .navigationTransition(.zoom). In the code below, .interactiveDismissDisabled(true) is also applied as another attempt to suppress the back-swipe gesture, but it has no effect. As a result, there’s currently no clean way to prevent back navigation when using the zoom transition. REPRO STEPS Create an iOS project then replace ContentView with code below, build and run. Leave nav type set to List Push. Open an item. Verify there is no back button, then try the left-edge back gesture. Return to the root view. Change nav type to Grid Zoom. Open an item. Verify there is no back button, then try the left-edge back gesture. ACTUAL In List Push mode, the left-edge back gesture is prevented. In Grid Zoom mode, the back button is hidden, but the left-edge back gesture still works and returns to the previous view. EXPECTED Behavior should be consistent across navigation transition styles. If this configuration is meant to suppress interactive backward navigation for a destination, it should also suppress the left-edge back gesture when using .navigationTransition(.zoom). SCREEN RECORDING SAMPLE CODE struct ContentView: View { private enum NavigationMode: String, CaseIterable { case listPush = "List Push" case gridZoom = "Grid Zoom" } @Namespace private var namespace @State private var navigationMode: NavigationMode = .listPush private let colors: [Color] = [.red, .blue] var body: some View { NavigationStack { VStack(spacing: 16) { Picker("Navigation Type", selection: $navigationMode) { ForEach(NavigationMode.allCases, id: \.self) { mode in Text(mode.rawValue).tag(mode) } } .pickerStyle(.segmented) if navigationMode == .gridZoom { HStack { ForEach(colors.indices, id: \.self) { index in NavigationLink(value: index) { VStack { RoundedRectangle(cornerRadius: 14) .fill(colors[index]) .frame(height: 120) Text("Grid Item \(index + 1)") .font(.subheadline.weight(.medium)) } .padding(12) .frame(maxWidth: .infinity) .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 16)) .matchedTransitionSource(id: index, in: namespace) } .buttonStyle(.plain) } } } else { ForEach(colors.indices, id: \.self) { index in NavigationLink(value: index) { HStack { Circle() .fill(colors[index]) .frame(width: 24, height: 24) Text("List Item \(index + 1)") Spacer() Image(systemName: "chevron.right") .foregroundStyle(.secondary) } .padding() .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 12)) } .buttonStyle(.plain) } } Spacer() } .padding(20) .navigationTitle("Prevent Back Swipe") .navigationSubtitle("Compare Grid Zoom vs List Push") .navigationDestination(for: Int.self) { index in if navigationMode == .gridZoom { DetailView(color: colors[index]) .navigationTransition(.zoom(sourceID: index, in: namespace)) } else { DetailView(color: colors[index]) } } } } } private struct DetailView: View { @Environment(\.dismiss) private var dismiss let color: Color var body: some View { ZStack { color.ignoresSafeArea() Text("Try left-edge swipe back") .font(.title.bold()) .multilineTextAlignment(.center) .padding(.horizontal, 24) } .navigationBarBackButtonHidden(true) .interactiveDismissDisabled(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", action: dismiss.callAsFunction) } } } }
3
0
784
2w
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
476
2w
UITextView for displaying long text?
My app displays vertically scrollable text to the user which could be as short as a single screen or as long as a book chapter: imagine something like an e-book reader which uses scrolling rather than page-turning. A long time ago when development first started, I tried using UITextView, but the size of text that could be handled was quite limited (a figure of 32767 points seems to stick in the memory). Accordingly I came up with a custom solution in which a UIScrollView handled scrolling gestures and a custom UIView rendered just the part of the text that was visible at that time. That works, but it becomes cumbersome to add support for features such as selection handles and loupes. 18 years later, is there a more smoothly integrated way of displaying long scrollable text?
Topic: UI Frameworks SubTopic: UIKit
2
0
134
2w
Debugging multi-window AppKit apps: Identifying the window associated with a breakpoint
When working on a multi-window AppKit app, how do you identify which window instance is associated with the current breakpoint? The same question applies when using LLDB. If execution stops inside an object that can exist in more than one window, such as a shared NSViewController subclass, how do you know which window’s object you are currently inspecting? Does Xcode provide a mechanism for showing the NSWindow associated with the current view, view controller or responder while debugging? My current approach is to print object identities and compare them manually. For example, if several identical windows are open, I might print the current object and its window: print(self, #function) Then I interact with one window, make a note of the printed memory addresses in the console, switch to another window and compare the output. This works, but it feels manual. I am not dealing with different window subclasses. The windows may be instances of the same class and may contain instances of the same view controller classes. I simply want an easier way to distinguish which window instance I am debugging. Is there a recommended Xcode, LLDB or AppKit workflow for this?
1
0
179
2w
X button disappeared on iPadOS 26.4 in MFMailComposeViewController
I’m using MFMailComposeViewController to send emails from my app. Since updating to iPadOS 26.4, there is no way to cancel the mail composer because the “X” button in the top-left corner has disappeared. On iPhone with iOS 26.4, everything still seems to work as expected. Is this a known issue, or am I missing something? Has anyone else experienced this, or found a workaround?
Topic: UI Frameworks SubTopic: UIKit
8
1
896
2w
Hide standard window buttons
How do I permanently hide the standard window buttons(Buttons on top left; close, minimize and zoom) In my app, I hide the standard buttons and put my own custom buttons. Some users have reported that the standard buttons appear again making the app look buggy and confuses the users At present I use following code to remove the buttons standardWindowButton(.closeButton)?.removeFromSuperview() standardWindowButton(.miniaturizeButton)?.removeFromSuperview() standardWindowButton(.zoomButton)?.removeFromSuperview() } I have to call this code from multiple places like on window initialization, when appearance changes and when the window size changes. I tried creating window without the titled stylemask, but it has other down sides like the standard window decoration is entirely skipped by the system. The resizing also is unpredictable. My question is, what is the right way to remove/hide the standard window buttons?
Topic: UI Frameworks SubTopic: AppKit
1
0
116
2w
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
71
2w
Crash occured in UIDatePicker Calendar type
I am encountering a consistent, reproducible crash in our app when presenting a UIDatePicker configured with the calendar style. The crash triggers every single time the picker is invoked and points directly to the modern date picker's internal UICollectionView. The Exception: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView internal inconsistency: attempted to set layout with the collection view requiring a reload' let datePicker = UIDatePicker() datePicker.datePickerMode = .date datePicker.preferredDatePickerStyle = .compact This crash is occuring in inline style too when I try to open the calendar. I tried this in other apps. It works fine. I didn't override any collectionView layouts
1
0
88
2w
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
124
Activity
1w
Liquid Glass tab bar doesn't update its background on tab switch until I scroll — expected? Workaround?
Hi, On iOS 26 I'm seeing a behavior with the new Liquid Glass tab bar in SwiftUI and I can't tell if it's expected or if there's a supported workaround. When I switch tabs in a TabView, the floating Liquid Glass tab bar keeps the glass appearance it derived from the previous tab's content. It only refreshes to match the newly selected tab once I scroll the new tab's scroll view — even by a single point. In Apple's own apps (e.g. Home) the bar updates immediately on tab change, which makes me think this isn't intended. I reduced it to a minimal example — no images, no glassEffect, no NavigationStack, just gradients: struct ContentView: View { enum TabID: Hashable { case blue, green, dark } @State private var selection: TabID = .blue var body: some View { TabView(selection: $selection) { Tab("Blue", systemImage: "drop.fill", value: .blue) { Page(colors: [.blue, .indigo]) } Tab("Green", systemImage: "leaf.fill", value: .green) { Page(colors: [.green, .teal]) } Tab("Dark", systemImage: "moon.fill", value: .dark) { Page(colors: [.gray, .black]) } } } } struct Page: View { let colors: [Color] var body: some View { ZStack { LinearGradient(colors: colors, startPoint: .top, endPoint: .bottom) .ignoresSafeArea() ScrollView { ForEach(0..<40, id: \.self) { Text("Row \($0)") .foregroundStyle(.white) .frame(maxWidth: .infinity, alignment: .leading) .padding() } } } } } Steps to reproduce: Run on a real device (iPhone 17 Pro Max, iOS 26.5.1, Xcode 26). Switch between the three tabs without scrolling. Expected: the tab bar's glass tint adapts to the destination tab's gradient immediately on switch. Actual: the bar keeps the previous tab's tint until I scroll the new view ~1pt. What I've already tried (no luck): Both the modern Tab API and the legacy .tabItem / .tag style — same behavior. Faking a scroll with setContentOffset (animated and non-animated) — doesn't reliably trigger the refresh; only a genuine user scroll does. toolbarBackgroundVisibility(...) and forcing a color scheme — no effect. Questions: Is this a known issue / expected behavior in iOS 26? Is there a supported way to make the tab bar re-sample its Liquid Glass backdrop when the selected tab changes (without requiring a user scroll)? I've also filed it via Feedback Assistant. Thanks! Configuration: iPhone 17 Pro Max, iOS 26.5.1, Xcode 26.
Replies
0
Boosts
0
Views
131
Activity
1w
Menu in the bottom bar flies to the top of the screen
I have a Menu in a Toolbar (specifically, the .bottomBar). If I open the menu quickly after it appears (within a few seconds), it flies to the top of the screen. I've created a minimum woking example below. This appears to be a pretty glaring iOS 26 bug that has been present since the early betas, but I can't seem to find much discussion about it (apart from this post from 8 months ago), so I'm wondering if I might be doing something wrong. Or maybe someone managed to figure out a workaround. If the Menu is very simple (just Text items), it seems to be okay. But if the Menu is even slightly complex (e.g. includes icons), then it exhibits the flying behavior. I've also been able to reproduce this bug under different types of navigation component (e.g. NavigationSplitView). I'm seeing this behavior in the current version of iOS (26.2.1), both on device and in the simulator. MWE struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink("Go to Detail") { DetailView() } } .navigationTitle("Root") } } } struct DetailView: View { var body: some View { VStack { Text("Detail View") } .navigationTitle("Detail") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .bottomBar) { Menu { Button { } label: { Label("Delete", systemImage: "trash") } } label: { Image(systemName: "ellipsis.circle") } } } } }
Topic: UI Frameworks SubTopic: SwiftUI
Replies
3
Boosts
1
Views
291
Activity
1w
NavigationSplitView no longer pops back to the root view when selection = nil in iOS 26.4 (with a nested TabView)
In iOS 26.4 (iPhone, not iPad), when a NavigationSplitView is combined with a nested TabView, it no longer pops back to the root sidebar view when the List selection is set to nil. This has been working fine for at least a few years, but has just stopped working in iOS 26.4. Here's a minimal working example: import SwiftUI struct ContentView: View { @State var articles: [Article] = [Article(articleTitle: "Dog"), Article(articleTitle: "Cat"), Article(articleTitle: "Mouse")] @State private var selectedArticle: Article? = nil var body: some View { NavigationSplitView { TabView { Tab { List(articles, selection: $selectedArticle) { article in Button { selectedArticle = article } label: { Text(article.title) } } } label: { Label("Explore", systemImage: "binoculars") } } } detail: { Group { if let selectedArticle { Text(selectedArticle.title) } else { Text("No selected article") } } .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", systemImage: "xmark") { selectedArticle = nil } } } } } } struct Article: Identifiable, Hashable { let id: String let title: String init(articleTitle: String) { self.id = articleTitle self.title = articleTitle } } First, I'm aware that nesting a TabView inside a NavigationSplitView is frowned upon: Apple seems to prefer NavigationSplitView nested inside a Tab. However, for my app, that leads to a very confusing user experience. Users quickly get lost because they end up with different articles open in different tabs and it doesn't align well with my core distinction between two "modes": article selection mode and article reading mode. When the user is in article selection mode (sidebar view), they can pick between different ways of selecting an article (Explore, Bookmarks, History, Search), which are implemented as "tabs". When they pick an article from any tab they jump into article reading mode (the detail view). Second, I'm using .navigationBarBackButtonHidden(true) to remove the auto back button that pops back to the sidebar view. This button does still work in iOS 26.4, even with the nested TabView. However, I can't use the auto back button because my detail view is actually a WebView with its own back/forward logic and UI. Therefore, I need a separate close button to exit from the detail view. My close button sets selectedArticle to nil, which (pre-iOS 26.4) would trigger the NavigationSplitView to pop back to the sidebar view. For some reason, in iOS 26.4 the NavigationSplitView doesn't seem to bind correctly to the List's selection parameter, specifically when there's a TabView nested between them. Or, rather, it binds, but fails to pop back when selection becomes nil. One option is to replace NavigationSplitView with NavigationStack (on iPhone). NavigationStack still works with a nested TabView, but it creates other downstream issues for me (as well as forcing me to branch for iPhone and iPad), so I'd prefer to continue using NavigationSplitView. Does anyone have any ideas about how to work around this problem? Is there some way of explicitly telling NavigationSplitView to pop back to the sidebar view on iPhone? (I've tried setting the column visibility but nothing seems to work). Thanks for any help!
Replies
2
Boosts
1
Views
276
Activity
1w
MapKit MapCamera
SwiftUI Map with MapCamera jerks on every GPS update instead of animating smoothly I'm trying to make camera to follow the user smoothly during navigation using MapCamera with heading and pitch, similar to Apple Maps or Google Maps. The camera updates on every GPS tick but instead of animating smoothly between positions it jerks , it snaps to the new position, pauses, snaps again, pauses...terrible UX. The blue user location (UserAnnotation) puck moves completely smoothly. Only the camera jerks I have tried all sort of animations and interpolation you may think of. Something is just not right, must be something missing from the puzzle. I have prepared a minimal reproducible example so you can copy and paste the only thing needed is to add the Privacy - Location When In Use Usage Description Run in Simulator, go to Features > Location > Freeway Drive and tap on Track then you'll notice how camera is following then stop then following and stops again Don't bother using AI, he has no clue what's this all about. I also went through docs to find anything useful like a magic modifier, but no joy Here is a video hosted online as well: [https://streamable.com/ear9cv] And a code snippet copy paste import MapKit import CoreLocation // You'll only need to add Privacy - Location When In Use Usage Description to the Info tab struct ContentView: View { @State private var locationManager = LocationManagerDelegate() @State private var cameraPosition: MapCameraPosition = .userLocation(followsHeading: false, fallback: .automatic) @State private var isTracking: Bool = false @State private var lastKnownHeading: Double = 0 var body: some View { Map(position: $cameraPosition) { UserAnnotation() } .onChange(of: locationManager.location) { _, location in guard isTracking, let location else { return } withAnimation(.linear(duration: 0.5)) { cameraPosition = .camera(MapCamera( centerCoordinate: location.coordinate, distance: 1000, heading: location.course, pitch: 60 )) } } .safeAreaInset(edge: .bottom) { // Added to the safeAreaInset to keep the Apple Logo visible Button("Track") { isTracking.toggle() locationManager.requestPermission() locationManager.startNavigating() } .buttonStyle(.glassProminent) .buttonSizing(.flexible) .controlSize(.extraLarge) .padding(.horizontal) } } } @MainActor @Observable final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate { var location: CLLocation? var authorizationStatus: CLAuthorizationStatus = .notDetermined let manager = CLLocationManager() private var liveUpdateTask: Task<Void, Never>? override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation authorizationStatus = manager.authorizationStatus } func requestPermission() { manager.requestWhenInUseAuthorization() } func startNavigating() { liveUpdateTask = Task { do { for try await update in CLLocationUpdate.liveUpdates(.automotiveNavigation) { guard let newLocation = update.location else { continue } self.location = newLocation } } catch { print("Live updates error: \(error)") } } } func stopNavigating() { liveUpdateTask?.cancel() liveUpdateTask = nil manager.requestLocation() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { location = locations.last } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { authorizationStatus = manager.authorizationStatus } }
Replies
0
Boosts
0
Views
56
Activity
1w
Xcode video rendering performance
video-probe test [object Object]
Topic: UI Frameworks SubTopic: SwiftUI
Replies
0
Boosts
0
Views
45
Activity
1w
How to build a picker wheel similar as the Calendar App?
How to build the below UI using SwiftUI? I tried to use Picker with wheel style, but it is not the same as the screenshot. The screenshot came from the iOS built-in calendar app. Add a new calendar event Click "Repeat" Choose "Custom" Click "Every day" The required picker wheel will be displayed Picker("Every", selection: $interval) { ForEach(1..<366) { interval in Text("\(interval)").tag(interval) } } .pickerStyle(.wheel)
Replies
0
Boosts
0
Views
92
Activity
1w
NSTokenField - How To Tell If I'm Editing an Existing Token in -tokenField:representedObjectForEditingString: ?
I'm trying to use NSTokenField for the first time. So my custom 'representedObject' for a token has additional model data tied to it (not just the editing/display string). I noticed when I edit an existing token, type text, and hit the enter key I get the following delegate callback: - (nullable id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString; This same delegate method is called when I type a brand new token. Is there a way to distinguish if I'm editing a token vs. creating a new one? My expectation is to be able to do something like this (made up enhancement): - (nullable id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString atIndex:(NSUInteger)existingTokenIndex { if (existingTokenIndex == NSNotFound) { // Token is new, create a new instance MyTokenObject *newToken = //create and configure. return newToken; } else { // This would update the editing string but wouldn't discard existing data held by the token. MyTokenObject *tokenObj = [self existingTokenAtIndex:existingTokenIndex]; tokenObj.editingString = editingString; return tokenObj; } }
Replies
2
Boosts
0
Views
186
Activity
1w
How do I get SwiftUI to let me determine a custom frame size for NSTextField
I have a NSViewRepresentable that wraps an NSTextField subclass which is displayed as larger than your typical text field. SwiftUI doesn't seem to allow me to set the size of the view when the underlying is an NSTextField. It forces it as a single line field. I've tried both setting the frame on creation as well as using SwiftUI .frame(width:height:) on the represented view. I always end up with a single line field. struct BigTextField: NSViewRepresentable { @Binding var text: String class Coordinator: NSObject, NSTextFieldDelegate { var parent: BigTextField init(_ parent: BigTextField) { self.parent = parent } func controlTextDidChange(_ obj: Notification) { if let textField = obj.object as? NSTextField { parent.text = textField.stringValue } } } func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: Context) -> NSTextField { //let frame = NSRect(x: 0, y: 0, width: 350, height: 140) //let textField = NSTextField(frame: frame) let textField = NSTextField() textField.isEditable = true textField.isBordered = true textField.isBezeled = true textField.delegate = context.coordinator // Assign the coordinator return textField } func updateNSView(_ nsView: NSTextField, context: Context) { if nsView.stringValue != text { nsView.stringValue = text } } } I've also included the SwiftUI declaration which demonstrates the problem. struct ContentView: View { @State var text : String = "Test string" var body: some View { VStack { BigTextField(text: $text) .frame(width: 350, height: 140) } .padding() } } NSTextField can be any arbitrary frame size. I already do this from AppKit but am trying to adapt this custom field to work within SwiftUI. SwiftUI seems to override the sizing of this NSViewRepresentable that I give it. Am I missing something here? Is there some way to override SwiftUI's sizing behavior? Thank you.
Replies
0
Boosts
0
Views
97
Activity
2w
Creating NSStatusBar.system.statusItem generates console warning
Putting: let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) at the top of AppDelegate.swift and no other code at all generates the warning below in the console when the app runs. Looks benign but it sure would be great to not have that happen. Anyone figure out how to stop this other than muting the warning? I tried all kinds of exotic deferring but that just makes the code brittle and unreadable. Here's the warning: It's not legal to call -layoutSubtreeIfNeeded on a view which is already being laid out. If you are implementing the view's -layout method, you can call -[super layout] instead. Break on void _NSDetectedLayoutRecursion(void) to debug. This will be logged only once. This may break in the future. Xcode 26.5, 26.4 - macOS 26.5.1
Topic: UI Frameworks SubTopic: AppKit
Replies
2
Boosts
0
Views
110
Activity
2w
How to animate NSSegmentedControl on macOS 26?
I noticed that in Xcode 27 Beta 1 on macOS 27 Beta 1, changing the selection of the segmented control used to switch between the Sidebar and Inspector tabs is animated. However, the standard segmented controls used by NSTabView/NSTabViewController, as well as a regular NSSegmentedControl, do not appear to have any such animation on macOS 27. I also tried using the animator proxy: segmentedControl.animator().selectedSegment = index but this did not change the behavior. How can I implement the same kind of segmented control selection animation that Xcode uses? Is there a public API for this, or is it achieved through a different mechanism?
Topic: UI Frameworks SubTopic: AppKit
Replies
1
Boosts
0
Views
93
Activity
2w
Back gesture not disabled with navigationBarBackButtonHidden(true) when using .zoom transition
[Submitted as FB22226720] For a NavigationStack destination, applying .navigationBarBackButtonHidden(true) hides the back button and also disables the interactive left-edge back gesture when using the standard push navigation transition. However, when the destination uses .navigationTransition(.zoom), the back button is hidden but the left-edge back gesture is still available—it can still be dismissed even though back is intentionally suppressed. This creates inconsistent behavior between navigation transition styles. navigationBarBackButtonHidden(_:) works with a standard push transition, but not with .navigationTransition(.zoom). In the code below, .interactiveDismissDisabled(true) is also applied as another attempt to suppress the back-swipe gesture, but it has no effect. As a result, there’s currently no clean way to prevent back navigation when using the zoom transition. REPRO STEPS Create an iOS project then replace ContentView with code below, build and run. Leave nav type set to List Push. Open an item. Verify there is no back button, then try the left-edge back gesture. Return to the root view. Change nav type to Grid Zoom. Open an item. Verify there is no back button, then try the left-edge back gesture. ACTUAL In List Push mode, the left-edge back gesture is prevented. In Grid Zoom mode, the back button is hidden, but the left-edge back gesture still works and returns to the previous view. EXPECTED Behavior should be consistent across navigation transition styles. If this configuration is meant to suppress interactive backward navigation for a destination, it should also suppress the left-edge back gesture when using .navigationTransition(.zoom). SCREEN RECORDING SAMPLE CODE struct ContentView: View { private enum NavigationMode: String, CaseIterable { case listPush = "List Push" case gridZoom = "Grid Zoom" } @Namespace private var namespace @State private var navigationMode: NavigationMode = .listPush private let colors: [Color] = [.red, .blue] var body: some View { NavigationStack { VStack(spacing: 16) { Picker("Navigation Type", selection: $navigationMode) { ForEach(NavigationMode.allCases, id: \.self) { mode in Text(mode.rawValue).tag(mode) } } .pickerStyle(.segmented) if navigationMode == .gridZoom { HStack { ForEach(colors.indices, id: \.self) { index in NavigationLink(value: index) { VStack { RoundedRectangle(cornerRadius: 14) .fill(colors[index]) .frame(height: 120) Text("Grid Item \(index + 1)") .font(.subheadline.weight(.medium)) } .padding(12) .frame(maxWidth: .infinity) .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 16)) .matchedTransitionSource(id: index, in: namespace) } .buttonStyle(.plain) } } } else { ForEach(colors.indices, id: \.self) { index in NavigationLink(value: index) { HStack { Circle() .fill(colors[index]) .frame(width: 24, height: 24) Text("List Item \(index + 1)") Spacer() Image(systemName: "chevron.right") .foregroundStyle(.secondary) } .padding() .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 12)) } .buttonStyle(.plain) } } Spacer() } .padding(20) .navigationTitle("Prevent Back Swipe") .navigationSubtitle("Compare Grid Zoom vs List Push") .navigationDestination(for: Int.self) { index in if navigationMode == .gridZoom { DetailView(color: colors[index]) .navigationTransition(.zoom(sourceID: index, in: namespace)) } else { DetailView(color: colors[index]) } } } } } private struct DetailView: View { @Environment(\.dismiss) private var dismiss let color: Color var body: some View { ZStack { color.ignoresSafeArea() Text("Try left-edge swipe back") .font(.title.bold()) .multilineTextAlignment(.center) .padding(.horizontal, 24) } .navigationBarBackButtonHidden(true) .interactiveDismissDisabled(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close", action: dismiss.callAsFunction) } } } }
Replies
3
Boosts
0
Views
784
Activity
2w
Am I disallowed to use Glass in the Dock in any means?
I tried Glass in NSApp.dockTile.contentView. Any Glass there isn't shown like it normally is in windows. Does this limitation exist intentionally?
Replies
1
Boosts
0
Views
78
Activity
2w
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
476
Activity
2w
UITextView for displaying long text?
My app displays vertically scrollable text to the user which could be as short as a single screen or as long as a book chapter: imagine something like an e-book reader which uses scrolling rather than page-turning. A long time ago when development first started, I tried using UITextView, but the size of text that could be handled was quite limited (a figure of 32767 points seems to stick in the memory). Accordingly I came up with a custom solution in which a UIScrollView handled scrolling gestures and a custom UIView rendered just the part of the text that was visible at that time. That works, but it becomes cumbersome to add support for features such as selection handles and loupes. 18 years later, is there a more smoothly integrated way of displaying long scrollable text?
Topic: UI Frameworks SubTopic: UIKit
Replies
2
Boosts
0
Views
134
Activity
2w
Debugging multi-window AppKit apps: Identifying the window associated with a breakpoint
When working on a multi-window AppKit app, how do you identify which window instance is associated with the current breakpoint? The same question applies when using LLDB. If execution stops inside an object that can exist in more than one window, such as a shared NSViewController subclass, how do you know which window’s object you are currently inspecting? Does Xcode provide a mechanism for showing the NSWindow associated with the current view, view controller or responder while debugging? My current approach is to print object identities and compare them manually. For example, if several identical windows are open, I might print the current object and its window: print(self, #function) Then I interact with one window, make a note of the printed memory addresses in the console, switch to another window and compare the output. This works, but it feels manual. I am not dealing with different window subclasses. The windows may be instances of the same class and may contain instances of the same view controller classes. I simply want an easier way to distinguish which window instance I am debugging. Is there a recommended Xcode, LLDB or AppKit workflow for this?
Replies
1
Boosts
0
Views
179
Activity
2w
X button disappeared on iPadOS 26.4 in MFMailComposeViewController
I’m using MFMailComposeViewController to send emails from my app. Since updating to iPadOS 26.4, there is no way to cancel the mail composer because the “X” button in the top-left corner has disappeared. On iPhone with iOS 26.4, everything still seems to work as expected. Is this a known issue, or am I missing something? Has anyone else experienced this, or found a workaround?
Topic: UI Frameworks SubTopic: UIKit
Replies
8
Boosts
1
Views
896
Activity
2w
Hide standard window buttons
How do I permanently hide the standard window buttons(Buttons on top left; close, minimize and zoom) In my app, I hide the standard buttons and put my own custom buttons. Some users have reported that the standard buttons appear again making the app look buggy and confuses the users At present I use following code to remove the buttons standardWindowButton(.closeButton)?.removeFromSuperview() standardWindowButton(.miniaturizeButton)?.removeFromSuperview() standardWindowButton(.zoomButton)?.removeFromSuperview() } I have to call this code from multiple places like on window initialization, when appearance changes and when the window size changes. I tried creating window without the titled stylemask, but it has other down sides like the standard window decoration is entirely skipped by the system. The resizing also is unpredictable. My question is, what is the right way to remove/hide the standard window buttons?
Topic: UI Frameworks SubTopic: AppKit
Replies
1
Boosts
0
Views
116
Activity
2w
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
71
Activity
2w
Crash occured in UIDatePicker Calendar type
I am encountering a consistent, reproducible crash in our app when presenting a UIDatePicker configured with the calendar style. The crash triggers every single time the picker is invoked and points directly to the modern date picker's internal UICollectionView. The Exception: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView internal inconsistency: attempted to set layout with the collection view requiring a reload' let datePicker = UIDatePicker() datePicker.datePickerMode = .date datePicker.preferredDatePickerStyle = .compact This crash is occuring in inline style too when I try to open the calendar. I tried this in other apps. It works fine. I didn't override any collectionView layouts
Replies
1
Boosts
0
Views
88
Activity
2w