The Issue in My Project:
In my SwiftUI project, I have a red rectangle whose size and position I want to control dynamically. The rectangle’s size is represented by a rect state variable, and its position is managed using an offset state variable. When I increase or decrease the rectangle’s width using buttons, the origin of the rectangle changes due to the size modification.
To maintain the rectangle’s visual position on the screen, I manually adjust the offset in the button actions. For example, if I add 20 points to the rectangle’s width, I also increase the offset.width to counteract the shift in its origin.
What I Want to Achieve:
I want to simplify this logic by automatically calculating the necessary offset adjustment within the .onChange modifier of the GeometryReader. The goal is to dynamically compute and update the offset whenever the frame of the rectangle changes, eliminating the need to manually adjust the offset in the button actions. This ensures that the rectangle stays visually stationary on the screen regardless of changes to its size.
Original code:
import SwiftUI
struct ContentView: View {
@State private var rect: CGRect = CGRect(origin: .zero, size: CGSize(width: 200.0, height: 50.0))
@State private var offset: CGSize = CGSize()
var body: some View {
VStack {
Spacer()
Rectangle()
.fill(Color.blue)
.frame(width: 200.0, height: 50.0)
Rectangle()
.fill(Color.red)
.frame(width: rect.width, height: rect.height)
.overlay(Text(String(describing: rect.origin) + String(describing: rect.size)).bold())
.background(
GeometryReader { geometryValue in
Color.clear
.onAppear {
rect = geometryValue.frame(in: .global)
}
.onChange(of: geometryValue.frame(in: .global)) { newValue in
rect = newValue
}
}
)
.offset(offset)
.animation(Animation.default, value: offset)
.animation(Animation.default, value: rect.size.width)
Spacer()
HStack {
Button("add width") {
rect.size.width += 20.0
offset.width += 10.0
}
Button("subtract width") {
rect.size.width -= 20.0
offset.width -= 10.0
}
}
}
.padding()
}
}
Attempt to solve the issue:
The attempted approach does not solve the issue and also introduces a new animation problem. I prefer to use the animation modifier instead of withAnimation.
import SwiftUI
struct ContentView: View {
@State private var rect: CGRect = CGRect(origin: .zero, size: CGSize(width: 200.0, height: 50.0))
@State private var previousRect: CGRect = CGRect(origin: .zero, size: CGSize(width: 200.0, height: 50.0))
@State private var offset: CGSize = CGSize()
var body: some View {
VStack {
Spacer()
Rectangle()
.fill(Color.blue)
.frame(width: 200.0, height: 50.0)
Rectangle()
.fill(Color.red)
.frame(width: rect.width, height: rect.height)
.overlay(Text(String(describing: rect.origin) + String(describing: rect.size)).bold())
.background(
GeometryReader { geometryValue in
Color.clear
.onAppear {
rect = geometryValue.frame(in: .global)
previousRect = rect
}
.onChange(of: geometryValue.frame(in: .global)) { newValue in
let deltaX: CGFloat = newValue.origin.x - previousRect.origin.x
let deltaY: CGFloat = newValue.origin.y - previousRect.origin.y
offset.width -= deltaX
offset.height -= deltaY
previousRect = newValue
rect = newValue
}
}
)
.offset(offset)
.animation(Animation.default, value: offset)
.animation(Animation.default, value: rect.size.width)
Spacer()
HStack {
Button("add width") {
rect.size.width += 20.0
}
Button("subtract width") {
rect.size.width -= 20.0
}
}
}
.padding()
}
}