有人可以使用 MapKit+LongPress 吗?

发布于 2025-01-12 18:12:30 字数 532 浏览 0 评论 0原文

我正在尝试让 SwiftUI + MapKit + LongPress 手势正常工作。当我在 ContentView 中添加地图时,效果很好。然后,我将 .onLongPressGesture 修改器添加到地图中,平移/缩放停止工作。长按虽然有效。

我正在处理的代码:

Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
  .onLongPressGesture {
    // How do I get the location (Lat/Long) I am pressed on?
    print("onLongPressGesture")
  }

另外,有没有办法从长按手势获取纬度/经度?

希望仅使用 SwiftUI 使其工作,而不将 UIKit 包装在 UIViewRepresentable 中。

I am trying to get SwiftUI + MapKit + LongPress gesture working. When I add the map within the ContentView, it works great. I then add the .onLongPressGesture modifier to the map, and the panning/zooming stops working. Long press works though.

The code I am working on:

Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
  .onLongPressGesture {
    // How do I get the location (Lat/Long) I am pressed on?
    print("onLongPressGesture")
  }

Also, is there a way to get the lat/long from the long press gesture?

Looking to make it work using SwiftUI only, without wrapping UIKit within a UIViewRepresentable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

醉酒的小男人 2025-01-19 18:12:30

不要问为什么,但这似乎有效:

    Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
        .gesture(DragGesture())
        .onLongPressGesture {
            print("Here!")
        }

Don't ask why but this seems to work:

    Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
        .gesture(DragGesture())
        .onLongPressGesture {
            print("Here!")
        }
浊酒尽余欢 2025-01-19 18:12:30

以下是我如何获取 LongPressGesture 的位置,同时保留地图初始值设定项的 selection 参数的功能(即现有地图对象上不允许长按):

struct MapLongPressTestView: View {
    @State private var selectedMapItemTag: String? = nil
    private let randomCoordinate = CLLocationCoordinate2D(latitude: .random(in: -90...90), longitude: .random(in: -180...180))

    var body: some View {
        MapReader { proxy in
            Map(selection: $selectedMapItemTag) {
                // Your Annotations, Markers, Shapes, & other map content

                // Selectable Test Example
                Annotation(coordinate: randomCoordinate) {
                    Image(systemName: "water.waves")
                        .foregroundStyle(.orange)
                } label: {
                    Text("Water, most likely")
                }
                .tag("example")
            }
            .gesture(DragGesture())
            .gesture(
                LongPressGesture(minimumDuration: 1, maximumDistance: 0)
                    .sequenced(before: SpatialTapGesture(coordinateSpace: .local))
                    .onEnded { value in
                        switch value {
                        case let .second(_, tapValue):
                            guard let point = tapValue?.location else {
                                print("Unable to retreive tap location from gesture data.")
                                return
                            }
                            
                            guard let coordinate = proxy.convert(point, from: .local) else {
                                print("Unable to convert local point to coordinate on map.")
                                return
                            }

                            print("Long press occured at: \(coordinate)")
                        default: return
                        }
                    }
            )
        }
        .onChange(of: selectedMapItemTag) {
            print(selectedMapItemTag.map { "\($0) is selected" } ?? "No selection")
        }
    }
}

或者,这里是对Map 类型以减少样板:

extension Map {
    func onLongPressGesture(minimumDuration: Double = 0, maximumDistance: CGFloat = 0, onTouchUp: @escaping (CGPoint) -> Void) -> some View {
        self
            .gesture(DragGesture())
            .gesture(
                LongPressGesture(minimumDuration: minimumDuration, maximumDistance: maximumDistance)
                    .sequenced(before: SpatialTapGesture(coordinateSpace: .local))
                    .onEnded { value in
                        switch value {
                        case .second(_, let tapValue):
                            guard let point = tapValue?.location else {
                                print("Unable to retreive tap location from gesture data.")
                                return
                            }

                            onTouchUp(point)
                        default: return
                        }
                    }
            )
    }
}

它的使用方式如下:

MapReader { proxy in
    Map(selection: $selectedMapItemTag) {
        // ...
    }
    .onLongPressGesture(minimumDuration: 1) { point in
        if let coordinate = proxy.convert(point, from: .local) {
            print("Long press occured at: \(coordinate)")
        }
    }
}

SpacialTapGesture 从 iOS 16+ 开始可用。

Here's how I was able to get the location of a LongPressGesture, while also preserving the functionality of the Map initializer's selection parameter (i.e. long pressing not allowed on existing map objects):

struct MapLongPressTestView: View {
    @State private var selectedMapItemTag: String? = nil
    private let randomCoordinate = CLLocationCoordinate2D(latitude: .random(in: -90...90), longitude: .random(in: -180...180))

    var body: some View {
        MapReader { proxy in
            Map(selection: $selectedMapItemTag) {
                // Your Annotations, Markers, Shapes, & other map content

                // Selectable Test Example
                Annotation(coordinate: randomCoordinate) {
                    Image(systemName: "water.waves")
                        .foregroundStyle(.orange)
                } label: {
                    Text("Water, most likely")
                }
                .tag("example")
            }
            .gesture(DragGesture())
            .gesture(
                LongPressGesture(minimumDuration: 1, maximumDistance: 0)
                    .sequenced(before: SpatialTapGesture(coordinateSpace: .local))
                    .onEnded { value in
                        switch value {
                        case let .second(_, tapValue):
                            guard let point = tapValue?.location else {
                                print("Unable to retreive tap location from gesture data.")
                                return
                            }
                            
                            guard let coordinate = proxy.convert(point, from: .local) else {
                                print("Unable to convert local point to coordinate on map.")
                                return
                            }

                            print("Long press occured at: \(coordinate)")
                        default: return
                        }
                    }
            )
        }
        .onChange(of: selectedMapItemTag) {
            print(selectedMapItemTag.map { "\($0) is selected" } ?? "No selection")
        }
    }
}

Alternatively, here's an extension on the Map type to reduce boilerplate:

extension Map {
    func onLongPressGesture(minimumDuration: Double = 0, maximumDistance: CGFloat = 0, onTouchUp: @escaping (CGPoint) -> Void) -> some View {
        self
            .gesture(DragGesture())
            .gesture(
                LongPressGesture(minimumDuration: minimumDuration, maximumDistance: maximumDistance)
                    .sequenced(before: SpatialTapGesture(coordinateSpace: .local))
                    .onEnded { value in
                        switch value {
                        case .second(_, let tapValue):
                            guard let point = tapValue?.location else {
                                print("Unable to retreive tap location from gesture data.")
                                return
                            }

                            onTouchUp(point)
                        default: return
                        }
                    }
            )
    }
}

It would be used like so:

MapReader { proxy in
    Map(selection: $selectedMapItemTag) {
        // ...
    }
    .onLongPressGesture(minimumDuration: 1) { point in
        if let coordinate = proxy.convert(point, from: .local) {
            print("Long press occured at: \(coordinate)")
        }
    }
}

SpacialTapGesture is available from iOS 16+.

挽心 2025-01-19 18:12:30

iOS 18

iOS 18 开始,我们有 UIGestureRecognizerRepresentable 我们可以使用任何 UIGestureRecognizerSwiftUI 中的 UILongPressGestureRecognizer

示例

地图

MapReader { proxy in
    Map()
        .gesture(MyLongPressGesture { position in
            let coordinate = proxy.convert(position, from: .global)
        })
}

手势识别器

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))
    }
}

最后注释

请记住,截至 2024 年 6 月,这仍处于测试阶段,可能会很快发生变化。

有关此内容的更多详细信息:WWDC 24 SwiftUI 中的新增功能

iOS 18

Starting on iOS 18 we have UIGestureRecognizerRepresentable and we can use any UIGestureRecognizer like UILongPressGestureRecognizer in SwiftUI.

Example

The map

MapReader { proxy in
    Map()
        .gesture(MyLongPressGesture { position in
            let coordinate = proxy.convert(position, from: .global)
        })
}

The gesture recognizer

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))
    }
}

Final notes

Remember as of June 2024 this is still in Beta and may change quickly.

For more details on this: WWDC 24 What's new in SwiftUI

悲歌长辞 2025-01-19 18:12:30

是的。

我将 MKMapView 包裹在 UIViewRepresentable 中,并在委托方法中添加了长按手势识别器,如下所示:

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
            // We need to update the region when the user changes it
            // otherwise when we zoom the mapview will return to its original region
            DispatchQueue.main.async {
                if self.longPress == nil {
                    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(recognizer:)))
                    mapView.addGestureRecognizer(recognizer)
                    self.longPress = recognizer
                }
                self.parent.region = mapView.region
            }
        }

然后

    @objc func longPressGesture(recognizer: UILongPressGestureRecognizer) {
        if let mapView = recognizer.view as? MKMapView {
            let touchPoint = recognizer.location(in: mapView)
            let touchMapCoordinate =  mapView.convert(touchPoint, toCoordinateFrom: mapView)
            
            // TODO: check if we already have this location and bail out
            if annotations.count > 0 {
                return
            }
            
            let annotation = Annotation(title: touchMapCoordinate.stringValue, coordinate: touchMapCoordinate)
            mapView.removeAnnotations(annotations.compactMap({ $0.pointAnnotation }))
            annotations.append(annotation)
            mapView.addAnnotation(annotation.pointAnnotation)
        }
    }

使用此答案中的代码: https://stackoverflow.com/a/71698741/1320010

Yep.

I wrapped MKMapView in UIViewRepresentable and added long press gesture recognizer in the delegate method like this:

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
            // We need to update the region when the user changes it
            // otherwise when we zoom the mapview will return to its original region
            DispatchQueue.main.async {
                if self.longPress == nil {
                    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(recognizer:)))
                    mapView.addGestureRecognizer(recognizer)
                    self.longPress = recognizer
                }
                self.parent.region = mapView.region
            }
        }

Then

    @objc func longPressGesture(recognizer: UILongPressGestureRecognizer) {
        if let mapView = recognizer.view as? MKMapView {
            let touchPoint = recognizer.location(in: mapView)
            let touchMapCoordinate =  mapView.convert(touchPoint, toCoordinateFrom: mapView)
            
            // TODO: check if we already have this location and bail out
            if annotations.count > 0 {
                return
            }
            
            let annotation = Annotation(title: touchMapCoordinate.stringValue, coordinate: touchMapCoordinate)
            mapView.removeAnnotations(annotations.compactMap({ $0.pointAnnotation }))
            annotations.append(annotation)
            mapView.addAnnotation(annotation.pointAnnotation)
        }
    }

Using the code from this answer: https://stackoverflow.com/a/71698741/1320010

转瞬即逝 2025-01-19 18:12:30

要回答问题的最后一部分 - Paul Hudson 展示了如何在 此页面 [编辑 - 长按似乎不起作用]

    MapReader { proxy in    
        Map()    
            .onTapGesture { position in    
                if let coordinate = proxy.convert(position, from: .local) {    
                    print(coordinate)    
                }    
            }    
    }    

To answer the last part of your question - Paul Hudson shows how to get the tap location in lat/long on this page [EDIT - doesn't seem to work for long press]

    MapReader { proxy in    
        Map()    
            .onTapGesture { position in    
                if let coordinate = proxy.convert(position, from: .local) {    
                    print(coordinate)    
                }    
            }    
    }    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文