Ideal Reality

興味の赴くままに

SwiftUIでUISwipeGestureRecognizerを使ってスワイプを検出する

SwiftUIにはUISwipeGestureRecognizerに代わるのもがありません。

そのため、SwiftUIでスワイプを実現するには代わりにDragGestureを使うのが一般的なのですが、UISwipeGestureRecognizerとは動作が違ってくるためなんかぎこちない。

あと、縦方向のScrollViewで横方向のスワイプを検出したい時とか、かなーり面倒になってしまう。

そういう時はUIViewControllerRepresentableUISwipeGestureRecognizerを使ってしまうのが手っ取り早いです。

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()しても余白が残ります)

スポンサーリンク

コメント

投稿されたコメントはありません

名前

メールアドレス(任意)

コメント

関連する投稿

[SwiftUI]Apple Watchで画面遷移から戻った際にアニメーションが崩壊するのを防ぐ

[Swift]Dictionaryに順番を持たせたい

SwiftUIでステータスバーの色を変える方法4つ

iOS14のUIDatePickerのデザインをカスタマイズする

[iOS, Swift]で画面回転時のアニメーションを無効化する

‘configure(withApplicationID:)’ is deprecatedと言われた