我有一段代码,允许您使用放大手势放大和缩小带有渐变的圆。如果我将手指放在屏幕中间并进行缩放,效果很好,但如果我将手指放在屏幕边缘并执行放大手势,我希望它放大手指之间的点。目前还是以屏幕中心为中心进行放大。
如何修改我的代码以允许用户在手指放置之间以 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 仍然(!)不支持以锚点为中心进行缩放。作为解决方法,我们可以在透明
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)
}
}
您可以使用放大手势 + 自定义逻辑来实现这一点视频结果
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
}
}
}
}