'SwiftUI: pinch to zoom on image

I want to allow the user to pinch-to-zoom in on an Image in SwiftUI. I figured the best way to go was to use a MagnificationGesture and, following along with the answer here, I ended up with this code:

// outside of `var body: some View`
@State private var scale: Int = 1.0
@State private var lastScale: Int = 1.0

// Image 
Image("dog")
.resizable()
.aspectRatio(contentMode: .fit)
.gesture(MagnificationGesture()
    .onChanged { val in
        let delta = val / self.lastScale
        self.lastScale = val
        let newScale = self.scale * delta
        self.scale = newScale
    }
    .onEnded { _ in
        self.lastScale = 1.0
    }
)
.scaleEffect(scale)

This code handles magnification fine, but does not let the user zoom in on a specific area. Instead, it always zooms in on the middle of the image.

How would I go about handling pinch-to-zoom behavior on an image in SwiftUI?

Thanks in advance!



Solution 1:[1]

The code creates a pinch-to-zoom effect by adding a drag gesture in addition to the magnification gesture. Use of viewState allows a changing offset position when using the drag gesture.

struct ContentView: View {

    @State private var scale: CGFloat = 1.0
    @State private var lastScale: CGFloat = 1.0
    @State private var viewState = CGSize.zero

    var body: some View {

    Image("dog")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .animation(.spring())
        .offset(x: viewState.width, y: viewState.height)
        .gesture(DragGesture()
            .onChanged { val in
                self.viewState = val.translation
            }
        )
        .gesture(MagnificationGesture()
            .onChanged { val in
                let delta = val / self.lastScale
                self.lastScale = val
                if delta > 0.94 { // if statement to minimize jitter
                    let newScale = self.scale * delta
                    self.scale = newScale
                }
            }
            .onEnded { _ in
                self.lastScale = 1.0
            }
        )
        .scaleEffect(scale)
        }
}

The 'if' statement was added to minimize the jitter caused by frequent updates. Nothing is special about the 0.94 value, just set by trial and error.

The .animation(spring()) statement was added for a more natural-looking dragging effect.

Solution 2:[2]

I found that the easiest way to achieve is to use PDFKit provided by Apple .

1.Start by creating PDFView

    import SwiftUI
    import PDFKit

    struct PhotoDetailView: UIViewRepresentable {
    let image: UIImage
    func makeUIView(context: Context) -> PDFView {
        let view = PDFView()
        view.document = PDFDocument()
        guard let page = PDFPage(image: image) else { return view }
        view.document?.insert(page, at: 0)
        view.autoScales = true
        view.backgroundColor = .clear
        return view
    }
    
    func updateUIView(_ uiView: PDFView, context: Context) {
        
    }
    }

2.Use in swiftUI view, like this

TabView(selection: $index,
                content:  {
                //this line
                PhotoDetailView(image: images[index])
                    .offset(imageViewerOffset)
                
        })
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Marcy
Solution 2 Haolong