【问题标题】:SwiftUI 2-Finger Pan GestureSwiftUI 2 指平移手势
【发布时间】:2021-10-23 16:20:19
【问题描述】:

我目前正在尝试构建一个与 SwiftUI 中的 procreate 有点相似的应用程序(当然不那么复杂;))。

我现在最大的问题是导航。我想使用 2 指平移手势在画布上导航。我进行了大量研究,但尚未找到有关如何使用 SwiftUI 手势实现此功能的解决方案。

我当前的解决方案是使用带有附加手势侦听器的 UIViewRepresentable。不幸的是,这会破坏子视图中的所有手势识别器(其中很多,我都需要它们。

目前的实现是这样的:

struct TwoFingerNavigationView: UIViewRepresentable {
    var draggedCallback: ((CGPoint) -> Void)
    var dragEndedCallback: (() -> Void)

    class Coordinator: NSObject {
        var draggedCallback: ((CGPoint) -> Void)
        var dragEndedCallback: (() -> Void)

        init(draggedCallback: @escaping ((CGPoint) -> Void),
             dragEndedCallback: @escaping (() -> Void)) {
            self.draggedCallback = draggedCallback
            self.dragEndedCallback = dragEndedCallback
        }

        @objc func dragged(gesture: UIPanGestureRecognizer) {
            if gesture.state == .ended {
                self.dragEndedCallback()
            } else {
                self.draggedCallback(gesture.translation(in: gesture.view))
            }
        }
    }

    func makeUIView(context: UIViewRepresentableContext<TwoFingerNavigationView>) -> TwoFingerNavigationView.UIViewType {
        let view = UIView(frame: .zero)
        let gesture = UIPanGestureRecognizer(target: context.coordinator,
                                             action: #selector(Coordinator.dragged))
        gesture.minimumNumberOfTouches = 2
        gesture.maximumNumberOfTouches = 2
        view.addGestureRecognizer(gesture)
        return view
    }

    func makeCoordinator() -> TwoFingerNavigationView.Coordinator {
        return Coordinator(draggedCallback: self.draggedCallback,
                           dragEndedCallback: self.dragEndedCallback)
    }

    func updateUIView(_ uiView: UIView,
                      context: UIViewRepresentableContext<TwoFingerNavigationView>) {
    }
}

用法如下:

ZStack {
    OtherView()
        .gesture() // This will break with this "hack"

    TwoFingerNavigationView(draggedCallback: { translation in
        var newLocation = startLocation ?? location
        newLocation.x += (translation.x / scale)
        newLocation.y += (translation.y / scale)
        self.location = newLocation
    }, dragEndedCallback: {
        startLocation = location
    })
}

有没有人知道如何使用 SwiftUI 手势识别器复制该功能,或者使其与 UIKit 实现一起使用而不会破坏所有孩子的手势?

非常感谢任何帮助和指点!

【问题讨论】:

    标签: swift swiftui uikit gesture uipangesturerecognizer


    【解决方案1】:

    我没有针对这个确切问题的解决方案,但我找到了迄今为​​止对我有用的解决方案。

    由于我的应用基于 SwiftUI(不能很好地处理高视图计数),我的性能受到了巨大的影响,我不得不做出切换到 SpriteKit 的艰难决定。

    这是我目前的解决方案:我应用了一个整体的触摸识别器,它只捕获所有发生的触摸事件,我现在自己对它们进行解释。然后,我通过回调将事件传回,用于将信息提供给适当的视图。我将扩展它以捕获对特定对象的点击,而不仅仅是拖动手势,但到目前为止,这工作正常。

    class TouchScene: SKScene {
        fileprivate let cameraNode = SKCameraNode()
        var world: SKSpriteNode = SKSpriteNode(color: .clear, size: .zero)
    
        fileprivate var previousPosition: CGPoint? = nil
    
        fileprivate var touchesToIgnore: Int = 10
        fileprivate var touchesIgnored: [CGPoint] = []
        fileprivate var isDrawing: Bool = false
    
        fileprivate var touchBegan: ((CGPoint) -> Void)? = nil
        fileprivate var touchMoved: ((CGPoint) -> Void)? = nil
        fileprivate var touchEnded: ((CGPoint) -> Void)? = nil
    
        fileprivate var panBegan: ((CGPoint) -> Void)? = nil
        fileprivate var panMoved: ((CGPoint) -> Void)? = nil
        fileprivate var panEnded: ((CGPoint) -> Void)? = nil
    
        init(documentSize: CGSize,
             touchBegan: ((CGPoint) -> Void)? = nil,
             touchMoved: ((CGPoint) -> Void)? = nil,
             touchEnded: ((CGPoint) -> Void)? = nil,
             panBegan: ((CGPoint) -> Void)? = nil,
             panMoved: ((CGPoint) -> Void)? = nil,
             panEnded: ((CGPoint) -> Void)? = nil) {
            world = SKSpriteNode(color: .clear, size: documentSize)
    
            super.init()
    
            self.touchBegan = touchBegan
            self.touchMoved = touchMoved
            self.touchEnded = touchEnded
    
            self.panBegan = panBegan
            self.panMoved = panMoved
            self.panEnded = panEnded
        }
    
        override init(size: CGSize) {
            super.init(size: size)
    
            addChild(world)
    
            self.addChild(cameraNode)
            self.camera = cameraNode
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override var isUserInteractionEnabled: Bool {
            get { return true }
            set { }
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            let locations = locationsFor(touches: touches)
    
            switch touches.count {
            case 1: touchBegan?(locations[0].local)
            default: panBegan?(locations[0].local)
            }
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            let locations = locationsFor(touches: touches)
    
            switch touches.count {
            case 1:
                guard touchesIgnored.count >= touchesToIgnore else { touchesIgnored.append(locations[0].local); return }
                if touchesIgnored.count == touchesToIgnore {
                    touchesIgnored.forEach { ignoredPoint in
                        touchMoved?(ignoredPoint)
                    }
    
                    touchesIgnored.append(locations[0].local)
                }
                touchMoved?(locations[0].local)
    
            default:
                touchesIgnored = []
                handlePanMoved(withTouches: touches)
                panMoved?(locations[0].local)
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            let locations = locationsFor(touches: touches)
    
            switch touches.count {
            case 1:
                touchEnded?(locations[0].local)
                break
            default: panEnded?(locations[0].local)
            }
    
            touchesIgnored = []
            previousPosition = nil
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
            let locations = locationsFor(touches: touches)
    
            switch touches.count {
            case 1: touchEnded?(locations[0].local)
            default: panEnded?(locations[0].local)
            }
    
            touchesIgnored = []
            previousPosition = nil
        }
    
        private func handlePanMoved(withTouches touches: Set<UITouch>) {
            let newPosition = locationsFor(touches: touches)[0].global
    
            defer { previousPosition = newPosition }
    
            guard let oldPosition = previousPosition else { return }
            world.position = world.position + (newPosition - oldPosition)
        }
    
        private func locationsFor(touches: Set<UITouch>) -> [(global: CGPoint, local: CGPoint)] {
            var locations: [(global: CGPoint, local: CGPoint)] = []
    
            for touch in touches {
                let globalLocation = touch.location(in: self)
                let localLocation = touch.location(in: world)
    
                locations.append((global: globalLocation, local: localLocation))
            }
    
            return locations
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-15
      • 2017-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多