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 View not Refreshing when View Model Controller Data is Updated
With the implementation below, my SwiftUI view is not refreshing when data is retrieved from an HTTP request and stored in a view model controller class. The SwiftUI view calls a shared class of which is the parent of the view model controller, and the view model controller stores the data being accessed from the SwiftUI view. GlobalDataManager: @MainActor @Observable class GlobalDataManager { static let shared = GlobalDataManager() var medications: MedicationsViewModel private init() { let medicationsVM = MedicationsViewModel() self.medications = medicationsVM medicationsVM.globalDataManager = self } }MedicationsViewModel: @MainActor @Observable final class MedicationsViewModel { weak var globalDataManager: GlobalDataManager? var medicationInformation: [MedicationInformation] var asNeededMedications: [AsNeededMedication] var timesMedicationsTaken: [TimeMedicationsTaken] init(globalDataManager: GlobalDataManager? = nil) { self.medicationInformation = [] self.asNeededMedications = [] self.timesMedicationsTaken = [] self.globalDataManager = globalDataManager } func request<T: Encodable>(data: T, requestType: RequestType) async { do { let response = try await HTTPRequestService.shared.request(requestType: requestType, body: data, dataID: nil) if response.success { let decodedData = try DataDecoderService.shared.decode(MedicationData.self, from: response.data) medicationInformation = decodedData.medicationInformation asNeededMedications = decodedData.asNeededMedications timesMedicationsTaken = decodedData.timesMedicationsTaken } } catch let error { globalDataManager?.updateError(error.localizedDescription) } } }Medications View: struct Medications: View { @State private var globalDataManager = GlobalDataManager.shared @State var sheetOpen: Bool = false var body: some View { MedicationTimesList(medicationTimes: globalDataManager.medications.timesMedicationsTaken) .sheet(isPresented: $sheetOpen) { CreateAndUpdateMedicationInformation(viewModel: globalDataManager.medications, sheetOpen: $sheetOpen) } }} Button within CreateAndUpdateMedicationInformation: struct CreateAndUpdateMedicationInformation: View { var viewModel: MedicationsViewModel var body: some View { WideButton(label: "Add Medication", buttonState: $buttonState) .onChange(of: buttonState) { _, newState in if newState { let medicationInformation = MedicationInformation() Task { await viewModel.request(data: medicationInformation, requestType: .addNewMedication) } sheetOpen = false } } }When pressing a button within the CreateAndUpdateMedicationInformation view, the request() function is called, which updates the variable timesMedicationTaken that is stores the data to be displayed in the Medications view. In the request function variables update, but the SwiftUI view does not. How can this be resolved so the Medications view refreshes when timesMedicationTaken and the other variables in the request() function are updated?
Topic: Design SubTopic: General Tags:
0
0
48
8h
A small scrolling ticker pins GPU at ~50% on a rich screen but ~0% on a blank one — is whole-tree compositing at the animation's rate the expected cost?
I have a SwiftUI clock-radio app on iPhone (ProMotion display). The face is a rich, layered screen (glass panels, gradients, shadows). One element is a 340×46 pt text ticker strip that scrolls continuously. Its GPU behaviour is reproducible and, to me, surprising: Measurements (Xcode GPU gauge used only for within-session relative contrasts; cadence from an in-app frame-pacing log): With the ticker scrolling, GPU sits at a ~50% plateau. The step is binary: one scroller or three scrollers cost the same. Frames are never dropped: 16.7 ms median frame time, ~3% of frames over 20 ms. The GPU bills heavily for frames it renders on time. The same strip scrolling on an otherwise blank screen costs ~0%. The ticker visible but static costs 0%. Motion is the trigger; the cost scales with what surrounds the strip, not with the strip itself. What I have tried: SwiftUI offset animation of the text — baseline behaviour above. A fully severed Canvas leaf: own layer subtree, own CADisplayLink, snapshot-fed, no view-tree publishing or subscriptions — identical cost on the full face (and literally 0% redrawing alone on a blank screen at full frame rate). Pre-rendered bitmap content scrolled instead of text — identical. The strip hosted in a separate UIWindow above the face — identical, measured with the strip visibly scrolling in its seat. Pinning the strip's CADisplayLink.preferredFrameRateRange to exactly 30 Hz (minimum = maximum = preferred = 30) — no change. .compositingGroup() on the panel subtree — a modest reduction only. What works: removing the motion duty (scroll once, then park — cost collapses), and, elsewhere in the app, a pull-model spectrum visualiser replaced a publish-driven one and its cost vanished. Severing worked for the visualiser but not for the ticker. Questions: Is this step the expected consequence of the WWDC22 session 10083 rule (the display refresh rate is set by the highest-frame-rate animation in the app) composed with whole-layer-tree compositing per presented frame? Is there any dirty-region scoping that a layer structure like ours defeats? The 30 Hz pin above was hard (min = max = preferred). Would a CAFrameRateRange in the 15–24 Hz band that Apple's ProMotion guidance suggests for small, slow animations behave materially differently, or is that distinction already settled by the 30 Hz result? Is there a known construction for a continuously scrolling ticker that lets the GPU idle between updates? Happy to share a minimal repro or the pacing logs if useful.
2
0
363
1d
Thin border around VideoPlayer
A thin gray border appears around assets presented using VideoPlayer. It was tested on iPhone 8, iPhone 13 mini, and iPhone 15, and the border only appeared on the 13 mini. The iOS version didn't matter, we checked the 13 mini on both iOS 18 and iOS 26 and the border appeared on both. I created a minimal version of the app with just the VideoPlayer and the asset and the frame still appeared. We tried different assets and it was visible on all of them. import SwiftUI import AVKit struct ContentView: View { private let player = AVPlayer(url: Bundle.main.url(forResource: "sample", withExtension: "mp4")!) var body: some View { VideoPlayer(player: player) .frame(width: 300, height: 200) } }I found someone that had the same exact problem on Reddit and StackOverflow: https://www.reddit.com/r/swift/comments/8s87hm/weird_borders_around_avplayerviewcontroller/ https://stackoverflow.com/questions/50927561/weird-borders-around-avplayerviewcontroller and their solution included setting: playerLayer.shouldRasterize = true playerLayer.rasterizationScale = UIScreen.main.scaleThis fixed the issue but isn't suitable for our app due to offscreen rendering performance. They also found that issue on other devices, noting: "After testing this code on multiple devices (actual physical devices, not simulators), this bug appears to happen only on Plus devices (I tested it on i8 on iOS 11, i8+ on iOS 11, iX on iOS 11 and i6+ on iOS 10)." The bug doesn't appear on simulator, isn't visible on a screenshot, the frame disappears when using accessibility zoom. When we encoded the video file to Apple ProRes the border disappeared, but the size of the video bumped from 342 KB to 6.3 MB so we couldn't do that. I'm attaching photo from this person's post since our issue looked exactly the same. Has anyone had a similar issue and maybe found another workaround?
1
1
274
1d
Tab grayed out, but not disabled
Bug report: FB25495244 This bug also appeared with Xcode 27; it worked previously. scenario: A TabView with, for example, 4 tabs. Data in ContentView is loaded via @Query. In ContentBindingView, the same thing is done, but using a parameter instead of SwiftData. ContentView: If no data is available, the system automatically switches to tab 4 at the start; while you can click on other tabs, they are disabled, and the system will not switch to them. ContentBindingView: When tabDisabled is set to "true", the system automatically switches to Tab 4 upon startup; while it is possible to click on other tabs, they are disabled, and the system will not switch to them. So far, so good. The first three tabs are only active if at least one data record exists or tabDisabled is "false". Tab 4 is always active and is used for data entry. When data is entered in Tab 4, tab 1 to 3 should become active. However, it appears as though tabs 1 through 3 remain disabled—the icons appear grayed out. That is actually not the case; you can click on the tabs, and as soon as you move away from a tab, its icon is displayed correctly. This issue did not exist in Xcode 26. In Xcode 26, all symbols were displayed correctly from the start, once data had been saved. What I noticed on the iPhone Duo: If you open the program on an iPhone Duo while it is closed, enter some data, then move the app to the background, open the iPhone Duo, and bring the application back to the foreground, tabs 1 through 3 will be active. The entire view is being updated, which is why tabs 1 through 3 are now active—though they should have been active already. A side note: The same issue described in the forum post "A layout bug when using Tab View in SwiftUI (FB25490881)" also occurs in this program. The entire view has shifted upward. The text field is no longer visible—only the Add button remains. In the other views, you can only see half of the text. import SwiftUI struct ContentBindingView: View { @State private var tabDisabled: Bool = true @State private var selection: TabSelection = .tab1 private var isTabDisabled: Bool { print("ContentView.isTabDisabled: \(tabDisabled)") return tabDisabled } var body: some View { TabView(selection: $selection) { Tab("Tab 1", systemImage: "waveform.badge.magnifyingglass", value: .tab1) { Text("tab 1") } .disabled(isTabDisabled) Tab("Tab 2", systemImage: "heart", value: .tab2) { Text("tab 2") } .disabled(tabDisabled) Tab("Tab 3", systemImage: "target", value: .tab3) { Text("tab 3") } .disabled(tabDisabled) Tab("Tab 4", systemImage: "gear", value: .tab4) { AddFriendBindingView(tabDisabled: $tabDisabled) } } .onAppear { print("ContentView onAppear disabled: \(tabDisabled)") if tabDisabled { selection = .tab4 } } .onChange(of: selection) { print("ContentView onChange disabled: \(tabDisabled)") } } }struct AddFriendBindingView: View { @Environment(\.dismiss) private var disdmiss @Binding var tabDisabled: Bool @State private var name: String = "" var body: some View { VStack { TextField("Name", text: $name) Button { addFriend() } label: { Text("Add") } .buttonStyle(.borderedProminent) } .padding() .onAppear { print("AddFriendView onAppear disabled: \(tabDisabled)") } } private func addFriend() { tabDisabled = false name = "" disdmiss() } }Christian
1
0
259
1d
macOS 27.2 makes AUv3 plug-ins completely useless as soon as the user accesses a button with a native submenu.
https://youtu.be/HnNGgYZQBvYJust submitted a bug report FB25497228, and posting it here in hopes to get Apple engineers' attention because it's such a bad bug that macOS 27.2 introduced, and don't think it has a workaround unless Apple fixes it. This wasn't an issue in 27.0. Summary: Opening a native submenu in an out-of-process AUv3 plug-in and dismissing it without selecting anything will make the plug-in UI completely unresponsive to mouse input, and any animation will stop also. Only restart solves it. I can reproduce this with Mela in both Logic Pro and Ableton Live 12. I've attached a demo project to the bug report. It's Xcode Audio Unti extension template with a small modification, just added a button with a submenu. Mela can be found here if needed: https://apps.apple.com/us/app/mela-auv3-synth-fx-midi/id1668299428 Steps to reproduce with the attached project Load Mela as an AU instrument in Logic Pro or Ableton Live Open a factory patch. Open a module’s Actions menu (ellipsis buttons) Hover over Insert or Replace until the submenu appears, then dismiss it without selecting anything. Expected result Mela's UI continues to be interactive, e.g. parameters can be easily adjusted and modulation animations continue to work. Actual result No interaction can be performed, wether adjusting a prameter or dragging a module or pressing a button. Animations also stop. This bug renders my plug-in completely useless on macOS as it makes heavy use of submenus. Please fix it! Here's a link to the video demo of the issue: https://youtu.be/HnNGgYZQBvY
0
0
63
2d
A layout bug when using Tab View in SwiftUI
I've been getting some unexpected behavior when using TabView as the root view in SwiftUI, which I believe to be a bug. I created a barebones app to isolate the bug and will paste the code at the end for anyone who wants to try to reproduce it if you have a physical Duo device. Details below copied from my Feedback ticket FB25490881. Xcode 27.1. What's the bug? When you have a TabView as the root view inside the ContentView.swift and you open the app on the outer screen, then swipe it to the background, unfold the phone and tap the app to bring it to the foreground, any UI element without a fixed frame size gets moved off screen. And any UI element with a fixed frame size gets moved to the very top of the screen. See my two screenshots of outer vs inner display. How to reproduce: 1- Unzip the project folder and open it in Xcode. 2- With a physical Duo on hand, run the app on the Duo. 3- On the outer screen, launch the app. 4- Swipe the app to the background (Do not swipe completely to close the app, just swipe it to the background so it is still running) 5- Unfold the iPhone Duo completely open. 6- Tap the app icon to bring it to the foreground. 7- Look at the broken UI. Also note even the side tab bar itself is broken, it has no padding with the edge of the screen. This is managed by the system. The workaround to fix it: 1- Go to the ContentView.Swift in Xcode. 2- Uncomment "NavigationStack {" on line 13. 3- Uncomment the closing bracket "}" on line 2. 4- Repeat the previous from how to reproduce the bug. My hypothesis: The system reports 0 height for the inner screen in that moment you bring the app to the foreground. Anything with a fixed frame will move to the top, anything with flexible height will not be visible. This only happens when TabView is the root of the scene. Other Useful Information: I hit a very similar bug to this in my main app that I'm still developing, unfortunately I was not able to reproduce it in a sandbox simple app. The bug was: 1- Open the app on outer screen 2- While the app is open, tilt the Duo to the back at a certain angle 3- Unfold the Duo 4- You get this exact same bug where everything with a flexible height disappears off screen or collapses to zero if it's in between UI elements with fixed heights. And everything moves to the top of the screen. I'm not sure why I wasn't able to reproduce it on a sandbox app, but the fix was the same, either remove the TabView completely or put it inside a NavigationStack. I want to stress that this one had to do with the backwards tilt of the phone itself while unfolding it, very weird I know. ContentView.swift: import SwiftUI @available(iOS 27.1, *) struct ContentView: View { var body: some View { // NavigationStack { TabView { HomeTabView() .tabItem { Label("Home", systemImage: "house") } StoreView() .tabItem { Label("Store", systemImage: "bag") } } .tint(Color.black) // } } } HomeTabView.swift: import SwiftUI @available(iOS 27.1, *) struct HomeTabView: View { var body: some View { VStack { Text("Jenny") .font(.largeTitle) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity) .frame(height: 200) .background(Color.gray, in: .rect(cornerRadius: 28)) Spacer() Circle() .fill(Color.blue) .frame(width: 100, height: 100) .padding(.bottom, 50) } .contentMargins(for: .container, edges: .horizontal) } } StoreView.swift is just an default empty swiftUI file with the default Text("Hello, World!")
2
0
378
2d
TabView(.page) extended under the tab bar shifts its pages up after the container height shrinks while visible
Summary A page-style TabView that extends under the bottom tab bar with .ignoresSafeArea(edges: .bottom) leaves its pages shifted upward after its height shrinks while it is on screen. The shift is half of the bottom safe area (41.67 pt with an 83.33 pt tab bar area), and the top of each page is hidden. The internal UICollectionView (contentInsetAdjustmentBehavior == .automatic) seems to briefly add the bottom safe area to adjustedContentInset.bottom during the resize. The inset goes back to 0 afterwards, but the visible cell is left at frame.minY == -41.67. Environment Xcode 27.0 (27A266a), iPhone Duo Simulator (iOS 27.1), deployment target iOS 18.0 In a unit test it also reproduces when only the window height shrinks (386×678 → 386×600), so it does not depend on the foldable device Steps to reproduce Run the code below on the iPhone Duo Simulator while it is folded Unfold the device Expected The red "Top of page 0" area stays directly below the yellow header, as it does when the app is launched unfolded. Actual The top of the red area is hidden under the header. Log output: folded (launch): height=628.00 adjustedContentInset.bottom=0.00 cell.minY=0.00 after unfolding: height=619.00 adjustedContentInset.bottom=0.00 cell.minY=-41.67In this sample the page returns to its position when the device is folded again. In our app it stays shifted, and after the shift, tapping to switch pages sometimes stops working until the user swipes. Notes Removing .ignoresSafeArea(edges: .bottom) avoids the issue, but the content then no longer extends under the translucent tab bar Setting edgesForExtendedLayout = [] on the hosting controller also avoids it, with the same visual change. safeAreaRegions = [] and setContentScrollView(nil, for:) do not help A non-transparent background on the TabView or its pages, as suggested in https://developer.apple.com/forums/thread/762286, does not help. The trigger here is a size change, not a page switch, so this seems to be a different issue Current workaround: find the internal UICollectionView through the superview chain and set contentInsetAdjustmentBehavior = .never. This works, but it relies on the private view hierarchy. Is there a supported way to avoid this? Minimal reproduction import SwiftUI import UIKit // A TabView(.page) extended under the tab bar with .ignoresSafeArea(edges: .bottom). // Resizing the window while it is visible (e.g. unfolding) shifts the page up. struct PagerView: View { @State private var selection = 0 var body: some View { VStack(spacing: 0) { Color.yellow.frame(height: 50).overlay(Text("Header")) TabView(selection: $selection) { ForEach(0..<2) { index in ScrollView { Color.red.frame(height: 120).overlay(Text("Top of page \(index)")) ForEach(0..<30) { Text("Row \($0)").frame(maxWidth: .infinity, minHeight: 44) } } .tag(index) } } .tabViewStyle(.page(indexDisplayMode: .never)) } .ignoresSafeArea(edges: .bottom) } } final class PagerViewController: UIHostingController<PagerView> { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in self?.logCollectionView() } } private func logCollectionView() { func find(_ view: UIView) -> UICollectionView? { view as? UICollectionView ?? view.subviews.lazy.compactMap(find).first } guard let collectionView = find(view) else { return } let cellMinY = collectionView.visibleCells.first?.frame.minY ?? .nan NSLog("FORUM height=%.2f adjustedContentInset.bottom=%.2f cell.minY=%.2f", collectionView.bounds.height, collectionView.adjustedContentInset.bottom, cellMinY) } } @main final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting session: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let configuration = UISceneConfiguration(name: nil, sessionRole: session.role) configuration.delegateClass = SceneDelegate.self return configuration } } final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let pager = PagerViewController(rootView: PagerView()) pager.tabBarItem = UITabBarItem(title: "Pager", image: UIImage(systemName: "square"), tag: 0) let other = UIViewController() other.tabBarItem = UITabBarItem(title: "Other", image: UIImage(systemName: "circle"), tag: 1) let tabBarController = UITabBarController() tabBarController.viewControllers = [pager, other] window = UIWindow(windowScene: scene as! UIWindowScene) window?.rootViewController = tabBarController window?.makeKeyAndVisible() } }
3
2
372
2d
iPhone: Inspector Sheet Issue with Navigation Toolbar Toggle
Hello, I think this might be a bug, but I'm not sure (see the code below). FB25486625 On iPhone, when the inspector is presented as a SHEET, swiping down to dismiss it (instead of tapping the close button inside the inspector) causes the navigation toolbar toggle to stop working properly: tapping it once won't reopen the inspector. You have to tap it twice (turning it on and off again) to make the inspector appear again. This doesn't happen when the inspector is presented as a SIDEBAR on iPad or macOS. import SwiftUI struct ContentView: View { @State private var showInspector = false var body: some View { NavigationStack{ Text("Hello, world!") .padding() .inspector(isPresented: $showInspector) { Button("Close Inspector", role: .cancel) { showInspector.toggle() } Text("On iPhone, with inspector as SHEET, if you swipe to close (instead of tap the close button), the toggle in the navigation toolbar won't open the inspector next time (half the time). It doesn't affect inspector as SIDEBAR on iPad") .padding() Spacer() } .toolbar { Toggle(isOn: $showInspector) { Label("Open", systemImage: "sidebar.trailing") } } } } } #Preview { ContentView() }
0
0
58
2d
Swift 6: Supported AVCaptureSession actor isolation and MainActor preview attachment
I’m developing a SwiftUI camera preview for iPhone and iPad using Xcode 27.2 and Swift 6.4.2. I’m looking for clarification about the supported concurrency boundary between an actor-owned AVCaptureSession and a MainActor-owned AVCaptureVideoPreviewLayer. Environment Xcode 27.2 (27B52) Apple Swift 6.4.2 iPhoneOS SDK 27.2 Deployment target: iOS 26.0 Swift 6 language mode Complete concurrency checking Default actor isolation: MainActor Approachable Concurrency enabled The isolated compiler reproductions also explicitly enable NonisolatedNonsendingByDefault, InferIsolatedConformances, and MemberImportVisibility. Architecture The intended architecture follows the general pattern demonstrated in Apple’s AVCam sample: A capture actor uses a dedicated DispatchSerialQueue executor. The capture actor creates, configures, starts, and stops AVCaptureSession. A MainActor adapter owns AVCaptureVideoPreviewLayer. The preview layer retains the same session after attachment. The capture actor retains its session reference because it must perform subsequent capture operations. Compiler behavior In a minimal reproduction, transferring the retained session directly to the MainActor adapter: await preview.attach(session)produces: Sources/CaptureActor.swift:19:23: error: sending 'session' risks causing data races [#RegionIsolation::SendingRisksDataRace]The compiler identifies a potential race between actor-isolated and MainActor-isolated uses. This reproduction produces one error and zero warnings. A second reproduction uses a small PreviewSource: Sendable value containing a private AVCaptureSession reference, with @preconcurrency import AVFoundation confined to that source file. Its MainActor method attaches the session to the preview layer. Under otherwise identical compiler settings, this second reproduction compiles with zero diagnostics. We understand that compiler acceptance does not establish runtime safety and that the compatibility mechanism does not itself synchronize access to the retained session. Apple AVCam reference Apple’s official AVCam sample demonstrates a similar interoperability pattern: CaptureService.swift: dedicated capture executor and retained preview source. PreviewSource.swift: compatibility import and MainActor attachment. CameraPreview.swift: preview-layer association. Reference: Apple’s AVCam: Building a Camera App sample. Pinned sample SHA-256: 5a1981f90bb30827121324d4b33198d58de9b9d68e3508b64f0aabc18bbc0e53The sample revision date is not established; we are not assuming this archive is identical to the latest downloadable revision. Questions 1. Supported Swift 6 interoperability pattern Is the narrow PreviewSource compatibility approach demonstrated by AVCam the recommended way to associate an actor-retained AVCaptureSession with a MainActor-owned AVCaptureVideoPreviewLayer under complete Swift 6 concurrency checking? Is there a newer recommended API, annotation, or ownership pattern? 2. Capture and preview synchronization Is the following operation sequence supported? Configure and commit the capture session on the dedicated capture executor. Attach the session to the preview layer on MainActor. Call startRunning() on the capture executor. Perform ordinary preview layout on MainActor while capture runs. Call stopRunning() on the capture executor. Detach the session from the preview layer on MainActor. Which preview-layer or preview-connection mutations, if any, must be excluded while startRunning() or stopRunning() is executing? 3. Supported detachment and release sequence After stopRunning() returns and MainActor executes: previewLayer.session = nilis it supported to release the remaining application-held session references once application-owned observers and asynchronous consumers have terminated? Does AVFoundation require or recommend any additional step before those references are released? We are asking about the supported framework lifecycle contract, not assuming that application-level cancellation drains internal AVFoundation work. Sources AVCam: Building a camera app Overview Apple’s official AVCam sample
0
1
256
5d
iOS options in place of opening 2nd window like on mcOS
I have an app that shows data in a 3-column NavigationSplitView, the columns being collections, items in each collection, and item details. Column 2 shows a toolbar button to add a new item. On Mac, that brings up a window where the user can search an online database and choose the data for that new item. But on iOS, Windows aren't allowed (as it says in the debugger when I try running on iPad in the Simulator). I've seen many examples of putting a NavigationStack inside the detail section of the NavigationSplitView. I want that to happen as the result of tapping the toolbar button. When I try, it does slide a new view into place (although it appears to be empty instead of showing the content in my custom View that provides the content for the 2nd window on Mac), and it then immediately slides away to reveal the original detail view. I'm using the code model presented in a SwiftyPlace example. class NavigationStateManager: ObservableObject { @Published var navPath = NavigationPath() func popToRoot() { navPath = NavigationPath() } func goToSearch() { self.navPath.append("search"); } }The main ContentView contains the NavigationSplitView. Its detail contains this: NavigationStack(path:$nav.navPath) { if !selectionStore.selectedItems.isEmpty { DetailView() } else { Text("Select an item."); } } .navigationDestination(for:String.self) { textValue in if textValue == "search" { SearchWindow() .modelContext(modelContext) } }Column 2's view handles the toolbar button, which calls this in that same view: private func addNewIItem() { #if os(macOS) withAnimation { openWindow(id:"Search"); } #else nav.goToSearch(); #endif }I hope I've added enough of this growing-larger-all-the-time project to show the basic setup. Why is the search view sliding in and then back out? Is there a better way to present another view over the top of the main window/view on iPad and iPhone?
Topic: Design SubTopic: General Tags:
2
0
967
1w
iOS 27: SwiftUI TextField text invisible while typing when the app runs in Arabic (FB25410049)
Since iOS 27, if an app runs in Arabic (or, presumably, any right-to-left language), typing digits or Latin letters into a SwiftUI TextField makes the field look empty. The binding updates and the caret shows, but the text isn't drawn until editing ends. What I measured: The text's layout fragment is placed at about x = −39,655 pt in a 370 pt wide field, so it's drawn far outside the field. A plain TextField and one with .environment(\.layoutDirection, .leftToRight) (phone and card numbers) are both affected. A UIKit UITextField on the same screen isn't affected. The same build on iOS 18.0 works, and iOS 27 in English works. Minimal repro: one SwiftUI file with four fields. Run it with App Language set to Arabic, tap the first field and type 523. Launch with -autoProbe to have it type into each field and print where the text was laid out. PROBE iOS 27.0, language ar, layout RTL PROBE field 1 text="523" fieldWidth=370 fragmentX=[-39655] visible=false // SwiftUI TextField PROBE field 2 text="523" fieldWidth=370 fragmentX=[-39655] visible=false // SwiftUI, LTR pinned PROBE field 3 text="523" fieldWidth=370 fragmentX=[314] visible=true // UIKit UITextFieldWorkaround that works for us, installed once at launch: import UIKit enum TextFieldDirection { static func install() { for name in [UITextField.textDidBeginEditingNotification, UITextField.textDidChangeNotification] { NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { note in guard let field = note.object as? UITextField else { return } MainActor.assumeIsolated { settle(field) } // SwiftUI rebuilds the paragraph style right after; settle again. DispatchQueue.main.async { MainActor.assumeIsolated { settle(field) } } } } } @MainActor static func settle(_ field: UITextField) { let direction: NSWritingDirection = field.effectiveUserInterfaceLayoutDirection == .rightToLeft ? .rightToLeft : .leftToRight if let whole = field.textRange(from: field.beginningOfDocument, to: field.endOfDocument) { field.setBaseWritingDirection(direction, for: whole) } func directed(_ attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { var attributes = attributes let style = (attributes[.paragraphStyle] as? NSParagraphStyle)?.mutableCopy() as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() style.baseWritingDirection = direction attributes[.paragraphStyle] = style return attributes } if let typing = field.typingAttributes { field.typingAttributes = directed(typing) } field.defaultTextAttributes = directed(field.defaultTextAttributes) field.setNeedsLayout() field.layoutIfNeeded() } }Don't pin textAlignment instead. SwiftUI puts .natural back on every keystroke and the caret jumps from side to side. If you see this too, please file a duplicate referencing FB25410049. Duplicates help Apple prioritise it.
1
0
88
1w
How do deal with NavigationSplitView not saving selection on iPhone
I have a detail view that has a @Binding var Thing, which is passed to it from the parent. It has TextFields that need to use that bound object's fields. // Using this to pass the selection around @Observable class AppSelection { var selectedThings:Set<Thing> = Set(); } struct ContentView: View { @State private var selectionStore = AppSelection(); var body: some View { NavigationSplitView { ... } content: { ...list that sets the selectionStore.selectedThings... } detail: { if !selectionStore.selectedThings.isEmpty { DetailView(thing:Binding( get: { selectionStore.selectedThings.first! }, // Crashes here set: { selectionStore.selectedThings = [$0] } )) .frame(maxHeight:.infinity, alignment:.top) } else { Text("Select a thing."); } } } } struct DetailView:View { @Binding var thing:Thing; var body:some View { TextField("Blah", $thing.name); TextField("Blerg", $thing.stuff); } }A crash happens when I navigate back from the DetailView in the line commented as such. I assume the NavigationSplitView is resetting the selection back to an empty state. Why the Binding given to the DetailView is even trying to get its value at that point is beyond me (so much weirdness in SwiftUI). I've taken so many different stabs at trying to make things work differently, but I just don't understand enough about SwiftUI. Like can I make the DetailView take an optional binding? If the Thing is nil, then all the TextFields complain that they require a non-optional binding. I don't even want to show the Detail view if nothing is selected (hence the "Select a thing.").
1
0
871
1w
Multiseat Subscriptions: Pricing Flexibility and Enterprise Implementation
I have questions about pricing flexibility and technical implementation. In WWDC26 Session 391, you introduced up to five volume pricing bands that allow developers to offer lower per-seat subscription prices as organizations purchase more seats. For example: 1–20 seats: $39.99/month per seat 21–40 seats: $29.99/month per seat 41+ seats: $19.99/month per seat Volume purchasing and volume discounting provide different benefits. Volume purchasing makes it easier for organizations to buy, deploy, and manage subscriptions. This is valuable independently of discounts. Consider a productivity application deployed to 10,000 employees. Unlike AI services that charge separately for tokens or usage credits, an application offering unlimited, unmetered usage receives no additional revenue beyond its subscription fees. Supporting 10,000 deployments also introduces ongoing responsibilities. Major updates, new capabilities, and performance improvements may require additional testing, deployment coordination, compatibility checks, and technical support. Enterprise customers often have greater requirements for reliability, timely updates, and deployment management. Reducing the fixed subscription price per seat does not reduce these responsibilities or account for the additional value delivered across an organization. My questions for the StoreKit team: Business pricing: Can developers create a separate business subscription with a higher base price than their consumer offering, such as $39.99/month for individuals and $59.99/month per business seat, with independent volume pricing bands? Business-only distribution: Can this subscription be offered exclusively through Apple Business while maintaining the existing consumer subscription? Business features in the same app: Can StoreKit 2 identify whether a user has an individual subscription or an organization-assigned seat, allowing developers to unlock additional business capabilities within the same app binary? Onboarding and deployment: What is the recommended approach for adapting onboarding, subscription unlocking, and access management for organization-assigned users? Are there specific considerations for MDM deployment and managed app configurations? Implementation resources: Are there technical documentation or sample projects covering multiseat purchases, entitlement verification, seat reassignment, subscription changes, and ongoing management after release? The primary opportunity is simplifying organizational purchasing and deployment, while allowing developers to deliver additional business value and price subscriptions according to the ongoing requirements of supporting organizations at scale—not simply discounting additional seats. I’d appreciate clarification on the recommended pricing configuration and StoreKit 2 implementation. Thank you. REF: October 22 Announcement — Offer your subscriptions to businesses and schools
0
0
117
1w
NavigationSplitView, endless loop detail<->content
Hi, I already had a problem with the NavigationSplitView before, and I'm still working on it. The NavigationSplitView issue on the phone occurs when trying to navigate back from the detail view to the content view; the app automatically navigates straight back to the detail view. I reproduced the issue in a small program and identified the critical section. However, I do not know why the problem is occurring. I did the same thing again, but this time with @Query, and there was no problem. The categories, recipes and single recipe, all loaded by @Query and no side effects. Requirements for the content: A state variable used in the view, e.g., isLoading A task that loads the data from a ModelActor in a ViewModel, setting isLoading to true beforehand and back to false upon completion Subsequently, it is only possible to exit the detail view after many attempts, because the app immediately navigates back to the detail view automatically upon trying to leave it. One issue is changing the isLoading status variable from within the task. That in itself isn't the problem; the issue only arises when reading data via a ModelActor. If the status variable is changed and, for example, the list is modified manually, there is no problem. It only occurs when a ModelActor is used. I based it on the NavigationCookbook: This is the view I use for the content section (list): @State private var vm: RecipeListViewModel = .init() @State var isLoading: Bool = false if isLoading { ProgressView("Loading...") } else { List(selection: $selectedRecipe) { ForEach(vm.recipes) { recipe in NavigationLink(recipe.name, value: recipe) } } } .task { // bug, when used in UI, because of ModelActor isLoading = true await vm.list(context, categoryName: selectedCategory.name) isLoading = false } .task { // works, because manually modifying the list isLoading = true await vm.addRecipe(context, categoryName: selectedCategory.name) isLoading = false } inside ViewModel // bug func list(_ modelContext: ModelContext, categoryName: String) async { recipes.removeAll() let dh: RecipeDataHandler = .init(modelContainer: modelContext.container) let values = await dh.listByName(categoryName: categoryName) for v in values { recipes.append(.init(recipeId: v.recipeId, name: v.name)) } } // works func addRecipe(_ modelContext: ModelContext, categoryName: String) async { isLoading = true defer { isLoading = false } recipes.removeAll() recipes.append(.init(recipeId: "a", name: "Pambazo")) } ModelActor @ModelActor actor RecipeDataHandler { func listByName(categoryName: String) async -> [RecipeData] { var list: [RecipeData] = [] do { let fd = FetchDescriptor<Recipe>(predicate: #Predicate { $0.category.name == categoryName }, sortBy: [SortDescriptor(\Recipe.name)]) let data = try modelContext.fetch(fd) for item in data { let rec: RecipeData = await .init(recipeId: item.recipeId, name: item.name) list.append(rec) } } catch { print("error: \(error.localizedDescription)") } return list } } Recipe is a @Model and RecipeData just a struct. In principle, everything should run on the MainActor, but the inclusion of the ModelActor still leads to this strange behavior. Any idea what the problem might be? Christian
3
0
361
1w
NavigationSplitView Binding to NavigationSplitViewColumn reset with every NavigationStack path change
I try to create a Navigation that works on iPhone, iPhone Duo and iPad. NavigationSplitView collapses for compact form factors and makes iPhone Duo support much simpler. The Problem is the semantic of the collapse. Switch from detail to sidebar on compact is not a 'back' action in my case. It should have my choosen Icon and has a meaning. So i wanted to control this using the NavigationSpliViewColumn Binding. This does work forward and backward as long as no NavigationStack was used before. But it stops working when you return to the detail root from a Navigation-Action. I left the Backbutton visible so you can test, that it continues to work, when no Navigation-Action preceeded the switch. Here is my Code: import SwiftUI @Observable final class Compass { var path: [Route] init() { self.path = [] } } enum Route: Hashable { case goal } struct ContentView: View { @Environment(\.horizontalSizeClass) private var hsize @State private var compass: Compass = Compass() @State var column: NavigationSplitViewColumn = .detail var body: some View { NavigationSplitView(preferredCompactColumn: $column) { OverviewScreen(column: $column) } detail: { TodayScreen() .toolbar { ToolbarItem(placement: .topBarLeading) { Button("overview", systemImage: "calendar") { self.column = .sidebar } } } } .environment(compass) .onChange(of: column) { old, new in print("column:", old, "→", new) } } } struct OverviewScreen: View { @Binding var column: NavigationSplitViewColumn @Environment(Compass.self) var compass var body: some View { @Bindable var compass = self.compass ScrollView { Text("Overview") .font(.largeTitle) .fontWeight(.heavy) Button("Switch") { self.column = .detail } } } } struct TodayScreen: View { @Environment(Compass.self) var compass var body: some View { @Bindable var compass = self.compass NavigationStack(path: $compass.path) { ScrollView { Text("Today") .font(.largeTitle) .fontWeight(.heavy) Button("Goals programtically", systemImage: "flag.fill") { self.compass.path = [.goal] } NavigationLink(value: Route.goal) { Label("Goal Link", systemImage: "flag.fill") } } .navigationDestination(for: Route.self, destination: self.screen) } } @ContentBuilder func screen(for route: Route) -> some View { switch route { case .goal: Text("Goals") } } } #Preview { ContentView() }This seems unexpected behaviour. If you ask AI the mental model is NavigationSplitViewColumn represents which column is currently visible for the user. That model would be wrong. Also i can't understand why a NavigationStack has to change the Binding? What am i missing here? Is my idea of the programmatic switch not supported? There are other ways to push from compact to your detail column again, but they all have trade-offs or run into other problems. I would appreciate any help here. AI has not understood this and only does surface level work arounds that fix nothing.
0
0
60
1w
Accessibility Inspector Element Detection and contrast failures with SwiftUI sheets
I am investigating Accessibility Inspector/XCTest audit results in a SwiftUI app (Target Keeper), without suppressing warnings. Environment: Xcode 27.1 (27A9275), iPadOS 27.0 Simulator, macOS 27.0.1. Debug and Release builds pass. The app uses scalable text and labeled controls. Element Detection / native sheet Main-screen Element Detection passes when the sheet is closed. With a SwiftUI .sheet presented, the iPad audit reports “Potentially inaccessible text”; XCUIAccessibilityAuditIssue.element is nil. Exported Inspector HTML highlights background labels behind/outside the sheet, rather than a foreground control. The separate, data-free SwiftUI application below reproduces the modal-only warning; it links no application modules or persistence. Actual iPad Simulator VoiceOver testing using XCUIDevice.shared.voiceOverService passes forward/backward traversal of the real application's modal controls, without reaching the background labels. This is simulator evidence, not physical-device acceptance. A trial explicit modal container/.isModal trait did not clear the audit and was reverted; no warnings are filtered. Minimal independent app: import SwiftUI @main struct ModalRepro: App { @State private var showing=false private let names=["Vegetables","Protein","Fruit","Dairy","Grains","Healthy Fats"] var body:some Scene {WindowGroup { NavigationStack { VStack(spacing:24) { Button("Nutrition") {}.accessibilityLabel("Choose Page").accessibilityValue("Nutrition") LazyVGrid(columns:[GridItem(.flexible()),GridItem(.flexible()),GridItem(.flexible())],spacing:40) { ForEach(names,id:\.self) { name in Button {} label: { VStack(spacing:10) { Circle().fill(.blue.opacity(0.3)).frame(width:250,height:250) Text(name).font(.system(size:17)).accessibilityHidden(true) } }.buttonStyle(.plain).accessibilityLabel(name) } } Button("Show modal") {showing=true} }.padding(24).frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.black) }.preferredColorScheme(.dark) .sheet(isPresented:$showing) { NavigationStack { VStack {Text("Foreground content");Button("Close") {showing=false}} .frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.black) .navigationTitle("Modal flow") }.preferredColorScheme(.dark) } }} }In an Xcode UI-test target, launch this app, run try app.performAccessibilityAudit(for: [.elementDetection]), tap “Show modal”, then run the same audit again. The first passes and the second fails. No issue handler ignores failures. Contrast / scrolling — separate unresolved finding All four iPhone combined-audit configurations (portrait/landscape, normal/maximum Dynamic Type) pass in the tested picker state. However, iPad maximum-text combined audits and a separate scrolled iPhone maximum-text landscape contrast audit still fail; we are not calling these resolved. In the scrolled iPhone case, XCTest identifies a count label whose frame is entirely above the visible results viewport. Inspector's exported HTML reports 1.37:1 using #252526 versus #000000; its marked screenshot rectangle overlaps the dark search/header area, not visible count text. Visible sampled labels instead have white or light foregrounds on opaque dark fills (e.g. #FFFFFF/#1C1C1E = 17.01:1; #98989F/#1C1C1E = 5.94:1). These are screenshot core-pixel measurements, not proof every warning is false. We compare ordinary text against 4.5:1 and interactive non-text against 3:1. iPad maximum-text warnings have not yet been matched to an exact visible color pair. Original failures and HTML exports are preserved privately. Questions Is Element Detection expected to inspect background elements behind a presented native SwiftUI sheet? Is this a known tool limitation/framework issue, or does the sample need different semantics? What is the supported way to establish modal accessibility boundaries with SwiftUI sheets, and when should .isModal be added explicitly? How should we distinguish a real contrast defect from clipping, occlusion, translucency, or sampling of an offscreen element? Which Inspector export fields/diagnostics should establish the actual sampled text/background pair? What additional evidence/manual and physical-device tests does Apple recommend before qualifying accessibility when the minimal audit reproduction fails but actual VoiceOver modal focus behaves correctly? We are not asking to waive audits or declare full accessibility from a VoiceOver pass alone. I read Apple's audit guidance and modal trait documentation: https://developer.apple.com/documentation/accessibility/performing-accessibility-audits-for-your-app https://developer.apple.com/documentation/swiftui/accessibilitytraits/ismodal https://developer.apple.com/help/app-store-connect/manage-app-accessibility/sufficient-contrast-evaluation-criteria/ Related discussion https://developer.apple.com/forums/thread/823968 recommends Feedback for a different apparent Dynamic Type false positive; it does not answer these modal/contrast questions. A Feedback draft is prepared but not submitted, so I have no Feedback ID yet. No private data, identifiers, credentials, app containers or confidential application source are included.
1
0
861
1w
SwiftUI View not Refreshing when View Model Controller Data is Updated
With the implementation below, my SwiftUI view is not refreshing when data is retrieved from an HTTP request and stored in a view model controller class. The SwiftUI view calls a shared class of which is the parent of the view model controller, and the view model controller stores the data being accessed from the SwiftUI view. GlobalDataManager: @MainActor @Observable class GlobalDataManager { static let shared = GlobalDataManager() var medications: MedicationsViewModel private init() { let medicationsVM = MedicationsViewModel() self.medications = medicationsVM medicationsVM.globalDataManager = self } }MedicationsViewModel: @MainActor @Observable final class MedicationsViewModel { weak var globalDataManager: GlobalDataManager? var medicationInformation: [MedicationInformation] var asNeededMedications: [AsNeededMedication] var timesMedicationsTaken: [TimeMedicationsTaken] init(globalDataManager: GlobalDataManager? = nil) { self.medicationInformation = [] self.asNeededMedications = [] self.timesMedicationsTaken = [] self.globalDataManager = globalDataManager } func request<T: Encodable>(data: T, requestType: RequestType) async { do { let response = try await HTTPRequestService.shared.request(requestType: requestType, body: data, dataID: nil) if response.success { let decodedData = try DataDecoderService.shared.decode(MedicationData.self, from: response.data) medicationInformation = decodedData.medicationInformation asNeededMedications = decodedData.asNeededMedications timesMedicationsTaken = decodedData.timesMedicationsTaken } } catch let error { globalDataManager?.updateError(error.localizedDescription) } } }Medications View: struct Medications: View { @State private var globalDataManager = GlobalDataManager.shared @State var sheetOpen: Bool = false var body: some View { MedicationTimesList(medicationTimes: globalDataManager.medications.timesMedicationsTaken) .sheet(isPresented: $sheetOpen) { CreateAndUpdateMedicationInformation(viewModel: globalDataManager.medications, sheetOpen: $sheetOpen) } }} Button within CreateAndUpdateMedicationInformation: struct CreateAndUpdateMedicationInformation: View { var viewModel: MedicationsViewModel var body: some View { WideButton(label: "Add Medication", buttonState: $buttonState) .onChange(of: buttonState) { _, newState in if newState { let medicationInformation = MedicationInformation() Task { await viewModel.request(data: medicationInformation, requestType: .addNewMedication) } sheetOpen = false } } }When pressing a button within the CreateAndUpdateMedicationInformation view, the request() function is called, which updates the variable timesMedicationTaken that is stores the data to be displayed in the Medications view. In the request function variables update, but the SwiftUI view does not. How can this be resolved so the Medications view refreshes when timesMedicationTaken and the other variables in the request() function are updated?
Topic: Design SubTopic: General Tags:
Replies
0
Boosts
0
Views
48
Activity
8h
A small scrolling ticker pins GPU at ~50% on a rich screen but ~0% on a blank one — is whole-tree compositing at the animation's rate the expected cost?
I have a SwiftUI clock-radio app on iPhone (ProMotion display). The face is a rich, layered screen (glass panels, gradients, shadows). One element is a 340×46 pt text ticker strip that scrolls continuously. Its GPU behaviour is reproducible and, to me, surprising: Measurements (Xcode GPU gauge used only for within-session relative contrasts; cadence from an in-app frame-pacing log): With the ticker scrolling, GPU sits at a ~50% plateau. The step is binary: one scroller or three scrollers cost the same. Frames are never dropped: 16.7 ms median frame time, ~3% of frames over 20 ms. The GPU bills heavily for frames it renders on time. The same strip scrolling on an otherwise blank screen costs ~0%. The ticker visible but static costs 0%. Motion is the trigger; the cost scales with what surrounds the strip, not with the strip itself. What I have tried: SwiftUI offset animation of the text — baseline behaviour above. A fully severed Canvas leaf: own layer subtree, own CADisplayLink, snapshot-fed, no view-tree publishing or subscriptions — identical cost on the full face (and literally 0% redrawing alone on a blank screen at full frame rate). Pre-rendered bitmap content scrolled instead of text — identical. The strip hosted in a separate UIWindow above the face — identical, measured with the strip visibly scrolling in its seat. Pinning the strip's CADisplayLink.preferredFrameRateRange to exactly 30 Hz (minimum = maximum = preferred = 30) — no change. .compositingGroup() on the panel subtree — a modest reduction only. What works: removing the motion duty (scroll once, then park — cost collapses), and, elsewhere in the app, a pull-model spectrum visualiser replaced a publish-driven one and its cost vanished. Severing worked for the visualiser but not for the ticker. Questions: Is this step the expected consequence of the WWDC22 session 10083 rule (the display refresh rate is set by the highest-frame-rate animation in the app) composed with whole-layer-tree compositing per presented frame? Is there any dirty-region scoping that a layer structure like ours defeats? The 30 Hz pin above was hard (min = max = preferred). Would a CAFrameRateRange in the 15–24 Hz band that Apple's ProMotion guidance suggests for small, slow animations behave materially differently, or is that distinction already settled by the 30 Hz result? Is there a known construction for a continuously scrolling ticker that lets the GPU idle between updates? Happy to share a minimal repro or the pacing logs if useful.
Replies
2
Boosts
0
Views
363
Activity
1d
Thin border around VideoPlayer
A thin gray border appears around assets presented using VideoPlayer. It was tested on iPhone 8, iPhone 13 mini, and iPhone 15, and the border only appeared on the 13 mini. The iOS version didn't matter, we checked the 13 mini on both iOS 18 and iOS 26 and the border appeared on both. I created a minimal version of the app with just the VideoPlayer and the asset and the frame still appeared. We tried different assets and it was visible on all of them. import SwiftUI import AVKit struct ContentView: View { private let player = AVPlayer(url: Bundle.main.url(forResource: "sample", withExtension: "mp4")!) var body: some View { VideoPlayer(player: player) .frame(width: 300, height: 200) } }I found someone that had the same exact problem on Reddit and StackOverflow: https://www.reddit.com/r/swift/comments/8s87hm/weird_borders_around_avplayerviewcontroller/ https://stackoverflow.com/questions/50927561/weird-borders-around-avplayerviewcontroller and their solution included setting: playerLayer.shouldRasterize = true playerLayer.rasterizationScale = UIScreen.main.scaleThis fixed the issue but isn't suitable for our app due to offscreen rendering performance. They also found that issue on other devices, noting: "After testing this code on multiple devices (actual physical devices, not simulators), this bug appears to happen only on Plus devices (I tested it on i8 on iOS 11, i8+ on iOS 11, iX on iOS 11 and i6+ on iOS 10)." The bug doesn't appear on simulator, isn't visible on a screenshot, the frame disappears when using accessibility zoom. When we encoded the video file to Apple ProRes the border disappeared, but the size of the video bumped from 342 KB to 6.3 MB so we couldn't do that. I'm attaching photo from this person's post since our issue looked exactly the same. Has anyone had a similar issue and maybe found another workaround?
Replies
1
Boosts
1
Views
274
Activity
1d
Tab grayed out, but not disabled
Bug report: FB25495244 This bug also appeared with Xcode 27; it worked previously. scenario: A TabView with, for example, 4 tabs. Data in ContentView is loaded via @Query. In ContentBindingView, the same thing is done, but using a parameter instead of SwiftData. ContentView: If no data is available, the system automatically switches to tab 4 at the start; while you can click on other tabs, they are disabled, and the system will not switch to them. ContentBindingView: When tabDisabled is set to "true", the system automatically switches to Tab 4 upon startup; while it is possible to click on other tabs, they are disabled, and the system will not switch to them. So far, so good. The first three tabs are only active if at least one data record exists or tabDisabled is "false". Tab 4 is always active and is used for data entry. When data is entered in Tab 4, tab 1 to 3 should become active. However, it appears as though tabs 1 through 3 remain disabled—the icons appear grayed out. That is actually not the case; you can click on the tabs, and as soon as you move away from a tab, its icon is displayed correctly. This issue did not exist in Xcode 26. In Xcode 26, all symbols were displayed correctly from the start, once data had been saved. What I noticed on the iPhone Duo: If you open the program on an iPhone Duo while it is closed, enter some data, then move the app to the background, open the iPhone Duo, and bring the application back to the foreground, tabs 1 through 3 will be active. The entire view is being updated, which is why tabs 1 through 3 are now active—though they should have been active already. A side note: The same issue described in the forum post "A layout bug when using Tab View in SwiftUI (FB25490881)" also occurs in this program. The entire view has shifted upward. The text field is no longer visible—only the Add button remains. In the other views, you can only see half of the text. import SwiftUI struct ContentBindingView: View { @State private var tabDisabled: Bool = true @State private var selection: TabSelection = .tab1 private var isTabDisabled: Bool { print("ContentView.isTabDisabled: \(tabDisabled)") return tabDisabled } var body: some View { TabView(selection: $selection) { Tab("Tab 1", systemImage: "waveform.badge.magnifyingglass", value: .tab1) { Text("tab 1") } .disabled(isTabDisabled) Tab("Tab 2", systemImage: "heart", value: .tab2) { Text("tab 2") } .disabled(tabDisabled) Tab("Tab 3", systemImage: "target", value: .tab3) { Text("tab 3") } .disabled(tabDisabled) Tab("Tab 4", systemImage: "gear", value: .tab4) { AddFriendBindingView(tabDisabled: $tabDisabled) } } .onAppear { print("ContentView onAppear disabled: \(tabDisabled)") if tabDisabled { selection = .tab4 } } .onChange(of: selection) { print("ContentView onChange disabled: \(tabDisabled)") } } }struct AddFriendBindingView: View { @Environment(\.dismiss) private var disdmiss @Binding var tabDisabled: Bool @State private var name: String = "" var body: some View { VStack { TextField("Name", text: $name) Button { addFriend() } label: { Text("Add") } .buttonStyle(.borderedProminent) } .padding() .onAppear { print("AddFriendView onAppear disabled: \(tabDisabled)") } } private func addFriend() { tabDisabled = false name = "" disdmiss() } }Christian
Replies
1
Boosts
0
Views
259
Activity
1d
macOS 27.2 makes AUv3 plug-ins completely useless as soon as the user accesses a button with a native submenu.
https://youtu.be/HnNGgYZQBvYJust submitted a bug report FB25497228, and posting it here in hopes to get Apple engineers' attention because it's such a bad bug that macOS 27.2 introduced, and don't think it has a workaround unless Apple fixes it. This wasn't an issue in 27.0. Summary: Opening a native submenu in an out-of-process AUv3 plug-in and dismissing it without selecting anything will make the plug-in UI completely unresponsive to mouse input, and any animation will stop also. Only restart solves it. I can reproduce this with Mela in both Logic Pro and Ableton Live 12. I've attached a demo project to the bug report. It's Xcode Audio Unti extension template with a small modification, just added a button with a submenu. Mela can be found here if needed: https://apps.apple.com/us/app/mela-auv3-synth-fx-midi/id1668299428 Steps to reproduce with the attached project Load Mela as an AU instrument in Logic Pro or Ableton Live Open a factory patch. Open a module’s Actions menu (ellipsis buttons) Hover over Insert or Replace until the submenu appears, then dismiss it without selecting anything. Expected result Mela's UI continues to be interactive, e.g. parameters can be easily adjusted and modulation animations continue to work. Actual result No interaction can be performed, wether adjusting a prameter or dragging a module or pressing a button. Animations also stop. This bug renders my plug-in completely useless on macOS as it makes heavy use of submenus. Please fix it! Here's a link to the video demo of the issue: https://youtu.be/HnNGgYZQBvY
Replies
0
Boosts
0
Views
63
Activity
2d
A layout bug when using Tab View in SwiftUI
I've been getting some unexpected behavior when using TabView as the root view in SwiftUI, which I believe to be a bug. I created a barebones app to isolate the bug and will paste the code at the end for anyone who wants to try to reproduce it if you have a physical Duo device. Details below copied from my Feedback ticket FB25490881. Xcode 27.1. What's the bug? When you have a TabView as the root view inside the ContentView.swift and you open the app on the outer screen, then swipe it to the background, unfold the phone and tap the app to bring it to the foreground, any UI element without a fixed frame size gets moved off screen. And any UI element with a fixed frame size gets moved to the very top of the screen. See my two screenshots of outer vs inner display. How to reproduce: 1- Unzip the project folder and open it in Xcode. 2- With a physical Duo on hand, run the app on the Duo. 3- On the outer screen, launch the app. 4- Swipe the app to the background (Do not swipe completely to close the app, just swipe it to the background so it is still running) 5- Unfold the iPhone Duo completely open. 6- Tap the app icon to bring it to the foreground. 7- Look at the broken UI. Also note even the side tab bar itself is broken, it has no padding with the edge of the screen. This is managed by the system. The workaround to fix it: 1- Go to the ContentView.Swift in Xcode. 2- Uncomment "NavigationStack {" on line 13. 3- Uncomment the closing bracket "}" on line 2. 4- Repeat the previous from how to reproduce the bug. My hypothesis: The system reports 0 height for the inner screen in that moment you bring the app to the foreground. Anything with a fixed frame will move to the top, anything with flexible height will not be visible. This only happens when TabView is the root of the scene. Other Useful Information: I hit a very similar bug to this in my main app that I'm still developing, unfortunately I was not able to reproduce it in a sandbox simple app. The bug was: 1- Open the app on outer screen 2- While the app is open, tilt the Duo to the back at a certain angle 3- Unfold the Duo 4- You get this exact same bug where everything with a flexible height disappears off screen or collapses to zero if it's in between UI elements with fixed heights. And everything moves to the top of the screen. I'm not sure why I wasn't able to reproduce it on a sandbox app, but the fix was the same, either remove the TabView completely or put it inside a NavigationStack. I want to stress that this one had to do with the backwards tilt of the phone itself while unfolding it, very weird I know. ContentView.swift: import SwiftUI @available(iOS 27.1, *) struct ContentView: View { var body: some View { // NavigationStack { TabView { HomeTabView() .tabItem { Label("Home", systemImage: "house") } StoreView() .tabItem { Label("Store", systemImage: "bag") } } .tint(Color.black) // } } } HomeTabView.swift: import SwiftUI @available(iOS 27.1, *) struct HomeTabView: View { var body: some View { VStack { Text("Jenny") .font(.largeTitle) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity) .frame(height: 200) .background(Color.gray, in: .rect(cornerRadius: 28)) Spacer() Circle() .fill(Color.blue) .frame(width: 100, height: 100) .padding(.bottom, 50) } .contentMargins(for: .container, edges: .horizontal) } } StoreView.swift is just an default empty swiftUI file with the default Text("Hello, World!")
Replies
2
Boosts
0
Views
378
Activity
2d
TabView(.page) extended under the tab bar shifts its pages up after the container height shrinks while visible
Summary A page-style TabView that extends under the bottom tab bar with .ignoresSafeArea(edges: .bottom) leaves its pages shifted upward after its height shrinks while it is on screen. The shift is half of the bottom safe area (41.67 pt with an 83.33 pt tab bar area), and the top of each page is hidden. The internal UICollectionView (contentInsetAdjustmentBehavior == .automatic) seems to briefly add the bottom safe area to adjustedContentInset.bottom during the resize. The inset goes back to 0 afterwards, but the visible cell is left at frame.minY == -41.67. Environment Xcode 27.0 (27A266a), iPhone Duo Simulator (iOS 27.1), deployment target iOS 18.0 In a unit test it also reproduces when only the window height shrinks (386×678 → 386×600), so it does not depend on the foldable device Steps to reproduce Run the code below on the iPhone Duo Simulator while it is folded Unfold the device Expected The red "Top of page 0" area stays directly below the yellow header, as it does when the app is launched unfolded. Actual The top of the red area is hidden under the header. Log output: folded (launch): height=628.00 adjustedContentInset.bottom=0.00 cell.minY=0.00 after unfolding: height=619.00 adjustedContentInset.bottom=0.00 cell.minY=-41.67In this sample the page returns to its position when the device is folded again. In our app it stays shifted, and after the shift, tapping to switch pages sometimes stops working until the user swipes. Notes Removing .ignoresSafeArea(edges: .bottom) avoids the issue, but the content then no longer extends under the translucent tab bar Setting edgesForExtendedLayout = [] on the hosting controller also avoids it, with the same visual change. safeAreaRegions = [] and setContentScrollView(nil, for:) do not help A non-transparent background on the TabView or its pages, as suggested in https://developer.apple.com/forums/thread/762286, does not help. The trigger here is a size change, not a page switch, so this seems to be a different issue Current workaround: find the internal UICollectionView through the superview chain and set contentInsetAdjustmentBehavior = .never. This works, but it relies on the private view hierarchy. Is there a supported way to avoid this? Minimal reproduction import SwiftUI import UIKit // A TabView(.page) extended under the tab bar with .ignoresSafeArea(edges: .bottom). // Resizing the window while it is visible (e.g. unfolding) shifts the page up. struct PagerView: View { @State private var selection = 0 var body: some View { VStack(spacing: 0) { Color.yellow.frame(height: 50).overlay(Text("Header")) TabView(selection: $selection) { ForEach(0..<2) { index in ScrollView { Color.red.frame(height: 120).overlay(Text("Top of page \(index)")) ForEach(0..<30) { Text("Row \($0)").frame(maxWidth: .infinity, minHeight: 44) } } .tag(index) } } .tabViewStyle(.page(indexDisplayMode: .never)) } .ignoresSafeArea(edges: .bottom) } } final class PagerViewController: UIHostingController<PagerView> { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in self?.logCollectionView() } } private func logCollectionView() { func find(_ view: UIView) -> UICollectionView? { view as? UICollectionView ?? view.subviews.lazy.compactMap(find).first } guard let collectionView = find(view) else { return } let cellMinY = collectionView.visibleCells.first?.frame.minY ?? .nan NSLog("FORUM height=%.2f adjustedContentInset.bottom=%.2f cell.minY=%.2f", collectionView.bounds.height, collectionView.adjustedContentInset.bottom, cellMinY) } } @main final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting session: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let configuration = UISceneConfiguration(name: nil, sessionRole: session.role) configuration.delegateClass = SceneDelegate.self return configuration } } final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let pager = PagerViewController(rootView: PagerView()) pager.tabBarItem = UITabBarItem(title: "Pager", image: UIImage(systemName: "square"), tag: 0) let other = UIViewController() other.tabBarItem = UITabBarItem(title: "Other", image: UIImage(systemName: "circle"), tag: 1) let tabBarController = UITabBarController() tabBarController.viewControllers = [pager, other] window = UIWindow(windowScene: scene as! UIWindowScene) window?.rootViewController = tabBarController window?.makeKeyAndVisible() } }
Replies
3
Boosts
2
Views
372
Activity
2d
iPhone: Inspector Sheet Issue with Navigation Toolbar Toggle
Hello, I think this might be a bug, but I'm not sure (see the code below). FB25486625 On iPhone, when the inspector is presented as a SHEET, swiping down to dismiss it (instead of tapping the close button inside the inspector) causes the navigation toolbar toggle to stop working properly: tapping it once won't reopen the inspector. You have to tap it twice (turning it on and off again) to make the inspector appear again. This doesn't happen when the inspector is presented as a SIDEBAR on iPad or macOS. import SwiftUI struct ContentView: View { @State private var showInspector = false var body: some View { NavigationStack{ Text("Hello, world!") .padding() .inspector(isPresented: $showInspector) { Button("Close Inspector", role: .cancel) { showInspector.toggle() } Text("On iPhone, with inspector as SHEET, if you swipe to close (instead of tap the close button), the toggle in the navigation toolbar won't open the inspector next time (half the time). It doesn't affect inspector as SIDEBAR on iPad") .padding() Spacer() } .toolbar { Toggle(isOn: $showInspector) { Label("Open", systemImage: "sidebar.trailing") } } } } } #Preview { ContentView() }
Replies
0
Boosts
0
Views
58
Activity
2d
Swift 6: Supported AVCaptureSession actor isolation and MainActor preview attachment
I’m developing a SwiftUI camera preview for iPhone and iPad using Xcode 27.2 and Swift 6.4.2. I’m looking for clarification about the supported concurrency boundary between an actor-owned AVCaptureSession and a MainActor-owned AVCaptureVideoPreviewLayer. Environment Xcode 27.2 (27B52) Apple Swift 6.4.2 iPhoneOS SDK 27.2 Deployment target: iOS 26.0 Swift 6 language mode Complete concurrency checking Default actor isolation: MainActor Approachable Concurrency enabled The isolated compiler reproductions also explicitly enable NonisolatedNonsendingByDefault, InferIsolatedConformances, and MemberImportVisibility. Architecture The intended architecture follows the general pattern demonstrated in Apple’s AVCam sample: A capture actor uses a dedicated DispatchSerialQueue executor. The capture actor creates, configures, starts, and stops AVCaptureSession. A MainActor adapter owns AVCaptureVideoPreviewLayer. The preview layer retains the same session after attachment. The capture actor retains its session reference because it must perform subsequent capture operations. Compiler behavior In a minimal reproduction, transferring the retained session directly to the MainActor adapter: await preview.attach(session)produces: Sources/CaptureActor.swift:19:23: error: sending 'session' risks causing data races [#RegionIsolation::SendingRisksDataRace]The compiler identifies a potential race between actor-isolated and MainActor-isolated uses. This reproduction produces one error and zero warnings. A second reproduction uses a small PreviewSource: Sendable value containing a private AVCaptureSession reference, with @preconcurrency import AVFoundation confined to that source file. Its MainActor method attaches the session to the preview layer. Under otherwise identical compiler settings, this second reproduction compiles with zero diagnostics. We understand that compiler acceptance does not establish runtime safety and that the compatibility mechanism does not itself synchronize access to the retained session. Apple AVCam reference Apple’s official AVCam sample demonstrates a similar interoperability pattern: CaptureService.swift: dedicated capture executor and retained preview source. PreviewSource.swift: compatibility import and MainActor attachment. CameraPreview.swift: preview-layer association. Reference: Apple’s AVCam: Building a Camera App sample. Pinned sample SHA-256: 5a1981f90bb30827121324d4b33198d58de9b9d68e3508b64f0aabc18bbc0e53The sample revision date is not established; we are not assuming this archive is identical to the latest downloadable revision. Questions 1. Supported Swift 6 interoperability pattern Is the narrow PreviewSource compatibility approach demonstrated by AVCam the recommended way to associate an actor-retained AVCaptureSession with a MainActor-owned AVCaptureVideoPreviewLayer under complete Swift 6 concurrency checking? Is there a newer recommended API, annotation, or ownership pattern? 2. Capture and preview synchronization Is the following operation sequence supported? Configure and commit the capture session on the dedicated capture executor. Attach the session to the preview layer on MainActor. Call startRunning() on the capture executor. Perform ordinary preview layout on MainActor while capture runs. Call stopRunning() on the capture executor. Detach the session from the preview layer on MainActor. Which preview-layer or preview-connection mutations, if any, must be excluded while startRunning() or stopRunning() is executing? 3. Supported detachment and release sequence After stopRunning() returns and MainActor executes: previewLayer.session = nilis it supported to release the remaining application-held session references once application-owned observers and asynchronous consumers have terminated? Does AVFoundation require or recommend any additional step before those references are released? We are asking about the supported framework lifecycle contract, not assuming that application-level cancellation drains internal AVFoundation work. Sources AVCam: Building a camera app Overview Apple’s official AVCam sample
Replies
0
Boosts
1
Views
256
Activity
5d
iOS options in place of opening 2nd window like on mcOS
I have an app that shows data in a 3-column NavigationSplitView, the columns being collections, items in each collection, and item details. Column 2 shows a toolbar button to add a new item. On Mac, that brings up a window where the user can search an online database and choose the data for that new item. But on iOS, Windows aren't allowed (as it says in the debugger when I try running on iPad in the Simulator). I've seen many examples of putting a NavigationStack inside the detail section of the NavigationSplitView. I want that to happen as the result of tapping the toolbar button. When I try, it does slide a new view into place (although it appears to be empty instead of showing the content in my custom View that provides the content for the 2nd window on Mac), and it then immediately slides away to reveal the original detail view. I'm using the code model presented in a SwiftyPlace example. class NavigationStateManager: ObservableObject { @Published var navPath = NavigationPath() func popToRoot() { navPath = NavigationPath() } func goToSearch() { self.navPath.append("search"); } }The main ContentView contains the NavigationSplitView. Its detail contains this: NavigationStack(path:$nav.navPath) { if !selectionStore.selectedItems.isEmpty { DetailView() } else { Text("Select an item."); } } .navigationDestination(for:String.self) { textValue in if textValue == "search" { SearchWindow() .modelContext(modelContext) } }Column 2's view handles the toolbar button, which calls this in that same view: private func addNewIItem() { #if os(macOS) withAnimation { openWindow(id:"Search"); } #else nav.goToSearch(); #endif }I hope I've added enough of this growing-larger-all-the-time project to show the basic setup. Why is the search view sliding in and then back out? Is there a better way to present another view over the top of the main window/view on iPad and iPhone?
Topic: Design SubTopic: General Tags:
Replies
2
Boosts
0
Views
967
Activity
1w
iOS 27: SwiftUI TextField text invisible while typing when the app runs in Arabic (FB25410049)
Since iOS 27, if an app runs in Arabic (or, presumably, any right-to-left language), typing digits or Latin letters into a SwiftUI TextField makes the field look empty. The binding updates and the caret shows, but the text isn't drawn until editing ends. What I measured: The text's layout fragment is placed at about x = −39,655 pt in a 370 pt wide field, so it's drawn far outside the field. A plain TextField and one with .environment(\.layoutDirection, .leftToRight) (phone and card numbers) are both affected. A UIKit UITextField on the same screen isn't affected. The same build on iOS 18.0 works, and iOS 27 in English works. Minimal repro: one SwiftUI file with four fields. Run it with App Language set to Arabic, tap the first field and type 523. Launch with -autoProbe to have it type into each field and print where the text was laid out. PROBE iOS 27.0, language ar, layout RTL PROBE field 1 text="523" fieldWidth=370 fragmentX=[-39655] visible=false // SwiftUI TextField PROBE field 2 text="523" fieldWidth=370 fragmentX=[-39655] visible=false // SwiftUI, LTR pinned PROBE field 3 text="523" fieldWidth=370 fragmentX=[314] visible=true // UIKit UITextFieldWorkaround that works for us, installed once at launch: import UIKit enum TextFieldDirection { static func install() { for name in [UITextField.textDidBeginEditingNotification, UITextField.textDidChangeNotification] { NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { note in guard let field = note.object as? UITextField else { return } MainActor.assumeIsolated { settle(field) } // SwiftUI rebuilds the paragraph style right after; settle again. DispatchQueue.main.async { MainActor.assumeIsolated { settle(field) } } } } } @MainActor static func settle(_ field: UITextField) { let direction: NSWritingDirection = field.effectiveUserInterfaceLayoutDirection == .rightToLeft ? .rightToLeft : .leftToRight if let whole = field.textRange(from: field.beginningOfDocument, to: field.endOfDocument) { field.setBaseWritingDirection(direction, for: whole) } func directed(_ attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { var attributes = attributes let style = (attributes[.paragraphStyle] as? NSParagraphStyle)?.mutableCopy() as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() style.baseWritingDirection = direction attributes[.paragraphStyle] = style return attributes } if let typing = field.typingAttributes { field.typingAttributes = directed(typing) } field.defaultTextAttributes = directed(field.defaultTextAttributes) field.setNeedsLayout() field.layoutIfNeeded() } }Don't pin textAlignment instead. SwiftUI puts .natural back on every keystroke and the caret jumps from side to side. If you see this too, please file a duplicate referencing FB25410049. Duplicates help Apple prioritise it.
Replies
1
Boosts
0
Views
88
Activity
1w
How do deal with NavigationSplitView not saving selection on iPhone
I have a detail view that has a @Binding var Thing, which is passed to it from the parent. It has TextFields that need to use that bound object's fields. // Using this to pass the selection around @Observable class AppSelection { var selectedThings:Set<Thing> = Set(); } struct ContentView: View { @State private var selectionStore = AppSelection(); var body: some View { NavigationSplitView { ... } content: { ...list that sets the selectionStore.selectedThings... } detail: { if !selectionStore.selectedThings.isEmpty { DetailView(thing:Binding( get: { selectionStore.selectedThings.first! }, // Crashes here set: { selectionStore.selectedThings = [$0] } )) .frame(maxHeight:.infinity, alignment:.top) } else { Text("Select a thing."); } } } } struct DetailView:View { @Binding var thing:Thing; var body:some View { TextField("Blah", $thing.name); TextField("Blerg", $thing.stuff); } }A crash happens when I navigate back from the DetailView in the line commented as such. I assume the NavigationSplitView is resetting the selection back to an empty state. Why the Binding given to the DetailView is even trying to get its value at that point is beyond me (so much weirdness in SwiftUI). I've taken so many different stabs at trying to make things work differently, but I just don't understand enough about SwiftUI. Like can I make the DetailView take an optional binding? If the Thing is nil, then all the TextFields complain that they require a non-optional binding. I don't even want to show the Detail view if nothing is selected (hence the "Select a thing.").
Replies
1
Boosts
0
Views
871
Activity
1w
Multiseat Subscriptions: Pricing Flexibility and Enterprise Implementation
I have questions about pricing flexibility and technical implementation. In WWDC26 Session 391, you introduced up to five volume pricing bands that allow developers to offer lower per-seat subscription prices as organizations purchase more seats. For example: 1–20 seats: $39.99/month per seat 21–40 seats: $29.99/month per seat 41+ seats: $19.99/month per seat Volume purchasing and volume discounting provide different benefits. Volume purchasing makes it easier for organizations to buy, deploy, and manage subscriptions. This is valuable independently of discounts. Consider a productivity application deployed to 10,000 employees. Unlike AI services that charge separately for tokens or usage credits, an application offering unlimited, unmetered usage receives no additional revenue beyond its subscription fees. Supporting 10,000 deployments also introduces ongoing responsibilities. Major updates, new capabilities, and performance improvements may require additional testing, deployment coordination, compatibility checks, and technical support. Enterprise customers often have greater requirements for reliability, timely updates, and deployment management. Reducing the fixed subscription price per seat does not reduce these responsibilities or account for the additional value delivered across an organization. My questions for the StoreKit team: Business pricing: Can developers create a separate business subscription with a higher base price than their consumer offering, such as $39.99/month for individuals and $59.99/month per business seat, with independent volume pricing bands? Business-only distribution: Can this subscription be offered exclusively through Apple Business while maintaining the existing consumer subscription? Business features in the same app: Can StoreKit 2 identify whether a user has an individual subscription or an organization-assigned seat, allowing developers to unlock additional business capabilities within the same app binary? Onboarding and deployment: What is the recommended approach for adapting onboarding, subscription unlocking, and access management for organization-assigned users? Are there specific considerations for MDM deployment and managed app configurations? Implementation resources: Are there technical documentation or sample projects covering multiseat purchases, entitlement verification, seat reassignment, subscription changes, and ongoing management after release? The primary opportunity is simplifying organizational purchasing and deployment, while allowing developers to deliver additional business value and price subscriptions according to the ongoing requirements of supporting organizations at scale—not simply discounting additional seats. I’d appreciate clarification on the recommended pricing configuration and StoreKit 2 implementation. Thank you. REF: October 22 Announcement — Offer your subscriptions to businesses and schools
Replies
0
Boosts
0
Views
117
Activity
1w
NavigationSplitView, endless loop detail<->content
Hi, I already had a problem with the NavigationSplitView before, and I'm still working on it. The NavigationSplitView issue on the phone occurs when trying to navigate back from the detail view to the content view; the app automatically navigates straight back to the detail view. I reproduced the issue in a small program and identified the critical section. However, I do not know why the problem is occurring. I did the same thing again, but this time with @Query, and there was no problem. The categories, recipes and single recipe, all loaded by @Query and no side effects. Requirements for the content: A state variable used in the view, e.g., isLoading A task that loads the data from a ModelActor in a ViewModel, setting isLoading to true beforehand and back to false upon completion Subsequently, it is only possible to exit the detail view after many attempts, because the app immediately navigates back to the detail view automatically upon trying to leave it. One issue is changing the isLoading status variable from within the task. That in itself isn't the problem; the issue only arises when reading data via a ModelActor. If the status variable is changed and, for example, the list is modified manually, there is no problem. It only occurs when a ModelActor is used. I based it on the NavigationCookbook: This is the view I use for the content section (list): @State private var vm: RecipeListViewModel = .init() @State var isLoading: Bool = false if isLoading { ProgressView("Loading...") } else { List(selection: $selectedRecipe) { ForEach(vm.recipes) { recipe in NavigationLink(recipe.name, value: recipe) } } } .task { // bug, when used in UI, because of ModelActor isLoading = true await vm.list(context, categoryName: selectedCategory.name) isLoading = false } .task { // works, because manually modifying the list isLoading = true await vm.addRecipe(context, categoryName: selectedCategory.name) isLoading = false } inside ViewModel // bug func list(_ modelContext: ModelContext, categoryName: String) async { recipes.removeAll() let dh: RecipeDataHandler = .init(modelContainer: modelContext.container) let values = await dh.listByName(categoryName: categoryName) for v in values { recipes.append(.init(recipeId: v.recipeId, name: v.name)) } } // works func addRecipe(_ modelContext: ModelContext, categoryName: String) async { isLoading = true defer { isLoading = false } recipes.removeAll() recipes.append(.init(recipeId: "a", name: "Pambazo")) } ModelActor @ModelActor actor RecipeDataHandler { func listByName(categoryName: String) async -> [RecipeData] { var list: [RecipeData] = [] do { let fd = FetchDescriptor<Recipe>(predicate: #Predicate { $0.category.name == categoryName }, sortBy: [SortDescriptor(\Recipe.name)]) let data = try modelContext.fetch(fd) for item in data { let rec: RecipeData = await .init(recipeId: item.recipeId, name: item.name) list.append(rec) } } catch { print("error: \(error.localizedDescription)") } return list } } Recipe is a @Model and RecipeData just a struct. In principle, everything should run on the MainActor, but the inclusion of the ModelActor still leads to this strange behavior. Any idea what the problem might be? Christian
Replies
3
Boosts
0
Views
361
Activity
1w
Non-collapsing title
In the Photos app, the Collections title doesn't collapse into a small inline title when scrolling, unlike a standard inline large title display mode. How is this implemented?
Replies
2
Boosts
0
Views
200
Activity
1w
NavigationSplitView Binding to NavigationSplitViewColumn reset with every NavigationStack path change
I try to create a Navigation that works on iPhone, iPhone Duo and iPad. NavigationSplitView collapses for compact form factors and makes iPhone Duo support much simpler. The Problem is the semantic of the collapse. Switch from detail to sidebar on compact is not a 'back' action in my case. It should have my choosen Icon and has a meaning. So i wanted to control this using the NavigationSpliViewColumn Binding. This does work forward and backward as long as no NavigationStack was used before. But it stops working when you return to the detail root from a Navigation-Action. I left the Backbutton visible so you can test, that it continues to work, when no Navigation-Action preceeded the switch. Here is my Code: import SwiftUI @Observable final class Compass { var path: [Route] init() { self.path = [] } } enum Route: Hashable { case goal } struct ContentView: View { @Environment(\.horizontalSizeClass) private var hsize @State private var compass: Compass = Compass() @State var column: NavigationSplitViewColumn = .detail var body: some View { NavigationSplitView(preferredCompactColumn: $column) { OverviewScreen(column: $column) } detail: { TodayScreen() .toolbar { ToolbarItem(placement: .topBarLeading) { Button("overview", systemImage: "calendar") { self.column = .sidebar } } } } .environment(compass) .onChange(of: column) { old, new in print("column:", old, "→", new) } } } struct OverviewScreen: View { @Binding var column: NavigationSplitViewColumn @Environment(Compass.self) var compass var body: some View { @Bindable var compass = self.compass ScrollView { Text("Overview") .font(.largeTitle) .fontWeight(.heavy) Button("Switch") { self.column = .detail } } } } struct TodayScreen: View { @Environment(Compass.self) var compass var body: some View { @Bindable var compass = self.compass NavigationStack(path: $compass.path) { ScrollView { Text("Today") .font(.largeTitle) .fontWeight(.heavy) Button("Goals programtically", systemImage: "flag.fill") { self.compass.path = [.goal] } NavigationLink(value: Route.goal) { Label("Goal Link", systemImage: "flag.fill") } } .navigationDestination(for: Route.self, destination: self.screen) } } @ContentBuilder func screen(for route: Route) -> some View { switch route { case .goal: Text("Goals") } } } #Preview { ContentView() }This seems unexpected behaviour. If you ask AI the mental model is NavigationSplitViewColumn represents which column is currently visible for the user. That model would be wrong. Also i can't understand why a NavigationStack has to change the Binding? What am i missing here? Is my idea of the programmatic switch not supported? There are other ways to push from compact to your detail column again, but they all have trade-offs or run into other problems. I would appreciate any help here. AI has not understood this and only does surface level work arounds that fix nothing.
Replies
0
Boosts
0
Views
60
Activity
1w
Accessibility Inspector Element Detection and contrast failures with SwiftUI sheets
I am investigating Accessibility Inspector/XCTest audit results in a SwiftUI app (Target Keeper), without suppressing warnings. Environment: Xcode 27.1 (27A9275), iPadOS 27.0 Simulator, macOS 27.0.1. Debug and Release builds pass. The app uses scalable text and labeled controls. Element Detection / native sheet Main-screen Element Detection passes when the sheet is closed. With a SwiftUI .sheet presented, the iPad audit reports “Potentially inaccessible text”; XCUIAccessibilityAuditIssue.element is nil. Exported Inspector HTML highlights background labels behind/outside the sheet, rather than a foreground control. The separate, data-free SwiftUI application below reproduces the modal-only warning; it links no application modules or persistence. Actual iPad Simulator VoiceOver testing using XCUIDevice.shared.voiceOverService passes forward/backward traversal of the real application's modal controls, without reaching the background labels. This is simulator evidence, not physical-device acceptance. A trial explicit modal container/.isModal trait did not clear the audit and was reverted; no warnings are filtered. Minimal independent app: import SwiftUI @main struct ModalRepro: App { @State private var showing=false private let names=["Vegetables","Protein","Fruit","Dairy","Grains","Healthy Fats"] var body:some Scene {WindowGroup { NavigationStack { VStack(spacing:24) { Button("Nutrition") {}.accessibilityLabel("Choose Page").accessibilityValue("Nutrition") LazyVGrid(columns:[GridItem(.flexible()),GridItem(.flexible()),GridItem(.flexible())],spacing:40) { ForEach(names,id:\.self) { name in Button {} label: { VStack(spacing:10) { Circle().fill(.blue.opacity(0.3)).frame(width:250,height:250) Text(name).font(.system(size:17)).accessibilityHidden(true) } }.buttonStyle(.plain).accessibilityLabel(name) } } Button("Show modal") {showing=true} }.padding(24).frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.black) }.preferredColorScheme(.dark) .sheet(isPresented:$showing) { NavigationStack { VStack {Text("Foreground content");Button("Close") {showing=false}} .frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.black) .navigationTitle("Modal flow") }.preferredColorScheme(.dark) } }} }In an Xcode UI-test target, launch this app, run try app.performAccessibilityAudit(for: [.elementDetection]), tap “Show modal”, then run the same audit again. The first passes and the second fails. No issue handler ignores failures. Contrast / scrolling — separate unresolved finding All four iPhone combined-audit configurations (portrait/landscape, normal/maximum Dynamic Type) pass in the tested picker state. However, iPad maximum-text combined audits and a separate scrolled iPhone maximum-text landscape contrast audit still fail; we are not calling these resolved. In the scrolled iPhone case, XCTest identifies a count label whose frame is entirely above the visible results viewport. Inspector's exported HTML reports 1.37:1 using #252526 versus #000000; its marked screenshot rectangle overlaps the dark search/header area, not visible count text. Visible sampled labels instead have white or light foregrounds on opaque dark fills (e.g. #FFFFFF/#1C1C1E = 17.01:1; #98989F/#1C1C1E = 5.94:1). These are screenshot core-pixel measurements, not proof every warning is false. We compare ordinary text against 4.5:1 and interactive non-text against 3:1. iPad maximum-text warnings have not yet been matched to an exact visible color pair. Original failures and HTML exports are preserved privately. Questions Is Element Detection expected to inspect background elements behind a presented native SwiftUI sheet? Is this a known tool limitation/framework issue, or does the sample need different semantics? What is the supported way to establish modal accessibility boundaries with SwiftUI sheets, and when should .isModal be added explicitly? How should we distinguish a real contrast defect from clipping, occlusion, translucency, or sampling of an offscreen element? Which Inspector export fields/diagnostics should establish the actual sampled text/background pair? What additional evidence/manual and physical-device tests does Apple recommend before qualifying accessibility when the minimal audit reproduction fails but actual VoiceOver modal focus behaves correctly? We are not asking to waive audits or declare full accessibility from a VoiceOver pass alone. I read Apple's audit guidance and modal trait documentation: https://developer.apple.com/documentation/accessibility/performing-accessibility-audits-for-your-app https://developer.apple.com/documentation/swiftui/accessibilitytraits/ismodal https://developer.apple.com/help/app-store-connect/manage-app-accessibility/sufficient-contrast-evaluation-criteria/ Related discussion https://developer.apple.com/forums/thread/823968 recommends Feedback for a different apparent Dynamic Type false positive; it does not answer these modal/contrast questions. A Feedback draft is prepared but not submitted, so I have no Feedback ID yet. No private data, identifiers, credentials, app containers or confidential application source are included.
Replies
1
Boosts
0
Views
861
Activity
1w