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