"The animation feels slow" is a symptom, not a diagnosis. It could be an expensive calculation in the update loop, repeated image work, a synchronous fetch, or an unrelated task contending for the same resources. Changing SwiftUI modifiers until the symptom fades makes it hard to know what actually improved.
Instruments in Xcode 27 adds Top Functions to make a time-range investigation more direct. Use it after you can reproduce the problematic interaction, not as a replacement for reproducing one.
Record one interaction at a time
Start from a small scenario that a teammate can repeat. For example: open a detail screen, trigger the chart animation, then scroll the first ten rows. Record that path and mark the time range where the interface feels wrong.
Avoid profiling a long exploratory session first. A broad trace can contain legitimate startup work, background activity, navigation, and many unrelated view updates. A focused trace produces a question that the tool can answer: what consumed time during this specific interaction?
Keep the setup stable while comparing runs:
- use the same build configuration and device type;
- start from the same app state where possible;
- perform the same interaction sequence;
- make one meaningful code change between recordings.
That discipline matters more than a perfectly identical millisecond result. You need to know whether the expensive path moved, not merely whether a single run was lucky.
Select the bad interval, then inspect Top Functions
After selecting the time range, open Top Functions. The view is designed to surface code paths that consumed significant time, especially work performed repeatedly. Start with the top application-owned function, then use the call path to understand why it ran so often.
The first function in the list is a lead, not a verdict. Ask:
- Is this work expected for every frame or view update?
- Is the cost per call too high, or is the call count wrong?
- Does it run on the main thread when it could be prepared earlier?
- Is the function a cause, or only the first visible caller of a deeper cost?
This prevents a common mistake: reducing a local loop while the real issue is that the parent recomputes the loop far more often than intended.
Fix the shape of the work
Once a hot path is known, make the smallest change that removes unnecessary work. In an animation path, that may mean reducing the amount of simulation, memoizing a derived value, or moving preparation out of the state update. In a list, it may mean avoiding repeated formatting or image decoding in the row body.
Here is the kind of calculation that should not be recalculated for every draw when its inputs have not changed:
struct ProgressRing: View {
let progress: Double
private var trimmedProgress: Double {
min(max(progress, 0), 1)
}
var body: some View {
Circle()
.trim(from: 0, to: trimmedProgress)
.stroke(.tint, lineWidth: 8)
.rotationEffect(.degrees(-90))
}
}This example is intentionally small. The useful question is whether the trace proves the calculation or its caller is a cost at the moment of the hitch. Do not pre-optimise every computed property because it resembles a hot path.
Record again to verify the hypothesis
Run the same interaction after the change and inspect the corresponding time range. The hot function should consume less time or disappear from the relevant top entries. If it does not, the change addressed a plausible theory rather than the observed cause.
Also check the product result. A faster animation that drops meaningful content updates or changes the timing of a state transition is not a free performance win. Instruments confirms resource usage, while the interface confirms that the work still delivers the intended experience.
Key takeaway
Top Functions shortens the path from an observed hitch to a code-level hypothesis. Use it with a reproducible interaction, read call count and cost together, and compare a focused recording after each fix. That is how a performance change becomes evidence rather than intuition.
