Among SwiftUI’s many APIs, .ignoredByLayout()
is something of an “understated member.” Information is scarce, usage scenarios are uncommon, and its very name tends to raise questions. It seems to suggest some kind of “ignoring” of the layout—but how does that differ from modifiers like offset
or scaleEffect
, which by default don’t affect their parent’s layout? When does ignoredByLayout
actually come into play, and what exactly does it “ignore” or “hide”? In this article, we’ll lift the veil on this subtle API in SwiftUI’s layout mechanism.
The Confusing Official Documentation
First, let’s look at how Apple introduces ignoredByLayout
in its official documentation:
Returns an effect that produces the same geometry transform as this effect, but only applies the transform while rendering its view. Use this method to disable layout changes during transitions. The view ignores the transform returned by this method while the view is performing its layout calculations.
This description—especially the part about “disabling layout changes”—can leave many developers scratching their heads. We know that in SwiftUI, modifiers like offset
, scaleEffect
, and rotationEffect
change a view’s appearance but by default don’t alter its container’s layout. So is ignoredByLayout
the core mechanism that enforces this default behavior?
The answer is: No.
In practice, whether you explicitly call .ignoredByLayout()
on a GeometryEffect
or not, transforms like scaleEffect
still do not affect the parent’s layout. For example, the following three approaches behave identically with respect to parent layout (i.e., none of them affect it):
// Approach 1: Standard modifier
Rectangle()
.scaleEffect(3)
// Approach 2: Directly using the underlying GeometryEffect
Rectangle()
.modifier(_ScaleEffect(scale: .init(width: 3, height: 3)))
// Approach 3: Underlying effect plus ignoredByLayout
Rectangle()
.modifier(_ScaleEffect(scale: .init(width: 3, height: 3)).ignoredByLayout())
So what exactly is .ignoredByLayout
ignoring? Where does its precise effect lie?
It’s critical to understand the difference between a view’s view size (the size used for rendering) and its layout size (the size it reports to the layout system). For a deep dive, see SwiftUI Layout: Cracking the Size Code.
The True Nature of ignoredByLayout
The most direct way to uncover what ignoredByLayout
really does is to compare two transformed rectangles: one using standard modifiers, the other using the underlying GeometryEffect
implementations with .ignoredByLayout()
. Each rectangle reports its geometry via a GeometryReader
overlay so we can see what the system believes its coordinates and size to be.
struct IgnoredByLayout: View {
@State var degree: Double = 0
@State var x: Double = 0
var body: some View {
Slider(value: $degree, in: 0 ... 360)
.padding()
Slider(value: $x, in: -100 ... 100)
.padding()
// Standard rotation+offset
Rectangle()
.frame(width: 200, height: 200)
.overlay(GeometryInfo())
.rotationEffect(.degrees(degree))
.offset(x: x)
// Underlying effects with ignoredByLayout
Rectangle()
.frame(width: 200, height: 200)
.overlay(GeometryInfo())
.modifier(_RotationEffect(angle: .degrees(degree)).ignoredByLayout())
.modifier(_OffsetEffect(offset: .init(width: x, height: 0)).ignoredByLayout())
}
}
// Displays geometry info
struct GeometryInfo: View {
var body: some View {
GeometryReader { proxy in
Color.clear
.overlay(
VStack {
Text("x = \(Int(proxy.frame(in: .global).minX))")
Text("\(Int(proxy.frame(in: .global).size.width)), \(Int(proxy.frame(in: .global).size.height))")
}
.font(.headline)
.foregroundColor(.white)
)
}
}
}
For the lower rectangle—transformed with _RotationEffect
and _OffsetEffect
plus .ignoredByLayout()
—its visual rotation and translation still track the sliders. Yet the GeometryReader
reports its global minX
and size unchanged, exactly as if no transform had been applied. In contrast, the upper rectangle’s reported coordinates and size (especially its bounding box after rotation) vary with the sliders.
Notably, .ignoredByLayout()
does not diminish the visual transform effect: it purely affects what the layout system “knows” about the view during its layout computations.
Based on this, we can give a precise summary of .ignoredByLayout
:
- Applicability: Only valid on types conforming to
GeometryEffect
. - Visual Rendering: It does not change the final visual transform. The view looks visually transformed (rotated, scaled, etc.) on screen.
- Core Mechanism (Layout Interaction): When SwiftUI’s layout system—during the layout calculation phase—queries a view’s geometry (for example via a
GeometryReader
) that has this effect applied, ignoredByLayout steps in. Its role is to ensure that, while performing these calculations, the layout system ignores the geometric transformation introduced by this specific GeometryEffect. As a result, the geometry properties (size, bounding box, etc.) that the view reports to the layout system reflect its original state before that particular GeometryEffect was applied.
In brief: The essence of .ignoredByLayout()
is that it alters how a view communicates its geometry to the layout system. It forces the view to respond to layout queries—such as those from a GeometryReader
—using its original layout blueprint, unaffected by the visual transformation introduced by the GeometryEffect. This effectively isolates the visual effect from interfering with the layout calculation process.
Practical Scenarios for ignoredByLayout
Most day-to-day SwiftUI work won’t require you to reach for ignoredByLayout()
, or you might encounter issues without realizing this API is the root cause. Once you understand its true purpose, though, it becomes a handy tool whenever geometric transforms clash with layout expectations. Here are two common use cases:
Case 1: Fixing Anchor Distortion from Transforms
In complex custom layouts, you often use PreferenceKey
(e.g., anchorPreference
) to pass a child view’s anchor information up to its parent for precise positioning. If the child view is visually transformed (rotated or scaled), directly reading its Anchor<CGRect>
yields the bounding box after transform, not the original boundaries—leading to misplaced or missized layout.
The following example shows that a red background rectangle distorts and shifts as you rotate, because it’s based on the rotated Text
bounding box:
struct SimpleFrameData: Equatable {
var anchor: Anchor<CGRect>? = nil
}
struct SimpleFramePreferenceKey: PreferenceKey {
typealias Value = [SimpleFrameData]
static var defaultValue: Value = []
static func reduce(value: inout Value, nextValue: () -> Value) {
value.append(contentsOf: nextValue())
}
}
struct SimplifiedRotationDistortion: View {
@State private var rotationAngle: Double = 0
var body: some View {
VStack(spacing: 30) {
Slider(value: $rotationAngle, in: 0...45)
.padding()
VStack {
Text("Item to Track")
.padding(15)
.background(Color.blue.opacity(0.7))
.border(Color.cyan, width: 2)
// Report its bounds via anchorPreference
.anchorPreference(
key: SimpleFramePreferenceKey.self,
value: .bounds
) { anchorValue in
[SimpleFrameData(anchor: anchorValue)]
}
}
.backgroundPreferenceValue(SimpleFramePreferenceKey.self) { preferences in
GeometryReader { geometryProxy in
if let itemAnchor = preferences.first?.anchor {
let itemBounds = geometryProxy[itemAnchor]
Rectangle()
.fill(Color.red.opacity(0.5))
.frame(width: itemBounds.width, height: itemBounds.height)
.offset(x: itemBounds.minX, y: itemBounds.minY)
.border(Color.orange, width: 2)
.overlay(Text("BG").font(.caption))
}
}
}
.border(Color.gray)
.rotationEffect(.degrees(rotationAngle))
.frame(width: 250, height: 150)
Spacer()
}
}
}
By replacing rotationEffect
with the underlying _RotationEffect
plus .ignoredByLayout()
, the anchor passed via PreferenceKey
stays in its pre-rotation state—letting your background rectangle draw in the correct original position and size:
// .rotationEffect(.degrees(rotationAngle))
.modifier(_RotationEffect(angle: .degrees(rotationAngle), anchor: .center).ignoredByLayout())
Note: You can apply
.ignoredByLayout()
to any custom or system-providedGeometryEffect
.
The more complex your layout, the more transforms you apply, and the more you rely on precise child geometry, the likelier you are to run into this distortion—making .ignoredByLayout()
your go-to solution.
Case 2: Avoiding Unwanted Safe-Area Expansion
SwiftUI’s .background
modifier, when using a ShapeStyle
fill, will extend to fill the safe area by default if the view is near the edges. You can tune this with ignoresSafeAreaEdges
, but it doesn’t always behave as expected when the view’s position is changed via a render-layer transform like offset
.
Consider this example: even though we set ignoresSafeAreaEdges: .all
, moving the view with .offset
near the bottom causes the background to spill into the safe area:
struct OffsetView: View {
@State var y: CGFloat = 0
var body: some View {
Slider(value: $y, in: -500 ... 500)
Color.clear
.background(.orange, ignoresSafeAreaEdges: .all)
.frame(width: 100, height: 100)
.offset(y: y)
}
}
If we swap .offset
for the _OffsetEffect
plus .ignoredByLayout()
, the layout system no longer “sees” that offset when deciding on background extension—so the background stays within the safe area:
// .offset(y: y)
.modifier(_OffsetEffect(offset: .init(width: 0, height: y)).ignoredByLayout())
Understanding the role of ignoredByLayout
clarifies why ignoresSafeAreaEdges
can fall short when the position is altered at the render layer; you’re literally hiding that displacement from the layout engine. Future SwiftUI releases may address this edge case, but for now ignoredByLayout
provides a reliable workaround.
Better Documentation for a Great API
In certain scenarios, .ignoredByLayout
is the key—or even the only—way to solve challenging layout problems, decoupling visual effects from layout calculations. Unfortunately, the official docs lack clarity and compelling examples, leaving this API “hidden” and underused.
SwiftUI has matured greatly, but there’s still room for improvement in documentation clarity, completeness, and depth. Solid docs are the bedrock for mastering any framework, and we can only hope Apple continues to elevate SwiftUI’s documentation—so more “hidden gems” like .ignoredByLayout
can be fully appreciated and leveraged.