Use .renderViewTree(of: self) to see the view tree visually next to your original view.
The screenshots above is produced by the code below:
import SwiftUIViewTree
struct ContentView: View {
@State var isTapped = false
var body: some View {
Button {
isTapped.toggle()
} label: {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(isTapped ? "Yo what?" : "Hello, world!")
.bold(isTapped ? true : false)
}
.padding()
}
.renderViewTree(of: self) // <--------
}
}Without the .renderViewTree(of: self) line the view would be just like:
You can also pinch to zoom and scroll to any direction.
Tip: Use Option + Shift to move both fingers on a simulator.
You can apply stepped zoom by double tapping on the view tree's canvas.
If something got updated due to a state change it gets a new color:
Note: structural changes like myEnum.case1 --> myEnum.case2(let someInteger) in the view tree are not yet handled properly (but it's on the top of the roadmap), only simple value changes: true --> false
When a node gets a new color you can also see it in the console:
🚨Changes detected in "_value": "Bool"
🟥Old value: "true"
🟩New value: "false"
🔺Diff at [0]: '...true...' --> '...false...'
If certain parts of the tree are redundant for you, then you can double tap to collapse and later reopen them:
A collapsed node is always gray and a badge indicates how many descendants it has:
You might not want to zoom in always to see the details of a specific node. In that case you can tap on any node to print its full details.
Node Details:
Label: _value
Type: Bool
Value: true
The solution is fully compatible with Swift 6.
You can browse the view tree on an iPad as well.
SwiftUI can produce unexpected updates that are not easy to troubleshoot with the built-in tools.
To troubleshoot and better understand the surprises of SwiftUI you might find this library useful.
There are numerous articles on the web that are about this topic. I'll collect some of my favorites and reference them here as the repository matures.
-
Track structural graph changes properly. E.g.
myEnum.case1 --> myEnum.case2(let someInteger) -
Add history of graph state to be able to track quick changes e.g. image blinking.
-
Detach view tree from source to stop receiving new updates
-
Setable frame for the view tree
-
Tapping on a connection line to scroll to the parent node
-
Better accessibility support, maybe with the usage of
OutlineGroupasrenderMode -
Supporting logs for production usage
-
Dedicated documentation page
-
Support for Mac
-
CI on PRs
-
Build a table in the documentation of tested view types - maybe
Probably the closest alternative is using SwiftUI's built-in, documented, but still private APIs:
let _ = Self._printChanges()
let _ = Self._logChanges()As their name suggests, these APIs do not provide any UI, unlike this library.
Another alternative to figure out the exact type of a SwiftUI.View you can simply change the some View return type of the body computed variable to a definitely wrong type like String.
With that you can see from the compiler error message the concrete type of your view.
In the below example the (more) concrete type is VStack<TupleView<(some View, Text)>>
struct ContentView: View {
var body: String { // Compiler error: Cannot convert return expression of type 'VStack<TupleView<(some View, Text)>>' to return type 'String'
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
.font(.largeTitle)
.bold()
}
}
}This means that you can change the type of the body to VStack<TupleView<(some View, Text)>> and the compiler will be okay with that.
struct ContentView: View {
var body: VStack<TupleView<(some View, Text)>> { // No error, this compiles
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
.font(.largeTitle)
.bold()
}
}
}However you might already see the two limitations of this approach:
- There's yet again a
some Viewin the type, so you still don't know the concrete type.
What's worse is that figuring that out is not as straightforward as it was in the above example.
- You don't see all the details that are made visible by this package. For example all the properties of the types.
If you face any problems, or have any feature request please feel free to open an Issue.
While I'm not new to open source development, I'm new to being the author of a library. Any help is welcome, please feel free to open PRs.
This project is based on the work of Chris Eidhof (objc.io). In his 2019 Swiftable conference talk called "SwiftUI under the hood" (link), he demoed a helper function which he called .mirror(). His mirror view modifier can be applied to any kind of SwiftUI View and will render its "viewtree" next to it:
While his talk is not primarily about this helper function, it got the attention of many people as it seems to be a really promising debugging tool.
Additionally when I started to work on my implementation of his tool, I researched GitHub for any open source Swift projects for building graphs and I found danielctull-playground/TreeView where the author describes the repository as "Implementation following the objc.io TreeView".
I ended up using this repository's implementation with some tweaks, so I have to give the credits again to Chris and the whole objc.io team.



