Isn't there an easy way to pinch to zoom in an image in Swiftui?

前端 未结 7 1515
自闭症患者
自闭症患者 2020-12-28 08:07

I want to be able to resize and move an image in SwiftUI (like if it were a map) with pinch to zoom and drag it around.

With UIKit I embedded the image into a

7条回答
  •  醉话见心
    2020-12-28 08:43

    Here's one way of adding pinch zooming to a SwiftUI view. It overlays a UIView with a UIPinchGestureRecognizer in a UIViewRepresentable, and forwards the relevant values back to SwiftUI with bindings.

    You can add the behaviour like this:

    Image("Zoom")
        .pinchToZoom()
    

    This adds behaviour similar to zooming photos in the Instagram feed. Here's the full code:

    import UIKit
    import SwiftUI
    
    class PinchZoomView: UIView {
    
        weak var delegate: PinchZoomViewDelgate?
    
        private(set) var scale: CGFloat = 0 {
            didSet {
                delegate?.pinchZoomView(self, didChangeScale: scale)
            }
        }
    
        private(set) var anchor: UnitPoint = .center {
            didSet {
                delegate?.pinchZoomView(self, didChangeAnchor: anchor)
            }
        }
    
        private(set) var offset: CGSize = .zero {
            didSet {
                delegate?.pinchZoomView(self, didChangeOffset: offset)
            }
        }
    
        private(set) var isPinching: Bool = false {
            didSet {
                delegate?.pinchZoomView(self, didChangePinching: isPinching)
            }
        }
    
        private var startLocation: CGPoint = .zero
        private var location: CGPoint = .zero
        private var numberOfTouches: Int = 0
    
        init() {
            super.init(frame: .zero)
    
            let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:)))
            pinchGesture.cancelsTouchesInView = false
            addGestureRecognizer(pinchGesture)
        }
    
        required init?(coder: NSCoder) {
            fatalError()
        }
    
        @objc private func pinch(gesture: UIPinchGestureRecognizer) {
    
            switch gesture.state {
            case .began:
                isPinching = true
                startLocation = gesture.location(in: self)
                anchor = UnitPoint(x: startLocation.x / bounds.width, y: startLocation.y / bounds.height)
                numberOfTouches = gesture.numberOfTouches
    
            case .changed:
                if gesture.numberOfTouches != numberOfTouches {
                    // If the number of fingers being used changes, the start location needs to be adjusted to avoid jumping.
                    let newLocation = gesture.location(in: self)
                    let jumpDifference = CGSize(width: newLocation.x - location.x, height: newLocation.y - location.y)
                    startLocation = CGPoint(x: startLocation.x + jumpDifference.width, y: startLocation.y + jumpDifference.height)
    
                    numberOfTouches = gesture.numberOfTouches
                }
    
                scale = gesture.scale
    
                location = gesture.location(in: self)
                offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y)
    
            case .ended, .cancelled, .failed:
                isPinching = false
                scale = 1.0
                anchor = .center
                offset = .zero
            default:
                break
            }
        }
    
    }
    
    protocol PinchZoomViewDelgate: AnyObject {
        func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool)
        func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat)
        func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint)
        func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize)
    }
    
    struct PinchZoom: UIViewRepresentable {
    
        @Binding var scale: CGFloat
        @Binding var anchor: UnitPoint
        @Binding var offset: CGSize
        @Binding var isPinching: Bool
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        func makeUIView(context: Context) -> PinchZoomView {
            let pinchZoomView = PinchZoomView()
            pinchZoomView.delegate = context.coordinator
            return pinchZoomView
        }
    
        func updateUIView(_ pageControl: PinchZoomView, context: Context) { }
    
        class Coordinator: NSObject, PinchZoomViewDelgate {
            var pinchZoom: PinchZoom
    
            init(_ pinchZoom: PinchZoom) {
                self.pinchZoom = pinchZoom
            }
    
            func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) {
                pinchZoom.isPinching = isPinching
            }
    
            func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) {
                pinchZoom.scale = scale
            }
    
            func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) {
                pinchZoom.anchor = anchor
            }
    
            func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) {
                pinchZoom.offset = offset
            }
        }
    }
    
    struct PinchToZoom: ViewModifier {
        @State var scale: CGFloat = 1.0
        @State var anchor: UnitPoint = .center
        @State var offset: CGSize = .zero
        @State var isPinching: Bool = false
    
        func body(content: Content) -> some View {
            content
                .scaleEffect(scale, anchor: anchor)
                .offset(offset)
                .animation(isPinching ? .none : .spring())
                .overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching))
        }
    }
    
    extension View {
        func pinchToZoom() -> some View {
            self.modifier(PinchToZoom())
        }
    }
    

提交回复
热议问题