iOS 26 SwiftUI .glassEffect() renders dark/gray on physical device - TestFlight doesn't fix it
.glassEffect() renders as dark/muddy gray on my physical iPhone instead of the light frosted glass like Apple Music's tab bar.
What I've confirmed:
Same code works perfectly on iOS Simulator
Apple Music and other Apple apps show correct Liquid Glass on same device
Brand new Xcode project with just .glassEffect() also renders dark
TestFlight build (App Store signing) has the SAME problem - still dark/gray
This rules out developer signing vs App Store signing as the cause
What I've tried:
Clean build, delete derived data, reinstall app
Completely reinstalled Xcode
All accessibility settings correct (Reduce Transparency OFF, Liquid Glass set to Clear)
Disabled Metal diagnostics
Debug and Release builds
Added window configuration for Extended sRGB/P3 color space
Added AppDelegate with configureWindowForLiquidGlass()
Tried .preferredColorScheme(nil)
Tried background animation to force "live" rendering
Environment:
iPhone 17 Pro, iOS 26.3
Xcode 26.2
macOS 26.3
The question:
Why does .glassEffect() work for Apple's apps but not third party apps, even with App Store signing via TestFlight? What am I missing?
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 am implementing an AI chat application and aiming to achieve ChatGPT-like behavior. Specifically, when a new message is sent, the ScrollView should automatically scroll to the top to display the latest message.
I am currently using the scrollTo method for this purpose, but the behavior is inconsistent—sometimes it works as expected, and other times it does not. I’ve noticed that this issue has been reported in multiple places, which suggests it may be a known SwiftUI limitation.
I’d like to know:
Has this issue been fixed in recent SwiftUI versions, or does it still persist?
If it still exists, is there a reliable solution or workaround that anyone can recommend?
Hi there,
I have an SwiftUI app that opens a user selected audio file (wave). For each audio file an additional file exists containing events that were extracted from the audio file. This additional file has the same filename and uses the extension bcCalls. I load the audio file using FileImporter view modifier and within access the audio file with a security scoped bookmark. That works well. After loading the audio I create a CallsSidecar NSFilePresenter with the url of the audio file. I make the presenter known to the NSFileCoordinator and upon this add it to the FileCoordinator. This fails with NSFileSandboxingRequestRelatedItemExtension: Failed to issue extension for; Error Domain=NSPOSIXErrorDomain Code=3 "No such process"
My Info.plist contains an entry for the document with NSIsRelatedItemType set to YES
I am using this kind of FilePresenter code in various live apps developed some years ago. Now when starting from scratch on a fresh macOS26 system with most current Xcode I do not manage to get it running. Any ideas welcome!
Here is the code:
struct ContentView: View {
@State private var sonaImg: CGImage?
@State private var calls: Array<CallMeasurements> = Array()
@State private var soundContainer: BatSoundContainer?
@State private var importPresented: Bool = false
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
if self.sonaImg != nil {
Image(self.sonaImg!, scale: 1.0, orientation: .left, label: Text("Sonagram"))
}
if !(self.calls.isEmpty) {
List(calls) {aCall in
Text("\(aCall.callNumber)")
}
}
Button("Load sound file") {
importPresented.toggle()
}
}
.fileImporter(isPresented: $importPresented, allowedContentTypes: [.audio, UTType(filenameExtension: "raw")!], onCompletion: { result in
switch result {
case .success(let url):
let gotAccess = url.startAccessingSecurityScopedResource()
if !gotAccess { return }
if let soundContainer = try? BatSoundContainer(with: url) {
self.soundContainer = soundContainer
self.sonaImg = soundContainer.overviewSonagram(expectedWidth: 800)
let callsSidecar = CallsSidecar(withSoundURL: url)
let data = callsSidecar.readData()
print(data)
}
url.stopAccessingSecurityScopedResource()
case .failure(let error):
// handle error
print(error)
}
})
.padding()
}
}
The file presenter according to the WWDC 19 example:
class CallsSidecar: NSObject, NSFilePresenter {
lazy var presentedItemOperationQueue = OperationQueue.main
var primaryPresentedItemURL: URL?
var presentedItemURL: URL?
init(withSoundURL audioURL: URL) {
primaryPresentedItemURL = audioURL
presentedItemURL = audioURL.deletingPathExtension().appendingPathExtension("bcCalls")
}
func readData() -> Data? {
var data: Data?
var error: NSError?
NSFileCoordinator.addFilePresenter(self)
let coordinator = NSFileCoordinator.init(filePresenter: self)
NSFileCoordinator.addFilePresenter(self)
coordinator.coordinate(readingItemAt: presentedItemURL!, options: [], error: &error) {
url in
data = try! Data.init(contentsOf: url)
}
return data
}
}
And from Info.plist
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>bcCalls</string>
</array>
<key>CFBundleTypeName</key>
<string>bcCalls document</string>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.property-list</string>
</array>
<key>LSTypeIsPackage</key>
<false/>
<key>NSIsRelatedItemType</key>
<true/>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>wav</string>
<string>wave</string>
</array>
<key>CFBundleTypeName</key>
<string>Windows wave</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.microsoft.waveform-audio</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string></string>
</dict>
</array>
Note that BatSoundContainer is a custom class for loading audio of various undocumented formats as well as wave, Flac etc. and this is working well displaying a sonogram of the audio.
Thx, Volker
This demonstrates an issue with SwiftUI's WebView on iPadOS.
To repro, testing on iPad Simulator OS 26.2, macOS 26.2, Xcode 26.2.
Download and unzip this project: https://drive.google.com/file/d/1z3MobjDf_RvvOtriXtinXvrJ7rGHwZRs/view?usp=share_link
Set up Signing and Run the swiftui-webview App target on simulator (I'm using iPad Pro 13-inch (M5 simulator)
Tap/click the fullscreen [ ] button in the bottom left corner of the webpage.
Tap/click the 'X' button in the top left, to exit fullscreen.
Result: The WebView exits fullscreen, but there is no content loaded, just a white background.
It's also now not possible to visit other URLs - the WebView appears to be unresponsive and not repaint.
This does not appear to affect macOS 26.2, just iPadOS.
When you use .navigationTransition(.zoom(sourceID: "placeholder", in: placehoder)) for navigation animation, going back using the swipe gesture is still very buggy on IOS26. I know it has been mentioned in other places like here: https://developer.apple.com/forums/thread/796805?answerId=856846022#856846022 but nothing seems to have been done to fix this issue.
Here is a video showing the bug comparing when the back button is used vs swipe to go back: https://imgur.com/a/JgEusRH
I wish there was a way to at least disable the swipe back gesture until this bug is fixed.
Scenario
A SwiftUI view has an overlay with alignment: .top, the content uses .alignmentGuide(.top) {} to adjust the placement.
Issue
When the content of the overlay is in an if-block, the alignment guide is not adjusted.
Example code
The example shows 2 views.
Not working example, where the content is an if-block.
Working example, where the content is not in an if-block
Screenshot: https://github.com/simonnickel/FB15248296-SwiftUIAlignmentGuideInOverlayConditional/blob/main/screenshot.png
Tested on
- Xcode Version 16.0 RC (16A242) on iOS 18.0
Code
// Not working
.overlay(alignment: .top) {
if true { // This line causes .alignmentGuide() to fail
Text("Test")
.alignmentGuide(.top, computeValue: { dimension in
dimension[.bottom]
})
}
}
// Working
.overlay(alignment: .top) {
Text("Test")
.alignmentGuide(.top, computeValue: { dimension in
dimension[.bottom]
})
}
Also created a Feedback: FB15248296
Example Project is here: https://github.com/simonnickel/FB15248296-SwiftUIAlignmentGuideInOverlayConditional/tree/main
I am new to swiftui and have a very small project I am working on to practice passing data between views. I have this error on a form "Trailing closure passed to parameter of type 'FormStyleConfiguration' that does not accept a closure"
If I comment the first section in the form then I get three errors in the ForEach. If anyone can help explain what is going on and what steps I could take to determine where the problem is coming from I would appreciate the help.
This is the model I created:
import Observation
import SwiftUI
@Model
final class Client {
var id = UUID()
var name: String
var location: String
var selectedJob: Person
init(id: UUID = UUID(), name: String, location: String, selectedJob: Person) {
self.id = id
self.name = name
self.location = location
self.selectedJob = selectedJob
}
}
extension Client {
enum Person: String, CaseIterable, Codable {
case homeOwner = "Home Owner"
case contractor = "Contractor"
case designer = "Designer"
}
}
@Model
class Enclosure {
var id = UUID()
var room: String = ""
var unitType: String = ""
init(id: UUID = UUID(), room: String, unitType: String) {
self.id = id
self.room = room
self.unitType = unitType
}
}
This is the detail view where the error is happening:
import SwiftData
import SwiftUI
struct DetailView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@Query(sort: \Enclosure.room, order: .forward, animation: .default) private var enclosures: [Enclosure]
@State private var showingAddEnclosure = false
@State private var showingAddMirror = false
@State private var name: String = ""
@State private var location: String = ""
@State private var selectedJob = Client.Person.homeOwner
@State var clients: Client
var body: some View {
NavigationStack {
Form {
Section("Details") {
TextField("Full Name", text: Client.$name)
TextField("Location", text: Client.location)
Picker("Job Type", selection: $selectedJob) {
ForEach(Client.Person.allCases, id: \.self) { selected in
Text(selected.rawValue).tag(selected)
}
}
}
Section("Enclosures") {
List {
ForEach($clients.enclosures) { enclosure in
NavigationLink(destination: EnclosureDetailView()) {
VStack {
Text(enclosure.room)
Text(enclosure.unitType)
}
}
.swipeActions {
Button("Delete", role: .destructive) {
modelContext.delete(enclosure)
}
}
}
}
}
}
.navigationTitle("Project Details")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Button {
showingAddEnclosure.toggle()
} label: {
Text("Add Enclosure")
}
Button {
showingAddMirror.toggle()
} label: {
Text("Add Mirror")
}
} label: {
Label("Add", systemImage: "ellipsis.circle")
}
}
}
.sheet(isPresented: $showingAddEnclosure) {
EnclosureView()
}
.sheet(isPresented: $showingAddMirror) {
EnclosureView()
}
}
}
}
I have a UIHostingController on which I have set:
hostingController.sizingOptions = [.intrinsicContentSize]
The size of my SwiftUI content changes with animation (I update a @Published property on an ObservableObject inside a withAnimation block). However, I notice that my hostingController.view just jumps to the new frame without animating the change.
Question: how can I animate the frame changes in UIHostingController that are caused by sizingOptions = [.intrinsicContentSize]
I have a custom UI to display date (for user birthday) and want if the user presses each part of the label , the date selection is displayed, the current issue is , when I try to reduce the DatePicker opacity or set the colorMultipli to clear color, it still clickable on the date area, while my object is a fullWidth object, how can I fix it?
This is the code:
VStack(alignment: .leading, spacing: 8) {
Text("Birthday")
.font(.SFProText.font(type: .medium, size: 13))
.foregroundStyle(Color(uiColor: .label))
HStack(alignment: .center) {
Text(userProfileAccountInfoviewModel.birthday.getShortFormat(format: "dd MMM yyyy"))
.font(.SFProText.font(type: .medium, size: 13))
.foregroundColor(Color(uiColor: .label))
.padding(.horizontal, 13)
Spacer()
}
.frame(height: 44)
.contentShape(Rectangle())
.overlay {
HStack {
DatePicker(selection: $userProfileAccountInfoviewModel.birthday, displayedComponents: .date) {}
.labelsHidden()
.colorMultiply(.clear)
.background(Color.clear)
.foregroundStyle(Color.baseBackgroundSecondary)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
// .opacity(0.011)
Spacer()
}
}
.background(Color.baseBackgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
Hey all,
I found a weird behaviour with the searchable component. I created a custom bottom nav bar (because I have custom design in my app) to switch between screens.
On one screen I display a List component with the searchable component. Whenever I enter the search screen the first time, the searchable component is displayed at the bottom.
This is wrong. It should be displayed at the top under the navigationTitle. When I enter the screen a second time, everything is correct.
This behaviour can be reproduced on all iOS 26 versions on the simulator and on a physical device with debug and release build.
On iOS 18 everything works fine.
Steps to reproduce:
Cold start of the app
Click on Search TabBarIcon (searchable wrong location)
Click on Home TabBarIcon
Click on Search TabBarIcon (searchable correct location)
Simple code example:
import SwiftUI
struct ContentView: View {
@State var selectedTab: Page = Page.main
var body: some View {
NavigationStack {
ZStack {
VStack {
switch selectedTab {
case .main:
MainView()
case .search:
SearchView()
}
}
VStack {
Spacer()
VStack(spacing: 0) {
HStack(spacing: 0) {
TabBarIcon(iconName: "house", selected: selectedTab == .main, displayName: "Home")
.onTapGesture {
selectedTab = .main
}
TabBarIcon(iconName: "magnifyingglass", selected: selectedTab == .search, displayName: "Search")
.onTapGesture {
selectedTab = .search
}
}
.frame(maxWidth: .infinity)
.frame(height: 55)
.background(Color.gray)
}
.ignoresSafeArea(.all, edges: .bottom)
}
}
}
}
}
struct TabBarIcon: View {
let iconName: String
let selected: Bool
let displayName: String
var body: some View {
ZStack {
VStack {
Image(systemName: iconName)
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.foregroundColor(Color.black)
.frame(width: 22, height: 22)
Text(displayName)
.font(Font.system(size: 10))
}
}
.frame(maxWidth: .infinity)
}
}
enum Page {
case main
case search
}
struct MainView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
.navigationTitle("Home")
}
}
struct SearchView: View {
@State private var searchText = ""
let items = [
"Apple",
"Banana",
"Pear",
"Strawberry",
"Orange",
"Peach",
"Grape",
"Mango"
]
var filteredItems: [String] {
if searchText.isEmpty {
return items
} else {
return items.filter {
$0.localizedCaseInsensitiveContains(searchText)
}
}
}
var body: some View {
List(filteredItems, id: \.self) { item in
Text(item)
}
.navigationTitle("Fruits")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search")
}
}
Description
I'm developing a custom keyboard extension using UIInputViewController and need to set a specific height of 268 points. The keyboard functions correctly, but there's a visible flicker and resize animation during launch that I cannot eliminate.
The Problem
When the keyboard launches, iOS provides incorrect heights before settling on the correct one. At launch, the view starts at 0×0. Around 295ms later, iOS sets the frame to 440×956 which is full screen height and wrong. Around 373ms, iOS changes it to 440×452 which is still wrong. Finally around 390ms, iOS settles at 440×268 which matches our constraint.
This causes visible flicker as the view resizes three times rapidly. The keyboard appears to shrink from full screen down to the correct height, and users can clearly see this animation happening.
What I've Tried
I've tried adding a height constraint on self.view which gives me the correct height but causes the visible flicker.
I created a custom UIInputView subclass and overrode intrinsicContentSize to return my desired height. iOS completely ignores this and gives random heights like 471pt, 680pt, or 956pt instead.
I set allowsSelfSizing to true on my UIInputView subclass. iOS ignores this property.
I set preferredContentSize on the view controller. iOS ignores this as well.
I tried adding the constraint in viewDidAppear instead of viewDidLoad, thinking iOS might have settled by then. It still causes flicker.
I overrode the frame and bounds setters on my UIInputView to clamp the height to my desired value. iOS bypasses these overrides somehow.
I overrode layoutSubviews to force the correct height after the super call. iOS still applies its own height.
Specific Question
What is the correct API or technique to specify a keyboard extension's height that iOS will respect immediately upon launch, without triggering the resize animation sequence?
Other third-party keyboards like Grammarly and SwiftKey appear to have solved this problem. Their keyboards appear at the correct height without any visible flicker. How do they achieve this?
Expected Outcome
The keyboard should appear at 268pt height on the first frame with no visible resize animation.
Steps to Reproduce
Create a new iOS App project in Xcode and add a Keyboard Extension target. In KeyboardViewController.swift, add a height constraint in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let heightConstraint = view.heightAnchor.constraint(equalToConstant: 268)
heightConstraint.priority = .defaultHigh
heightConstraint.isActive = true
let label = UILabel()
label.text = "Demo Keyboard"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
Build and run on a physical device. Enable the keyboard in Settings, then General, then Keyboard, then Keyboards. Open any app with a text field and switch to the custom keyboard using the globe button. Observe the height changing from around 956pt to 452pt to 268pt with visible animation.
Environment
iOS 17 and iOS 18 and 26.2, Xcode 16 and Xcode 26, affects all iPhone models tested, reproducible on both simulator and physical device.
Hi everyone,
I’ve encountered an issue where using a popover inside the toolbar of a Catalyst app causes a crash on macOS 26 beta 5 with Xcode 26 beta 5. Here’s a simplified code snippet:
import SwiftUI
struct ContentView: View {
@State private var isPresentingPopover = false
var body: some View {
NavigationStack {
VStack {
}
.padding()
.toolbar {
ToolbarItem {
Button(action: { isPresentingPopover.toggle() }) {
Image(systemName: "bubble")
}
.popover(isPresented: $isPresentingPopover) {
Text("Hello")
.font(.largeTitle)
.padding()
}
}
}
}
}
}
Steps to reproduce:
Create a new iOS app using Xcode 26 beta 5.
Enable Mac Catalyst (Match iPad).
Add the above code to show a Popover from a toolbar button.
Run the app on macOS 26, then click the toolbar button.
The app crashes immediately upon clicking the toolbar button.
Has anyone else run into this? Any workarounds or suggestions would be appreciated!
Thanks!
I remember this integration being demoed at WWDC25.
Ability to trigger app intent for iOS application from Spotlight search on MacOS.
How Do I extend my iOS Application to be able to do this?
Where is the documentation for implementing this mechanism?
Thank you in advance for your help. I believe this integration is a powerful productivity unlock!
I have some really straight forward code in a sample project. For some reason when the app launches the title is blurred obscured by scrolledgeeffect blur. If I scroll down the title goes small as it should do and all looks fine. If I scroll back to the top, just before it reaches the top the title goes large and it looks correct, but once it actually reaches/snaps to the top, is then incorrectly blurs again.
Is there anything obvious I'm doing wrong? Is this a bug?
struct ContentView: View {
var body: some View {
NavigationStack {
ScrollView {
VStack {
Rectangle().fill(Color.red.opacity(0.2)).frame(height: 200)
Rectangle().frame(height: 200)
Rectangle().frame(height: 200)
Rectangle().frame(height: 200)
Rectangle().frame(height: 200)
}
}
.safeAreaBar(edge: .top) {
Text("Test")
}
.navigationTitle(Title")
}
}
}
Xcode tells me
Previewing in executable targets now requires a new build layout for unoptimized builds. Either set ENABLE_DEBUG_DYLIB to YES for this target, or break out your preview code into a separate framework with its own scheme.
How do enable that in Package.swift. swiftSettings don't work (.define and unsafeFlags with -D ...).
Creating a library product that the executable then depends on doesn't help either.
I have two targets, one is an executable target. The #Preview macro is in the non-executable target.
I'm sending local push notifications and want to show specific content based on the id of any notification the user opens. I'm able to do this with no issues when the app is already running in the background using the code below.
final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
let container = AppContainer()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let center = UNUserNotificationCenter.current()
center.delegate = self
return true
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
container.notifications.handleResponse(response)
completionHandler()
}
}
However, the delegate never fires if the app was terminated before the user taps the notification. I'm looking for a way to fix this without switching my app lifecycle to UIKit.
This is a SwiftUI lifecycle app using UIApplicationDelegateAdaptor.
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
I’m aware notification responses may be delivered via launchOptions on cold start, but I’m unsure how to bridge that cleanly into a SwiftUI lifecycle app without reverting to UIKit.
I'm trying to understand the behavior I'm seeing here. In the following example, I have a custom @Observable class that adopts RandomAccessCollection and am attempting to populate a List with it.
If I use an inner collection property of the instance (even computed as this shows), the top view identifies additions to the list.
However, if I just use the list as a collection in its own right, it detects when a change is made, but not that the change increased the length of the list. If you add text that has capital letters you'll see them get sorted correctly, but the lower list retains its prior count. The choice of a List initializer with the model versus an inner ForEach doesn't change the outcome, btw.
If I cast that type as an Array(), effectively copying its contents, it works fine which leads me to believe there is some additional Array protocol conformance that I'm missing, but that would be unfortunate since I'm not sure how I would have known that. Any ideas what's going on here? The new type can be used with for-in scenarios fine and compiles great with List/ForEach, but has this issue. I'd like the type to not require extra nonsense to be used like an array here.
import SwiftUI
fileprivate struct _VExpObservable6: View {
@Binding var model: ExpModel
@State private var text: String = ""
var body: some View {
NavigationStack {
VStack(spacing: 20) {
Spacer()
.frame(height: 40)
HStack {
TextField("Item", text: $text)
.textFieldStyle(.roundedBorder)
.textContentType(.none)
.textCase(.none)
Button("Add Item") {
guard !text.isEmpty else { return }
model.addItem(text)
text = ""
print("updated model #2 using \(Array(model.indices)):")
for s in model {
print("- \(s)")
}
}
}
InnerView(model: model)
OuterView(model: model)
}
.listStyle(.plain)
.padding()
}
}
}
// - displays the model data using an inner property expressed as
// a collection.
fileprivate struct InnerView: View {
let model: ExpModel
var body: some View {
VStack {
Text("Model Inner Collection:")
.font(.title3)
List {
ForEach(model.sorted, id: \.self) { item in
Text("- \(item)")
}
}
.border(.darkGray)
}
}
}
// - displays the model using the model _as the collection_
fileprivate struct OuterView: View {
let model: ExpModel
var body: some View {
VStack {
Text("Model as Collection:")
.font(.title3)
// - the List/ForEach collections do not appear to work
// by default using the @Observable model (RandomAccessCollection)
// itself, unless it is cast as an Array here.
List {
// ForEach(Array(model), id: \.self) { item in
ForEach(model, id: \.self) { item in
Text("- \(item)")
}
}
.border(.darkGray)
}
}
}
#Preview {
@Previewable @State var model = ExpModel()
_VExpObservable6(model: $model)
}
@Observable
fileprivate final class ExpModel: RandomAccessCollection {
typealias Element = String
var startIndex: Int { 0 }
var endIndex: Int { sorted.count }
init() {
_listData = ["apple", "yellow", "about"]
}
subscript(_ position: Int) -> String {
sortedData()[position]
}
var sorted: [String] {
sortedData()
}
func addItem(_ item: String) {
_listData.append(item)
_sorted = nil
}
private var _listData: [String]
private var _sorted: [String]?
private func sortedData() -> [String] {
if let ret = _sorted { return ret }
let ret = _listData.sorted()
_sorted = ret
return ret
}
}
Hey there,
Is there a way to launch another view by tapping on the preview of a context menu? Something like the behavior of the Photos app where tapping on the preview navigates to the details view.
Tap gesture handlers on the preview don't seem to get called, even as high priority gestures.
Thanks for the help!
Gab
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))")
}
}
I have an iOS app (1Address) which allows users to share their address with family and friends using CloudKit Sharing.
Users share their address record (CKRecord) via a share link/url which when tapped allows the receiving user to accept the share and have a persistent view into the sharing user's address record (CKShare).
However, most users when they recieve a sharing link do not have the app installed yet, and so when a new receiving user taps the share link, it prompts them to download the app from the app store.
After the new user downloads the app from the app store and opens the app, my understanding is that the system (iOS) will/should then vend to my app the previously tapped cloudKitShareMetadata (or share url), however, this metadata is not being vended by the system. This forces the user to re-tap the share link and leads to some users thinking the app doesn't work or not completing the sharing / onboarding flow.
Is there a workaround or solve for this that doesn't require the user to tap the share link a second time?
In my scene delegate I am implementing:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {...}
And also
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {...}
And also:
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {...}
And:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {...}
Unfortunately, none of these are called or passed metadata on the initial app run after install. Only after the user goes back and taps a link again can they accept the share.
This documentation: https://developer.apple.com/documentation/cloudkit/ckshare says that adding the CKSharingSupported key to your app's Info.plist file allows the system to launch your app when a user taps or clicks a share URL, but it does not clarify what should happen if your app is being installed for the first time.
This seems to imply that the system is holding onto the share metadata and/or url, but for some reason it is not being vended to the app on first run.
Open to any ideas here for how to fix and I also filed feedback: FB20934189.