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

NavigationSplitView and NavigationStack persistence
Let's say you have a NavigationModel that contains three NavigationPaths, one for each option in the sidebar for a NavigationSplitView. This NavigationModel is created by the App and passed down to the root view for the scene in its environment. Each option has a separate NavigationStack and is passed a binding to the appropriate NavigationPath from the NavigationModel. Is it expected that when the user changes the selection in the sidebar, the NavigationPath for the newly selected view should be erased? This is what's currently happening on macOS 26. It seems like the default action when creating a NavigationStack and passing it a binding to a NavigationPath is to clear that path and start from the root view. Is this normal, intended behaviour or is it a bug? Or, perhaps, an option or modifier I am missing?
3
0
59
57m
Request Review alert is unresponsive in iOS 26.1
Try this simple code: import SwiftUI import StoreKit struct ReviewView: View { @Environment(\.requestReview) var requestReview var body: some View { Button("Leave a review") { requestReview() } } } When the Review Alert shows, the "Not Now" button is disabled for some reason!? It was always tappable in all iOS versions that I remember. And there is no way to opt out, unless the user taps on the stars first. Is it a bug or a feature? Thanks for looking into it!
0
0
106
17h
NSButton + TtGC6AppKit18_NSCoreHostingViewVS_12AppKitButton - Image Alignment Changed/Broken in Minor macOS 26.1 Update
I just updated to macOS 26.1. I have a pure AppKit app (I guess that's not possible anymore but as close to a pure AppKit app as you can get). I use NSButton with the glass bezel style and SF symbol images. It looks like the minor OS update brought layout changes because now some of these buttons are scaling the symbol image much larger than was being done on macOS 26. The image can sometimes draw outside the glass 'bezel'. It looks like using the 'info' symbol in a button results in much larger image scaling than it did on the previous Tahoe for some SF Symbols. With the glass bezel style and a SF Symbol image how am I supposed to consistently make the button look good? With certain symbols I have to use imageScaling NSImageScaleProportionallyUpOrDown and on others I have to use NSImageScaleProportionallyDown. If I'm using a system image shouldn't it just do the right thing? Is trial and error the only way to tell? That's what I was doing before but it seems that the minor 26.1 update changed things. Additionally calling -sizeToFit on a button multiple times can cause it to shrink for example: [button sizeToFit]; // <-- At fitting size // Then later [button sizeToFit]; // Now button is shrunk But if the button is already at its fitting size an additional call later shouldn't make it shrink, it should stay the same size. FB20517174 I see I now inherit SwiftUI, not sure if that has anything to do with this but if I wanted to opt in to fragile layout I wouldn't be using Appkit...
1
0
37
1d
proxy.ScrollTo Unitpoint Y Value uses negative tenths for positioning?
I am using a ScrollViewReader, ScrollView, LazyVStack to organize a list of elements I want to be able to scroll to a specific location so i use elementID in the list and a UnitPoint value. But the y value for unitpoint uses -0.1 to represent 100%. Is this intended behavior, a bug, or am i using something incorrectly? I could not find this in the docs and it was only after debugging I found that proxy.scrollTo(id, UnitPoint(x:0, y:-0.1)) is how you scroll to the end of an item or proxy.scrollTo(id, UnitPoint(x:0, y:-0.05)) to scroll to the center. Am I accessing the scrollTo property wrong or is this just how we use it? Also it seems .bottom is broken due to this aswell (as it uses 1 but the actual value that scrolls to the bottom is -0.1). This seems like unintended behvaior as UnitPoints are supposed to be have values of 0-1 Here is a view which reproduces this behavior struct ScrollTestView: View { @State private var scrollToId: String = "" @State private var scrollToAnchorY: String = "0.0" @State private var scrollProxy: ScrollViewProxy? var body: some View { VStack { HStack(spacing: 12) { TextField("Enter ID (1-30)", text: $scrollToId) .frame(width: 120) .padding(8) .background(Color.gray.opacity(0.1)) .cornerRadius(8) TextField("Anchor Y (0-1)", text: $scrollToAnchorY) .frame(width: 120) .padding(8) .background(Color.gray.opacity(0.1)) .cornerRadius(8) Button { guard let targetId = Int(scrollToId), let anchorY = Double(scrollToAnchorY), let proxy = scrollProxy else { return } let anchorPoint = UnitPoint(x: 0.5, y: anchorY) proxy.scrollTo(targetId, anchor: anchorPoint) } label: { Text("Scroll") .font(.subheadline) .padding(.horizontal, 16) .padding(.vertical, 8) .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } Spacer() } .padding(.horizontal, 16) .padding(.vertical, 8) ScrollViewReader { proxy in ScrollView { LazyVStack { ForEach(1...30, id: \.self) { itemId in VStack { HStack { Text("Item \(itemId)") .font(.title2) .bold() Spacer() } .padding(.vertical, 16) Divider() .background(Color.gray.opacity(0.6)) } .id(itemId) } } .padding() } .onAppear { scrollProxy = proxy } } } } }
0
0
28
1d
Modal presentation of SwiftUI view with TextField leads to frozen UI, missing keyboard and memory leak
Hello, I’m trying to present my custom SwiftUI dialog with text field in UIKit with modalPresentationStyle = .overFullScreen, but it leads to the UI being completely frozen once I select the TextField and memory constantly leaking. The minimal reproducible code is: class ModalBugViewController: UIViewController { var hostingController: UIHostingController<Content>! struct Content: View { @State private var text = "" var body: some View { ZStack { Color.black.opacity(0.5).ignoresSafeArea() VStack { TextField("Test", text: $text) .textFieldStyle(.roundedBorder) .padding() } } } } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .clear hostingController = UIHostingController(rootView: Content()) addChild(hostingController) view.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) hostingController.didMove(toParent: self) } } And then in UIKit source view: let viewController = ModalBugViewController() viewController.modalPresentationStyle = .overFullScreen present(viewController, animated: true) The bug is reproducible on iOS 18 - 26.1, even on the simulator, although on iOS 26 it's in landscape mode only. Is there some workaround for this issue that doesn't involve rewriting the whole dialog in UIKit?
1
0
42
2d
Bar button item showing wrong overlay after pop transition on iOS 26.1
I am experiencing a frustrating bug on iOS 26.1 that makes my app look as if it lacks attention to detail. Basically, when having a NavigationStack, where the root view has a top-right confirmation bar button item, and a pushed detail view does not have any button in the navigation bar trailing position, upon poping back to the root view, the confirmation bar button item shows a white overlay for about 1-2 seconds before finally disappearing and showing the correct appearance. Here is the incorrect appearance right after the pop transition: Eventually after about 1,5 seconds it gets turned back to what it should look like: Here is the full code that you can use to reliably reproduce this issue on iOS 26.1: @State private var path: [Int] = [] var body: some View { NavigationStack(path: $path) { VStack { Text("First View") .font(.title) } .navigationDestination(for: Int.self, destination: { param in Text("Detail View") .font(.title) }) .navigationTitle("First") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .confirmationAction) { Button("Next", role: .confirm, action: { self.path.append(1) }) } } } } } I submitted a Feedback for it: FB21010613 . Is there anything at all I can do on my end to work around this issue?
0
0
31
2d
iOS Dark mode Liquid Glass Tab bar with Reduce Transparency Issue
Hi, I have an iOS app that I’m trying to update with Liquid Glass. In this app, I’m using a tab bar, which works fine with Liquid Glass, but as soon as I enable the “Reduce Transparency” setting in dark mode, I get a strange effect: at launch, the tab bar appears correctly in dark mode, but after scrolling a bit in the view, it eventually switches to light mode 😅 At launch: After a bit of scrolling: I can’t figure out whether this is intended behavior from the framework or not (I don’t have this issue with other apps). I can reproduce it in a project built from scratch, here is the code (don't forget to set dark mode to the device and activate the reduce transparency option in the accessibility menu): struct ContentView: View { var body: some View { TabView { ScrollView { LazyVStack { ForEach(0..<100) { _ in Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello world").foregroundStyle(.primary) } } .padding() } .tabItem { Label("Menu", systemImage: "list.dash") } } } } Do you know if this is expected behavior? Or if there’s something that can be done about it? Thanks,
0
0
48
2d
WebView sits under the notch
Such a simple piece of code: import SwiftUI import WebKit struct ContentView: View { var body: some View { WebView(url: URL(string: "https://www.apple.com")) } } When I run this, the web content shows under the top notch’s safe area, and buttons inside that region aren’t tappable. I tried a bunch of things and the only “fix” that seems to work is .padding(.top, 1), but that leaves a noticeable white strip in non-portrait orientations. What’s the proper way to solve this? Safari handles the safe area correctly and doesn’t render content there.
1
1
83
2d
popoverTip prevents tap recognition
I am noticing an issue that when .popoverTip() is presented, any tap gesture will only dismiss the tip and will not be passed down. This means that if tip is applied to a button, tapping the button will only dismiss the tip but will not trigger the action. Which logically breaks user expectation and defeats the whole point of a popover tip, as user will need to tap twice on the button to activate intended functionality. Button("Settings", systemImage: "gear") { // Will not trigger until tip is dismissed and button is tapped again. showSettings.toggle() } .popoverTip(SettingsTip())
3
0
157
2d
`NewDocumentButton(contentType:)` gives "Content serialization failed, document won't be saved."
I'm working on an iOS document-based app. It uses ReferenceFileDocument and custom creation of documents via DocumentGroupLaunchScene + NewDocumentButton. It works fine when I use the plain NewDocumentButton("Whatever") (without any more arguments), but when I want to perform additional setup via preapreDocumentURL or even just add a contentType it gives such output in the console when I hit it: Content serialization failed, document won't be saved. UTType.replayable is correctly wired up in the plist. It looks like a bug in the SDK, but maybe there is a chance that I'm doing something wrong? Here's a code: import SwiftUI import UniformTypeIdentifiers import Combine @main struct MyApp: App { var body: some Scene { DocumentGroup { Document() } editor: { documentConfiguration in EmptyView() } DocumentGroupLaunchScene("Yoyo") { NewDocumentButton(contentType: .replayable) { return URL(string: "whatever, it doesnt even go there...")! } } } } final class Document: ReferenceFileDocument { static var readableContentTypes: [UTType] { [.replayable] } @Published var x = 0 init() {} init(configuration: ReadConfiguration) throws {} func snapshot(contentType: UTType) throws -> Data { Data() } func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper { .init(regularFileWithContents: snapshot) } } extension UTType { static var replayable: UTType { UTType(exportedAs: "com.whatever.yo") } }
2
0
54
2d
refreshable modifier causes misaligned button tap targets in ScrollView
I have a ScrollView with several Buttons and a .refreshable modifier. As soon as I pull to refresh and the refresh indicator appears, the tap targets no longer match the visible button positions. For example, tapping button A triggers button B’s action, as if the hit-testing region didn’t move along with the content. This only happens while the refresh indicator is shown. Before pulling to refresh, everything is correct and afterwards as well. Tested on iOS 18 and 26 (Xcode 16.4, Xcode 26). Here is a minimal reproducible example: import SwiftUI struct ContentView: View { var body: some View { ScrollView { VStack { Button("Button A") { print("A") } .buttonStyle(.borderedProminent) Button("Button B") { print("B") } .buttonStyle(.borderedProminent) Button("Button C") { print("C") } .buttonStyle(.borderedProminent) Button("Button D") { print("D") } .buttonStyle(.borderedProminent) Button("Button E") { print("E") } .buttonStyle(.borderedProminent) } .frame(maxWidth: .infinity) } .refreshable { try? await Task.sleep(for: .seconds(60)) } } }
1
0
57
2d
SwiftData & CloudKit: Arrays of Codable Structs Causing NSKeyedUnarchiveFromData Error
I have SwiftData models containing arrays of Codable structs that worked fine before adding CloudKit capability. I believe they are the reason I started seeing errors after enabling CloudKit. Example model: @Model final class ProtocolMedication { var times: [SchedulingTime] = [] // SchedulingTime is Codable // other properties... } After enabling CloudKit, I get this error logged to the console: 'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release CloudKit Console shows this times data as "plain text" instead of "bplist" format. Other struct/enum properties display correctly (I think) as "bplist" in CloudKit Console. The local SwiftData storage handled these arrays fine - this issue only appeared with CloudKit integration. What's the recommended approach for storing arrays of Codable structs in SwiftData models that sync with CloudKit?
7
1
341
3d
VZVirtualMachineView in SwiftUI App
I have a working XIB App to run a Linux VM with graphics interface. I am trying to rewrite it in SwiftUI but fall into all sorts of problems when using a combination of a Representable of the VZVirtualMachineView, an associated Coordinator, and @StateObjects. a) The VM display is not updated when running but is displayed if I close the window and reopen it. As the underlying VZVirtualMachineView is created/dismantled many times, there are warnings about negative scanouts that end up crashing the App b) Keyboard focus is not really working. https://developer.apple.com/forums/thread/766014 reports that there is probably a solution making a NSViewControllerReporesentable rather than a VZVirtualMachineViewRepresentable. I think I am fighting against proper SwiftUI lifecycle and would love to have a hint at what shall be the right organization of model and SwiftUI constructs.
3
0
68
4d
SwifUI Performance With Rapid UI Updates
The code below is a test to trigger UI updates every 30 seconds. I'm trying to keep most work off main and only push to main once I have the string (which is cached). Why is updating SwiftUI 30 times per second so expensive? This code causes 10% CPU on my M4 Mac, but comment out the following line: Text(model.timeString) and it's 0% CPU. The reason why I think I have too much work on main is because of this from instruments. But I'm no instruments expert. import SwiftUI import UniformTypeIdentifiers @main struct RapidUIUpdateTestApp: App { var body: some Scene { DocumentGroup(newDocument: RapidUIUpdateTestDocument()) { file in ContentView(document: file.$document) } } } struct ContentView: View { @Binding var document: RapidUIUpdateTestDocument @State private var model = PlayerModel() var body: some View { VStack(spacing: 16) { Text(model.timeString) // only this changes .font(.system(size: 44, weight: .semibold, design: .monospaced)) .transaction { $0.animation = nil } // no implicit animations HStack { Button(model.running ? "Pause" : "Play") { model.running ? model.pause() : model.start() } Button("Reset") { model.seek(0) } Stepper("FPS: \(Int(model.fps))", value: $model.fps, in: 10...120, step: 1) .onChange(of: model.fps) { _, _ in model.applyFPS() } } } .padding() .onAppear { model.start() } .onDisappear { model.stop() } } } @Observable final class PlayerModel { // Publish ONE value to minimize invalidations var timeString: String = "0.000 s" var fps: Double = 30 var running = false private var formatter: NumberFormatter = { let f = NumberFormatter() f.minimumFractionDigits = 3 f.maximumFractionDigits = 3 return f }() @ObservationIgnored private let q = DispatchQueue(label: "tc.timer", qos: .userInteractive) @ObservationIgnored private var timer: DispatchSourceTimer? @ObservationIgnored private var startHost: UInt64 = 0 @ObservationIgnored private var pausedAt: Double = 0 @ObservationIgnored private var lastFrame: Int = -1 // cache timebase once private static let secsPerTick: Double = { var info = mach_timebase_info_data_t() mach_timebase_info(&info) return Double(info.numer) / Double(info.denom) / 1_000_000_000.0 }() func start() { guard timer == nil else { running = true; return } let desiredUIFPS: Double = 30 // or 60, 24, etc. let periodNs = UInt64(1_000_000_000 / desiredUIFPS) running = true startHost = mach_absolute_time() let t = DispatchSource.makeTimerSource(queue: q) // ~30 fps, with leeway to let the kernel coalesce wakeups t.schedule( deadline: .now(), repeating: .nanoseconds(Int(periodNs)), // 33_333_333 ns ≈ 30 fps leeway: .milliseconds(30) // allow coalescing ) t.setEventHandler { [weak self] in self?.tick() } timer = t t.resume() } func pause() { guard running else { return } pausedAt = now() running = false } func stop() { timer?.cancel() timer = nil running = false pausedAt = 0 lastFrame = -1 } func seek(_ seconds: Double) { pausedAt = max(0, seconds) startHost = mach_absolute_time() lastFrame = -1 // force next UI update } func applyFPS() { lastFrame = -1 } // next tick will refresh string // MARK: - Tick on background queue private func tick() { let s = now() let str = formatter.string(from: s as NSNumber) ?? String(format: "%.3f", s) let display = "\(str) s" DispatchQueue.main.async { [weak self] in self?.timeString = display } } private func now() -> Double { guard running else { return pausedAt } let delta = mach_absolute_time() &- startHost return pausedAt + Double(delta) * Self.secsPerTick } } nonisolated struct RapidUIUpdateTestDocument: FileDocument { var text: String init(text: String = "Hello, world!") { self.text = text } static let readableContentTypes = [ UTType(importedAs: "com.example.plain-text") ] init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents, let string = String(data: data, encoding: .utf8) else { throw CocoaError(.fileReadCorruptFile) } text = string } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { let data = text.data(using: .utf8)! return .init(regularFileWithContents: data) } }
1
0
125
4d
MapKit detailAccessoryView buttons not working on macOS Tahoe
Hi, I have been working with an implementation of MapKit which show custom annotations with a detailCalloutAccessoryView built using SwiftUI. This has been working fine for many years, but starting with macOS Tahoe, somehow the SwiftUI buttons in this view have stopped being tappable. I have reproduced the issue in the code below ... same code works fine in macOS14 and macOS15 now doesn't work correctly in macOS26: import Cocoa import MapKit import SwiftUI class ViewController: NSViewController { private var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() setupMapView() } private func setupMapView() { // Create and configure the map view mapView = MKMapView() mapView.translatesAutoresizingMaskIntoConstraints = false mapView.delegate = self view.addSubview(mapView) // Pin the map to all edges of the view NSLayoutConstraint.activate([ mapView.topAnchor.constraint(equalTo: view.topAnchor), mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // Create an annotation for San Francisco let sanFranciscoCoordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) let annotation = MKPointAnnotation() annotation.coordinate = sanFranciscoCoordinate annotation.title = "San Francisco" annotation.subtitle = "The City by the Bay" // Add the annotation to the map mapView.addAnnotation(annotation) // Center the map on San Francisco let region = MKCoordinateRegion(center: sanFranciscoCoordinate, latitudinalMeters: 5000, longitudinalMeters: 5000) mapView.setRegion(region, animated: false) } } // MARK: - MKMapViewDelegate extension ViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { let identifier = "CustomAnnotation" var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView if annotationView == nil { annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier) annotationView?.canShowCallout = true // Create the SwiftUI view for the callout let calloutView = CalloutContentView() let hostingView = NSHostingView(rootView: calloutView) hostingView.frame = NSRect(x: 0, y: 0, width: 200, height: 100) // Set the SwiftUI view as the detail callout accessory annotationView?.detailCalloutAccessoryView = hostingView } else { annotationView?.annotation = annotation } return annotationView } } // MARK: - SwiftUI Callout View struct CalloutContentView: View { var body: some View { VStack(spacing: 12) { Text("Welcome to San Francisco!") .font(.headline) .multilineTextAlignment(.center) HStack(spacing: 12) { Button(action: { print("Directions button tapped") }) { Label("Directions", systemImage: "arrow.triangle.turn.up.right.circle.fill") .font(.caption) } .buttonStyle(.borderedProminent) Button(action: { print("Info button tapped") }) { Label("Info", systemImage: "info.circle.fill") .font(.caption) } .buttonStyle(.bordered) } } .padding() .frame(width: 200) } } I've looked at other problems with Map and onTap handlers not getting called, but this is a SwiftUI view inside an AppKit MapKit annotation's callout view. Any idea of how to handle this?
2
0
89
4d
EditButton selection gets cleared when confirmationDialog appears in SwiftUI List
I'm experiencing an issue where my List selection (using EditButton) gets cleared when a confirmationDialog is presented, making it impossible to delete the selected items. Environment: Xcode 16.0.1 Swift 5 iOS 18 (targeting iOS 17+) Issue Description: When I select items in a List using EditButton and tap a delete button that shows a confirmationDialog, the selection is cleared as soon as the dialog appears. This prevents me from accessing the selected items to delete them. Code: State variables: @State var itemsSelection = Set<Item>() @State private var showDeleteConfirmation = false List with selection: List(currentItems, id: \.self, selection: $itemsSelection) { item in NavigationLink(value: item) { ItemListView(item: item) } } .navigationDestination(for: Item.self) { item in ItemViewDetail(item: item) } .toolbar { ToolbarItem(placement: .primaryAction) { EditButton() } } Delete button with confirmation: Button { if itemsSelection.count > 1 { showDeleteConfirmation = true } else { deleteItemsSelected() } } label: { Image(systemName: "trash.fill") .font(.system(size: 12)) .foregroundStyle(Color.red) } .padding(8) .confirmationDialog( "Delete?", isPresented: $showDeleteConfirmation, titleVisibility: .visible ) { Button("Delete", role: .destructive) { deleteItemsSelected() } Button("Cancel", role: .cancel) {} } message: { Text("Going to delete: \(itemsSelection.count) items?") } Expected Behavior: The selected items should remain selected when the confirmationDialog appears, allowing me to delete them after confirmation. Actual Behavior: As soon as showDeleteConfirmation becomes true and the dialog appears, itemsSelection becomes empty (count = 0), making it impossible to delete the selected items. What I've Tried: Moving the confirmationDialog to different view levels Checking if this is related to the NavigationLink interaction Has anyone encountered this issue? Is there a workaround to preserve the selection when showing a confirmation dialog?
1
0
80
4d
App Not Appearing in "Available Apps" List in Watch App
I’ve developed an Apple Watch extension for an existing iOS app. When I run the app on the watch via Xcode using the simulator, everything works fine. However, when I try to install it on my iPhone, the Watch app doesn’t show it in the "Available Apps" list, so I can't install it on the watch. The Apple Watch is connected to my iPhone, and I can see other apps available for installation without any issues. I also created a brand new project with watchOS support to troubleshoot, but the same problem occurred. Any ideas on how to resolve this?
2
0
533
4d
ScrollView + LazyVStack + dynamic height views cause scroll glitches on iOS 26
I’m seeing unexpected scroll behavior when embedding a LazyVStack with dynamically sized views inside a ScrollView. Everything works fine when the item height is fixed (e.g. colored squares), but when I switch to text views with variable height, the scroll position jumps and glitches—especially when the keyboard appears or disappears. This only happens on iOS 26, it works fine on iOS 18. Working version struct Model: Identifiable { let id = UUID() } struct ModernScrollView: View { @State private var models: [Model] = [] @State private var scrollPositionID: String? @State private var text: String = "" @FocusState private var isFocused // MARK: - View var body: some View { scrollView .safeAreaInset(edge: .bottom) { controls } .task { reset() } } // MARK: - Subviews private var scrollView: some View { ScrollView { LazyVStack { ForEach(models) { model in SquareView(color: Color(from: model.id)) .id(model.id.uuidString) } } .scrollTargetLayout() } .scrollPosition(id: $scrollPositionID) .scrollDismissesKeyboard(.interactively) .defaultScrollAnchor(.bottom) .onTapGesture { isFocused = false } } private var controls: some View { VStack { HStack { Button("Add to top") { models.insert(contentsOf: makeModels(3), at: 0) } Button("Add to bottom") { models.append(contentsOf: makeModels(3)) } Button("Reset") { reset() } } HStack { Button { scrollPositionID = models.first?.id.uuidString } label: { Image(systemName: "arrow.up") } Button { scrollPositionID = models.last?.id.uuidString } label: { Image(systemName: "arrow.down") } } TextField("Input", text: $text) .padding() .background(.ultraThinMaterial, in: .capsule) .focused($isFocused) .padding(.horizontal) } .padding(.vertical) .buttonStyle(.bordered) .background(.regularMaterial) } // MARK: - Private private func makeModels(_ count: Int) -> [Model] { (0..<count).map { _ in Model() } } private func reset() { models = makeModels(3) } } // MARK: - Color+UUID private extension Color { init(from uuid: UUID) { let hash = uuid.uuidString.hashValue let r = Double((hash & 0xFF0000) >> 16) / 255.0 let g = Double((hash & 0x00FF00) >> 8) / 255.0 let b = Double(hash & 0x0000FF) / 255.0 self.init(red: abs(r), green: abs(g), blue: abs(b)) } } Not working version When I replace the square view with a text view that generates random multiline text: struct Model: Identifiable { let id = UUID() let text = generateRandomText(range: 1...5) // MARK: - Utils private static func generateRandomText(range: ClosedRange<Int>) -> String { var result = "" for _ in 0..<Int.random(in: range) { if let sentence = sentences.randomElement() { result += sentence } } return result.trimmingCharacters(in: .whitespaces) } private static let sentences = [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." ] } and use it like this: ForEach(models) { model in Text(model.text) .padding() .multilineTextAlignment(.leading) .background(Color(from: model.id)) .id(model.id.uuidString) } Then on iOS 26, opening the keyboard makes the scroll position jump unpredictably. It is more visible if you play with the app, but I could not upload a video here. Environment Xcode 26.0.1 - Simulators and devices on iOS 26.0 - 18.0 Questions Is there any known change in ScrollView / scrollPosition(id:) behavior on iOS 26 related to dynamic height content? Am I missing something in the layout setup that makes this layout unstable with variable-height cells? Is there a workaround or recommended approach for keeping scroll position stable when keyboard appears?
6
0
270
4d