A UI test case that checks for the existence of a SwiftUI Text element initialized with an empty string previously reliably passed in iOS 17.
In iOS 18 the assertion reliably fails.
I searched the iOS 18 developer release notes for anything possibly related to this but didn't find much. I'd like to point to a conclusive change in iOS handling before changing the test permantently. Can anyone confirm that this is indeed a change in iOS 18?
Post
Replies
Boosts
Views
Activity
I'm porting over some code that uses ARKit to Swift 6 (with Complete Strict Concurrency Checking enabled).
Some methods on ARSCNViewDelegate, namely Coordinator.renderer(_:didAdd:for:) among at least one other is causing a consistent crash. On Swift 5 this code works absolutely fine.
The above method consistently crashes with _dispatch_assert_queue_fail. My assumption is that in Swift 6 a trap has been inserted by the compiler to validate that my downstream code is running on the main thread.
In Implementing a Main Actor Protocol That’s Not @MainActor, Quinn “The Eskimo!” seems to address scenarios of this nature with 3 proposed workarounds yet none of them seem feasible here.
For #1, marking ContentView.addPlane(renderer:node:anchor:) nonisolated and using @preconcurrency import ARKit compiles but still crashes :(
For #2, applying @preconcurrency to the ARSCNViewDelegate conformance declaration site just yields this warning: @preconcurrency attribute on conformance to 'ARSCNViewDelegate' has no effect
For #3, as Quinn recognizes, this is a non-starter as ARSCNViewDelegate is out of our control.
The minimal reproducible set of code is below. Simply run the app, scan your camera back and forth across a well lit environment and the app should crash within a few seconds. Switch over to Swift Language Version 5 in build settings, retry and you'll see the current code works fine.
import ARKit
import SwiftUI
struct ContentView: View {
@State private var arViewProxy = ARSceneProxy()
private let configuration: ARWorldTrackingConfiguration
@State private var planeFound = false
init() {
configuration = ARWorldTrackingConfiguration()
configuration.worldAlignment = .gravityAndHeading
configuration.planeDetection = [.horizontal]
}
var body: some View {
ARScene(proxy: arViewProxy)
.onAddNode { renderer, node, anchor in
addPlane(renderer: renderer, node: node, anchor: anchor)
}
.onAppear {
arViewProxy.session.run(configuration)
}
.onDisappear {
arViewProxy.session.pause()
}
.overlay(alignment: .top) {
if !planeFound {
Text("Slowly move device horizontally side to side to calibrate")
} else {
Text("Plane found!")
.bold()
.foregroundStyle(.green)
}
}
}
private func addPlane(renderer: SCNSceneRenderer, node: SCNNode, anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor,
let device = renderer.device,
let planeGeometry = ARSCNPlaneGeometry(device: device)
else { return }
planeFound = true
planeGeometry.update(from: planeAnchor.geometry)
let material = SCNMaterial()
material.isDoubleSided = true
material.diffuse.contents = UIColor.white.withAlphaComponent(0.65)
planeGeometry.materials = [material]
let planeNode = SCNNode(geometry: planeGeometry)
node.addChildNode(planeNode)
}
}
struct ARScene {
private(set) var onAddNodeAction: ((SCNSceneRenderer, SCNNode, ARAnchor) -> Void)?
private let proxy: ARSceneProxy
init(proxy: ARSceneProxy) {
self.proxy = proxy
}
func onAddNode(
perform action: @escaping (SCNSceneRenderer, SCNNode, ARAnchor) -> Void
) -> Self {
var view = self
view.onAddNodeAction = action
return view
}
}
extension ARScene: UIViewRepresentable {
func makeUIView(context: Context) -> ARSCNView {
let arView = ARSCNView()
arView.delegate = context.coordinator
arView.session.delegate = context.coordinator
proxy.arView = arView
return arView
}
func updateUIView(_ uiView: ARSCNView, context: Context) {
context.coordinator.onAddNodeAction = onAddNodeAction
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
extension ARScene {
class Coordinator: NSObject, ARSCNViewDelegate, ARSessionDelegate {
var onAddNodeAction: ((SCNSceneRenderer, SCNNode, ARAnchor) -> Void)?
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
onAddNodeAction?(renderer, node, anchor)
}
}
}
@MainActor
class ARSceneProxy: NSObject, @preconcurrency ARSessionProviding {
fileprivate var arView: ARSCNView!
@objc dynamic var session: ARSession {
arView.session
}
}
Any help is greatly appreciated!
A ToolbarItem with navigationBar* placement disappears when placed in a toolbar on a detail view of NavigationSplitView.
Expectation: The toolbar item doesn't disappear after device rotation.
Reality: The toolbar item disappears after device rotation.
Reproducible on iPhone 15 Pro Max simulator running 17.4
import SwiftUI
@main
struct MyApp: App {
@State private var selection: Int?
@State private var visibility: NavigationSplitViewVisibility = .detailOnly
var body: some Scene {
WindowGroup {
NavigationSplitView(columnVisibility: $visibility) {
List(1..<10, selection: $selection) { number in
NavigationLink(value: number) {
Button(number.description) {
selection = number
}
}
}
} detail: {
if let selection {
LinearGradient(colors: [.blue, .black], startPoint: .top, endPoint: .bottom)
.navigationTitle(selection.description)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Text("A button")
}
}
} else {
Text("Please make a selection")
}
}
}
}
}
The problem doesn't exhibit itself when using a NavigationStack instead.
import SwiftUI
@main
struct MyApp: App {
@State private var selection: Int?
var body: some Scene {
WindowGroup {
NavigationStack {
List(1..<10) { number in
NavigationLink(value: number) {
Button(number.description) {
selection = number
}
}
}
.navigationDestination(for: Int.self) { selection in
LinearGradient(colors: [.blue, .black], startPoint: .top, endPoint: .bottom)
.navigationTitle(selection.description)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Text("A button")
}
}
}
}
}
}
}
If a TextField is being used within a ScrollView, when the TextField becomes tall enough the input caret can go offscreen behind the keyboard. There's no straightforward way to keep the caret visible.
One could use .id(_:) on something at the bottom of the TextField and scroll to that with the ScrollViewProxy but how would they know when to scroll?
They could detect a height change of the TextField but if the user is typing towards the center of the field then we end up scrolling them away from their edit location to the bottom.
They could detect a length change in the edited text but that has the same issue as #1.
They could diff the edited text to see if the edits were at the end but then we're getting much more complex.
TLDR: We need a straightforward way to keep the TextField's input caret visible.
What I could see working would be something like a modifier for TextField like .cursorID(_:). We could then just scroll to that ID on input changes.
Here's a similar post on StackOverflow.
FB13699023
A conditionally shown DatePicker with .datePickerStyle(.graphical) has the time input cutoff until a new selection is made.
Repro steps:
Tap “Select a date” to show the conditionally hidden DatePicker.
What happens: The time input is cutoff. What I expect: The time input is not cutoff.
Choose a new date.
The time input is shown as expected.
Tested on a 9th gen iPad running 17.4.1, a iPhone 15 Pro Max simulator running 17.4
Using Xcode Version 15.3 (15E204a)
struct ContentView: View {
@State private var selection = Date.now
@State private var isOpen = false
var body: some View {
Button("Select a date") {
isOpen.toggle()
}
if isOpen {
DatePicker("Date Picker", selection: $selection)
.datePickerStyle(.graphical)
}
}
}
UIDevice.orientationDidChangeNotification works as expected without calls to beginGeneratingDeviceOrientationNotifications() and endGeneratingDeviceOrientationNotifications() but the respective documentation for these two methods state:
You must call this method before attempting to get orientation data from the device.
and
You call this method after a previous call to the beginGeneratingDeviceOrientationNotifications() method.
Were they a requirement at one time and never deprecated?
I'm using Xcode 15.0 (15A240d) and I need to test some code on iOS 15. I'm unable to create a new iOS 15 simulator.
If I select the "Download more" option I'm presented with the "Platforms" tab in settings.
There's no option to actually select a platform here (which as you can see Xcode itself indicates I have on disk).
Neither double-click nor secondary-click do anything for me either. Am I doing something wrong or is this just bad design?
The Settings app crashes on launch for me on the iOS 15 iPhone 15 Pro simulator.
Version 15.0 (1015.2)
My problem is similar to this post but switching to an iOS 17 simulator does not fix the issue.
As far as I know there should be no OS_ACTIVITY_DT_MODE set anywhere in my project but I'm not sure exactly where to check.
I've tried in Beta 6 and 7 so far and I've set all of my minimums to iOS 17.
Given the following code, if line 17 isn't present there'll be a crash upon presenting the sheet.
Since content shouldn't be nil at the time of presentation, why does this crash?
import SwiftUI
struct ContentView: View {
@State private var isPresented = false
@State private var content: String?
@State private var startTask = false
var body: some View {
Text("Tap me")
.onTapGesture {
startTask = true
}
.task(id: startTask) {
guard startTask else { return }
startTask = false // <===== Crashes if removed
content = "Some message"
isPresented = true
}
.sheet(isPresented: $isPresented) {
Text(content!)
}
}
}
iOS 16.4, Xcode 14.3.1
My usage of TextField.focused() works fine in Xcode 14.3.1 but is broken as of Xcode 15. I first noticed it in the second beta and it's still broken as of the 4th beta.
Feedback / OpenRadar # FB12432084
import SwiftUI
struct ContentView: View {
@State private var text = ""
@FocusState var isFocused: Bool
var body: some View {
ScrollView {
TextField("Test", text: $text)
.textFieldStyle(.roundedBorder)
.focused($isFocused)
Text("Text Field Is Focused: \(isFocused.description)")
}
}
}
When passing an empty closure to a LibraryItem's snippet parameter, unexpected extra text is added to the file when the item is dragged in from the Xcode library.
Expected:
.setAction {
}
Actual:
.setAction /*@START_MENU_TOKEN@*/{
}/*@END_MENU_TOKEN@*/
Full code:
import SwiftUI
struct ContentView: View {
var body: some View {
MyView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MyView: View {
var action: (() -> Void)?
var body: some View {
Text("MyView")
.onTapGesture {
action?()
}
}
func setAction(_ action: @escaping () -> Void) -> MyView {
var copy = self
copy.action = action
return copy
}
}
struct LibraryContent: LibraryContentProvider {
func modifiers(base: MyView) -> [LibraryItem] {
[
LibraryItem(
base.setAction {
},
title: "MyView - Set action"
)
]
}
}
FB11936092
macOS 13.0.1 (22A400)
Xcode 14.1 (14B47b)
In iOS 15.5 the correct GeometryProxy.size.height value is available when a UIDevice.orientationDidChangeNotification is received. In iOS 16.0 the GeometryProxy.size.height value is outdated it (reflects the value before rotation).
Expected behavior: iPhone SE (3rd generation) (15.5) Landscape: Height is logged at 375.0. Portrait: Height is logged at 647.0.
Actual behavior: iPhone SE (3rd generation) (16.0) Landscape: Height is logged at 647.0. Portrait: Height is logged at 375.0.
Feedback FB10448199
import SwiftUI
struct ContentView: View {
let orientationDidChangeNotification =
NotificationCenter
.default
.publisher(for: UIDevice.orientationDidChangeNotification)
var body: some View {
GeometryReader { geometryProxy in
Color.clear
.onReceive(orientationDidChangeNotification) { _ in
print(geometryProxy.size.height)
}
}
}
}