I am trying to perform swiftUI instrumentation on my ios app. whenever i hit the rocord button, the app launches on target device and closes with the error:
Failed to start the recording: Failed starting ktrace session.
How do i resolve this please?
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Posts under SwiftUI tag
200 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I've reset and reinstalled Xcode several times, reset finder files for Xcode, but the options in the Swift tutorial aren't showing up
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...
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
}
}
}
}
}
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?
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?
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.
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())
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")
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Tags:
Files and Storage
File Provider
SwiftUI
Uniform Type Identifiers
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?
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.
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)
}
}
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?
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?
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?
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?
Hi Everyone,
I’m encountering a layout issue in SwiftUI starting with iOS 26 / Xcode 26 (also reproduced on iPadOS 26). I would like to see whether anyone else has seen the same behaviour, and if there’s any known workaround or fix from Apple.
Reproduction
Minimal code:
struct ContentView: View {
@State private var inputText: String = ""
var body: some View {
TextField("", text: $inputText)
.font(.system(size: 14, weight: .semibold))
.background(Capsule().foregroundStyle(.gray))
}
}
Environment:
Xcode: 26.1 (17B55)
Devices / Simulators: iOS 26.0, iOS 26.0.1, iOS 26.1 on iPhone 17 Pro Max; iPadOS 26 on iPad.
OS: iOS 26 / iPadOS 26
Observed behaviour
When the TextField is tapped for the first time (focus enters), its height suddenly decreases compared to the unfocused state.
After this initial focus-tap, subsequent unfocus/focus cycles preserve the height (i.e., height remains “normal” and stable).
Prior to iOS 26 (i.e., in older iOS versions) I did not observe this behaviour — same code worked fine.
I have not explicitly set .frame(height:) or extra padding, and I’m relying on the default intrinsic height + the Capsule background.
Work-arounds
Adding .frame(height: X) (fixed height) removes the issue (height stable).
Thanks in advance for any feedback!
[Submitted as FB20978913]
When using .navigationTransition(.zoom) with a fullScreenCover, deleting the source item from the destination view causes a brief black flash during the dismiss animation. This is only visible in Light mode.
REPRO STEPS
Build and run the sample code below.
Set the device to Light mode.
Tap any row to open its detail view.
In the detail view, tap Delete.
Watch the dismiss animation as the list updates.
EXPECTED
The zoom transition should return smoothly to the list with no dark or black flash.
ACTUAL
A visible black flash appears over the deleted row during the collapse animation. It starts black, shortens, and fades out in sync with the row-collapse motion. The flash lasts about five frames and is consistently visible in Light mode.
NOTES
Occurs only when deleting from the presented detail view.
Does not occur when deleting directly from the list.
Does not occur or is not visible in Dark mode.
Reproducible on both simulator and device.
Removing .navigationTransition(.zoom) or using .sheet instead of .fullScreenCover avoids the issue.
SYSTEM INFO
Version 26.1 (17B55)
iOS 26.1
Devices: iPhone 17 Pro simulator, iPhone 13 Pro hardware
Appearance: Light
Reproducible 100% of the time
SAMPLE CODE
import SwiftUI
struct ContentView: View {
@State private var items = (0..<20).map { Item(id: $0, title: "Item \($0)") }
@State private var selectedItem: Item?
@Namespace private var ns
var body: some View {
NavigationStack {
List {
ForEach(items) { item in
Button {
selectedItem = item
} label: {
HStack {
Text(item.title)
Spacer()
}
.padding(.vertical, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.matchedTransitionSource(id: item.id, in: ns)
.swipeActions {
Button(role: .destructive) {
delete(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
.listStyle(.plain)
.navigationTitle("Row Delete Issue")
.navigationSubtitle("In Light mode, tap item then tap Delete to see black flash")
.fullScreenCover(item: $selectedItem) { item in
DetailView(item: item, ns: ns) {
delete(item)
selectedItem = nil
}
}
}
}
private func delete(_ item: Item) {
withAnimation {
items.removeAll { $0.id == item.id }
}
}
}
struct DetailView: View {
let item: Item
let ns: Namespace.ID
let onDelete: () -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
VStack(spacing: 30) {
Text(item.title)
Button("Delete", role: .destructive, action: onDelete)
}
.navigationTitle("Detail")
.toolbar {
Button("Close") { dismiss() }
}
}
.navigationTransition(.zoom(sourceID: item.id, in: ns))
}
}
struct Item: Identifiable, Hashable {
let id: Int
let title: String
}
SCREEN RECORDING
What is the correct way to implement scrolling in a looong list that uses ScrollView and LazyVstack
Imagine I have some api that returns a longs list of comments with replies
The basic usecase is to scroll to the bottom(to the last comment) Most of the time this works fine
But, imagine some of the comments have many replies like 35 or more (or even 300)
User expands replies for the first post, then presses scroll to bottom.
The scrollbar reaches the bottom and I see the blank screen.
Sometimes the scrollbar may jump for a while before lazyvstack finishes loading or until I manually scroll up a bit or all the way up and down
What should I do in this case? Is this the swiftui performance problem that has no cure?
Abstract example:
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(comments) { comment in
CommentView(comment: comment)
.id("comment-\(comment.id)")
}
}
}
}
struct CommentView: View {
let comment: Comment
@State var isExpanded = false
var body: some View {
VStack {
Text(comment.text)
if isExpanded {
RepliesView(replies: comment.replies) // 35-300+ replies
}
}
}
}
...
scroll
proxy.scrollTo("comment-\(lastComment.id)", anchor: .bottom)
sometimes when i add an info plist from info in project to project navigator. sometimes it dissapears from project navigator.how can i bring it back to show the info.plist on the priject navigator? I want to keep it there