The following code worked just fine as expected until I threw in an additional Text
views write below the Toggle
(it is commented out in the code below). When I do that I get a Geometry action is cycling between duplicate values. error and the program eventually hangs (on X86 15.2). It does not really hang but seems stuck in some infinite update loop consuming a 100% of a core and eating up RAM. Same error on M3 15.1 except haven't been able to make it hang/get stuck yet.
Seems like a new error since Google returns no results for it.
Anyone seen anything like that, if yes, what does it mean?
Is there a problem with the code or is this a sign of a possible bug in SwiftUI?
struct CaptureSizeView: ViewModifier {
@Binding var size: CGSize
func body(content: Self.Content) -> some View {
content
.onGeometryChange(for: CGSize.self) { proxy in proxy.size }
action: { newValue in size = newValue }
}
}
public extension View {
func captureSize(_ size: Binding<CGSize>) -> some View {
modifier(CaptureSizeView(size: size))
}
}
public extension CGSize {
var aspectRatio: CGFloat { self.width / self.height }
}
struct ContentView: View {
@State private var clearSize: CGSize = .zero
@State private var isSquare: Bool = false
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
Toggle("Keep Square", isOn: $isSquare).toggleStyle(.switch)
// Uncomment: error "Geometry action is cycling between duplicate values."
// Text("available size: \(clearSize)")
}
.padding()
Divider()
VStack {
Text("Canvas")
.font(.largeTitle)
.fontWeight(.black)
Text("available size: \(clearSize)")
ZStack {
Color.clear
.border(.green)
.captureSize($clearSize)
.overlay {
Canvas { _, _ in
}
.aspectRatio(isSquare ? 1 : clearSize.aspectRatio, contentMode: .fit)
.background(.yellow)
}
}
.padding()
}
}
}
}
I was able to reproduce the problem by running your code on macOS 15.1.1 (a MacBook Pro with Apple M1 Pro).
Your view is divided into a left and a right side:
Text
output that displays the size. This is because the width of the text is greater than the width needed for the toggle control that is shown above it on the left side.ZStack
with a Color
(and a Canvas
as overlay).When the window size is changed, it causes the size of the ZStack
to change. When the new size is displayed, the text (probably) has a different width to the size that was being displayed before, because a proportional font is being used to display the size. So the change in Text
size causes another change to the ZStack
size. The latest change to the ZStack
size causes another change to the Text
width. And so it goes on.
To fix, you need to prevent a change in the size of the ZStack
from causing a ripple effect that results in another change to the ZStack
size.
VStack(alignment: .leading) {
Toggle("Keep Square", isOn: $isSquare).toggleStyle(.switch)
Text("available size: \(clearSize)")
}
.lineLimit(1) // 👈 added
.frame(maxWidth: 250, alignment: .leading) // 👈 added
.padding()
VStack(alignment: .leading) {
Toggle("Keep Square", isOn: $isSquare).toggleStyle(.switch)
Text("available size: 8888.888 x 8888.888")
.hidden()
.overlay(alignment: .leading) {
Text("available size: ") +
Text(String(format: "%.3f", clearSize.width)) +
Text(" x ") +
Text(String(format: "%.3f", clearSize.height))
}
}
.padding()