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
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
The WatchOS app and view lifecycles for WatchKit and SwiftUI are documented in https://developer.apple.com/documentation/watchkit/working-with-the-watchos-app-life-cycle and https://developer.apple.com/documentation/swiftui/migrating-to-the-swiftui-life-cycle.
WatchOS 26 appears to change the app & view lifecycle from the behavior in WatchOS 11, and no longer matches the documented lifecycles.
On WatchOS 11, with a @WKApplicationDelegateAdaptor set, the following sequence of events would occur on app launch:
WKApplicationDelegate applicationDidFinishLaunching in WKApplicationState .inactive.
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .inactive.
View .onAppear @Environment(.scenePhase) .inactive
App onChange(of: @Environment(.scenePhase)): .active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: .@Environment(.scenePhase) .active
In WatchOS 26, this is now:
WKApplicationDelegate applicationDidFinishLaunching in WKApplicationState .inactive.
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .inactive.
App onChange(of: @Environment(.scenePhase)): .active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
View .onAppear @Environment(.scenePhase) .active
When resuming from the background in WatchOS 11:
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .background.
App onReceive(.willEnterForegroundNotification): WKApplicationState(rawValue: 2)
View .onChange of: .@Environment(.scenePhase) inactive
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: .@Environment(.scenePhase) active
The resume from background process in WatchOS 26 is baffling and seems like it must be a bug:
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillEnterForeground in WKApplicationState .background.
App onReceive(.willEnterForegroundNotification): WKApplicationState(rawValue: 2)
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) active
App onChange(of: @Environment(.scenePhase)): inactive
WKApplicationDelegate applicationWillResignActive in WKApplicationState .active.
App onReceive(.willResignActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) inactive
App onChange(of: @Environment(.scenePhase)): active
WKApplicationDelegate applicationDidBecomeActive in WKApplicationState .active.
App onReceive(.didBecomeActiveNotification): WKApplicationState(rawValue: 0)
View .onChange of: @Environment(.scenePhase) active
The app becomes active, then inactive, then active again.
The issues with these undocumented changes are:
It is undocumented.
If you relied on the previous process, this change can break your app.
A view no longer receives .onChange of: .@Environment(.scenePhase) .active state change during the launch process.
This bizarre applicationWillEnterForeground - applicationDidBecomeActive - applicationWillResignActive - applicationDidBecomeActive process on app resume does not match the documented process and is just...strange.
Is this new process what is intended? Is it a bug? Can an Apple engineer explain this new App resume from background process and why the View is created slightly later in the App launch process, so it does not receive the .onChange of @Environment(.scenePhase) message?
In contrast, the iOS 26 app lifecycle has not changed, and the iOS 18/26 app lifecycle closely follows the watchOS 11 app lifecycle (or watchOS 11 closely mimics iOS 18/26 with the exception that watchOS does not have a SceneDelegate).
Hello
I'm trying to use a TabView inside of the Sidebar in a NavigationSplitView.
I'm wanting to use .listStyle(.sidebar) in order to get the Liquid Glass effect.
However I can not find a way to remove the background of the TabView without changing the behavior of the TabView itself to paging with .tabViewStyle(.page)
NavigationSplitView {
TabView(
selection: .constant("List")
) {
Tab(value: "List") {
List {
Text("List")
}
}
}
} detail: {
}
Note: I wanting change the background of the TabView container itself. Not the TabBar.
When following the official SwiftUI Tutorial "Landmarks", I should be able to Cmd-Ctrl-Click an element of a preview in Selectable mode to view a list of options. But Xcode shows only one option (Embed). Furthermore, if I try to type anything the popover breaks completely and grows in length infinitely. I am not sure whether this is a bug, or I missed something when installing Xcode, or both
I'm being faced with an issue when using SwiftUI's WebView on iOS 26. In many websites, the top/bottom content is unaccessible due to being under the app's toolbars. It feels like the WebView doesn't really understand the safe areas where it's being shown, because the content should start right below the navigation bar, and only when the user scrolls down, the content should move under the bar (but it's always reachable if the users scroll back up).
Here's a demo of the issue:
Here's a 'fix' by ensuring that the content of the WebView never leaves its bounds. But as you can see, it feels out of place on iOS 26 (would be fine on previous OS versions if you had a fully opaque toolbar):
Code:
struct ContentView: View {
var body: some View {
NavigationStack {
WebView(url: URL(string: "https://apple.com")).toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Top content covered, unaccessible.") {}
}
}
}
}
}
Does anyone know if there's a way to fix it using some sort of view modifier combination or it's just broken as-is?
I've being playing aground with long press gesture in scroll view and noticed gesture(LongPressGesture()) doesn't seem to work with scroll view's scrolling which doesn't seem to be the intended behavior to me.
Take the following example: the blue rectangle is modified with onLongPressGesture and the red rectangle is modified with LongPressGesture (_EndedGesture<LongPressGesture> to be specific).
ScrollView {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
.onLongPressGesture {
print("onLongPressGesture performed")
} onPressingChanged: { _ in
print("onLongPressGesture changed")
}
.overlay {
Text("onLongPressGesture")
}
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.gesture(LongPressGesture()
.onEnded { _ in
print("gesture ended")
})
.overlay {
Text("gesture(LongPressGesture)")
}
}
If you start scrolling from either of the rectangles (that is, start scrolling with your finger on either of the rectangles), the ScrollView will scroll.
However, if LongPressGesture is modified with either onChanged or .updating, ScrollView won't respond to scroll if the scroll is started from red rectangle. Even setting the maximumDistance to 0 won't help. As for its counter part onLongPressGesture, even though onPressingChanged to onLongPressGesture, scrolling still works if it's started from onLongPressGesture modified view.
ScrollView {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
.onLongPressGesture {
print("onLongPressGesture performed")
} onPressingChanged: { _ in
print("onLongPressGesture changed")
}
.overlay {
Text("onLongPressGesture")
}
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.gesture(LongPressGesture(maximumDistance: 0)
// scroll from the red rectangle won't work if I add either `updating` or `onChanged` but I put both here just to demonstrate
// you will need to add `@GestureState private var isPressing = false` to your view body
.updating($isPressing) { value, state, transaction in
state = value
print("gesture updating")
}
.onChanged { value in
print("gesture changed")
}
.onEnded { _ in
print("gesture ended")
})
.overlay {
Text("gesture(LongPressGesture)")
}
}
This doesn't seem right to me. I would expect the view modified by LongPressGesture(), no matter if the gesture has onChanged or updating, should be able to start scroll in a scroll view, just like onLongPressGesture.
I observed this behavior in a physical device running iOS 26.1, and I do not know the behavior on other versions.
Hi there! I'm having this issue with my main windows. I'm having a big space on top of that without any logic explanation (at least for my poor knowledge).
Using the code below I'm getting this Windows layout:
Does anybody have any guidance on how to get out that extra space at the beginning?
Thanks a lot!
import SwiftUI
import SwiftData
#if os(macOS)
import AppKit
#endif
// Helper to access and control NSWindow for size/position persistence
#if os(macOS)
struct WindowAccessor: NSViewRepresentable {
let onWindow: (NSWindow) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
if let window = view.window {
onWindow(window)
}
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
DispatchQueue.main.async {
if let window = nsView.window {
onWindow(window)
}
}
}
}
#endif
@main
struct KaraoPartyApp: App {
@StateObject private var songsModel = SongsModel()
@Environment(\.openWindow) private var openWindow
var body: some Scene {
Group {
WindowGroup {
#if os(macOS)
WindowAccessor { window in
window.minSize = NSSize(width: 900, height: 700)
// Configure window to eliminate title bar space
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
}
#endif
ContentView()
.environmentObject(songsModel)
}
.windowToolbarStyle(.unifiedCompact)
.windowResizability(.contentSize)
.defaultSize(width: 1200, height: 900)
.windowStyle(.titleBar)
#if os(macOS)
.windowToolbarStyle(.unified)
#endif
WindowGroup("CDG Viewer", id: "cdg-viewer", for: CDGWindowParams.self) { $params in
if let params = params {
ZStack {
#if os(macOS)
WindowAccessor { window in
window.minSize = NSSize(width: 600, height: 400)
// Restore window frame if available
let key = "cdgWindowFrame"
let defaults = UserDefaults.standard
if let frameString = defaults.string(forKey: key) {
let frame = NSRectFromString(frameString)
if window.frame != frame {
window.setFrame(frame, display: true)
}
} else {
// Open CDG window offset from main window
if let mainWindow = NSApp.windows.first {
let mainFrame = mainWindow.frame
let offsetFrame = NSRect(x: mainFrame.origin.x + 60, y: mainFrame.origin.y - 60, width: 800, height: 600)
window.setFrame(offsetFrame, display: true)
}
}
// Observe frame changes and save
NotificationCenter.default.addObserver(forName: NSWindow.didMoveNotification, object: window, queue: .main) { _ in
let frameStr = NSStringFromRect(window.frame)
defaults.set(frameStr, forKey: key)
}
NotificationCenter.default.addObserver(forName: NSWindow.didEndLiveResizeNotification, object: window, queue: .main) { _ in
let frameStr = NSStringFromRect(window.frame)
defaults.set(frameStr, forKey: key)
}
}
#endif
CDGView(
cancion: Cancion(
title: params.title ?? "",
artist: params.artist ?? "",
album: "",
genre: "",
year: "",
bpm: "",
playCount: 0,
folderPath: params.cdgURL.deletingLastPathComponent().path,
trackName: params.cdgURL.deletingPathExtension().lastPathComponent + ".mp3"
),
backgroundType: params.backgroundType,
videoURL: params.videoURL,
cdfContent: params.cdfContent.flatMap { String(data: $0, encoding: .utf8) },
artist: params.artist,
title: params.title
)
}
} else {
Text("No se pudo abrir el archivo CDG.")
}
}
.windowResizability(.contentSize)
.defaultSize(width: 800, height: 600)
WindowGroup("Metadata Editor", id: "metadata-editor") {
MetadataEditorView()
.environmentObject(songsModel)
}
.windowResizability(.contentSize)
.defaultSize(width: 400, height: 400)
WindowGroup("Canciones DB", id: "canciones-db") {
CancionesDBView()
}
.windowResizability(.contentSize)
.defaultSize(width: 800, height: 500)
WindowGroup("Importar canciones desde carpeta", id: "folder-song-importer") {
FolderSongImporterView()
}
.windowResizability(.contentSize)
.defaultSize(width: 500, height: 350)
}
.modelContainer(for: Cancion.self)
// Add menu command under Edit
.commands {
CommandGroup(replacing: .pasteboard) { }
CommandMenu("Edit") {
Button("Actualizar Metadatos") {
openWindow(id: "metadata-editor")
}
.keyboardShortcut(",", modifiers: [.command, .shift])
}
CommandMenu("Base de Datos") {
Button("Ver Base de Datos de Canciones") {
openWindow(id: "canciones-db")
}
.keyboardShortcut("D", modifiers: [.command, .shift])
}
}
}
init() {
print("\n==============================")
print("[KaraoParty] Nueva ejecución iniciada: \(Date())")
print("==============================\n")
}
}
Apparently now with iOS 26.1 if you have .tabViewBottomAccessory { } you get a pill shape floater all the time. That was not like that in 26.0.
Hi everyone,
I’m building a full-screen Map (MapKit + SwiftUI) with persistent top/bottom chrome (menu buttons on top, session stats + map controls on bottom). I have three working implementations and I’d like guidance on which pattern Apple recommends long-term (gesture correctness, safe areas, Dynamic Island/home indicator, and future compatibility).
Version 1 — overlay(alignment:) on Map
Idea: Draw chrome using .overlay(alignment:) directly on the map and manage padding manually.
Map(position: $viewModel.previewMapCameraPosition, scope: mapScope) {
UserAnnotation {
UserLocationCourseMarkerView(angle: viewModel.userCourse - mapHeading)
}
}
.mapStyle(viewModel.mapType.mapStyle)
.mapControls {
MapUserLocationButton().mapControlVisibility(.hidden)
MapCompass().mapControlVisibility(.hidden)
MapPitchToggle().mapControlVisibility(.hidden)
MapScaleView().mapControlVisibility(.hidden)
}
.overlay(alignment: .top) { mapMenu } // manual padding inside
.overlay(alignment: .bottom) { bottomChrome } // manual padding inside
Version 2 — ZStack + .safeAreaPadding
Idea: Place the map at the back, then lay out top/bottom chrome in a VStack inside a ZStack, and use .safeAreaPadding(.all) so content respects safe areas.
ZStack(alignment: .top) {
Map(...).ignoresSafeArea()
VStack {
mapMenu
Spacer()
bottomChrome
}
.safeAreaPadding(.all)
}
Version 3 — .safeAreaInset on the Map
Idea: Make the map full-bleed and then reserve top/bottom space with safeAreaInset, letting SwiftUI manage insets
Map(...).ignoresSafeArea()
.mapStyle(viewModel.mapType.mapStyle)
.mapControls {
MapUserLocationButton().mapControlVisibility(.hidden)
MapCompass().mapControlVisibility(.hidden)
MapPitchToggle().mapControlVisibility(.hidden)
MapScaleView().mapControlVisibility(.hidden)
}
.safeAreaInset(edge: .top) { mapMenu } // manual padding inside
.safeAreaInset(edge: .bottom) { bottomChrome } // manual padding inside
Question
I noticed:
Safe-area / padding behavior
– Version 2 requires the least extra padding and seems to create a small but partial safe-area spacing automatically.
– Version 3 still needs roughly the same manual padding as Version 1, even though it uses safeAreaInset. Why doesn’t safeAreaInset fully handle that spacing?
Rotation crash (Metal)
When using Version 3 (safeAreaInset + ignoresSafeArea), rotating the device portrait↔landscape several times triggers a
Metal crash:
failed assertion 'The following Metal object is being destroyed while still required… CAMetalLayer Display Drawable'
The same crash can happen with Version 1, though less often. I haven’t tested it much with Version 2.
Is this a known issue or race condition between Map’s internal Metal rendering and view layout changes?
Expected behavior
What’s the intended or supported interaction between safeAreaInset, safeAreaPadding, and overlay when embedding persistent chrome inside a SwiftUI Map?
Should safeAreaInset normally remove the need for manual padding, or is that by design?
Any view that is content for the tabViewBottomAccessory API fails to retain its state as of the last couple of 26.1 betas (and RC). The loss of state happens (at least) when the currently selected tab is switched (filed as FB20901325).
Here's code to reproduce the issue:
struct ContentView: View {
@State private var selectedTab = TabSelection.one
enum TabSelection: Hashable {
case one, two
}
var body: some View {
TabView(selection: $selectedTab) {
Tab("One", systemImage: "1.circle", value: .one) {
BugExplanationView()
}
Tab("Two", systemImage: "2.circle", value: .two) {
BugExplanationView()
}
}
.tabViewBottomAccessory {
AccessoryView()
}
}
}
struct AccessoryView: View {
@State private var counter = 0 // This guy's state gets lost (as of iOS 26.1)
var body: some View {
Stepper("Counter: \(counter)", value: $counter)
.padding(.horizontal)
}
}
struct BugExplanationView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("(1) Manipulate the counter state")
Text("(2) Then switch tabs")
Text("BUG: The counter state gets unexpectedly reset!")
}
.multilineTextAlignment(.leading)
}
}
}
Hi,
How to enable multitouch on ARView?
Touch functions (touchesBegan, touchesMoved, ...) seem to only handle one touch at a time. In order to handle multiple touches at a time with ARView, I have to either:
Use SwiftUI .simultaneousGesture on top of an ARView representable
Position a UIView on top of ARView to capture touches and do hit testing by passing a reference to ARView
Expected behavior:
ARView should capture all touches via touchesBegan/Moved/Ended/Cancelled.
Here is what I tried, on iOS 26.1 and macOS 26.1:
ARView Multitouch
The setup below is a minimal ARView presented by SwiftUI, with touch events handled inside ARView. Multitouch doesn't work with this setup.
Note that multitouch wouldn't work either if the ARView is presented with a UIViewController instead of SwiftUI.
import RealityKit
import SwiftUI
struct ARViewMultiTouchView: View {
var body: some View {
ZStack {
ARViewMultiTouchRepresentable()
.ignoresSafeArea()
}
}
}
#Preview {
ARViewMultiTouchView()
}
// MARK: Representable ARView
struct ARViewMultiTouchRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARViewMultiTouch(frame: .zero)
let anchor = AnchorEntity()
arView.scene.addAnchor(anchor)
let boxWidth: Float = 0.4
let boxMaterial = SimpleMaterial(color: .red, isMetallic: false)
let box = ModelEntity(mesh: .generateBox(size: boxWidth), materials: [boxMaterial])
box.name = "Box"
box.components.set(CollisionComponent(shapes: [.generateBox(width: boxWidth, height: boxWidth, depth: boxWidth)]))
anchor.addChild(box)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) { }
}
// MARK: ARView
class ARViewMultiTouch: ARView {
required init(frame: CGRect) {
super.init(frame: frame)
/// Enable multi-touch
isMultipleTouchEnabled = true
cameraMode = .nonAR
automaticallyConfigureSession = false
environment.background = .color(.gray)
/// Disable gesture recognizers to not conflict with touch events
/// But it doesn't fix the issue
gestureRecognizers?.forEach { $0.isEnabled = false }
}
required dynamic init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
/// # Problem
/// This should print for every new touch, up to 5 simultaneously on an iPhone (multi-touch)
/// But it only fires for one touch at a time (single-touch)
print("Touch began at: \(touch.location(in: self))")
}
}
}
Multitouch with an Overlay
This setup works, but it doesn't seem right. There must be a solution to make ARView handle multi touch directly, right?
import SwiftUI
import RealityKit
struct MultiTouchOverlayView: View {
var body: some View {
ZStack {
MultiTouchOverlayRepresentable()
.ignoresSafeArea()
Text("Multi touch with overlay view")
.font(.system(size: 24, weight: .medium))
.foregroundStyle(.white)
.offset(CGSize(width: 0, height: -150))
}
}
}
#Preview {
MultiTouchOverlayView()
}
// MARK: Representable Container
struct MultiTouchOverlayRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
/// The view that SwiftUI will present
let container = UIView()
/// ARView
let arView = ARView(frame: container.bounds)
arView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
arView.cameraMode = .nonAR
arView.automaticallyConfigureSession = false
arView.environment.background = .color(.gray)
let anchor = AnchorEntity()
arView.scene.addAnchor(anchor)
let boxWidth: Float = 0.4
let boxMaterial = SimpleMaterial(color: .red, isMetallic: false)
let box = ModelEntity(mesh: .generateBox(size: boxWidth), materials: [boxMaterial])
box.name = "Box"
box.components.set(CollisionComponent(shapes: [.generateBox(width: boxWidth, height: boxWidth, depth: boxWidth)]))
anchor.addChild(box)
/// The view that will capture touches
let touchOverlay = TouchOverlayView(frame: container.bounds)
touchOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
touchOverlay.backgroundColor = .clear
/// Pass an arView reference to the overlay for hit testing
touchOverlay.arView = arView
/// Add views to the container.
/// ARView goes in first, at the bottom.
container.addSubview(arView)
/// TouchOverlay goes in last, on top.
container.addSubview(touchOverlay)
return container
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
// MARK: Touch Overlay View
/// A UIView to handle multi-touch on top of ARView
class TouchOverlayView: UIView {
weak var arView: ARView?
override init(frame: CGRect) {
super.init(frame: frame)
isMultipleTouchEnabled = true
isUserInteractionEnabled = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let totalTouches = event?.allTouches?.count ?? touches.count
print("--- Touches Began --- (New: \(touches.count), Total: \(totalTouches))")
for touch in touches {
let location = touch.location(in: self)
/// Hit testing.
/// ARView and Touch View must be of the same size
if let arView = arView {
let entity = arView.entity(at: location)
if let entity = entity {
print("Touched entity: \(entity.name)")
} else {
print("Touched: none")
}
}
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
let totalTouches = event?.allTouches?.count ?? touches.count
print("--- Touches Cancelled --- (Cancelled: \(touches.count), Total: \(totalTouches))")
}
}
The document-based SwiftUI example app (https://developer.apple.com/documentation/swiftui/building-a-document-based-app-with-swiftui) doesn't specify a launch image.
It would seem per the HIG that the "pinkJungle" background in the app would be a decent candidate for a launch image, since it will be in the background when the document browser comes up.
However when specifying it as the UIImageName, it is not aligned the same as the background image. I'm having trouble figuring out how it should be aligned to match the image. The launch image seems to be scaled up a bit over scaledToFill.
I suppose a launch storyboard might make this more explicit, but I still should be able to do it without one.
This is the image when displayed as the launch image:
and this is how it's rendered in the background right before the document browser comes up:
List {
Text("ITEM 1")
.onHover(perform: { hovering in
debugPrint("hovering: ", hovering)
})
.help("ITEM 1")
Text("ITEM 2")
.onHover(perform: { hovering in
debugPrint("hovering: ", hovering)
})
.help("ITEM 2")
Text("ITEM 3")
.onHover(perform: { hovering in
debugPrint("hovering: ", hovering)
})
.help("ITEM 3")
}
.fixedSize(horizontal: false, vertical: true)
.frame(maxHeight: 200)
}
Hello everyone!!!
Considering the snippet above, seems like the onHover action, including help modifiers, doesn't work for the elements of a List, on macOS Tahoe.
The situation changes using a ScrollView embedding a LazyVStack, or disabling Liquid Glass from the info plist, so my guess is that the new Liquid Glass style has something to do with this issue though I didn't find any clue about it.
Does anyone have any idea?
Maybe there's a layer above that doesn't allow to trigger the onHover modifier?
Thanks in advance for your help!
I’m seeing a strange visual bug in iOS 26 when building chat-style UIs that use an inverted ScrollView or List (via .rotationEffect(.radians(.pi)) and .scaleEffect(x: -1, y: 1)) to anchor messages at the bottom.
When I add .ignoresSafeArea() to let the chat bleed behind the navigation bar - the new navigation bar fade (that subtle top-to-bottom gradient Apple added in iOS 26) behaves incorrectly. Instead of fading from the top of the screen toward the nav bar, it fades upward from the bottom of the view, effectively covering the entire screen with the gradient.
This only happens when the view is inverted.
If I remove .ignoresSafeArea(), the fade looks correct — but then my chat no longer extends behind the nav bar.
It looks like the fade effect is being applied in the transformed coordinate space of the inverted scroll view rather than in visual screen space. I haven’t found a reliable workaround besides disabling the fade (which isn’t really possible).
Has anyone found a proper solution or a modifier that prevents the fade inversion when using flipped ScrollViews?
Would love to know if Apple is aware of this or if there’s a hidden API for disabling that fade effect.
I have made a report about this in Feedback Assistant: FB20540755
I'm encountering unexpected behavior with @Binding in a UIViewRepresentable's Coordinator. The same Coordinator instance returns different values depending on how the property is accessed.
Environment:
iOS 17+ / Xcode 15+
SwiftUI with UIViewRepresentable
Issue:
When I access @Binding var test inside the Coordinator:
✅ Via self.test in Coordinator methods: Returns the updated value
❌ Via context.coordinator.test in updateUIView: Returns the stale/initial value
Both access the same Coordinator instance (verified by memory address), yet return different values.
Minimal Reproducible Example:
struct ContentView: View {
@State private var test: [Int] = [1, 2, 3, 4, 5]
var body: some View {
VStack {
TestRepresentable(test: $test)
Text("State: \(test.description)")
}
}
}
struct TestRepresentable: UIViewRepresentable {
@Binding var test: [Int]
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addTarget(
context.coordinator,
action: #selector(Coordinator.buttonTapped),
for: .touchUpInside
)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {
// Log coordinator instance address
let coordAddr = String(describing: Unmanaged.passUnretained(context.coordinator).toOpaque())
print("[updateUIView] Coordinator address: \(coordAddr)")
// Log values
print("[updateUIView] self.test: \(self.test)")
print("[updateUIView] context.coordinator.test: \(context.coordinator.test)")
// These should be the same but they're not!
// self.test shows updated value
// context.coordinator.test shows stale value
}
func makeCoordinator() -> Coordinator {
Coordinator(test: $test)
}
class Coordinator: NSObject {
@Binding var test: [Int]
var idx: Int = 0
init(test: Binding<[Int]>) {
_test = test
}
@objc func buttonTapped() {
idx += 1
if idx < test.count {
test[idx] += 5
}
// Log coordinator instance address
let selfAddr = String(describing: Unmanaged.passUnretained(self).toOpaque())
print("[buttonTapped] Coordinator address: \(selfAddr)")
// Log value - this shows the UPDATED value
print("[buttonTapped] self.test: \(test)")
}
}
}
Actual Output:
[Initial]
[updateUIView] Coordinator address: 0x600001234567
[updateUIView] self.test: [1, 2, 3, 4, 5]
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5]
[After first tap]
[buttonTapped] Coordinator address: 0x600001234567
[buttonTapped] self.test: [1, 7, 3, 4, 5] ✅ Updated!
[updateUIView] Coordinator address: 0x600001234567 ← Same instance
[updateUIView] self.test: [1, 7, 3, 4, 5] ✅ Updated!
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5] ❌ Stale!
[After second tap]
[buttonTapped] Coordinator address: 0x600001234567
[buttonTapped] self.test: [1, 7, 8, 4, 5] ✅ Updated!
[updateUIView] Coordinator address: 0x600001234567 ← Same instance
[updateUIView] self.test: [1, 7, 8, 4, 5] ✅ Updated!
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5] ❌ Still stale!
Questions:
Why does context.coordinator.test return a stale value when it's the same Coordinator instance?
Is this intended behavior or a bug?
What's the correct pattern to access Coordinator's @Binding properties in updateUIView?
Workaround Found:
Using self.test instead of context.coordinator.test in updateUIView works, but I'd like to understand why accessing the same property through context yields different results.
Related:
I've seen suggestions to update coordinator.parent = self in updateUIView, but this doesn't explain why the same object's property returns different values.
Binding documentation states it's "a pair of get and set closures," but there's no explanation of how Context affects closure behavior.
Any insights would be greatly appreciated!
P.S - https://stackoverflow.com/questions/69552418/why-does-a-binding-in-uiviewrepresentables-coordinator-have-a-constant-read-valu
This is the link that I found out online with same problem that I have but not answered well.
Hello, I've a question about performance when trying to render lots of items coming from SwiftData via a @Query on a SwiftUI List. Here's my setup:
// Item.swift:
@Model final class Item: Identifiable {
var timestamp: Date
var isOptionA: Bool
init() {
self.timestamp = Date()
self.isOptionA = Bool.random()
}
}
// Menu.swift
enum Menu: String, CaseIterable, Hashable, Identifiable {
var id: String { rawValue }
case optionA
case optionB
case all
var predicate: Predicate<Item> {
switch self {
case .optionA: return #Predicate { $0.isOptionA }
case .optionB: return #Predicate { !$0.isOptionA }
case .all: return #Predicate { _ in true }
}
}
}
// SlowData.swift
@main
struct SlowDataApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([Item.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
return try! ModelContainer(for: schema, configurations: [modelConfiguration])
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
// ContentView.swift
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@State var selection: Menu? = .optionA
var body: some View {
NavigationSplitView {
List(Menu.allCases, selection: $selection) { menu in
Text(menu.rawValue).tag(menu)
}
} detail: {
DemoListView(selectedMenu: $selection)
}.onAppear {
// Do this just once
// (0..<15_000).forEach { index in
// let item = Item()
// modelContext.insert(item)
// }
}
}
}
// DemoListView.swift
struct DemoListView: View {
@Binding var selectedMenu: Menu?
@Query private var items: [Item]
init(selectedMenu: Binding<Menu?>) {
self._selectedMenu = selectedMenu
self._items = Query(filter: selectedMenu.wrappedValue?.predicate,
sort: \.timestamp)
}
var body: some View {
// Option 1: touching `items` = slow!
List(items) { item in
Text(item.timestamp.description)
}
// Option 2: Not touching `items` = fast!
// List {
// Text("Not accessing `items` here")
// }
.navigationTitle(selectedMenu?.rawValue ?? "N/A")
}
}
When I use Option 1 on DemoListView, there's a noticeable delay on the navigation. If I use Option 2, there's none. This happens both on Debug builds and Release builds, just FYI because on Xcode 16 Debug builds seem to be slower than expected: https://indieweb.social/@curtclifton/113273571392595819
I've profiled it and the SwiftData fetches seem blazing fast, the Hang occurs when accessing the items property from the List. Is there anything I'm overlooking or it's just as fast as it can be right now?
We have an UIViewController called InfoPlayerViewController. Its main subview is from a child view controller backed by SwiftUI via UIHostingController. The InfoPlayerViewController conforms to UIViewControllerTransitioningDelegate. The animation controller for dismissing is DismissPlayerAnimationController. It runs UIKit keyframe animations via UIViewPropertyAnimator. When the keyframe animation is executed there’s an occasional crash for end users in production. It only happens on iOS 26.
FB Radar: FB20871547
An example crash is below.
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Reason: +[_SwiftUILayerDelegate _screen]: unrecognized selector sent to class 0x20c95da08
Termination Reason: SIGNAL 6 Abort trap: 6
Triggered by Thread: 0
Last Exception Backtrace:
0 CoreFoundation 0x1a23828c8 __exceptionPreprocess + 164 (NSException.m:249)
1 libobjc.A.dylib 0x19f2f97c4 objc_exception_throw + 88 (objc-exception.mm:356)
2 CoreFoundation 0x1a241e6cc +[NSObject(NSObject) doesNotRecognizeSelector:] + 364 (NSObject.m:158)
3 CoreFoundation 0x1a22ff4f8 ___forwarding___ + 1472 (NSForwarding.m:3616)
4 CoreFoundation 0x1a23073a0 _CF_forwarding_prep_0 + 96 (:-1)
5 UIKitCore 0x1a948e880 __35-[UIViewKeyframeAnimationState pop]_block_invoke + 300 (UIView.m:2973)
6 CoreFoundation 0x1a22cb170 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 (NSDictionaryHelpers.m:10)
7 CoreFoundation 0x1a245d7cc -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 288 (NSDictionaryM.m:271)
8 UIKitCore 0x1a948e6bc -[UIViewKeyframeAnimationState pop] + 376 (UIView.m:2955)
9 UIKitCore 0x1a7bc40e8 +[UIViewAnimationState popAnimationState] + 60 (UIView.m:1250)
10 UIKitCore 0x1a94acc44 +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 684 (UIView.m:17669)
11 UIKitCore 0x1a94ae334 +[UIView(UIViewKeyframeAnimations) animateKeyframesWithDuration:delay:options:animations:completion:] + 224 (UIView.m:17945)
12 MyApp 0x102c78dec static UIView.animateNestedKeyframe(withRelativeStartTime:relativeDuration:animations:) + 208 (UIView+AnimateNestedKeyframe.swift:10)
13 MyApp 0x102aef3c0 closure #1 in DismissPlayerAnimationController.slideDownBelowTabBarTransitionAnimator(using:) + 156 (DismissPlayerAnimationController.swift:229)
14 MyApp 0x102a2d3d4 <deduplicated_symbol> + 28
15 UIKitCore 0x1a7d5ae5c -[UIViewPropertyAnimator _runAnimations] + 172 (UIViewPropertyAnimator.m:2123)
16 UIKitCore 0x1a83e1594 __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke_3 + 92 (UIViewPropertyAnimator.m:3557)
17 UIKitCore 0x1a83e1464 __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke + 96 (UIViewPropertyAnimator.m:3547)
18 UIKitCore 0x1a83e1518 __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke_2 + 144 (UIViewPropertyAnimator.m:3553)
19 UIKitCore 0x1a83e0e64 -[UIViewPropertyAnimator _setupAnimationTracking:] + 100 (UIViewPropertyAnimator.m:3510)
20 UIKitCore 0x1a83e1264 -[UIViewPropertyAnimator startAnimationAsPaused:] + 728 (UIViewPropertyAnimator.m:3610)
21 UIKitCore 0x1a83de42c -[UIViewPropertyAnimator pauseAnimation] + 68 (UIViewPropertyAnimator.m:2753)
22 UIKitCore 0x1a87d5328 -[UIPercentDrivenInteractiveTransition _startInterruptibleTransition:] + 244 (UIViewControllerTransitioning.m:982)
23 UIKitCore 0x1a87d5514 -[UIPercentDrivenInteractiveTransition startInteractiveTransition:] + 184 (UIViewControllerTransitioning.m:1012)
24 UIKitCore 0x1a7c7931c ___UIViewControllerTransitioningRunCustomTransitionWithRequest_block_invoke_3 + 152 (UIViewControllerTransitioning.m:1579)
25 UIKitCore 0x1a892aefc +[UIKeyboardSceneDelegate _pinInputViewsForKeyboardSceneDelegate:onBehalfOfResponder:duringBlock:] + 96 (UIKeyboardSceneDelegate.m:3518)
26 UIKitCore 0x1a7c79238 ___UIViewControllerTransitioningRunCustomTransitionWithRequest_block_invoke_2 + 236 (UIViewControllerTransitioning.m:1571)
27 UIKitCore 0x1a94ab4b8 +[UIView(Animation) _setAlongsideAnimations:toRunByEndOfBlock:animated:] + 188 (UIView.m:17089)
28 UIKitCore 0x1a7c79070 _UIViewControllerTransitioningRunCustomTransitionWithRequest + 556 (UIViewControllerTransitioning.m:1560)
29 UIKitCore 0x1a86cb7cc __77-[UIPresentationController runTransitionForCurrentStateAnimated:handoffData:]_block_invoke_3 + 1784 (UIPresentationController.m:1504)
30 UIKitCore 0x1a7c43888 -[_UIAfterCACommitBlock run] + 72 (_UIAfterCACommitQueue.m:137)
31 UIKitCore 0x1a7c437c0 -[_UIAfterCACommitQueue flush] + 168 (_UIAfterCACommitQueue.m:228)
32 UIKitCore 0x1a7c436d0 _runAfterCACommitDeferredBlocks + 260 (UIApplication.m:3297)
33 UIKitCore 0x1a7c43c34 _cleanUpAfterCAFlushAndRunDeferredBlocks + 80 (UIApplication.m:3275)
34 UIKitCore 0x1a7c1f104 _UIApplicationFlushCATransaction + 72 (UIApplication.m:3338)
35 UIKitCore 0x1a7c1f024 __setupUpdateSequence_block_invoke_2 + 352 (_UIUpdateScheduler.m:1634)
36 UIKitCore 0x1a7c2cee8 _UIUpdateSequenceRunNext + 128 (_UIUpdateSequence.mm:189)
37 UIKitCore 0x1a7c2c378 schedulerStepScheduledMainSectionContinue + 60 (_UIUpdateScheduler.m:1185)
38 UpdateCycle 0x28c58f5f8 UC::DriverCore::continueProcessing() + 84 (UCDriver.cc:288)
39 CoreFoundation 0x1a2323230 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 (CFRunLoop.c:2021)
40 CoreFoundation 0x1a23231a4 __CFRunLoopDoSource0 + 172 (CFRunLoop.c:2065)
41 CoreFoundation 0x1a2300c6c __CFRunLoopDoSources0 + 232 (CFRunLoop.c:2102)
42 CoreFoundation 0x1a22d68b0 __CFRunLoopRun + 820 (CFRunLoop.c:2983)
43 CoreFoundation 0x1a22d5c44 _CFRunLoopRunSpecificWithOptions + 532 (CFRunLoop.c:3462)
44 GraphicsServices 0x2416a2498 GSEventRunModal + 120 (GSEvent.c:2049)
45 UIKitCore 0x1a7c50ddc -[UIApplication _run] + 792 (UIApplication.m:3899)
46 UIKitCore 0x1a7bf5b0c UIApplicationMain + 336 (UIApplication.m:5574)
// ...
In the Keynote/Numbers/Pages apps for iPhone, there is a Help menu item for presenting a Help modal view.
The view has a button for showing the hierarchical table of contents, and a field for searching the Help pages.
How is such a Help modal view built through SwiftUI ?
In which format is the Help content stored ?
Hi everyone,
I’m currently experimenting an issue with TipKit’s popoverTip in combination with a TabView.
On iOS 26, the popover tip keeps showing every time I switch tabs, even though it should only appear once. The same code behaves as expected on older iOS versions (for example, iOS 17 or 18), where the tip is displayed only once, as documented.
Here’s a simplified example reproducing the issue:
import TipKit
struct ContentView: View {
init() {
try? Tips.configure()
}
var body: some View {
TabView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
.popoverTip(HomeTip(), arrowEdge: .bottom)
Text("Hello, world!")
}
.tabItem {
Image(systemName: "house")
Text("Home")
}
.tag(0)
NavigationStack {
List {
Label("Reset Data store", systemImage: "gear")
.onTapGesture {
try? Tips.resetDatastore()
}
}
.navigationTitle("Explore")
}
.tabItem {
Image(systemName: "sparkles")
Text("Settings")
}
.tag(1)
}
}
private struct HomeTip: Tip {
let id = "HomeTip"
let title = Text("Test Tool Tip")
}
}
Expected behavior:
The tip appears once and does not reappear when switching between tabs.
Observed behavior on iOS 26:
The tip keeps reappearing every time the user switches back to the tab.
I'm experiencing a critical issue with SwiftData custom migrations where objects created during migration appear to be inserted successfully but aren't persisted or found by queries after migration completes. The migration logs show objects being created, but subsequent queries return zero results.
I'm migrating from schema version V2 to V2_5, which involves:
Renaming Person class to GroupData
Keeping the same data structure but changing the class name while keeping the old class.
Using a custom migration stage to copy data from old to new schema
Below is an extract of my two schema and migration plan:
Environment:
Xcode 16.0,
iOS 18.0,
Swift 6.0
SchemaV2
enum LinkMapV2: VersionedSchema {
static let versionIdentifier: Schema.Version = .init(2, 0, 0)
static var models: [any PersistentModel.Type] {
[AnnotationData.self, Person.self, History.self]
}
@Model
final class Person {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var statue: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.statue = status
self.annotationId = annotationId
self.number = number
}
}
}
Schema V2_5
static let versionIdentifier: Schema.Version = .init(2, 5, 0)
static var models: [any PersistentModel.Type] {
[AnnotationData.self, Person.self, GroupData.self, History.self]
}
// Keep the old Person model for migration
@Model
final class Person {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var statue: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.statue = status
self.annotationId = annotationId
self.number = number
}
}
// Add the new GroupData model that mirrors Person
@Model
final class GroupData {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var status: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.status = status
self.annotationId = annotationId
self.number = number
}
}
}
Migration Plan
static let migrationV2toV2_5 = MigrationStage.custom(
fromVersion: LinkMapV2.self,
toVersion: LinkMapV2_5.self,
willMigrate: { context in
do {
let persons = try context.fetch(FetchDescriptor<LinkMapV2.Person>())
print("=== MIGRATION STARTED ===")
print("Found \(persons.count) Person objects to migrate")
guard !persons.isEmpty else {
print("No Person data requires migration")
return
}
for person in persons {
print("Migrating Person: '\(person.name)' with ID: \(person.id)")
let newGroup = LinkMapV2_5.GroupData(
id: person.id, // Keep the same ID
name: person.name,
photo: person.photo,
requirement: person.requirement,
status: person.statue,
annotationId: person.annotationId,
number: person.number
)
context.insert(newGroup)
print("Inserted new GroupData: '\(newGroup.name)'")
// Don't delete the old Person yet to avoid issues
// context.delete(person)
}
try context.save()
print("=== MIGRATION COMPLETED ===")
print("Successfully migrated \(persons.count) Person objects to GroupData")
} catch {
print("=== MIGRATION ERROR ===")
print("Migration failed with error: \(error)")
}
},
didMigrate: { context in
do {
// Verify migration in didMigrate phase
let groups = try context.fetch(FetchDescriptor<LinkMapV2_5.GroupData>())
let oldPersons = try context.fetch(FetchDescriptor<LinkMapV2_5.Person>())
print("=== MIGRATION VERIFICATION ===")
print("New GroupData count: \(groups.count)")
print("Remaining Person count: \(oldPersons.count)")
// Now delete the old Person objects
for person in oldPersons {
context.delete(person)
}
if !oldPersons.isEmpty {
try context.save()
print("Cleaned up \(oldPersons.count) old Person objects")
}
// Print all migrated groups for debugging
for group in groups {
print("Migrated Group: '\(group.name)', Status: \(group.status), Number: \(group.number)")
}
} catch {
print("Migration verification error: \(error)")
}
}
)
And I've attached console output below:
Console Output