Hi everyone I'm working on a black rectangle that contains a yellow rectangle inside it.. I implemented the DragGesture
and the MagnificationGesture
to interact with the yellow rectangle. The drag gesture works perfectly by keeping the movement of the yellow rectangle always within the limits of the black rectangle that contains it.
My problem is when I use the MagnificationGesture
because when I zoom in on the yellow rectangle I can no longer move it, it's as if the drag gesture is not taken into consideration..
I need the yellow rectangle to be able to be moved as desired even after zooming but always remaining within the limits of the black rectangle set within the DragGesture
. Thanks for all
struct ContentView: View {
@State private var offset: CGSize = .zero
@State private var lastOffset: CGSize = .zero
@State private var scale: CGFloat = 1.0
@State private var lastScale: CGFloat = 1.0
let imageSize: CGSize = .init(width: 150, height: 150)
let containerSize: CGSize = .init(width: 300, height: 300)
var body: some View {
Rectangle()
.stroke(.white, lineWidth: 2)
.frame(containerSize)
.background {
GeometryReader {
let size = $0.size
Rectangle()
.fill(.yellow)
.frame(width: imageSize.width * scale, height: imageSize.height * scale)
.scaleEffect(scale)
.offset(offset)
.gesture(
MagnificationGesture()
.onChanged { value in
let newScale = lastScale * value
scale = min(max(newScale, 1), 3)
}
.onEnded { _ in
lastScale = scale
})
.simultaneousGesture(
DragGesture()
.onChanged { value in
let translation = value.translation
let scaledImageSize: CGSize = .init(width: imageSize.width * scale, height: imageSize.height * scale)
let xLimit = max(min(lastOffset.width + translation.width, size.width - scaledImageSize.width), 0)
let yLimit = max(min(lastOffset.height + translation.height, size.height - scaledImageSize.height), 0)
offset = CGSize(width: xLimit, height: yLimit)
}
.onEnded { value in
lastOffset = offset
}
)
}
}
}
}
It looks like you are applying the scaling twice, because you are also scaling the sizes being applied with the .frame
modifier.
Try commenting out the scaleEffect
modifier:
Rectangle()
.fill(.yellow)
.frame(width: imageSize.width * scale, height: imageSize.height * scale)
// .scaleEffect(scale)
// + other modifiers
This resolves the bounds issue when dragged. However, you were asking in a comment, how to let the yellow rectangle scale in all directions while still staying within bounds? This will require updating the offset inside the magnification gesture. Something like:
MagnificationGesture()
.onChanged { value in
let maxScale = min(size.width / imageSize.width, size.height / imageSize.height)
let newScale = min(max(lastScale * value, 1), maxScale)
// Adjust the offset to simulate a scaling anchor of .center
// and to keep the scaled rectangle within bounds
let prevAdjustmentX = imageSize.width * (scale - 1) / 2
let newAdjustmentX = imageSize.width * (newScale - 1) / 2
let dx = prevAdjustmentX - newAdjustmentX
let prevAdjustmentY = imageSize.height * (scale - 1) / 2
let newAdjustmentY = imageSize.height * (newScale - 1) / 2
let dy = prevAdjustmentY - newAdjustmentY
let x = min(max(0, offset.width + dx), size.width - (imageSize.width * newScale))
let y = min(max(0, offset.height + dy), size.height - (imageSize.height * newScale))
offset = CGSize(width: x, height: y)
lastOffset = offset
scale = newScale
}
.onEnded { _ in
lastScale = scale
}