SwiftUIでUISwipeGestureRecognizerを使ってスワイプを検出する
SwiftUIにはUISwipeGestureRecognizerに代わるのもがありません。
そのため、SwiftUIでスワイプを実現するには代わりにDragGestureを使うのが一般的なのですが、UISwipeGestureRecognizerとは動作が違ってくるためなんかぎこちない。
あと、縦方向のScrollViewで横方向のスワイプを検出したい時とか、かなーり面倒になってしまう。
そういう時はUIViewControllerRepresentableでUISwipeGestureRecognizerを使ってしまうのが手っ取り早いです。
struct SwipeReader<Body: View>: UIViewControllerRepresentable {
    
    private let viewController: UIViewController
    private let coordinator: Coordinator
    private let label: () -> Body
    
    init(label: @escaping () -> Body) {
        viewController = UIHostingController(rootView: label().ignoresSafeArea())
        viewController.view.backgroundColor = .clear
        coordinator = Coordinator()
        self.label = label
    }
    
    func makeUIViewController(context: Context) -> UIViewController {
        return viewController
    }
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        (uiViewController as? UIHostingController)?.rootView = label().ignoresSafeArea()
    }
    
    class Coordinator {
        var actions = [UInt: (() -> Void)]()
        @objc func onSwipeGesture(gesture: UISwipeGestureRecognizer) {
            actions[gesture.direction.rawValue]?()
        }
    }
    func makeCoordinator() -> Coordinator {
        return coordinator
    }
    func onSwipe(_ direction: UISwipeGestureRecognizer.Direction, action: @escaping () -> Void) -> Self {
        if coordinator.actions[direction.rawValue] == nil {
            let gesture = UISwipeGestureRecognizer(target: coordinator, action: #selector(Coordinator.onSwipeGesture(gesture:)))
            gesture.direction = direction
            viewController.view.addGestureRecognizer(gesture)
        }
        coordinator.actions[direction.rawValue] = action
        return self
    }
}このようなUIViewControllerRepresentableを継承したSwipeReaderというSwiftUIのViewを作ったら、
import SwiftUI
struct ContentView: View {
    @State var text = "Swipe Test"
    var body: some View {
        SwipeReader {
            Text(text)
                .padding()
        }
        .onSwipe(.up, action: { text = "Swipe Up" })
        .onSwipe(.down, action: { text = "Swipe Down" })
        .onSwipe(.left, action: { text = "Swipe Left" })
        .onSwipe(.right, action: { text = "Swipe Right" })
    }
}スワイプを検知したいViewをSwipeReaderで囲って、onSwipe(_ direction: UISwipeGestureRecognizer.Direction, action: @escaping () -> Void)を呼び出してコールバックを登録します。
結構簡単にできるので、もしiOSしか考えなくていいならDragGestureで迷うよりもこの方法使った方がいいんじゃないでしょうか。
ちなみに、ちょいとキャストがあったりするのはignoresSafeArea()をするためです。これがないと親のViewでignoresSafeArea()されていてもSafe Areaの余白が発生してしまいます。(親のViewでignoresSafeArea()されていない場合は親の方で余白が作られているので、UIHostingController内でignoresSafeArea()しても余白が残ります)
コメント