{"slug": "swiftui-performance-find-redraws-in-xcode-instruments", "title": "SwiftUI Performance: Find Redraws in Xcode Instruments", "summary": "A guide published on SwiftLee shows developers how to locate SwiftUI view redraws and their cost using Xcode Instruments' SwiftUI template, which combines the SwiftUI instrument with Time Profiler and the Hangs and Hitches instruments. In a 27-second recording of the intentionally slow RocketTrace Slow Lab app, Instruments reported 228 long updates totaling 1.10 seconds, and a selected range of view body updates took 24.53 ms. The walkthrough covers reading the Update Groups lane, the Long View Body Updates, Long Representable Updates and Other Long Updates lanes, and the View Body Updates hierarchy and table, which show how often each view updates, how long each update takes and what triggered it.", "body_md": "SwiftUI performance problems often come down to views that redraw too often or take too long to run their `body`. Before you can fix them, you need to find out which views are responsible and what triggers their updates. Xcode Instruments can show you exactly that, as long as you know where to look.\n\nIn this article, I’ll show you how to find SwiftUI redraws and their cost in Xcode Instruments. You’ll record the SwiftUI template, read the SwiftUI track, and use the View Body Updates views to see which view updates how often, how long each update takes, and what caused it. If you’ve used [Self._printChanges()](https://www.avanderlee.com/swiftui/debugging-swiftui-views/)\n\n[Unlimited macOS runners. No more CI queue.](https://www.warpbuild.com/start?utm_source=swiftlee&utm_medium=web&utm_campaign=warpbuild)More pull requests from AI tools mean more macOS jobs waiting in line. WarpBuild gives you unlimited concurrency on M5 Pro Macs, so every job starts right away. Teams at Raycast, Braintrust and WhatNot run CI on WarpBuild. Pay only for what you use. Use code START50 for $50 in free credits.\n\n## \n\nStart with Product → Profile (`⌘I`) in Xcode and choose the SwiftUI template. Profiling builds your app with the Release configuration by default, which is what you want. [Debug builds skew your measurements](https://rockettrace.app/blog/xcode-instruments-debug-vs-release-builds/), as they don’t run the code your users run.\n\nThe template combines the SwiftUI instrument with Time Profiler and the Hangs and Hitches instruments. That combination is useful, as it lets you check whether SwiftUI work lines up with hangs and hitches.\n\nRecord one focused interaction that shows the symptom, like opening a screen or scrolling a list. A short, targeted recording is easier to read than a long tour through your app. On top of that, Instruments takes longer to process when your recording is longer. A longer recording means more data, after all.\n\nIn this article, I’m using the RocketTrace Slow Lab. This is an intentionally slow SwiftUI app that I use to test [RocketTrace](https://www.rockettrace.app), and one of its workloads publishes 600 state changes at roughly 60 Hz: a great way to show bad signals in Instruments!\n\n## \n\nAfter you stop the recording, Instruments shows the SwiftUI track at the top of the timeline.\n\nThe first lane, **Update Groups**, shows when SwiftUI is doing work. According to Apple’s [Optimize SwiftUI performance with Instruments](https://developer.apple.com/videos/play/wwdc2025/306/) session, an empty lane while your CPU usage spikes means the problem is likely outside of SwiftUI. Below it, you find three lanes for long updates: **Long View Body Updates**, **Long Representable Updates**, and **Other Long Updates**. Updates are shown in orange and red based on how likely they are to contribute to a hitch or a hang.\n\nOnce expanded, the SwiftUI track shows the same split in three subtracks: **View Body Updates**, **Representable Updates**, and **Other Updates**. In the screenshot above, the Hitches lane is full of red and there are two hangs, so it’s worth finding out how much of that comes from view body updates.\n\nThe summary at the bottom lists the long updates. In this 27-second recording, it shows 228 long updates that add up to 1.10 seconds. That’s a good starting point, but it only includes updates that Instruments considers long. A view that updates hundreds of times with a cheap body won’t stand out here.\n\n## \n\nSelect the **View Body Updates** track and drag over the part of the timeline you want to inspect. The detail view at the bottom now shows the view body updates in that range.\n\nYou can use `⌘3` and `⌘4` to switch between the **View Body Updates View Hierarchy** and the **View Body Updates** table. Let’s start with the hierarchy.\n\nThe hierarchy lists the views that ran their body within the selected range, together with the time they spent. In my selection, all view body updates together took 24.53 ms. `WorkloadCard` took 8.35 ms and `InvalidationWorkloadCard` 6.71 ms, which makes them the two most expensive views in that range.\n\nThe filter options at the bottom make a big difference here. Click **Call Tree** to open them. You’ll find:\n\n- Separate by Category\n- Separate by Thread\n- Hide System Libraries\n- Hide Inlined Functions\n- Hide Modifiers\n- Hide Primitive Elements\n- Flatten Recursion\n- Invert Call Tree\n\nIn the screenshot, I have **Separate by Thread**, **Hide System Libraries**, and **Invert Call Tree** enabled. Hiding system libraries leaves only the views from your own app, which is the list you want to start with. If the hierarchy is still too noisy, you can hide modifiers and primitive elements as well.\n\n## \n\nPress `⌘4` to switch to the **View Body Updates** table. This is where you see every individual update instead of the grouped totals.\n\nEach row is one body update, with columns for the start time, duration, module, view name, category, description, root causes, and process. In the screenshot, `InvalidationWorkloadCard.body` and `WorkloadCard.body` appear again and again, only milliseconds apart. Most of those updates take only around 40 to 50 µs.\n\nThat’s the important lesson in this table. A single update is cheap, but view bodies run on the main thread, so a lot of cheap updates still add up. You can only see that by looking at how often a view updates in combination with how long each update takes.\n\nThe **Root Causes** column tells you what triggered each update. For these rows, it shows `@Observable WorkloadController.(Int)`. In other words, an `Int` property on an observable `WorkloadController` changed, and every view that read it ran its body again. If you select a row, the inspector on the right shows the start time, the duration, and the view hierarchy of that update.\n\n## \n\nRoot Causes gives you a quick answer, but sometimes you need more context. Apple’s [Understanding and improving SwiftUI performance](https://developer.apple.com/documentation/xcode/understanding-and-improving-swiftui-performance) documentation explains how to open the cause-and-effect graph for an update group. Set the inspection range on the group, hold the pointer over an update, click the arrow that appears, and choose **Show Causes**. Blue nodes are objects from your own code, and gray nodes are defined by the system.\n\nCompare what the graph shows with what you expect to happen in your UI. Apple lists two common reasons for too-frequent updates:\n\n- A view observes an object with other properties, and updates when one of those other properties changes. Apple recommends migrating to the `@Observable` macro, which only tracks the properties a view reads. I explained the performance benefits in my article on the[@Observable macro](https://www.avanderlee.com/swiftui/observable-macro-performance-increase-observableobject/) .\n- A view’s update causes the views it contains to update without any meaningful change in the UI.\n\nFor updates that are expensive instead of frequent, the fix is usually to move work out of `body`. Do the heavy work when the data changes, and let the view render the result.\n\nAfter each change, record the same interaction again. That’s the only way to confirm that the update count or duration actually went down. If you want to read more about the other half of this workflow, take a look at my article on [Xcode Instruments Time Profiler](https://www.avanderlee.com/debugging/xcode-instruments-time-profiler/).\n\n## \n\nLook back at the steps above. You record, expand the track, select a range, switch detail views with `⌘3` and `⌘4`, toggle filter options, sort a table, and read root causes one update at a time. Then you change your code and do all of it again to verify the fix.\n\nI find this really hard to do manually, and it takes a lot of time. You need to know what to look for, and the answer is spread over several lanes and views. That’s the reason I built RocketTrace.\n\n## \n\n[RocketTrace](https://rockettrace.app) is a Mac app that analyzes your Xcode Instruments recordings. You either record a build of your app from RocketTrace or import an existing trace, and it ranks what it finds by measured impact. Analysis runs locally on your Mac.\n\nInstead of going through the lanes and tables yourself, RocketTrace checks your recording for seven SwiftUI patterns. You can read the details on the [What RocketTrace Detects](https://rockettrace.app/detections/) page, but in short they cover:\n\n- Broad invalidation fan-out, where a single dependency invalidates many views\n- State churn, where one property changes so often that it drives constant redraws\n- Expensive view body updates, with their duration and a backtrace\n- Redraws without a recorded value change\n- Views that update on a steady cadence\n- Your most expensive view\n- Repeated runtime warnings\n\nEach finding includes the measured evidence from the trace, so you can see right away which view updates the most or takes the longest. You still decide whether an update pattern is a bug or expected behavior for your UI. The [SwiftUI findings guide](https://rockettrace.app/docs/analysis/swiftui/) explains how to read them.\n\nYou can copy a prompt for your coding agent from a finding, or let your agent run the whole flow. I wrote about that in [Introducing RocketTrace: Using Xcode Instruments with AI Agents](https://www.avanderlee.com/ai-development/rockettrace-xcode-instruments-ai-agents/). If you want to understand the three patterns behind most redraw problems, the RocketTrace blog covers them in [Debugging SwiftUI Redraws](https://rockettrace.app/blog/debugging-swiftui-redraws/).\n\n[Unlimited macOS runners. No more CI queue.](https://www.warpbuild.com/start?utm_source=swiftlee&utm_medium=web&utm_campaign=warpbuild)More pull requests from AI tools mean more macOS jobs waiting in line. WarpBuild gives you unlimited concurrency on M5 Pro Macs, so every job starts right away. Teams at Raycast, Braintrust and WhatNot run CI on WarpBuild. Pay only for what you use. Use code START50 for $50 in free credits.\n\n## \n\nFinding SwiftUI redraws in Xcode Instruments comes down to a few steps: record the SwiftUI template, read the SwiftUI track, check the View Body Updates hierarchy and table, and follow the root cause back to the dependency that triggers the updates. It works, but it takes time to do by hand.\n\nYou can [download RocketTrace](https://apps.apple.com/app/apple-store/id6798298905?pt=117264678&ct=swiftlee-swiftui-performance&mt=12) for free, and your first three analyses with findings are included. Record the screen you’ve been suspicious about and see what it finds. If you want to improve your SwiftUI knowledge even more, check out the [SwiftUI category page](https://www.avanderlee.com/category/swiftui/). Feel free to contact me or [tweet me on Twitter](https://twitter.com/twannl) if you have any additional tips or feedback.\n\nThanks!", "url": "https://wpnews.pro/news/swiftui-performance-find-redraws-in-xcode-instruments", "canonical_source": "https://www.avanderlee.com/swiftui/swiftui-performance-xcode-instruments/", "published_at": "2026-10-19 11:27:45+00:00", "updated_at": "2026-11-09 09:23:20.156818+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Xcode Instruments", "SwiftUI", "Apple", "SwiftLee", "RocketTrace", "RocketTrace Slow Lab", "WarpBuild"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/swiftui-performance-find-redraws-in-xcode-instruments", "markdown": "https://wpnews.pro/news/swiftui-performance-find-redraws-in-xcode-instruments.md", "text": "https://wpnews.pro/news/swiftui-performance-find-redraws-in-xcode-instruments.txt", "jsonld": "https://wpnews.pro/news/swiftui-performance-find-redraws-in-xcode-instruments.jsonld"}}