140 lines
5.3 KiB
Swift
140 lines
5.3 KiB
Swift
//
|
||
// PinchZoomView.swift
|
||
// 双指捏合放大扩展
|
||
//
|
||
// Created by CC-star on 2025/7/22.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
//图片查看器-用法:
|
||
//Image("Zoom").pinchToZoom()
|
||
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
|
||
//加动画--在源代码上改动的地方
|
||
//withAnimation(.interactiveSpring(response: 0.2, dampingFraction: 0.75, blendDuration: 0.1))
|
||
withAnimation(.interactiveSpring(response: 0.2, dampingFraction: 0.65, blendDuration: 0.1)) {
|
||
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)
|
||
//原代码中有这个,但效果不好,需要在上面pinch中加withanimation才行,故注释
|
||
// .animation(isPinching ? .none : .spring(), value: scale)
|
||
// .animation(.spring(duration: 1), value: scale)
|
||
.overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching))
|
||
// .onChange(of: isPinching) {
|
||
// if !isPinching {
|
||
// // 在松开手势时触发动画恢复
|
||
// withAnimation(.spring()) {
|
||
// scale = 1.0
|
||
// offset = .zero
|
||
// anchor = .center
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
}
|
||
extension View {
|
||
func pinchToZoom() -> some View { self.modifier(PinchToZoom()) }
|
||
}
|