如何在当前位置设置 pin,将 pin 用作地理围栏,提醒用户 onEnter,然后全部重置/重新开始?

Posted

技术标签:

【中文标题】如何在当前位置设置 pin,将 pin 用作地理围栏,提醒用户 onEnter,然后全部重置/重新开始?【英文标题】:How do I set a pin on Current Location, use Pin as Geofence, alert user onEnter, then reset all / start over again? 【发布时间】:2021-07-03 19:05:54 【问题描述】:

我是一名产品设计师,我试图提出一个基于位置的提醒概念来验证一个相当早期的想法。我可以在 Figma 中对所有这些屏幕进行原型制作,但实时位置和上下文警报是我假设的基础,所以我在这里竭尽全力尝试在 SwiftUI 中实现这一点????

https://www.figma.com/proto/jxYwPbqe4oqsXBbf6CZDDi/Location-Reminder-Spec?page-id=0%3A1&node-id=1%3A189&viewport=161%2C384%2C0.12701110541820526&scaling=scale-down

我一直在从各种教程(Ray Wenderlich、Hacking Swift、Apple 的 Swift UI)和关于 Location 的 sn-ps 等中拼凑起来,但大多数教程都有更多的对象和视图以及其他我不知道的东西'不需要这使我斗志昂扬的 MVP 拼贴方法变得复杂。

到目前为止,我的 MKMapView 以我的用户位置坐标为中心,并显示了我的按钮。

我想知道

如何使用来自用户位置的坐标创建一个 CLCircularRegion?

如何使用来自用户位置的坐标在地图上放置 MKMapAnnotation,以及如何在按下通知内的按钮后删除注释?

通过以上内容,我想我将能够理清如何触发通知并在 Enter 上显示通知。

到目前为止,我的项目中有 4 个文件...

LocationManager.swift

import Foundation
import MapKit
import CoreLocation

class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject 
    @Published var region = MKCoordinateRegion()
    private let manager = CLLocationManager()
        override init() 
                super.init()
                manager.delegate = self
                manager.desiredAccuracy = kCLLocationAccuracyBest
                manager.requestWhenInUseAuthorization()
                manager.startUpdatingLocation()
        
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
        locations.last.map
            region = MKCoordinateRegion(
                center: CLLocationCoordinate2D(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude),
                span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
            )
        
    


extension CLLocation 
    var latitude: Double 
        return self.coordinate.latitude
    
    
    var longitude: Double 
        return self.coordinate.longitude
    

ContentView.swift

import CoreLocation
import MapKit
import SwiftUI

struct ContentView: View 
    @StateObject var manager = LocationManager()
    @State private var locations = [MKPointAnnotation] ()
    
    
var body: some View 
    VStack 
        HStack 
            Text("Location Reminder")
                .font(.largeTitle)
                .bold()
                .foregroundColor(.black)
            Spacer()
        
        .padding()

        ZStack (alignment: .bottomTrailing) 
            Map(coordinateRegion: $manager.region,
                showsUserLocation: true)
                .ignoresSafeArea()
            ReminderButton()
                .padding()
            
        
    



struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

ReminderButton.swift

import Foundation
import SwiftUI

struct ReminderButton: View 
    
    var body: some View 
        VStack 
            Spacer()
                Button(action: 
            ) 
                HStack 
                    Image(systemName: "plus.circle.fill")
                        .resizable()
                        .frame(width: 40, height: 40)
                    Text("Add a Reminder")
                
                .padding()
                .background(Color.white)
                .cornerRadius(40)
                .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
            
        
    


我有一个名为 Geofence.swift 的空文件,我计划在其中创建一个 CLCircularRegion 并设置通知/警报 onEnter 等。

感谢所有帮助/建议/建议。如果有办法改进我的帖子和/或它是否属于其他地方,也请 LMK。

【问题讨论】:

【参考方案1】:
import SwiftUI
import MapKit
import CoreLocation
struct SingleLocationView: View 
    @StateObject var manager = LocationManager()
    @State private var locations = [PinnedLocation] ()
    @State var userTrackingMode: MapUserTrackingMode = .follow
    let radius: CGFloat = 160 //meters approx 0.1 miles
    var body: some View 
        
        VStack 
            HStack 
                VStack
                    Text("Location Reminder")
                        .font(.largeTitle)
                        .bold()
                        .foregroundColor(.black)
                
                Spacer()
                
            
            .padding()
            
            ZStack (alignment: .bottomTrailing) 
                GeometryReader geo in
                    Map(coordinateRegion: $manager.region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $userTrackingMode, annotationItems: locations, annotationContent:  annotation in
                        MapAnnotation(coordinate: annotation.coordinate, anchorPoint: CGPoint(x: 0.5, y: 0.5), content: 
                            //Use this vs Circle because your map is a rectangle circumference is an oval
                            //Maybe a stretched Circle could be a little more accurate
                            RoundedRectangle(cornerRadius: 25)
                                .foregroundColor(.white)
                            
                                .overlay(
                                    Image(systemName: "mappin").foregroundColor(.red))
                            
                            // The screen vertical and horizontal multiplied by your radius divided Map vertial/horizontal span in meters
                                .frame(width: (geo.size.width/manager.regionSpanInLongitudeDeltaMeters.value) * radius, height: (geo.size.height/manager.regionSpanInLatitudeDeltaMeters.value) * radius, alignment: .center)
                            
                            
                        )
                    )
                    
                        .ignoresSafeArea()
                    
                
                ReminderButton(locations: $locations).environmentObject(manager)
                    .padding()
            
        
    


struct SingleLocationView_Previews: PreviewProvider 
    static var previews: some View 
        SingleLocationView()
    


//You MKAnnotaion is a Protocol you need to create your own class
//MKPointAnnotation is a MKShape for use with MKMapView
class PinnedLocation: NSObject, MKAnnotation, Identifiable
    var title: String?
    var subtitle: String?
    var coordinate: CLLocationCoordinate2D
    
    init(title: String? = nil, subtitle: String? = nil, coordinate: CLLocationCoordinate2D) 
        self.title = title
        self.subtitle = subtitle
        self.coordinate = coordinate
    
    ///Get the CLCircularRegion with the given radius
    func getCLCircularRegion(radius: CGFloat) -> CLCircularRegion
        CLCircularRegion(center: self.coordinate, radius: radius, identifier: "\(self.id)")
    


extension UnitLength
    //This is approx do research on degrees to meters
    static let degree = UnitLength(symbol: "degree", converter: UnitConverterLinear(coefficient: 111111))

class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject 
    @Published var region = MKCoordinateRegion()
    //If you do it the way you were doing it get the most current location from here to append to your array
    @Published var lastLocation: CLLocation = CLLocation()
    ///Region gives span in degrees. This is a rough conversion
    var regionSpanInLatitudeDeltaMeters: Measurement<UnitLength>
        Measurement(value: region.span.latitudeDelta, unit: UnitLength.degree).converted(to: .meters)
    
    ///Region gives span in degrees. This is a rough conversion
    var regionSpanInLongitudeDeltaMeters: Measurement<UnitLength>
        Measurement(value: region.span.longitudeDelta, unit: UnitLength.degree).converted(to: .meters)
    
    private let manager = CLLocationManager()
    override init() 
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        //Don't need it SwiftUI does this for you
        //manager.requestWhenInUseAuthorization()
        //manager.startUpdatingLocation()
    
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
        locations.last.map
            region = MKCoordinateRegion(
                center: CLLocationCoordinate2D(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude),
                span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
            )
        
        lastLocation = locations.last!
        
    


extension CLLocationCoordinate2D
    var annotation: PinnedLocation
        PinnedLocation(coordinate: CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude))
    

struct ReminderButton: View 
    //The button joins your location manager with the locations displayed. It needs access to both
    @EnvironmentObject var manager: LocationManager
    @Binding var locations: [PinnedLocation]
    
    var body: some View 
        VStack 
            Spacer()
            Button(action: 
                //Append the current center/location to your list of annotions
                locations.append(manager.region.center.annotation)
            ) 
                HStack 
                    Image(systemName: "plus.circle.fill")
                        .resizable()
                        .frame(width: 40, height: 40)
                    Text("Add a Reminder")
                
                .padding()
                .background(Color.white)
                .cornerRadius(40)
                .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
            
        
    

【讨论】:

以上是关于如何在当前位置设置 pin,将 pin 用作地理围栏,提醒用户 onEnter,然后全部重置/重新开始?的主要内容,如果未能解决你的问题,请参考以下文章

在当前用户位置放置 pin iphone mkmapview

在 Swift 中使用 MKLocalSearch 将 pin 附加到用户位置而不是蓝点

为啥我无法为注释的字幕设置 pin 地址?

在java中生成6位pin

如何返回地理位置响应对象以用作另一个函数的参数

如何将不需要pin的蓝牙设备与android配对