SwiftUI 2 指滑动手势

3

我目前正在尝试使用SwiftUI构建一个与Procreate相似的应用程序(当然要简单得多 ;) )。

我目前遇到的最大问题是导航。我希望使用两指滑动手势在画布上进行导航。我已经进行了大量的研究,但还没有找到如何在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实现一起工作?

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

1个回答

0

我没有针对这个确切问题的解决方案,但我找到了一个对我有用的解决方案。

由于我使用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
    }
}

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