📍
SwiftUI MapKit LongPressGesture Detection
SwiftUI MapKit において、地図を長押ししたらそこにピンを立てるということをしたい。
結論
現状は iOS18 以上で独自の LongPressGesture
struct を作って検知するしかない模様。
import SwiftUI
struct MyLongPressGesture: UIGestureRecognizerRepresentable {
private let longPressAt: (_ position: CGPoint) -> Void
init(longPressAt: @escaping (_ position: CGPoint) -> Void) {
self.longPressAt = longPressAt
}
func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer {
UILongPressGestureRecognizer()
}
func handleUIGestureRecognizerAction(_ gesture: UILongPressGestureRecognizer, context: Context) {
guard gesture.state == .began else { return }
longPressAt(gesture.location(in: gesture.view))
}
}
struct MapView: View {
var body: some View {
MapReader { proxy in
Map()
.gesture(MyLongPressGesture { position in
let coordinate = proxy.convert(position, from: .global)
})
}
}
}
updating: 長押しを検知したらピンを立てる
これは結局うまく行かなかった。が .sequenced
の使い方などをメモ。
基本的に .sequenced
を使うと2つの異なるジェスチャー(ここでは長押しとドラッグ)を組み合わせて検知なり実装することが可能なようで、ここで長押しにドラッグを組み合わせているのは長押しした地図上の位置を検出するために drag
が必要だから。
.gesture(
LongPressGesture(minimumDuration: 0.5)
.sequenced(before: DragGesture(minimumDistance: 0))
.updating($isLongPress, body: { value, state, transaction in
switch value { // .first が long press gesture 0.5秒
case .second(true, let drag): // sequenced で定義しているので .second が DragGesture となる
print("drag: ", drag) // FIXME: ここが nil になる。ブレイクポイントで止めたら値は入っているのに
if let location = drag?.location {
if let coordinate = proxy.convert(location, from: .local) {
let placemark = MKPlacemark(coordinate: coordinate)
let item = MKMapItem(placemark: placemark)
viewModel.savedMapItems.append(item)
}
}
default:
break
}
})
)
なお .updating
のコールバックの引数の使い方として、長押しを検知したらリアルタイムで UI をどうこうしたいときに gestureState = currentState
のようにするようだがイマイチピンと来ていない。
struct CounterView: View {
@GestureState private var isDetectingLongPress = false
var body: some View {
let press = LongPressGesture(minimumDuration: 1)
.updating($isDetectingLongPress) { currentState, gestureState, transaction in
gestureState = currentState
}
return Circle()
.fill(isDetectingLongPress ? Color.yellow : Color.green)
.frame(width: 100, height: 100, alignment: .center)
.gesture(press)
}
}
onEnded: 長押しを離したらピンを立てる
.gesture(
LongPressGesture(minimumDuration: 0.5)
.sequenced(before: DragGesture(minimumDistance: 0))
.onEnded { value in // FIXME: change to onStarted
switch value {
case .second(true, let drag):
if let position = drag?.location {
if let coordinate = proxy.convert(position, from: .local) {
let placemark = MKPlacemark(coordinate: coordinate)
let item = MKMapItem(placemark: placemark)
viewModel.savedMapItems.append(item)
}
}
default:
break
}
}
)
Discussion