SwiftUI:放大手势以 CGPoint 为中心放大

问题描述 投票:0回答:2

我有一段代码,允许您使用放大手势放大和缩小带有渐变的圆。如果我将手指放在屏幕中间并进行缩放,效果很好,但如果我将手指放在屏幕边缘并执行放大手势,我希望它放大手指之间的点。目前还是以屏幕中心为中心进行放大。

如何修改我的代码以允许用户在手指放置之间以 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)
        }
    }
}
swift swiftui gesture magnification
2个回答
1
投票

SwiftUI 仍然(!)不支持以锚点为中心进行缩放。作为解决方法,我们可以在透明

UIPinchGestureRecognizer
上使用
UIView
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)
    }
}

0
投票

您可以使用放大手势 + 自定义逻辑来实现这一点视频结果

struct ZoomInOutView: View {
    // Scale value
    @State private var scale: CGFloat = 1.0
    // Scale value for detecting in/out direction
    @State private var scaleValue: CGFloat = 0
    // Scale step, if need faster speed update step
    @State private var zoomStep: CGFloat = 0.2
    // Scale bounds
    let minZoomStep: CGFloat = 1.0
    let maxZoomStep: CGFloat = 20.0
    
    var body: some View {
        ZStack {
            // Could be any UI
            Color.red.frame(width: 50, height: 50)
                .clipShape(Circle())
            // Subscribe scale update to this element
                .scaleEffect(scale)
            
            // "Invisible" view for detecting gestures, just put it at top of the stack
            Color.white.opacity(0.0001)
                .gesture(MagnificationGesture().onChanged { updateScale($0) })
        }
        .onChange(of: scale, perform: { value in
            // Any logic based on updated value gradient something else etc.
            print("Zoom scale", value)
        })
    }
    
    private func updateScale(_ scale: MagnificationGesture.Value) {
        let zoonIn = scale > scaleValue ? false : true
        let scale = min(max(scale.magnitude, 0), 20.0)
        scaleValue = scale
        if zoonIn {
            if self.scale > minZoomStep {
                self.scale -= zoomStep
            }
        } else {
            if self.scale < maxZoomStep {
                self.scale += zoomStep
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.