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

Posts under SwiftUI tag

200 Posts

Post

Replies

Boosts

Views

Activity

SwiftUI List optional refreshable
Currently refreshable modifier does not support nil as a value and there's no way of disabling refreshable without recreating the whole view. There are a few posts showing how refreshable could be optionally disabled on scrollViews using: \EnvironmentValues.refresh as? WritableKeyPath<EnvironmentValues, RefreshAction?> https://stackoverflow.com/a/77587703 However, this approach doesn't seem to work with Lists. Has anyone find any solutions for this?
0
0
177
Sep ’25
Universal Link
Hello, I'm developing a feature for my app, that allows users to challenge their friends. The friend request functionality is built using Universal Links, but I've run into a significant issue. The Universal Links are correctly deep-linking into the app. However, once the app opens, nothing happens—the friend request acceptance or rejection flow does not occur. This prevents users from completing friend requests and building their friend list. Here are examples of the Universal Links I'm generating: https://www.strike-force.app/invite?type=invite&amp;amp;userID=... https://www.strike-force.app/invite?type=invite&amp;amp;friendRequestID=... https://www.strike-force.app/profile?userID=... I've recently updated my cloudflare-worker.js to serve a paths array of ["*"] in the AASA file, so I believe the links themselves should be valid. Technical Details &amp;amp; Error Logs In the console, I am consistently seeing the following error message: Cannot issue sandbox extension for URL:https://www.strike-force.app/invite?token=7EF1E439-090B-4DF2-BE64-9904F50A3F8B Received port for identifier response: &amp;lt;(null)&amp;gt; with error:Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.process-state, NSLocalizedFailureReason=Client not entitled, RBSPermanent=false} elapsedCPUTimeForFrontBoard couldn't generate a task port This error appears to be related to entitlements and process state, but I am not sure if it's the root cause of the Universal Link issue or a separate problem. The 'Client not entitled' error on line 3 has had me chasing down entitlements issues. But, I've added the Associated Domains entitlement with the proper applink URLs and verified this in my Developer Portal. I've regenerated my provisioning profile, manually installed it, and selected/de-selected Automatically Manage Signing. As well I've verified my AASA file and it's correctly being served via HTTPS and returning a 200. curl -i https://strike-force.app/.well-known/apple-app-site-association curl -i https://www.strike-force.app/.well-known/apple-app-site-association I am looking for guidance on why the friend request flow is not being triggered after a successful deep-link and how I can fix the related error. Any insights or suggestions would be greatly appreciated.
6
0
871
Sep ’25
Title and Subtitle in Control Widget on macOS Tahoe?
Hi 🙋 Has anybody gotten subtitles in macOS Tahoe Control Widgets to show up in their custom control widgets? Seems macOS is able to do it (see attached screenshot of the Bluetooth control widget), but my widget, which shows a title and subtitle on iOS, will only show the title on macOS. I tried all the different ControlWidgetButton init methods to no avail. I tried a VStack for my title and subtitle Texts, I tried just two Texts without a VStack, I tried the controlWidgetStatus and controlWidgetActionHint modifiers out of desperation... nothing worked. Any pointers much appreciated! Thank you, – Matthias
2
1
213
Sep ’25
How to disable the default focus effect and detect keyboard focus in SwiftUI?
I’m trying to customize the keyboard focus appearance in SwiftUI. In UIKit (see WWDC 2021 session Focus on iPad keyboard navigation), it’s possible to remove the default UIFocusHaloEffect and change a view’s appearance depending on whether it has focus or not. In SwiftUI I’ve tried the following: .focusable() // .focusable(true, interactions: .activate) .focusEffectDisabled() .focused($isFocused) However, I’m running into several issues: .focusable(true, interactions: .activate) causes an infinite loop, so keyboard navigation stops responding .focusEffectDisabled() doesn’t seem to remove the default focus effect on iOS Using @FocusState prevents Space from triggering the action when the view has keyboard focus My main questions: How can I reliably detect whether a SwiftUI view has keyboard focus? (Is there an alternative to FocusState that integrates better with keyboard navigation on iOS?) What’s the recommended way in SwiftUI to disable the default focus effect (the blue overlay) and replace it with a custom border? Any guidance or best practices would be greatly appreciated! Here's my sample code: import SwiftUI struct KeyboardFocusExample: View { var body: some View { // The ScrollView is required, otherwise the custom focus value resets to false after a few seconds. I also need it for my actual use case ScrollView { VStack { Text("First button") .keyboardFocus() .button { print("First button tapped") } Text("Second button") .keyboardFocus() .button { print("Second button tapped") } } } } } // MARK: - Focus Modifier struct KeyboardFocusModifier: ViewModifier { @FocusState private var isFocused: Bool func body(content: Content) -> some View { content .focusable() // ⚠️ Must come before .focused(), otherwise the FocusState won’t be recognized // .focusable(true, interactions: .activate) // ⚠️ This causes an infinite loop, so keyboard navigation no longer responds .focusEffectDisabled() // ⚠️ Has no effect on iOS .focused($isFocused) // Custom Halo effect .padding(4) .overlay( RoundedRectangle(cornerRadius: 18) .strokeBorder( isFocused ? .red : .clear, lineWidth: 2 ) ) .padding(-4) } } extension View { public func keyboardFocus() -> some View { modifier(KeyboardFocusModifier()) } } // MARK: - Button Modifier /// ⚠️ Using a Button view makes no difference struct ButtonModifier: ViewModifier { let action: () -> Void func body(content: Content) -> some View { content .contentShape(Rectangle()) .onTapGesture { action() } .accessibilityAction { action() } .accessibilityAddTraits(.isButton) .accessibilityElement(children: .combine) .accessibilityRespondsToUserInteraction() } } extension View { public func button(action: @escaping () -> Void) -> some View { modifier(ButtonModifier(action: action)) } }
1
0
623
Sep ’25
HideAllTips launch argument does not work in xctestplan
The launch argument -com.apple.TipKit.HideAllTips 1 does not work if it is defined in xctestplan arguments passed on launch. I tested it with the apple provided example app, where I created simple UI test and added xctestplan with launch argument -com.apple.TipKit.HideAllTips 1. The app does not hide the tips when it is running the UI Tests. Is there any solution that works? Thanks for reply.
2
0
139
Sep ’25
tabViewBottomAccessory selective hidden bug
import SwiftUI struct ContentView: View { @State private var selection = 1 var body: some View { TabView(selection: $selection) { Tab("1", systemImage: "1.circle", value: 1) { Text("Tab 1") } Tab("2", systemImage: "2.circle", value: 2) { Text("Tab 2") } } .tabViewBottomAccessory { if selection == 1 { Text("Bottom Bar for Tab 1") } } } } With this structure, I'm supposing when I select tab 2, the bottom accessory will be hidden, but it is not hidden for the first time, after I click back to tab 1, then click tab 2, it is hidden. I think this is a bug?
0
1
131
Sep ’25
How to use iOS15-specific modifiers in SwiftUI on iOS 14 and earlier?
There are many new iOS15-specific modifiers that were added in SwiftUI. For example, we have a .focused() modifier, which can be used like this: TextField("Username", text: $username) .focused($focusedField, equals: .username) However, this code fails to compile if the app supports iOS 14 and earlier. How can I make this code to compile? Ideally, I'd like to do something like this: TextField("Username", text: $username) #if os(iOS, 15.0, *) .focused($focusedField, equals: .username) #endif But obviously this won't work because #if os() can only specify the target OS, not the version.. Thanks!
3
0
6k
Sep ’25
View presented as sheet are properly updated to correct color scheme
I’m facing an issue and I’d like to know if anyone has already run into this. I have a ContentView that presents a SettingsView as a sheet. SettingsView applies a change to the app’s colorScheme. ContentView reacts correctly to the change, and SettingsView does too (so far, so good). What’s strange is that when I set nil on the preferredColorScheme modifier (which, according to the docs, corresponds to the system color scheme), ContentView correctly picks up the change and refreshes, while SettingsView does pick up the change but doesn’t refresh. (In the video you can clearly see that when I switch from Dark to System, the parent view refreshes properly but not the presented sheet.) I’ve tried everything—switching to UIKit, changing the sheet’s ID… nothing works Another strange thing: if I present SettingsView through a NavigationLink, everything works normally… Here is a sample code to reproduce: import SwiftUI enum AppTheme: Int { case system = 0 case dark = 1 case light = 2 var colorScheme: ColorScheme? { switch self { case .system: return nil case .light: return .light case .dark: return .dark } } } struct SettingsView: View { @AppStorage("theme") var appTheme: AppTheme = .system var body: some View { VStack(spacing: 8) { Button { select(theme: .system) } label: { Text("Systeme") } Button { select(theme: .dark) } label: { Text("Dark") } Button { select(theme: .light) } label: { Text("Light") } } .preferredColorScheme(appTheme.colorScheme) } func select(theme: AppTheme) { appTheme = theme } } struct ContentView: View { @AppStorage("theme") var appTheme: AppTheme = .system @State var isPresented = false var body: some View { NavigationStack { VStack { Button { isPresented = true } label: { Text("Present settings") } // NavigationLink("Present settings") { // SettingsView() // } } .preferredColorScheme(appTheme.colorScheme) .sheet(isPresented: $isPresented) { SettingsView() } } } } #Preview { ContentView() }
2
0
119
Sep ’25
SwiftUI's List backed by CoreData using @FetchRequest fails to update on iOS 26 when compiled with Xcode 26
Hey there! I've been tracking a really weird behavior with a List backed by @FetchRequest from CoreData. When I toggle a bool on the CoreData model, the first time it updates correctly, but if I do it a second time, the UI doesn't re-render as expected. This does not happen if I compile the app using Xcode 16 (targeting both iOS 18 and iOS 26), nor it happens when using Xcode 26 and targeting iOS 18. It only happens when building the app using Xcode 26 and running it on iOS 26. Here are two demos: the first one works as expected, when I toggle the state twice, both times updates. The second one, only on iOS 26, the second toggle fails to re-render. Demo (running from Xcode 16): Demo (running from Xcode 26): The code: import SwiftUI import CoreData @main struct CoreDataTestApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } } } struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]) private var items: FetchedResults<Item> var body: some View { NavigationView { List { ForEach(items) { item in HStack { Text(item.timestamp!.formatted()) Image(systemName: item.isFavorite ? "heart.fill" : "heart").foregroundStyle(.red) }.swipeActions(edge: .leading, allowsFullSwipe: true) { Button(item.isFavorite ? "Unfavorite" : "Favorite", systemImage: item.isFavorite ? "heart" : "heart.fill") { toggleFavoriteStatus(item: item) } } } } .toolbar { ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } } private func addItem() { withAnimation { let newItem = Item(context: viewContext) newItem.timestamp = Date() newItem.isFavorite = Bool.random() try! viewContext.save() } } private func toggleFavoriteStatus(item: Item) { withAnimation { item.isFavorite.toggle() try! viewContext.save() } } } struct PersistenceController { static let shared = PersistenceController() let container: NSPersistentContainer init() { container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: { _, _ in }) container.viewContext.automaticallyMergesChangesFromParent = true } }
5
1
333
Sep ’25
iOS 26 Webview and alert issue
Hello, In iOS 26 beta, we are seeing an unexpected behavior when using SwiftUI WebView (or a custom WKWebView via UIViewRepresentable). When an alert is presented above the WebView, the WebView immediately reloads to its initial page. The alert itself also disappears instantly, making it impossible for the user to interact with it. This issue occurs both with the new SwiftUI WebView / WebPage API and with a wrapped WKWebView. The problem was not present in previous iOS versions (iOS 17/18). Steps to reproduce: Create a SwiftUI view with a WebView (pointing to any URL). Add a toolbar button that toggles a SwiftUI alert. Run the app on iOS 26 beta. Tap the button to trigger the alert. Expected behavior: The WebView should remain as-is, and the alert should stay visible until the user dismisses it. Actual behavior: As soon as the alert appears, the WebView reloads and resets to the initial page. The alert disappears immediately. Minimal Example: struct ContentView: View { @State private var showAlert = false var body: some View { NavigationStack { WebView(URL(string: "https://apple.com")!) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close") { showAlert = true } } } .alert("Confirm close?", isPresented: $showAlert) { Button("Cancel", role: .cancel) {} Button("Close", role: .destructive) {} } } } } I'm using Xcode Version 26.0 beta 7 Thanks for your help.
2
1
899
Sep ’25
Dividers not appearing in menu bar on iPadOS 26
On macOS 26 I can see the dividers when I open my Help menu: However, on iPadOS 26 the dividers don't appear: I am simply using Divider() to separate my menu bar items in my CommandGroup. iPadOS does support dividers as I can see them for the system generated Edit menu but for some reason it's not working here. Does anyone know if I am doing something wrong with the iPadOS implementation?
2
1
180
Sep ’25
Blurred selected button on tvOS
The following code shows that a selected button in a list gots blurred if a glass effect is applied to the list. This happens if the button style is plain or glass. It does not happen if the button style is bordered. Is this a wanted documented behavior or is this a bug? struct ContentView: View { @State private var items = [ "Item 1", "Item 2", "Item 3", "Item 4"] var body: some View { ZStack { Image(systemName: "globe") .resizable() List(items, id: \.self) { item in Button(action: {}, label: { Text(item) }) } .padding() .glassEffect(in: Rectangle()) } } }
2
0
138
Sep ’25
Concentric corner radius for grouped cells in UICollectionView on iOS 26
What is the recommended way to obtain the concentric corner radius for views within grouped UICollectionView cells? In the most basic example, a UICollectionView with one section and one cell, we observe the cell takes almost the shape of a capsule, but it is indeed not a capsule. What is the way to obtain the radius of the grouped area from within the cell or its registration? I would like to layer elements on top that are concentric to the cell's clip shape. I've tried using custom views with .concentric UICornerConfigurations, setting .cornerConfiguration on the cell and on a custom backgroundView and I've even tried obtaining the .effectiveRadius of the cell after layout (returns 0.0). As of Xcode 26.0 Beta 7, nothing works. This seems like a huge omission; what am I missing here?
1
0
237
Sep ’25
Liquid Glass clear variant isn't clear
I've been experimenting with Liquid Glass quite a bit and watched all the WWDC videos. I'm trying to create a glassy segmented picker, like the one used in Camera: however, it seems that no matter what I do there's no way to recreate a truly clear (passthrough) bubble that just warps the light underneath around the edges. Both Glass.regular and Glass.clear seem to add a blur that can not be evaded, which is counter to what clear ought to mean. Here are my results: I've used SwiftUI for my experiment but I went through the UIKit APIs and there doesn't seem to be anything that suggests full transparency. Here is my test SwiftUI code: struct GlassPicker: View { @State private var selected: Int? var body: some View { ScrollView([.horizontal], showsIndicators: false) { HStack(spacing: 0) { ForEach(0..<20) { i in Text("Row \(i)") .id(i) .padding() } } .scrollTargetLayout() } .contentMargins(.horizontal, 161) .scrollTargetBehavior(.viewAligned) .scrollPosition(id: $selected, anchor: .center) .background(.foreground.opacity(0.2)) .clipShape(.capsule) .overlay { DefaultGlassEffectShape() .fill(.clear) // Removes a semi-transparent foreground fill .frame(width: 110, height: 50) .glassEffect(.clear) } } } Is there any way to achieve the above result or does Apple not trust us devs with more granular control over these liquid glass elements?
2
3
220
Aug ’25
Inconsistent subviews redrawing in LazyVStack
Hello Apple forum ! I spotted a weird behaviour with LazyVStack in a ScrollView. I understand that it loads its views only once upon appearance unlinke VStack that loads everything in one shot. What I noticed also, it seems to reload its views sometimes when scrolling back up to earlier loaded views. The thing is, it isn't always the case. struct LazyVStackTest: View { var body: some View { ScrollView { LazyVStack { ForEach(0..<1000, id: \.self) { _ in // if true { MyText() // } } } } } struct MyText: View { var body: some View { let _ = Self._printChanges() HStack { Text("hello") } } } } If we consider the code above on XCode 26 beta 7 on an iOS 26 or iOS 18.2 simulator. Scroll to the bottom : you'll see one "LazyVStackTest.MyText: @self changed" for each row. Then scroll back up to the top, we'll see again the same message printed multiple times. --> So I gather from this that LazyVStack not only loads lazily but also removes old rows from memory & recreates them upon reappearance. What I don't get however is that if you uncomment the "if true" statement, you won't see the reloading happening. And I have absolutely no clue as to why 😅 If someone could help shed some light on this weird behaviour, it would be greatly appreciated ^^ PS : the issue is also present with XCode 16.2 but at a deeper lever (ex: if we embed another custom View "MyText2" inside "MyText", the reloading is in "MyText2" & not "MyText")
0
0
276
Aug ’25
NavigationStack path is being reset in NavigationSplitView details columns
I'm building a SwiftUI app for iPad using a NavigationSplitView as the navigation root. Below is a simplified version of the app's navigation. There are a Home Page and a Settings Page, each with its own NavigationStack. The page that appears in the detail column depends on the sidebar's selection value. The issue I'm facing is that when I navigate deeply into the Home Page's NavigationStack (e.g., to a Home Page Child view), switch to the Settings Page, and then switch back to the Home Page, the Home Page's navigation path has been reset to [] and the previous state is lost. The same issue occurs if I navigate deeply into the Settings Page (e.g., to a Settings Page Child view), switch to the Home Page, and then return to the Settings Page: the navigation state for the Settings Page is lost, and it reverts to the root of the NavigationStack. Why is this happening and how can I fix it so that switching pages in the sidebar doesn't reset the NavigationStack of each individual page in the detail column? Thank you. struct ContentView: View { @State var selection: String? @State var firstPath = [String]() @State var secondPath = [String]() var body: some View { NavigationSplitView { List(selection: $selection) { Text("Home") .tag("home") Text("Settings") .tag("settings") } } detail: { if selection == "home" { HomePage(path: $firstPath) } else { SettingsPage(path: $secondPath) } } } } struct HomePage: View { @Binding var path: [String] var body: some View { NavigationStack(path: $path) { NavigationLink("Home Page", value: "Home") .navigationDestination(for: String.self) { _ in Text("Home Page Child") } } } } struct SettingsPage: View { @Binding var path: [String] var body: some View { NavigationStack(path: $path) { NavigationLink("Settings Page", value: "Settings") .navigationDestination(for: String.self) { _ in Text("Settings Page Child") } } } } #Preview { ContentView() }
0
0
309
Aug ’25
Textfield not updating integer with optional value
I am trying to get integer input by using textfield. However, I noticed that if I changed the binding variable as optional with an initial value of null, the textfield would not work. I would like to keep it as null initially because I want the placeholder to show text before the input, and if the int variable starts with any valid value, the text would not be shown. Is there a way to fix things here? struct TextFieldNumberInputView: View {     @Binding var intVariable: Int?     @State var isEditing: Bool = false     @State var placeholderText: String     @State var number: Int = 0          var body: some View {         VStack(alignment: .leading, spacing: 2){             TextField(placeholderText, value: $number, formatter: NumberFormatter()){             }             .textFieldStyle(InputTextFieldStyle())             .keyboardType(.numberPad)             .onReceive(Just(number)) {_ in                 print("number pad being editing")                 if isEditing == false && intVariable != nil {                                         isEditing = true                     print("number is being edited")                 } else if isEditing == true && intVariable != nil{                     isEditing = false                 }             }                          Text(placeholderText)                 .font(.caption2)                 .foregroundColor(isEditing ? Color(.systemGray3):Color.clear)                 .padding(.horizontal)                 .padding(.horizontal, 12)                  }.onTapGesture {             print("number pad being tapped, intVariable \(intVariable), \(number)")                          if number != nil {                 print("checking number")                 UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)             }                      }     }      }
2
0
842
Aug ’25
iOS 18 hit testing functionality differs from iOS 17
I created a Radar for this FB14766095, but thought I would add it here for extra visibility, or if anyone else had any thoughts on the issue. Basic Information Please provide a descriptive title for your feedback: iOS 18 hit testing functionality differs from iOS 17 What type of feedback are you reporting? Incorrect/Unexpected Behavior Description: Please describe the issue and what steps we can take to reproduce it: We have an issue in iOS 18 Beta 6 where hit testing functionality differs from the expected functionality in iOS 17.5.1 and previous versions of iOS. iOS 17: When a sheet is presented, the hit-testing logic considers subviews of the root view, meaning the rootView itself is rarely the hit view. iOS 18: When a sheet is presented, the hit-testing logic changes, sometimes considering the rootView itself as the hit view. Code: import SwiftUI struct ContentView: View { @State var isPresentingView: Bool = false var body: some View { VStack { Text("View One") Button { isPresentingView.toggle() } label: { Text("Present View Two") } } .padding() .sheet(isPresented: $isPresentingView) { ContentViewTwo() } } } #Preview { ContentView() } struct ContentViewTwo: View { @State var isPresentingView: Bool = false var body: some View { VStack { Text("View Two") } .padding() } } extension UIWindow { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { /// Get view from superclass. guard let hitView = super.hitTest(point, with: event) else { return nil } print("RPTEST rootViewController = ", rootViewController.hashValue) print("RPTEST rootViewController?.view = ", rootViewController?.view.hashValue) print("RPTEST hitView = ", hitView.hashValue) if let rootView = rootViewController?.view { print("RPTEST rootViewController's view memory address: \(Unmanaged.passUnretained(rootView).toOpaque())") print("RPTEST hitView memory address: \(Unmanaged.passUnretained(hitView).toOpaque())") print("RPTEST Are they equal? \(rootView == hitView)") } /// If the returned view is the `UIHostingController`'s view, ignore. print("MTEST: hitTest rootViewController?.view == hitView", rootViewController?.view == hitView) print("MTEST: -") return hitView } } Looking at the print statements from the provided sample project:
 iOS 17 presenting a sheet from a button tap on the ContentView(): RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fd25000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fd25000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false iOS 17 dismiss from presented view: RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fe04080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fe04080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false iOS 18 presenting a sheet from a button tap on the ContentView(): RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103342080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x000000010333e3c0 RPTEST Are they equal? true MTEST: hitTest rootViewController?.view == hitView true You can see here ☝️ that in iOS 18 the views have the same memory address on the second call and are evaluated to be the same. This differs from iOS 17. iOS 18 dismiss RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103e80000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103e80000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false The question I want to ask: Is this an intended change, meaning the current functionality in iOS 18 is expected? Or is this a bug and it's something that needs to be fixed? As a user, I would expect that the hit testing functionality would remain the same from iOS 17 to iOS 18. Thank you for your time.
14
13
4.9k
Aug ’25
SwiftUI drag & drop: reliable cancellation
Summary In a SwiftUI drag-and-drop flow, the only robust way I’ve found to detect cancellation (user drops outside any destination) is to rely on the NSItemProvider created in .onDrag and run cleanup when it’s deallocated, via a custom onEnded callback tied to its lifecycle. On iOS 26, the provider appears to be deallocated immediately after .onDrag returns (unless I keep a strong reference), so a deinit/onEnded-based cancel hook fires right away and no longer reflects the true end of the drag session. I’d like to know: 1. Is there a supported, reliable way to detect when a drag ends outside any drop target so I can cancel and restore the source row? 2. Is the iOS 26 NSItemProvider deallocation timing a bug/regression or intended behavior? Minimal SwiftUI Repro This example shows: • creating a custom NSItemProvider subclass with an onEnded closure • retaining it to avoid immediate dealloc (behavior change on iOS 26) • using performDrop to mark the drag as finished import SwiftUI import UniformTypeIdentifiers final class DragProvider: NSItemProvider { var onEnded: (() -> Void)? deinit { // Historically: called when the system drag session ended (drop or cancel). // On iOS 26: can fire immediately after .onDrag returns unless the provider is retained. onEnded?() } } struct ContentView: View { struct Item: Identifiable, Equatable { let id = UUID(); let title: String } @State private var pool: [Item] = (1...4).map { .init(title: "Option \($0)") } @State private var picked: [Item] = [] @State private var dragged: Item? @State private var dropFinished: Bool = true @State private var activeProvider: DragProvider? // Retain to avoid immediate dealloc private let dragType: UTType = .plainText var body: some View { HStack(spacing: 24) { // Destination list (accepts drops) VStack(alignment: .leading, spacing: 8) { Text("Picked").bold() VStack(spacing: 8) { ForEach(picked) { item in row(item) } } .padding() .background(RoundedRectangle(cornerRadius: 8).strokeBorder(.quaternary)) .onDrop(of: [dragType], delegate: Dropper( picked: $picked, pool: $pool, dragged: $dragged, dropFinished: $dropFinished, activeProvider: $activeProvider )) } .frame(maxWidth: .infinity, alignment: .topLeading) // Source list (draggable) VStack(alignment: .leading, spacing: 8) { Text("Pool").bold() VStack(spacing: 8) { ForEach(pool) { item in row(item) .onDrag { startDrag(item) return makeProvider(for: item) } preview: { row(item).opacity(0.9).frame(width: 200, height: 44) } .overlay( RoundedRectangle(cornerRadius: 8) .fill(item == dragged ? Color(.systemBackground) : .clear) // keep space ) } } .padding() .background(RoundedRectangle(cornerRadius: 8).strokeBorder(.quaternary)) } .frame(maxWidth: .infinity, alignment: .topLeading) } .padding() } private func row(_ item: Item) -> some View { RoundedRectangle(cornerRadius: 8) .strokeBorder(.secondary) .frame(height: 44) .overlay( HStack { Text(item.title); Spacer(); Image(systemName: "line.3.horizontal") } .padding(.horizontal, 12) ) } // MARK: Drag setup private func startDrag(_ item: Item) { dragged = item dropFinished = false } private func makeProvider(for item: Item) -> NSItemProvider { let provider = DragProvider(object: item.id.uuidString as NSString) // NOTE: If we DO NOT retain this provider on iOS 26, // its deinit can run immediately (onEnded fires too early). activeProvider = provider provider.onEnded = { [weak self] in // Intended: run when system drag session ends (drop or cancel). // Observed on iOS 26: may run too early unless retained; // if retained, we lose a reliable "session ended" signal here. DispatchQueue.main.async { guard let self else { return } if let d = self.dragged, self.dropFinished == false { // Treat as cancel: restore the source item if !self.pool.contains(d) { self.pool.append(d) } self.picked.removeAll { $0 == d } } self.dragged = nil self.dropFinished = true self.activeProvider = nil } } return provider } // MARK: DropDelegate private struct Dropper: DropDelegate { @Binding var picked: [Item] @Binding var pool: [Item] @Binding var dragged: Item? @Binding var dropFinished: Bool @Binding var activeProvider: DragProvider? func validateDrop(info: DropInfo) -> Bool { dragged != nil } func performDrop(info: DropInfo) -> Bool { guard let item = dragged else { return false } if let idx = pool.firstIndex(of: item) { pool.remove(at: idx) } picked.append(item) // Mark drag as finished so provider.onEnded won’t treat it as cancel dropFinished = true dragged = nil activeProvider = nil return true } } } Questions Is there a documented, source-side callback (or best practice) to know the drag session ended without any performDrop so we can cancel and restore the item? Has the NSItemProvider deallocation timing (for the object returned from .onDrag) changed intentionally on iOS 26? If so, what’s the recommended replacement signal? Is there a SwiftUI-native event to observe the end of a drag session that doesn’t depend on the provider’s lifecycle?
0
1
160
Aug ’25
PasteButton asks for permissions every time
Hi, i'm using the PasteButton component to paste content from clipboard. On the docs it says that the PasteButton will handle internally permissions and will not present the prompt. But in my case seems to be not true. I removed all the occurrencies of UIPasteboard.general.string since i read that this will cause the prompt to appear. Also as you can see on the code below i'm not doing fancy or weird things, even with the base component this behaviour is still there. PasteButton(payloadType: String.self) { strings in if let first = strings.first { print("clipboard text: \(first)") } } I can see other apps using this paste button without asking for permissions every time, but i cannot see any issue in my code.
0
0
52
Aug ’25
SwiftUI List optional refreshable
Currently refreshable modifier does not support nil as a value and there's no way of disabling refreshable without recreating the whole view. There are a few posts showing how refreshable could be optionally disabled on scrollViews using: \EnvironmentValues.refresh as? WritableKeyPath<EnvironmentValues, RefreshAction?> https://stackoverflow.com/a/77587703 However, this approach doesn't seem to work with Lists. Has anyone find any solutions for this?
Replies
0
Boosts
0
Views
177
Activity
Sep ’25
Universal Link
Hello, I'm developing a feature for my app, that allows users to challenge their friends. The friend request functionality is built using Universal Links, but I've run into a significant issue. The Universal Links are correctly deep-linking into the app. However, once the app opens, nothing happens—the friend request acceptance or rejection flow does not occur. This prevents users from completing friend requests and building their friend list. Here are examples of the Universal Links I'm generating: https://www.strike-force.app/invite?type=invite&amp;amp;userID=... https://www.strike-force.app/invite?type=invite&amp;amp;friendRequestID=... https://www.strike-force.app/profile?userID=... I've recently updated my cloudflare-worker.js to serve a paths array of ["*"] in the AASA file, so I believe the links themselves should be valid. Technical Details &amp;amp; Error Logs In the console, I am consistently seeing the following error message: Cannot issue sandbox extension for URL:https://www.strike-force.app/invite?token=7EF1E439-090B-4DF2-BE64-9904F50A3F8B Received port for identifier response: &amp;lt;(null)&amp;gt; with error:Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.process-state, NSLocalizedFailureReason=Client not entitled, RBSPermanent=false} elapsedCPUTimeForFrontBoard couldn't generate a task port This error appears to be related to entitlements and process state, but I am not sure if it's the root cause of the Universal Link issue or a separate problem. The 'Client not entitled' error on line 3 has had me chasing down entitlements issues. But, I've added the Associated Domains entitlement with the proper applink URLs and verified this in my Developer Portal. I've regenerated my provisioning profile, manually installed it, and selected/de-selected Automatically Manage Signing. As well I've verified my AASA file and it's correctly being served via HTTPS and returning a 200. curl -i https://strike-force.app/.well-known/apple-app-site-association curl -i https://www.strike-force.app/.well-known/apple-app-site-association I am looking for guidance on why the friend request flow is not being triggered after a successful deep-link and how I can fix the related error. Any insights or suggestions would be greatly appreciated.
Replies
6
Boosts
0
Views
871
Activity
Sep ’25
Title and Subtitle in Control Widget on macOS Tahoe?
Hi 🙋 Has anybody gotten subtitles in macOS Tahoe Control Widgets to show up in their custom control widgets? Seems macOS is able to do it (see attached screenshot of the Bluetooth control widget), but my widget, which shows a title and subtitle on iOS, will only show the title on macOS. I tried all the different ControlWidgetButton init methods to no avail. I tried a VStack for my title and subtitle Texts, I tried just two Texts without a VStack, I tried the controlWidgetStatus and controlWidgetActionHint modifiers out of desperation... nothing worked. Any pointers much appreciated! Thank you, – Matthias
Replies
2
Boosts
1
Views
213
Activity
Sep ’25
How to disable the default focus effect and detect keyboard focus in SwiftUI?
I’m trying to customize the keyboard focus appearance in SwiftUI. In UIKit (see WWDC 2021 session Focus on iPad keyboard navigation), it’s possible to remove the default UIFocusHaloEffect and change a view’s appearance depending on whether it has focus or not. In SwiftUI I’ve tried the following: .focusable() // .focusable(true, interactions: .activate) .focusEffectDisabled() .focused($isFocused) However, I’m running into several issues: .focusable(true, interactions: .activate) causes an infinite loop, so keyboard navigation stops responding .focusEffectDisabled() doesn’t seem to remove the default focus effect on iOS Using @FocusState prevents Space from triggering the action when the view has keyboard focus My main questions: How can I reliably detect whether a SwiftUI view has keyboard focus? (Is there an alternative to FocusState that integrates better with keyboard navigation on iOS?) What’s the recommended way in SwiftUI to disable the default focus effect (the blue overlay) and replace it with a custom border? Any guidance or best practices would be greatly appreciated! Here's my sample code: import SwiftUI struct KeyboardFocusExample: View { var body: some View { // The ScrollView is required, otherwise the custom focus value resets to false after a few seconds. I also need it for my actual use case ScrollView { VStack { Text("First button") .keyboardFocus() .button { print("First button tapped") } Text("Second button") .keyboardFocus() .button { print("Second button tapped") } } } } } // MARK: - Focus Modifier struct KeyboardFocusModifier: ViewModifier { @FocusState private var isFocused: Bool func body(content: Content) -> some View { content .focusable() // ⚠️ Must come before .focused(), otherwise the FocusState won’t be recognized // .focusable(true, interactions: .activate) // ⚠️ This causes an infinite loop, so keyboard navigation no longer responds .focusEffectDisabled() // ⚠️ Has no effect on iOS .focused($isFocused) // Custom Halo effect .padding(4) .overlay( RoundedRectangle(cornerRadius: 18) .strokeBorder( isFocused ? .red : .clear, lineWidth: 2 ) ) .padding(-4) } } extension View { public func keyboardFocus() -> some View { modifier(KeyboardFocusModifier()) } } // MARK: - Button Modifier /// ⚠️ Using a Button view makes no difference struct ButtonModifier: ViewModifier { let action: () -> Void func body(content: Content) -> some View { content .contentShape(Rectangle()) .onTapGesture { action() } .accessibilityAction { action() } .accessibilityAddTraits(.isButton) .accessibilityElement(children: .combine) .accessibilityRespondsToUserInteraction() } } extension View { public func button(action: @escaping () -> Void) -> some View { modifier(ButtonModifier(action: action)) } }
Replies
1
Boosts
0
Views
623
Activity
Sep ’25
HideAllTips launch argument does not work in xctestplan
The launch argument -com.apple.TipKit.HideAllTips 1 does not work if it is defined in xctestplan arguments passed on launch. I tested it with the apple provided example app, where I created simple UI test and added xctestplan with launch argument -com.apple.TipKit.HideAllTips 1. The app does not hide the tips when it is running the UI Tests. Is there any solution that works? Thanks for reply.
Replies
2
Boosts
0
Views
139
Activity
Sep ’25
tabViewBottomAccessory selective hidden bug
import SwiftUI struct ContentView: View { @State private var selection = 1 var body: some View { TabView(selection: $selection) { Tab("1", systemImage: "1.circle", value: 1) { Text("Tab 1") } Tab("2", systemImage: "2.circle", value: 2) { Text("Tab 2") } } .tabViewBottomAccessory { if selection == 1 { Text("Bottom Bar for Tab 1") } } } } With this structure, I'm supposing when I select tab 2, the bottom accessory will be hidden, but it is not hidden for the first time, after I click back to tab 1, then click tab 2, it is hidden. I think this is a bug?
Replies
0
Boosts
1
Views
131
Activity
Sep ’25
How to use iOS15-specific modifiers in SwiftUI on iOS 14 and earlier?
There are many new iOS15-specific modifiers that were added in SwiftUI. For example, we have a .focused() modifier, which can be used like this: TextField("Username", text: $username) .focused($focusedField, equals: .username) However, this code fails to compile if the app supports iOS 14 and earlier. How can I make this code to compile? Ideally, I'd like to do something like this: TextField("Username", text: $username) #if os(iOS, 15.0, *) .focused($focusedField, equals: .username) #endif But obviously this won't work because #if os() can only specify the target OS, not the version.. Thanks!
Replies
3
Boosts
0
Views
6k
Activity
Sep ’25
View presented as sheet are properly updated to correct color scheme
I’m facing an issue and I’d like to know if anyone has already run into this. I have a ContentView that presents a SettingsView as a sheet. SettingsView applies a change to the app’s colorScheme. ContentView reacts correctly to the change, and SettingsView does too (so far, so good). What’s strange is that when I set nil on the preferredColorScheme modifier (which, according to the docs, corresponds to the system color scheme), ContentView correctly picks up the change and refreshes, while SettingsView does pick up the change but doesn’t refresh. (In the video you can clearly see that when I switch from Dark to System, the parent view refreshes properly but not the presented sheet.) I’ve tried everything—switching to UIKit, changing the sheet’s ID… nothing works Another strange thing: if I present SettingsView through a NavigationLink, everything works normally… Here is a sample code to reproduce: import SwiftUI enum AppTheme: Int { case system = 0 case dark = 1 case light = 2 var colorScheme: ColorScheme? { switch self { case .system: return nil case .light: return .light case .dark: return .dark } } } struct SettingsView: View { @AppStorage("theme") var appTheme: AppTheme = .system var body: some View { VStack(spacing: 8) { Button { select(theme: .system) } label: { Text("Systeme") } Button { select(theme: .dark) } label: { Text("Dark") } Button { select(theme: .light) } label: { Text("Light") } } .preferredColorScheme(appTheme.colorScheme) } func select(theme: AppTheme) { appTheme = theme } } struct ContentView: View { @AppStorage("theme") var appTheme: AppTheme = .system @State var isPresented = false var body: some View { NavigationStack { VStack { Button { isPresented = true } label: { Text("Present settings") } // NavigationLink("Present settings") { // SettingsView() // } } .preferredColorScheme(appTheme.colorScheme) .sheet(isPresented: $isPresented) { SettingsView() } } } } #Preview { ContentView() }
Replies
2
Boosts
0
Views
119
Activity
Sep ’25
SwiftUI's List backed by CoreData using @FetchRequest fails to update on iOS 26 when compiled with Xcode 26
Hey there! I've been tracking a really weird behavior with a List backed by @FetchRequest from CoreData. When I toggle a bool on the CoreData model, the first time it updates correctly, but if I do it a second time, the UI doesn't re-render as expected. This does not happen if I compile the app using Xcode 16 (targeting both iOS 18 and iOS 26), nor it happens when using Xcode 26 and targeting iOS 18. It only happens when building the app using Xcode 26 and running it on iOS 26. Here are two demos: the first one works as expected, when I toggle the state twice, both times updates. The second one, only on iOS 26, the second toggle fails to re-render. Demo (running from Xcode 16): Demo (running from Xcode 26): The code: import SwiftUI import CoreData @main struct CoreDataTestApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } } } struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]) private var items: FetchedResults<Item> var body: some View { NavigationView { List { ForEach(items) { item in HStack { Text(item.timestamp!.formatted()) Image(systemName: item.isFavorite ? "heart.fill" : "heart").foregroundStyle(.red) }.swipeActions(edge: .leading, allowsFullSwipe: true) { Button(item.isFavorite ? "Unfavorite" : "Favorite", systemImage: item.isFavorite ? "heart" : "heart.fill") { toggleFavoriteStatus(item: item) } } } } .toolbar { ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } } private func addItem() { withAnimation { let newItem = Item(context: viewContext) newItem.timestamp = Date() newItem.isFavorite = Bool.random() try! viewContext.save() } } private func toggleFavoriteStatus(item: Item) { withAnimation { item.isFavorite.toggle() try! viewContext.save() } } } struct PersistenceController { static let shared = PersistenceController() let container: NSPersistentContainer init() { container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: { _, _ in }) container.viewContext.automaticallyMergesChangesFromParent = true } }
Replies
5
Boosts
1
Views
333
Activity
Sep ’25
iOS 26 Webview and alert issue
Hello, In iOS 26 beta, we are seeing an unexpected behavior when using SwiftUI WebView (or a custom WKWebView via UIViewRepresentable). When an alert is presented above the WebView, the WebView immediately reloads to its initial page. The alert itself also disappears instantly, making it impossible for the user to interact with it. This issue occurs both with the new SwiftUI WebView / WebPage API and with a wrapped WKWebView. The problem was not present in previous iOS versions (iOS 17/18). Steps to reproduce: Create a SwiftUI view with a WebView (pointing to any URL). Add a toolbar button that toggles a SwiftUI alert. Run the app on iOS 26 beta. Tap the button to trigger the alert. Expected behavior: The WebView should remain as-is, and the alert should stay visible until the user dismisses it. Actual behavior: As soon as the alert appears, the WebView reloads and resets to the initial page. The alert disappears immediately. Minimal Example: struct ContentView: View { @State private var showAlert = false var body: some View { NavigationStack { WebView(URL(string: "https://apple.com")!) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Close") { showAlert = true } } } .alert("Confirm close?", isPresented: $showAlert) { Button("Cancel", role: .cancel) {} Button("Close", role: .destructive) {} } } } } I'm using Xcode Version 26.0 beta 7 Thanks for your help.
Replies
2
Boosts
1
Views
899
Activity
Sep ’25
Dividers not appearing in menu bar on iPadOS 26
On macOS 26 I can see the dividers when I open my Help menu: However, on iPadOS 26 the dividers don't appear: I am simply using Divider() to separate my menu bar items in my CommandGroup. iPadOS does support dividers as I can see them for the system generated Edit menu but for some reason it's not working here. Does anyone know if I am doing something wrong with the iPadOS implementation?
Replies
2
Boosts
1
Views
180
Activity
Sep ’25
Blurred selected button on tvOS
The following code shows that a selected button in a list gots blurred if a glass effect is applied to the list. This happens if the button style is plain or glass. It does not happen if the button style is bordered. Is this a wanted documented behavior or is this a bug? struct ContentView: View { @State private var items = [ "Item 1", "Item 2", "Item 3", "Item 4"] var body: some View { ZStack { Image(systemName: "globe") .resizable() List(items, id: \.self) { item in Button(action: {}, label: { Text(item) }) } .padding() .glassEffect(in: Rectangle()) } } }
Replies
2
Boosts
0
Views
138
Activity
Sep ’25
Concentric corner radius for grouped cells in UICollectionView on iOS 26
What is the recommended way to obtain the concentric corner radius for views within grouped UICollectionView cells? In the most basic example, a UICollectionView with one section and one cell, we observe the cell takes almost the shape of a capsule, but it is indeed not a capsule. What is the way to obtain the radius of the grouped area from within the cell or its registration? I would like to layer elements on top that are concentric to the cell's clip shape. I've tried using custom views with .concentric UICornerConfigurations, setting .cornerConfiguration on the cell and on a custom backgroundView and I've even tried obtaining the .effectiveRadius of the cell after layout (returns 0.0). As of Xcode 26.0 Beta 7, nothing works. This seems like a huge omission; what am I missing here?
Replies
1
Boosts
0
Views
237
Activity
Sep ’25
Liquid Glass clear variant isn't clear
I've been experimenting with Liquid Glass quite a bit and watched all the WWDC videos. I'm trying to create a glassy segmented picker, like the one used in Camera: however, it seems that no matter what I do there's no way to recreate a truly clear (passthrough) bubble that just warps the light underneath around the edges. Both Glass.regular and Glass.clear seem to add a blur that can not be evaded, which is counter to what clear ought to mean. Here are my results: I've used SwiftUI for my experiment but I went through the UIKit APIs and there doesn't seem to be anything that suggests full transparency. Here is my test SwiftUI code: struct GlassPicker: View { @State private var selected: Int? var body: some View { ScrollView([.horizontal], showsIndicators: false) { HStack(spacing: 0) { ForEach(0..<20) { i in Text("Row \(i)") .id(i) .padding() } } .scrollTargetLayout() } .contentMargins(.horizontal, 161) .scrollTargetBehavior(.viewAligned) .scrollPosition(id: $selected, anchor: .center) .background(.foreground.opacity(0.2)) .clipShape(.capsule) .overlay { DefaultGlassEffectShape() .fill(.clear) // Removes a semi-transparent foreground fill .frame(width: 110, height: 50) .glassEffect(.clear) } } } Is there any way to achieve the above result or does Apple not trust us devs with more granular control over these liquid glass elements?
Replies
2
Boosts
3
Views
220
Activity
Aug ’25
Inconsistent subviews redrawing in LazyVStack
Hello Apple forum ! I spotted a weird behaviour with LazyVStack in a ScrollView. I understand that it loads its views only once upon appearance unlinke VStack that loads everything in one shot. What I noticed also, it seems to reload its views sometimes when scrolling back up to earlier loaded views. The thing is, it isn't always the case. struct LazyVStackTest: View { var body: some View { ScrollView { LazyVStack { ForEach(0..<1000, id: \.self) { _ in // if true { MyText() // } } } } } struct MyText: View { var body: some View { let _ = Self._printChanges() HStack { Text("hello") } } } } If we consider the code above on XCode 26 beta 7 on an iOS 26 or iOS 18.2 simulator. Scroll to the bottom : you'll see one "LazyVStackTest.MyText: @self changed" for each row. Then scroll back up to the top, we'll see again the same message printed multiple times. --> So I gather from this that LazyVStack not only loads lazily but also removes old rows from memory & recreates them upon reappearance. What I don't get however is that if you uncomment the "if true" statement, you won't see the reloading happening. And I have absolutely no clue as to why 😅 If someone could help shed some light on this weird behaviour, it would be greatly appreciated ^^ PS : the issue is also present with XCode 16.2 but at a deeper lever (ex: if we embed another custom View "MyText2" inside "MyText", the reloading is in "MyText2" & not "MyText")
Replies
0
Boosts
0
Views
276
Activity
Aug ’25
NavigationStack path is being reset in NavigationSplitView details columns
I'm building a SwiftUI app for iPad using a NavigationSplitView as the navigation root. Below is a simplified version of the app's navigation. There are a Home Page and a Settings Page, each with its own NavigationStack. The page that appears in the detail column depends on the sidebar's selection value. The issue I'm facing is that when I navigate deeply into the Home Page's NavigationStack (e.g., to a Home Page Child view), switch to the Settings Page, and then switch back to the Home Page, the Home Page's navigation path has been reset to [] and the previous state is lost. The same issue occurs if I navigate deeply into the Settings Page (e.g., to a Settings Page Child view), switch to the Home Page, and then return to the Settings Page: the navigation state for the Settings Page is lost, and it reverts to the root of the NavigationStack. Why is this happening and how can I fix it so that switching pages in the sidebar doesn't reset the NavigationStack of each individual page in the detail column? Thank you. struct ContentView: View { @State var selection: String? @State var firstPath = [String]() @State var secondPath = [String]() var body: some View { NavigationSplitView { List(selection: $selection) { Text("Home") .tag("home") Text("Settings") .tag("settings") } } detail: { if selection == "home" { HomePage(path: $firstPath) } else { SettingsPage(path: $secondPath) } } } } struct HomePage: View { @Binding var path: [String] var body: some View { NavigationStack(path: $path) { NavigationLink("Home Page", value: "Home") .navigationDestination(for: String.self) { _ in Text("Home Page Child") } } } } struct SettingsPage: View { @Binding var path: [String] var body: some View { NavigationStack(path: $path) { NavigationLink("Settings Page", value: "Settings") .navigationDestination(for: String.self) { _ in Text("Settings Page Child") } } } } #Preview { ContentView() }
Replies
0
Boosts
0
Views
309
Activity
Aug ’25
Textfield not updating integer with optional value
I am trying to get integer input by using textfield. However, I noticed that if I changed the binding variable as optional with an initial value of null, the textfield would not work. I would like to keep it as null initially because I want the placeholder to show text before the input, and if the int variable starts with any valid value, the text would not be shown. Is there a way to fix things here? struct TextFieldNumberInputView: View {     @Binding var intVariable: Int?     @State var isEditing: Bool = false     @State var placeholderText: String     @State var number: Int = 0          var body: some View {         VStack(alignment: .leading, spacing: 2){             TextField(placeholderText, value: $number, formatter: NumberFormatter()){             }             .textFieldStyle(InputTextFieldStyle())             .keyboardType(.numberPad)             .onReceive(Just(number)) {_ in                 print("number pad being editing")                 if isEditing == false && intVariable != nil {                                         isEditing = true                     print("number is being edited")                 } else if isEditing == true && intVariable != nil{                     isEditing = false                 }             }                          Text(placeholderText)                 .font(.caption2)                 .foregroundColor(isEditing ? Color(.systemGray3):Color.clear)                 .padding(.horizontal)                 .padding(.horizontal, 12)                  }.onTapGesture {             print("number pad being tapped, intVariable \(intVariable), \(number)")                          if number != nil {                 print("checking number")                 UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)             }                      }     }      }
Replies
2
Boosts
0
Views
842
Activity
Aug ’25
iOS 18 hit testing functionality differs from iOS 17
I created a Radar for this FB14766095, but thought I would add it here for extra visibility, or if anyone else had any thoughts on the issue. Basic Information Please provide a descriptive title for your feedback: iOS 18 hit testing functionality differs from iOS 17 What type of feedback are you reporting? Incorrect/Unexpected Behavior Description: Please describe the issue and what steps we can take to reproduce it: We have an issue in iOS 18 Beta 6 where hit testing functionality differs from the expected functionality in iOS 17.5.1 and previous versions of iOS. iOS 17: When a sheet is presented, the hit-testing logic considers subviews of the root view, meaning the rootView itself is rarely the hit view. iOS 18: When a sheet is presented, the hit-testing logic changes, sometimes considering the rootView itself as the hit view. Code: import SwiftUI struct ContentView: View { @State var isPresentingView: Bool = false var body: some View { VStack { Text("View One") Button { isPresentingView.toggle() } label: { Text("Present View Two") } } .padding() .sheet(isPresented: $isPresentingView) { ContentViewTwo() } } } #Preview { ContentView() } struct ContentViewTwo: View { @State var isPresentingView: Bool = false var body: some View { VStack { Text("View Two") } .padding() } } extension UIWindow { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { /// Get view from superclass. guard let hitView = super.hitTest(point, with: event) else { return nil } print("RPTEST rootViewController = ", rootViewController.hashValue) print("RPTEST rootViewController?.view = ", rootViewController?.view.hashValue) print("RPTEST hitView = ", hitView.hashValue) if let rootView = rootViewController?.view { print("RPTEST rootViewController's view memory address: \(Unmanaged.passUnretained(rootView).toOpaque())") print("RPTEST hitView memory address: \(Unmanaged.passUnretained(hitView).toOpaque())") print("RPTEST Are they equal? \(rootView == hitView)") } /// If the returned view is the `UIHostingController`'s view, ignore. print("MTEST: hitTest rootViewController?.view == hitView", rootViewController?.view == hitView) print("MTEST: -") return hitView } } Looking at the print statements from the provided sample project:
 iOS 17 presenting a sheet from a button tap on the ContentView(): RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fd25000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fd25000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false iOS 17 dismiss from presented view: RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fe04080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x0000000120009200 RPTEST hitView memory address: 0x000000011fe04080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false iOS 18 presenting a sheet from a button tap on the ContentView(): RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103342080 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x000000010333e3c0 RPTEST Are they equal? true MTEST: hitTest rootViewController?.view == hitView true You can see here ☝️ that in iOS 18 the views have the same memory address on the second call and are evaluated to be the same. This differs from iOS 17. iOS 18 dismiss RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103e80000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false RPTEST rootViewController's view memory address: 0x000000010333e3c0 RPTEST hitView memory address: 0x0000000103e80000 RPTEST Are they equal? false MTEST: hitTest rootViewController?.view == hitView false The question I want to ask: Is this an intended change, meaning the current functionality in iOS 18 is expected? Or is this a bug and it's something that needs to be fixed? As a user, I would expect that the hit testing functionality would remain the same from iOS 17 to iOS 18. Thank you for your time.
Replies
14
Boosts
13
Views
4.9k
Activity
Aug ’25
SwiftUI drag & drop: reliable cancellation
Summary In a SwiftUI drag-and-drop flow, the only robust way I’ve found to detect cancellation (user drops outside any destination) is to rely on the NSItemProvider created in .onDrag and run cleanup when it’s deallocated, via a custom onEnded callback tied to its lifecycle. On iOS 26, the provider appears to be deallocated immediately after .onDrag returns (unless I keep a strong reference), so a deinit/onEnded-based cancel hook fires right away and no longer reflects the true end of the drag session. I’d like to know: 1. Is there a supported, reliable way to detect when a drag ends outside any drop target so I can cancel and restore the source row? 2. Is the iOS 26 NSItemProvider deallocation timing a bug/regression or intended behavior? Minimal SwiftUI Repro This example shows: • creating a custom NSItemProvider subclass with an onEnded closure • retaining it to avoid immediate dealloc (behavior change on iOS 26) • using performDrop to mark the drag as finished import SwiftUI import UniformTypeIdentifiers final class DragProvider: NSItemProvider { var onEnded: (() -> Void)? deinit { // Historically: called when the system drag session ended (drop or cancel). // On iOS 26: can fire immediately after .onDrag returns unless the provider is retained. onEnded?() } } struct ContentView: View { struct Item: Identifiable, Equatable { let id = UUID(); let title: String } @State private var pool: [Item] = (1...4).map { .init(title: "Option \($0)") } @State private var picked: [Item] = [] @State private var dragged: Item? @State private var dropFinished: Bool = true @State private var activeProvider: DragProvider? // Retain to avoid immediate dealloc private let dragType: UTType = .plainText var body: some View { HStack(spacing: 24) { // Destination list (accepts drops) VStack(alignment: .leading, spacing: 8) { Text("Picked").bold() VStack(spacing: 8) { ForEach(picked) { item in row(item) } } .padding() .background(RoundedRectangle(cornerRadius: 8).strokeBorder(.quaternary)) .onDrop(of: [dragType], delegate: Dropper( picked: $picked, pool: $pool, dragged: $dragged, dropFinished: $dropFinished, activeProvider: $activeProvider )) } .frame(maxWidth: .infinity, alignment: .topLeading) // Source list (draggable) VStack(alignment: .leading, spacing: 8) { Text("Pool").bold() VStack(spacing: 8) { ForEach(pool) { item in row(item) .onDrag { startDrag(item) return makeProvider(for: item) } preview: { row(item).opacity(0.9).frame(width: 200, height: 44) } .overlay( RoundedRectangle(cornerRadius: 8) .fill(item == dragged ? Color(.systemBackground) : .clear) // keep space ) } } .padding() .background(RoundedRectangle(cornerRadius: 8).strokeBorder(.quaternary)) } .frame(maxWidth: .infinity, alignment: .topLeading) } .padding() } private func row(_ item: Item) -> some View { RoundedRectangle(cornerRadius: 8) .strokeBorder(.secondary) .frame(height: 44) .overlay( HStack { Text(item.title); Spacer(); Image(systemName: "line.3.horizontal") } .padding(.horizontal, 12) ) } // MARK: Drag setup private func startDrag(_ item: Item) { dragged = item dropFinished = false } private func makeProvider(for item: Item) -> NSItemProvider { let provider = DragProvider(object: item.id.uuidString as NSString) // NOTE: If we DO NOT retain this provider on iOS 26, // its deinit can run immediately (onEnded fires too early). activeProvider = provider provider.onEnded = { [weak self] in // Intended: run when system drag session ends (drop or cancel). // Observed on iOS 26: may run too early unless retained; // if retained, we lose a reliable "session ended" signal here. DispatchQueue.main.async { guard let self else { return } if let d = self.dragged, self.dropFinished == false { // Treat as cancel: restore the source item if !self.pool.contains(d) { self.pool.append(d) } self.picked.removeAll { $0 == d } } self.dragged = nil self.dropFinished = true self.activeProvider = nil } } return provider } // MARK: DropDelegate private struct Dropper: DropDelegate { @Binding var picked: [Item] @Binding var pool: [Item] @Binding var dragged: Item? @Binding var dropFinished: Bool @Binding var activeProvider: DragProvider? func validateDrop(info: DropInfo) -> Bool { dragged != nil } func performDrop(info: DropInfo) -> Bool { guard let item = dragged else { return false } if let idx = pool.firstIndex(of: item) { pool.remove(at: idx) } picked.append(item) // Mark drag as finished so provider.onEnded won’t treat it as cancel dropFinished = true dragged = nil activeProvider = nil return true } } } Questions Is there a documented, source-side callback (or best practice) to know the drag session ended without any performDrop so we can cancel and restore the item? Has the NSItemProvider deallocation timing (for the object returned from .onDrag) changed intentionally on iOS 26? If so, what’s the recommended replacement signal? Is there a SwiftUI-native event to observe the end of a drag session that doesn’t depend on the provider’s lifecycle?
Replies
0
Boosts
1
Views
160
Activity
Aug ’25
PasteButton asks for permissions every time
Hi, i'm using the PasteButton component to paste content from clipboard. On the docs it says that the PasteButton will handle internally permissions and will not present the prompt. But in my case seems to be not true. I removed all the occurrencies of UIPasteboard.general.string since i read that this will cause the prompt to appear. Also as you can see on the code below i'm not doing fancy or weird things, even with the base component this behaviour is still there. PasteButton(payloadType: String.self) { strings in if let first = strings.first { print("clipboard text: \(first)") } } I can see other apps using this paste button without asking for permissions every time, but i cannot see any issue in my code.
Replies
0
Boosts
0
Views
52
Activity
Aug ’25