在 Swiftui 中是不是有一种简单的方法可以通过捏合来放大图像?

Posted

技术标签:

【中文标题】在 Swiftui 中是不是有一种简单的方法可以通过捏合来放大图像?【英文标题】:Isn't there an easy way to pinch to zoom in an image in Swiftui?在 Swiftui 中是否有一种简单的方法可以通过捏合来放大图像? 【发布时间】:2019-10-11 13:00:32 【问题描述】:

我希望能够在 SwiftUI 中调整和移动图像(就像它是地图一样),通过捏合来缩放和拖动它。

使用 UIKit,我将图像嵌入到 UIScrollView 中,它会处理它,但我不知道如何在 SwiftUI 中执行此操作。 我尝试使用MagnificationGesture,但无法使其顺利运行。

我已经搜索了一段时间了,有人知道是否有更简单的方法吗?

【问题讨论】:

做同样的事情,将图像添加到滚动视图中。 SwiftUI 仍然可以使用滚动视图 不知道 SwiftUI 中的 ScrollView 是否原生支持缩放。或者我只是找不到它。我得到了这个,但它的行为很奇怪。 ScrollView(Axis.Set(arrayLiteral: [.horizo​​ntal, .vertical]), showsIndicators: false) Image("building").resizable().scaledToFit().scaleEffect(self.scale) .gesture(MagnificationGesture() .onChanged scale in self.scale = scale ) 对于仍在寻找的任何人,唯一有效的答案来自 jtbandes - 尽管我建议您查看他在 GitHub 中的工作示例。我已经查看了网络上的每篇文章,了解如何在本地执行此操作,但这根本不可能。正确计算各种变量(中心、锚点、边界等)所需的状态以及将拖动和缩放合并在一起的能力,在 SwiftUI 2.0 版本中根本不存在。我已经为图像实现了 jtbandes 解决方案,它工作得非常好,就像 Apple Photos 一样。 Pastebin 链接:pastebin.com/embed_js/rpSRTddm 【参考方案1】:

这里的其他答案对于自定义缩放逻辑过于复杂。如果您想要经过实战考验的标准 UIScrollView 缩放行为,您可以使用 UIScrollView

SwiftUI 允许您使用 UIViewRepresentableUIViewControllerRepresentable 将任何 UIView 放入其他 SwiftUI 视图层次结构中。然后要将更多 SwiftUI 内容放入该视图中,您可以使用 UIHostingController。在Interfacing with UIKit 和API docs 中阅读有关 SwiftUI–UIKit 互操作的更多信息。

您可以在https://github.com/jtbandes/SpacePOD/blob/main/SpacePOD/ZoomableScrollView.swift 找到我在实际应用中使用此功能的更完整示例(该示例还包括更多使图像居中的技巧。)

var body: some View 
  ZoomableScrollView 
    Image("Your image here")
  


struct ZoomableScrollView<Content: View>: UIViewRepresentable 
  private var content: Content

  init(@ViewBuilder content: () -> Content) 
    self.content = content()
  

  func makeUIView(context: Context) -> UIScrollView 
    // set up the UIScrollView
    let scrollView = UIScrollView()
    scrollView.delegate = context.coordinator  // for viewForZooming(in:)
    scrollView.maximumZoomScale = 20
    scrollView.minimumZoomScale = 1
    scrollView.bouncesZoom = true

    // create a UIHostingController to hold our SwiftUI content
    let hostedView = context.coordinator.hostingController.view!
    hostedView.translatesAutoresizingMaskIntoConstraints = true
    hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    hostedView.frame = scrollView.bounds
    scrollView.addSubview(hostedView)

    return scrollView
  

  func makeCoordinator() -> Coordinator 
    return Coordinator(hostingController: UIHostingController(rootView: self.content))
  

  func updateUIView(_ uiView: UIScrollView, context: Context) 
    // update the hosting controller's SwiftUI content
    context.coordinator.hostingController.rootView = self.content
    assert(context.coordinator.hostingController.view.superview == uiView)
  

  // MARK: - Coordinator

  class Coordinator: NSObject, UIScrollViewDelegate 
    var hostingController: UIHostingController<Content>

    init(hostingController: UIHostingController<Content>) 
      self.hostingController = hostingController
    

    func viewForZooming(in scrollView: UIScrollView) -> UIView? 
      return hostingController.view
    
  

【讨论】:

这行得通,我知道,但我认为你误解了这个问题。我在寻求一种简单的“原生”方式来使用 SwiftUI,而不使用 UIKit。无论如何感谢您的回复!可能对许多人有用。 我确实希望 SwiftUI 的 ScrollView 尽快获得此功能,但与此同时,我认为没有太多理由更喜欢“本机”(纯 SwiftUI)解决方案而不是混合一些 UIKit 的解决方案。逃生舱的存在是有原因的!我将在我的项目中使用它:) 很棒的样品!我一直在尝试使用原生 SwiftUI 甚至是带有手势的 UIKit 视图,而 scaleEffect 刚刚损坏。但是,尝试使用您的代码时,我会在放置图像时得到一个非常奇怪的“跳跃”或“捕捉”。我认为图像与它没有任何关系,但是您的代码非常简单。你有过这样的经历吗? 感激不尽,这完全救了我!我采用了您在上面发布的代码并进行了一些修改以供我使用,即我添加了一个 UIImageView 而不是嵌入式 SwiftUI 视图,这就是我所需要的。我不得不迎合安全区域等,但结果非常棒。我看了你的美国宇航局照片项目,当我有更多时间时,我会考虑将它整合到我的其他非图像项目中。再次,谢谢。 完美!谢谢【参考方案2】:

SwiftUI API 在这里非常无用:onChanged 给出了相对于当前缩放手势开始的数字,并且在回调中没有明显的方法来获取初始值。还有一个 onEnded 回调,但很容易错过/忘记。

解决方法,添加:

@State var lastScaleValue: CGFloat = 1.0

然后在回调中:

.gesture(MagnificationGesture().onChanged  val in
            let delta = val / self.lastScaleValue
            self.lastScaleValue = val
            let newScale = self.scale * delta

//... anything else e.g. clamping the newScale
.onEnded  val in
  // without this the next gesture will be broken
  self.lastScaleValue = 1.0

其中 newScale 是您自己对比例的跟踪(可能是状态或绑定)。如果你直接设置你的比例,它会变得一团糟,因为在每个刻度上,金额都将与之前的金额相关。

【讨论】:

它实际上更...自然。现在我很好地理解了 onChange 值和 onEnded 值。谢谢! :D【参考方案3】:

这是向 SwiftUI 视图添加捏合缩放的一种方法。它将UIViewUIPinchGestureRecognizer 覆盖在UIViewRepresentable 中,并通过绑定将相关值转发回SwiftUI。

您可以像这样添加行为:

Image("Zoom")
    .pinchToZoom()

这增加了类似于在 Instagram 供稿中缩放照片的行为。完整代码如下:

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())
    

【讨论】:

嗨,我打算做一些更像 Facebook 捏的东西,女巫保持放大,你可以滚动。我首先尝试在pinch 函数中调整您的代码,将.ended.canceled.failed 分开,并从.ended 案例中删除scale。有了这个改变,我可以保持比例,但我不能缩小(使用捏)也不能环顾四周,你能指出我错过了什么吗?我扫描了您的代码几次,无法弄清楚还需要什么。在我看来,至少捏出应该可以工作,然后只需添加拖动手势。 嗨@PedroCavaleiro。我认为这段代码不适用于这个用例。我建议您改用 UIScrollView 及其缩放功能。 我真的很喜欢这个解决方案,但它会阻止内容视图中的所有 SwiftUI 手势识别器。如果内容视图是可表示的,它似乎也阻止了它的所有 UIGestureRecognizers。 你刚刚救了我的命!!谢谢你:) 您认为您可以在您的班级中添加 2 个手指翻译吗?这将允许在用户定义的中心进行缩放。例如,对于绘图应用程序非常有用!【参考方案4】:

在 SwiftUI 的 ScrollView 中似乎没有原生支持,但是,仍然有一个非常简单的方法可以做到这一点。

像你想要的那样创建一个MagnificationGesture,但一定要把你当前的比例乘以你在手势的.onChanged闭包中得到的值。此闭包为您提供缩放变化,而不是当前的缩放值。

当您缩小并开始放大时,它不会从当前比例增加(0.5 到 0.6 作为任意示例),它会从 1 增加到 1.1。这就是你看到奇怪行为的原因。

如果MagnificationGesture 位于与.scaleEffect 相同的视图上,此答案将有效。否则,詹姆斯的答案会更好。

struct ContentView: View 
    @State var scale: CGFloat
    var body: some View 
        let gesture = MagnificationGesture(minimumScaleDelta: 0.1)
            .onChanged  scaleDelta in
                self.scale *= scaleDelta
        
        return ScrollView 
            // Your ScrollView content here :)
        
            .gesture(gesture)
            .scaleEffect(scale)
    

附:您可能会发现为此目的使用ScrollView 很笨拙,并且您无法同时拖动和缩放。如果是这种情况并且您对此不满意,我会考虑添加多个手势并手动调整内容的偏移量,而不是使用 ScrollView

【讨论】:

我认为这行不通。回调的比例是相对的,所以手势的开始。因此,将每个回调乘以 delta 会使事情变得混乱,例如如果你扩大到两倍大小,那么在每个刻度上它都会让你的比例加倍。可能不是你想要的。 在某些情况下确实如此。这取决于您如何设置层次结构。如果手势不在缩放的视图上,您​​将需要使用您的答案,如果手势在缩放的同一视图上,我的回答可以解决问题:)【参考方案5】:

我也在为这个问题而苦恼。但是这个视频制作了一些工作样本-(https://www.youtube.com/watch?v=p0SwXJYJp2U)

这还没有完成。很难用锚点进行缩放。希望这是对其他人的暗示。

struct ContentView: View 

    let maxScale: CGFloat = 3.0
    let minScale: CGFloat = 1.0

    @State var lastValue: CGFloat = 1.0
    @State var scale: CGFloat = 1.0
    @State var draged: CGSize = .zero
    @State var prevDraged: CGSize = .zero
    @State var tapPoint: CGPoint = .zero
    @State var isTapped: Bool = false

    var body: some View 
        let magnify = MagnificationGesture(minimumScaleDelta: 0.2)
            .onChanged  value in
                let resolvedDelta = value / self.lastValue
                self.lastValue = value
                let newScale = self.scale * resolvedDelta
                self.scale = min(self.maxScale, max(self.minScale, newScale))

                print("delta=\(value) resolvedDelta=\(resolvedDelta)  newScale=\(newScale)")
        

        let gestureDrag = DragGesture(minimumDistance: 0, coordinateSpace: .local)
            .onChanged  (value) in
                self.tapPoint = value.startLocation
                self.draged = CGSize(width: value.translation.width + self.prevDraged.width,
                                     height: value.translation.height + self.prevDraged.height)
        

        return GeometryReader  geo in
                Image("dooli")
                    .resizable().scaledToFit().animation(.default)
                    .offset(self.draged)
                    .scaleEffect(self.scale)
//                    .scaleEffect(self.isTapped ? 2 : 1,
//                                 anchor: UnitPoint(x: self.tapPoint.x / geo.frame(in: .local).maxX,
//                                                   y: self.tapPoint.y / geo.frame(in: .local).maxY))
                    .gesture(
                        TapGesture(count: 2).onEnded(
                            self.isTapped.toggle()
                            if self.scale > 1 
                                self.scale = 1
                             else 
                                self.scale = 2
                            
                            let parent = geo.frame(in: .local)
                            self.postArranging(translation: CGSize.zero, in: parent)
                        )
                        .simultaneously(with: gestureDrag.onEnded( (value) in
                            let parent = geo.frame(in: .local)
                            self.postArranging(translation: value.translation, in: parent)
                        )
                    ))
                    .gesture(magnify.onEnded  value in
                        // without this the next gesture will be broken
                        self.lastValue = 1.0
                        let parent = geo.frame(in: .local)
                        self.postArranging(translation: CGSize.zero, in: parent)
                    )
            
            .frame(height: 300)
            .clipped()
            .background(Color.gray)

    

    private func postArranging(translation: CGSize, in parent: CGRect) 
        let scaled = self.scale
        let parentWidth = parent.maxX
        let parentHeight = parent.maxY
        let offset = CGSize(width: (parentWidth * scaled - parentWidth) / 2,
                            height: (parentHeight * scaled - parentHeight) / 2)

        print(offset)
        var resolved = CGSize()
        let newDraged = CGSize(width: self.draged.width * scaled,
                               height: self.draged.height * scaled)
        if newDraged.width > offset.width 
            resolved.width = offset.width / scaled
         else if newDraged.width < -offset.width 
            resolved.width = -offset.width / scaled
         else 
            resolved.width = translation.width + self.prevDraged.width
        
        if newDraged.height > offset.height 
            resolved.height = offset.height / scaled
         else if newDraged.height < -offset.height 
            resolved.height = -offset.height / scaled
         else 
            resolved.height = translation.height + self.prevDraged.height
        
        self.draged = resolved
        self.prevDraged = resolved
    


【讨论】:

希望苹果以后能提供一种标准简单的方式来做这些拖拽操作。注意,在 SwiftUI 最新版本中,simultaneously 被重命名为 simultaneousGesture【参考方案6】:

其他答案很好,这里有一个额外的提示:如果您使用的是 SwiftUI 手势,您可以使用 @GestureState 而不是 @State 来存储手势状态。它会在手势结束后自动将状态重置为初始值,因此您可以简化这种代码:

@State private var scale: CGFloat = 1.0

.gesture(MagnificationGesture().onChanged  value in
  // Anything with value
  scale = value
.onEnded  value in
  scale = 1.0
)

与:

@GestureState private var scale: CGFloat = 1.0

.gesture(MagnificationGesture().updating($scale)  (newValue, scale, _) in
  // Anything with value
  scale = newValue
)

【讨论】:

【参考方案7】:

我认为值得一提的极其简单的方法 - 使用 Apple 的 PDFKit

import SwiftUI
import PDFKit

struct PhotoDetailView: UIViewRepresentable 
    let image: UIImage

    func makeUIView(context: Context) -> PDFView 
        let view = PDFView()
        view.document = PDFDocument()
        guard let page = PDFPage(image: image) else  return view 
        view.document?.insert(page, at: 0)
        view.autoScales = true
        return view
    

    func updateUIView(_ uiView: PDFView, context: Context) 
        // empty
    

优点:

需要 0 个逻辑 感觉很专业 由 Apple 编写(未来不太可能中断)

如果您只是展示图像以供查看,则此方法可能非常适合您。但是,如果您想添加图像注释等,我会遵循其他答案之一。

根据 maka 的建议编辑添加 view.autoScales = true

【讨论】:

你说得对,这对我来说是完美的。谢谢! 想补充一点,它为我解决了这个问题,但这条线有帮助,因为我的图像以一个巨大的缩放开始(这是一个高分辨率图像):view.autoScales = true 缩放看起来不错,但不能拖动,它总是会回到中心。 感谢您的提及。它解决了我的问题。干杯! 这确实是最好的方法。其他一切都感觉很笨拙,代码太多......谢谢【参考方案8】:

这是@James 接受响应的完整示例,它还具有基本支持,通过调整隐藏矩形来滚动新缩放的图像,该矩形会根据图像比例调整滚动视图的内容大小:

import SwiftUI

struct EnlargedImage: View 
    var image = UIImage(named: "YourImageName")
    @State var scale: CGFloat = 1.0
    @State var lastScaleValue: CGFloat = 1.0

    var body: some View 
   
        ScrollView([.vertical, .horizontal], showsIndicators: false)
            ZStack
                
                Rectangle().foregroundColor(.clear).frame(width: image!.size.width * scale, height: image!.size.height * scale, alignment: .center)
                
                Image(uiImage: image!).scaleEffect(scale)
                .gesture(MagnificationGesture().onChanged  val in
                    let delta = val / self.lastScaleValue
                    self.lastScaleValue = val
                    var newScale = self.scale * delta
                    if newScale < 1.0
                    
                        newScale = 1.0
                    
                    scale = newScale
                .onEndedval in
                    lastScaleValue = 1
                )
                
               
            
        .background(Color(.systemBackground).edgesIgnoringSafeArea(.all))
    
    

我在my GitHub 有一个更好的版本。

【讨论】:

【参考方案9】:

这是另一种解决方案,基于 jtbandes 的回答。它仍然将 UIScrollView 包装在 UIViewRepresentable 中,但有一些变化:

它专门用于UIImage,而不是通用的 SwiftUI 内容:它适用于这种情况,并且不需要将底层 UIImage 包装到 SwiftUI Image 它基于自动布局约束来布局图像视图,而不是自动调整掩码大小 它通过根据当前缩放级别计算顶部和前导约束的适当值,使图像居中于视图的中间

用途:

struct EncompassingView: View 
    let uiImage: UIImage

    var body: some View 
        GeometryReader  geometry in
            ZoomableView(uiImage: uiImage, viewSize: geometry.size)
        
    

定义:

struct ZoomableView: UIViewRepresentable 
    let uiImage: UIImage
    let viewSize: CGSize

    private enum Constraint: String 
        case top
        case leading
    
    
    private var minimumZoomScale: CGFloat 
        let widthScale = viewSize.width / uiImage.size.width
        let heightScale = viewSize.height / uiImage.size.height
        return min(widthScale, heightScale)
    
    
    func makeUIView(context: Context) -> UIScrollView 
        let scrollView = UIScrollView()
        
        scrollView.delegate = context.coordinator
        scrollView.maximumZoomScale = minimumZoomScale * 50
        scrollView.minimumZoomScale = minimumZoomScale
        scrollView.bouncesZoom = true
        
        let imageView = UIImageView(image: uiImage)
        scrollView.addSubview(imageView)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        
        let topConstraint = imageView.topAnchor.constraint(equalTo: scrollView.topAnchor)
        topConstraint.identifier = Constraint.top.rawValue
        topConstraint.isActive = true
        
        let leadingConstraint = imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
        leadingConstraint.identifier = Constraint.leading.rawValue
        leadingConstraint.isActive = true
        
        imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true

        return scrollView
    
    
    func makeCoordinator() -> Coordinator 
        return Coordinator()
    
    
    func updateUIView(_ scrollView: UIScrollView, context: Context) 
        guard let imageView = scrollView.subviews.first as? UIImageView else 
            return
        
        
        // Inject dependencies into coordinator
        context.coordinator.zoomableView = imageView
        context.coordinator.imageSize = uiImage.size
        context.coordinator.viewSize = viewSize
        let topConstraint = scrollView.constraints.first  $0.identifier == Constraint.top.rawValue 
        let leadingConstraint = scrollView.constraints.first  $0.identifier == Constraint.leading.rawValue 
        context.coordinator.topConstraint = topConstraint
        context.coordinator.leadingConstraint = leadingConstraint

        // Set initial zoom scale
        scrollView.zoomScale = minimumZoomScale
    


// MARK: - Coordinator

extension ZoomableView 
    class Coordinator: NSObject, UIScrollViewDelegate 
        var zoomableView: UIView?
        var imageSize: CGSize?
        var viewSize: CGSize?
        var topConstraint: NSLayoutConstraint?
        var leadingConstraint: NSLayoutConstraint?

        func viewForZooming(in scrollView: UIScrollView) -> UIView? 
            zoomableView
        
        
        func scrollViewDidZoom(_ scrollView: UIScrollView) 
            let zoomScale = scrollView.zoomScale
            print("zoomScale = \(zoomScale)")
            guard
                let topConstraint = topConstraint,
                let leadingConstraint = leadingConstraint,
                let imageSize = imageSize,
                let viewSize = viewSize
            else 
                return
            
            topConstraint.constant = max((viewSize.height - (imageSize.height * zoomScale)) / 2.0, 0.0)
            leadingConstraint.constant = max((viewSize.width - (imageSize.width * zoomScale)) / 2.0, 0.0)
        
    

【讨论】:

【参考方案10】:

这是@James 和@ethoooo 的另一种方法。最终缩放状态和瞬态手势状态是分开的(瞬态将始终返回 1),因此除了手势本身之外,您还可以通过按钮或步进器设置该状态。

  @State var scrollContentZoom: CGFloat = 1
  @GestureState var scrollContentGestureZoom: CGFloat = 1
  var contentZoom: CGFloat  scrollContentZoom*scrollContentGestureZoom 
  
  var magnification: some Gesture 
    MagnificationGesture()
      .updating($scrollContentGestureZoom)  state, gestureState, transaction in
        print("Magnifed: \(state)")
        gestureState = state
      
      .onEnded  (state) in
        scrollContentZoom = contentZoom*state
      
  

【讨论】:

【参考方案11】:
struct DetailView: View 
    var item: MenuItem
    @State private var zoomed:Bool = false
    @State var scale: CGFloat = 1.0
    @State var isTapped: Bool = false
    @State var pointTaped: CGPoint = CGPoint.zero
    @State var draggedSize: CGSize = CGSize.zero
    @State var previousDraged: CGSize = CGSize.zero

    var width = UIScreen.main.bounds.size.width
    var height = UIScreen.main.bounds.size.height

    var body: some View 
        GeometryReader   reader in
            VStack(alignment: .center) 
                ScrollView()
                    HStack 
                        ScrollView(.vertical)
                            Image(self.item.mainImage)
                                .resizable()
                                .scaledToFill()
                                .animation(.default).offset(x: self.draggedSize.width, y: 0)
                                .scaleEffect(self.scale).scaleEffect(self.isTapped ? 2 : 1, anchor: UnitPoint(x : (self.pointTaped.x) / (reader.frame(in : .global).maxX),y: (self.pointTaped.y) / (reader.frame(in : .global).maxY )))
                                .gesture(TapGesture(count: 2)
                                    .onEnded( value in
                                        self.isTapped = !self.isTapped
                                    )
                                    .simultaneously(with: DragGesture(minimumDistance: 0, coordinateSpace: .global)  .onChanged  (value) in
                                        self.pointTaped = value.startLocation
                                        self.draggedSize = CGSize(width: value.translation.width + self.previousDraged.width, height: value.translation.height + self.previousDraged.height)
                                    
                                    .onEnded( (value) in
                                        let offSetWidth = (reader.frame(in :.global).maxX * self.scale) - (reader.frame(in :.global).maxX) / 2
                                        let newDraggedWidth = self.previousDraged.width * self.scale
                                        if (newDraggedWidth > offSetWidth)
                                            self.draggedSize = CGSize(width: offSetWidth / self.scale, height: value.translation.height + self.previousDraged.height)
                                        
                                        else if (newDraggedWidth < -offSetWidth)
                                            self.draggedSize = CGSize(width:  -offSetWidth / self.scale, height: value.translation.height + self.previousDraged.height)
                                        
                                        else
                                            self.draggedSize = CGSize(width: value.translation.width + self.previousDraged.width, height: value.translation.height + self.previousDraged.height)
                                        
                                        self.previousDraged =  self.draggedSize
                                    )))

                                .gesture(MagnificationGesture()
                                    .onChanged  (value) in
                                        self.scale = value.magnitude

                                .onEnded  (val) in
                                    //self.scale = 1.0
                                    self.scale = val.magnitude
                                    
                            )
                        
                    

                        HStack 
                            Text(self.item.description)
                                .foregroundColor(Color.black)
                                .multilineTextAlignment(.leading)
                                .padding(4)
                        
                
            .navigationBarTitle("Menu Detail")
        
    

【讨论】:

虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性! 您的示例代码包含很多与问题无关的代码。这只会让我们更难提取重要部分。

以上是关于在 Swiftui 中是不是有一种简单的方法可以通过捏合来放大图像?的主要内容,如果未能解决你的问题,请参考以下文章

ListView,是不是有一种简单的方法可以允许在内部拖动项目(内置)?

如果从某个 html 文件接近,是不是有一种简单的方法可以只执行 if 语句?

WPF Grid:如果我需要插入新行,是不是有一种简单的方法可以重新调整行项目?

当您使用 Python 的 Paramiko 库进行 SSH 并从远程机器的 CLI 获取输出时,是不是有一种简单的方法可以消除垃圾值?

当您使用 Python 的 Paramiko 库进行 SSH 并从远程机器的 CLI 获取输出时,是不是有一种简单的方法可以消除垃圾值?

当您使用 Python 的 Paramiko 库进行 SSH 并从远程机器的 CLI 获取输出时,是不是有一种简单的方法可以消除垃圾值?