[SwiftUI] ScrollViewの中にtouchDownとtouchUpを取得できるViewを置く

4 min読了の目安(約4400字TECH技術記事

onTapGestureは離した瞬間にしか検知できず、DragGestureを使うとスクロールできなかったので。
こういう感じでTouchDownTouchUpを両方検知する透明なViewを作っておいて、これを目的のViewに重ねるとうまくいきました。

touchMovedfailedにする前に許容移動量を設定したかったのですがうまくいきませんでした。ご存知の方がいたら教えていただけると助かります。

ContentView.swift

struct ContentView: View {
    var body: some View {
        ScrollView{
            ForEach(0..<10){_ in
                Rectangle()
                    .frame(width: 100, height: 100)
                    .overlay(
                        TouchDownAndTouchUpGestureView{
                            print("touch down")
                        } touchMovedCallBack: {
                            print("touch failed")
                        } touchUpCallBack: {
                            print("touch up")
                        }
                    )
            }
        }
    }
}
TouchDownAndTouchUpGestureView.swift
import SwiftUI

struct TouchDownAndTouchUpGestureView: UIViewRepresentable {
    let touchDownCallBack: (() -> Void)
    let touchMovedCallBack: (() -> Void)
    let touchUpCallBack: (() -> Void)

    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
        let view = UIView(frame: .zero)
        let touchDown = SingleTouchDownGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.touchDown))
        touchDown.delegate = context.coordinator
        view.addGestureRecognizer(touchDown)
        let touchUp = SingleTouchUpGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.touchUp))
        touchUp.delegate = context.coordinator
        view.addGestureRecognizer(touchUp)
        let touchMoved = SingleTouchMovedGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.touchMoved))
        touchMoved.delegate = context.coordinator
        view.addGestureRecognizer(touchMoved)
        return view
    }

    class Coordinator: NSObject, UIGestureRecognizerDelegate {
        var touchDownCallback: (() -> Void)
        var touchMovedCallBack: (() -> Void)
        var touchUpCallback: (() -> Void)

        init(touchDownCallback: @escaping (() -> Void), touchMovedCallBack: @escaping (() -> Void), touchUpCallback: @escaping (() -> Void)) {
            self.touchDownCallback = touchDownCallback
            self.touchMovedCallBack = touchMovedCallBack
            self.touchUpCallback = touchUpCallback
        }

        @objc func touchDown(gesture: UITapGestureRecognizer) {
            self.touchDownCallback()
        }

        @objc func touchUp(gesture: UITapGestureRecognizer) {
            self.touchUpCallback()
        }

        @objc func touchMoved(gesture: UITapGestureRecognizer) {
            self.touchMovedCallBack()
        }

        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }

    }

    func makeCoordinator() -> Coordinator {
        Coordinator(touchDownCallback: touchDownCallBack, touchMovedCallBack: touchMovedCallBack, touchUpCallback: touchUpCallBack)
    }

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

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .recognized
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
}


class SingleTouchUpGestureRecognizer: UIGestureRecognizer {
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .failed
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .recognized
        }
    }
}

class SingleTouchMovedGestureRecognizer: UIGestureRecognizer {
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .recognized
        }
    }
}

参考

https://stackoverflow.com/questions/62837754/capture-touchdown-location-of-onlongpressgesture-in-swiftui