Thank you. A slightly modified version of that worked. I needed to send two .enters, one to terminate path entry and the other to close the open dialog.
Post
Replies
Boosts
Views
Activity
I see I forgot to enable email notifications of replies... fixed.
I'm using a Swift table with 4 columns. In testing I replaced the columns with a fixed Text view to see if that was an issue. It wasn't. There was in issue in row: closure to the table in that the array consisted of large-ish structs and a lot of time was being taken allocating and freeing memory to copy the array entries. I changed the struct to a class and saved about 40% of the delay time. Still, there is something going on. When I select an item in the table the user experience is 1) the row turns blue, 2) longish delay, 3) text in the first column changes color. Normally lots of other stuff goes on but I disabled all of it looking for the source of the longish delay.
That delay is the "hang" reported above. If I show system libraries it looks like this:
Note that I also scrolled the tracks to show View Body and the Core Animation Commits -- fwiw I am not doing any additional animation over whatever is built into SwiftUI.
Thanks for your help.
The Call Tree was set to Invert Call Tree in the above screen shots. Selecting the GeoTag main thread gives close to the same output as selecting the Time Profiler. This time I've selected the main thread. The selection is different from above as I don't remember which hang I'd selected. Also, this time I've hidden system libraries.
Also, please right-click on a cell in the heaviest stacktrace view to show the context menu for it and show how you configured it.
You can probably tell that I am an Instruments novice.
I've tried to create a sample project to show this issue but have not been successful. There is something about my application code that triggers whatever is happening that I'm missing when I create a sample. My app slows way down with the number of entries. At about 1200 entries it takes 2-3 seconds to fully process a change in selection . A test app with 10,000 entries shows no slowdown at all.
I will still create a feedback report. The app source is on GitHub.
I was preparing feedback, but using my app as it exists on github, not the version I was using above where I replaced all views not related to the slowdown with a simple Text view. With about 1200 items in the table instruments reports a severe hang of 2.21 seconds. The slowdown is in SwiftUI code. But certainly I'm doing something to trigger that slowdown.
Anyway, I'll have to re-create the test version of the app to file a feedback addressing the unexpected call tree/stack trace. Assuming I can reproduce it.
I submitted FB12211784 the end of May. It contains a do-nothing-much app that creates a two column table of 10K entries. Selecting an item in the table causes the color of the text in the first column to change. On my 2019 intel iMac there is about a 140 msec delay between selection and the color change. That is noticeable. I just re-ran the code using a brand new Mac Studio with M2 Max. It still takes over 100 msec. Still noticeable.
Nothing in the SwiftUI track accounts for the time. Ditto the View Properties track. There is very little in either track.
I thought I'd narrowed down the cause of the slowdown because this specific statement took 2.53 seconds in one test.
mostSelected = self[proposedSelection.first]
That code is running in a function called from an onChange modifier for the table. mostSelected is a property on an @Observable class. The delay was due to waiting for the MainActor to become free to process the update. If I forced the code onto the MainActor using DispatchQueue.main.async then there is no delay -- but the overall hang is the same. I simply explicitly told the update to wait instead of having it block on whatever it blocks. I also instrumented the subscript operator. It is not the cause of the delay, either.
The code is on github: https://github.com/marchyman/GeoTag in the branch macos14 for any that care to take a look.
Ahh, I'd forgotten I posted this to the forum. After getting a response from Apple on my feedback I changed the code to something like this:
struct ContentView: View {
// values normally specified by the caller....
let location = CLLocationCoordinate2D(latitude: 37.0, longitude: -122.0)
let name = "name"
@State private var position: MapCameraPosition = .automatic
@State private var selectedId: String?
var body: some View {
let markers = makeMarkers(for: name, location: location)
Map(position: $position, selection: $selectedId) {
ForEach(markers) { marker in
Marker(marker.title, coordinate: marker.location)
.tint(.red)
}
}
}
// Apparently needed for marker selection
struct MarkerModel: Identifiable {
var id: String = UUID().uuidString
var location: CLLocationCoordinate2D
var title: String
}
private func makeMarkers(for text: String, location: CLLocationCoordinate2D) -> [MarkerModel] {
let marker = MarkerModel(location: location,
title: text)
return [marker]
}
}
Code typed into this reply and may well contain typos. Anyway, using MKMapItem did not work. Not shown is the code that checks if a marker is selected where I put up a LookAround view. I do that in an overlay of the Map view.
Four months later... None of the changes I've made have done more than shave a few milliseconds off of a 3 and a half second hang. That is the approx wait after clicking a row for the selection to be fully processed with about 1000 items in the table. Each item is an Observable object. The Object has 8 or 9 observed properties. From a users perspective the blue bar of the selection shows immediately, then there is a hang, and finally the rest of the UI updates.
Almost all of the hang time is in __CFRunLoopDoObservers split between CFRunLoopRunSpecific and __CFRunLoopRun.
I've commented out much of the code and reduced the table to one column. With approx 1000 items in the table it takes about 900 msec to change the selected item. Measurment is between the time I click on the item and the UI is ready to process the next click. Instruments shows:
every time I select an item the foreach loop runs for every row in the table. That accounts for about 200 msec.
the remaining portion of the hang is in SwiftUI code. None of my instrumented code is called.
I don't know what SwiftUI is doing, but it is doing it slowly.
Well, that was interesting.
I started a new version of my project, bringing code over bit-by-bit and doing performance testing after every change. It boils down to this
Table(...) {
} rows: {
ForEach(someArray) { item in
TableRow(item)
}
}
That worked fine. But I don't want to show every item. So I added this:
ForEach(someArray) { item in
if item.isValid {
TableRow(item)
}
}
Checking the item when building the row breaks things badly. Now to figure out a workaround.
My issue was self inflicted. Anything that messes with the number of rows to be displayed will have drastic performance impact.
TableView {
// ...
} rows: {
ForEach(foos) { foo in
TableRow(foo)
}
}
is acceptable. I was a using conditional to determine if TableRow should be invoked for each item. Don't do that. Filter the array of input if necessary.
That was my fix. You may be running into a different issue.
Just ran into this. But... I don't have any views above the map. In my case the Y coordinate seems to be off by 25 points. If I add 25.0 to the Y coordinate of the reported position before conversion I get what I believe to be the correct value. Or certainly something much, much closer to the correct value.
macOS 14.3.1.
Sounds simple. Except when the user opens a folder and the app only cares about the contents of the folder. Calling start/stop on the contents may not hurt, but my testing says it does not help. Apparently I must call start/stop on the folder to access the contents later in the program. But only for fileImporter. I don't need to do that for dragged folders or folders opened with NSOpenPanel.
I have a solution that works for my app, but it seems strange that I need to go through extra steps on some input (fileImporter), but not others (drag).
Anyway, thanks for your reply.
Task { @MainActor in ... } forces the code between the braces to run on the main actor. It also allows the code to run when the main actor is not otherwise busy doing something else. The code is not synchronous. createSampleData will create the task and immediately return.