Search code examples
swiftuiscrollview

Cannot pan image in zoomed ScrollView?


I would like to build a simple view that allows me to show an image in a scroll view and let the user pinch to zoom on the image, and pan.

I've looked around and started with thisScrollView:

struct TestView: View {
    
    var body: some View {
        
        ScrollView {
            Image("test")
                .border(Color(.yellow))
        }
        .border(Color(.red))
        
    }
}

That would not handle zooming.

I then did this:

struct TestView: View {


    /// https://wp.usatodaysports.com/wp-content/uploads/sites/88/2014/03/sunset-in-the-dolos-mikadun.jpg
    var image = UIImage(named: "test")!
    @State var scale: CGFloat = 1.0
    @State var lastScaleValue: CGFloat = 1.0

    var body: some View {


        GeometryReader { geo in

            ScrollView([.vertical, .horizontal], showsIndicators: false){

                ZStack{

                    Image(uiImage: image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: geo.size.width, height: geo.size.width)
                        .scaleEffect(scale)
                        .gesture(MagnificationGesture().onChanged { val in
                            let delta = val / self.lastScaleValue
                            self.lastScaleValue = val
                            var newScale = self.scale * delta
                            if newScale < 1.0 {
                                newScale =  1.0
                            }
                            scale = newScale
                        }.onEnded{val in
                            lastScaleValue = 1
                        })

                }

            }
            .frame(width: geo.size.width, height: geo.size.width)
            .border(Color(.red))
            .background(Color(.systemBackground).edgesIgnoringSafeArea(.all))

        }

    }

}

This allows me to zoom in and out, however, I cannot pan the image:

enter image description here

How can I code up things so I can support zoom and panning?


Solution

  • in order to get the panning functionality you will have to change the size of your Image container, in this case the ZStack. So first we need a variable to save the current latest scale value.

    @State var scaledFrame: CGFloat = 1.0
    

    Then just change the size of the container each time the gesture ends.

     ZStack{
            Image(uiImage: image)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: geo.size.width, height: geo.size.width )
                .scaleEffect(scale)
                .gesture(MagnificationGesture().onChanged { val in
                    let delta = val / self.lastScaleValue
                    self.lastScaleValue = val
                    var newScale = self.scale * delta
                    if newScale < 1.0 {
                        newScale =  1.0
                    }
                    scale = newScale
                }.onEnded{val in
                    scaledFrame = scale//Update the value once the gesture is over
                    lastScaleValue = 1
                    
                })
                .draggable()
        }
        .frame(width: geo.size.width * scaledFrame, height: geo.size.width * scaledFrame)
        //change the size of the frame once the drag is complete
    

    This is due to the way ScrollView works, when you were zooming in, the real size of the container was not changing, therefore the ScrollView was only moving accordingly.