SwiftUI: 使用MagnificationGesture在CGPoint为中心进行缩放

12
我有一段代码,可以通过手势放大和缩小一个有渐变的圆形。如果我把手指放在屏幕中央并进行缩放,这段代码能正常工作,但如果我把手指放在屏幕边缘并进行缩放,我希望它能够在我的手指之间的点上进行缩放。目前,它仍然以屏幕中心为放大中心来进行放大。
我该如何修改代码,使用户可以以恰好位于其手指位置之间的CGPoint为中心?
struct ContentView: View {
    @GestureState var magnificationState = MagnificationState.inactive
    @State var viewMagnificationState = CGFloat(1.0)
    
    var magnificationScale: CGFloat {
        return viewMagnificationState * magnificationState.scale
    }
    
    var body: some View {
        let gradient = Gradient(colors: [.red, .yellow, .green, .blue, .purple, .red, .yellow, .green, .blue, .purple, .red, .yellow, .green, .blue, .purple])
        
        let magnificationGesture = MagnificationGesture()
            .updating($magnificationState) { value, state, transaction in
                state = .zooming(scale: value)
            }.onEnded { value in
                self.viewMagnificationState *= value
            }
        
        
        Circle()
            .fill(
                RadialGradient(gradient: gradient, center: .center, startRadius: 50, endRadius: 2000)
            )
            .frame(width: 2000, height: 2000)
            .scaleEffect(magnificationScale)
            .gesture(magnificationGesture)
    }
}

enum MagnificationState {
    case inactive
    case zooming(scale: CGFloat)
    
    var scale: CGFloat {
        switch self {
        case .zooming(let scale):
            return scale
        default:
            return CGFloat(1.0)
        }
    }
}

很遗憾,目前在SwiftUI中仍然(!)无法实现这一点。你最好的选择是创建一个UIViewRepresentable,使用一个透明视图,并在该视图上使用UIPinchGestureRecognizer来获取捏合手势的中心点。不过要正确实现这一点有些棘手,尤其是如果你想将其与平移和旋转结合使用的话。 - undefined
1个回答

0
在SwiftUI中,仍然(!)不支持以锚点为中心进行缩放。作为一种解决方法,我们可以在一个透明的UIView上使用UIPinchGestureRecognizer,并使用UIViewRepresentable。以锚点为中心进行缩放实际上是进行缩放和平移。我们可以将这个应用到一个视图上,使用transformEffect视图修饰符。这个视图修饰符将一个CGAffineTransform应用到视图上。
下面的扩展简化了围绕锚点进行缩放的操作:
extension CGAffineTransform {
    func scaled(by scale: CGFloat, with anchor: CGPoint) -> CGAffineTransform {
        self
            .translatedBy(x: anchor.x, y: anchor.y)
            .scaledBy(x: scale, y: scale)
            .translatedBy(x: -anchor.x, y: -anchor.y)
    }
}

GestureTransformView 是一个带有绑定到变换的 UIViewRepresentable。我们将在 UIPinchGestureRecognizer 的委托中更新变换。

struct GestureTransformView: UIViewRepresentable {
    @Binding var transform: CGAffineTransform

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        
        let zoomRecognizer = UIPinchGestureRecognizer(
            target: context.coordinator,
            action: #selector(Coordinator.zoom(_:)))
        
        zoomRecognizer.delegate = context.coordinator
        view.addGestureRecognizer(zoomRecognizer)
        context.coordinator.zoomRecognizer = zoomRecognizer
        
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

extension GestureTransformView {
    class Coordinator: NSObject, UIGestureRecognizerDelegate {
        var parent: GestureTransformView
        var zoomRecognizer: UIPinchGestureRecognizer?

        var startTransform: CGAffineTransform = .identity
        var pivot: CGPoint = .zero
        
        init(_ parent: GestureTransformView){
            self.parent = parent
        }
        
        func setGestureStart(_ gesture: UIGestureRecognizer) {
            startTransform = parent.transform
            pivot = gesture.location(in: gesture.view)
        }
        
        @objc func zoom(_ gesture: UIPinchGestureRecognizer) {
            switch gesture.state {
            case .began:
                setGestureStart(gesture)
                break
            case .changed:
                applyZoom()
                break
            case .cancelled:
                fallthrough
            case .ended:
                applyZoom()
                startTransform = parent.transform
                zoomRecognizer?.scale = 1
            default:
                break
            }
        }
        
        func applyZoom() {
            let gestureScale = zoomRecognizer?.scale ?? 1
            parent.transform = startTransform
                .scaled(by: gestureScale, with: pivot)
        }
    }
}

这就是你如何使用GestureTransformView。请注意,transformEffect是应用于Stack而不是Circle。这样可以确保(之前的)转换正确应用于叠加层。
struct ContentView: View {
    @State var transform: CGAffineTransform = .identity
    
    var body: some View {
        let gradient = Gradient(colors: [.red, .yellow, .green, .blue, .purple,
                                         .red, .yellow, .green, .blue, .purple,
                                         .red, .yellow, .green, .blue, .purple])
        ZStack {
            Circle()
                .fill(
                    RadialGradient(gradient   : gradient,
                                   center     : .center,
                                   startRadius: 50,
                                   endRadius  : 2000)
                )
                .frame(width: 2000, height: 2000)
                .overlay {
                    GestureTransformView(transform: $transform)
                }
        }   .transformEffect(transform)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接