kAiN
kAiN

Reputation: 2793

Update offset in Magnification Gesture

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
                                }
                        )
                }
            }
    }
}

Upvotes: 0

Views: 53

Answers (1)

Benzy Neez
Benzy Neez

Reputation: 22253

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
    }

Animation

Upvotes: 1

Related Questions