版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.12.12 星期六 |
前言
很多的app都有定位功能,比如说滴滴,美团等,他们都需要获取客户所在的位置,并且根据位置推送不同的模块数据以及服务,可以说,定位方便了我们的生活,接下来这几篇我们就说一下定位框架
CoreLocation
。感兴趣的可以看我写的上面几篇。
1. CoreLocation框架详细解析 —— 基本概览(一)
2. CoreLocation框架详细解析 —— 选择定位服务的授权级别(二)
3. CoreLocation框架详细解析 —— 确定定位服务的可用性(三)
4. CoreLocation框架详细解析 —— 获取用户位置(四)
5. CoreLocation框架详细解析 —— 监控用户与地理区域的距离(五)
6. CoreLocation框架详细解析 —— 确定接近iBeacon(六)
7. CoreLocation框架详细解析 —— 将iOS设备转换为iBeacon(七)
8. CoreLocation框架详细解析 —— 获取指向和路线信息(八)
9. CoreLocation框架详细解析 —— 在坐标和用户友好的地名之间转换(九)
10. CoreLocation框架详细解析(十) —— 跟踪访问位置简单示例(一)
11. CoreLocation框架详细解析(十一) —— 跟踪访问位置简单示例(二)
12. CoreLocation框架详细解析(十二) —— 仿Runkeeper的简单实现(一)
13. CoreLocation框架详细解析(十三) —— 仿Runkeeper的简单实现(二)
14. CoreLocation框架详细解析(十四) —— 仿Runkeeper的简单实现(三)
15. CoreLocation框架详细解析(十五) —— 仿Runkeeper的简单实现(四)
16. CoreLocation框架详细解析(十六) —— 基于Core Location地理围栏的实现(一)
源码
1. Swift
首先看下工程组织结构
然后再看下sb中的内容:
下面就是源码啦
1. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let options: UNAuthorizationOptions = [.badge, .sound, .alert]
UNUserNotificationCenter.current().requestAuthorization(options: options) { _, error in
if let error = error {
print("Error: \(error)")
}
}
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
application.applicationIconBadgeNumber = 0
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
}
}
2. SceneDelegate.swift
import UIKit
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let locationManager = CLLocationManager()
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
}
// MARK: - Location Manager Delegate
extension SceneDelegate: CLLocationManagerDelegate {
func locationManager(
_ manager: CLLocationManager,
didEnterRegion region: CLRegion
) {
if region is CLCircularRegion {
handleEvent(for: region)
}
}
func locationManager(
_ manager: CLLocationManager,
didExitRegion region: CLRegion
) {
if region is CLCircularRegion {
handleEvent(for: region)
}
}
func handleEvent(for region: CLRegion) {
// Show an alert if application is active
if UIApplication.shared.applicationState == .active {
guard let message = note(from: region.identifier) else { return }
window?.rootViewController?.showAlert(withTitle: nil, message: message)
} else {
// Otherwise present a local notification
guard let body = note(from: region.identifier) else { return }
let notificationContent = UNMutableNotificationContent()
notificationContent.body = body
notificationContent.sound = .default
notificationContent.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(
identifier: "location_change",
content: notificationContent,
trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error: \(error)")
}
}
}
}
func note(from identifier: String) -> String? {
let geotifications = Geotification.allGeotifications()
let matched = geotifications.first { $0.identifier == identifier }
return matched?.note
}
}
3. GeotificationsViewController.swift
import UIKit
import MapKit
import CoreLocation
enum PreferencesKeys: String {
case savedItems
}
class GeotificationsViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
var geotifications: [Geotification] = []
lazy var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// 1
locationManager.delegate = self
// 2
locationManager.requestAlwaysAuthorization()
// 3
loadAllGeotifications()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addGeotification",
let navigationController = segue.destination as? UINavigationController,
let addVC = navigationController.viewControllers.first as? AddGeotificationViewController {
addVC.delegate = self
}
}
// MARK: Loading and saving functions
func loadAllGeotifications() {
geotifications.removeAll()
let allGeotifications = Geotification.allGeotifications()
allGeotifications.forEach { add($0) }
}
func saveAllGeotifications() {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(geotifications)
UserDefaults.standard.set(data, forKey: PreferencesKeys.savedItems.rawValue)
} catch {
print("error encoding geotifications")
}
}
// MARK: Functions that update the model/associated views with geotification changes
func add(_ geotification: Geotification) {
geotifications.append(geotification)
mapView.addAnnotation(geotification)
addRadiusOverlay(forGeotification: geotification)
updateGeotificationsCount()
}
func remove(_ geotification: Geotification) {
guard let index = geotifications.firstIndex(of: geotification) else { return }
geotifications.remove(at: index)
mapView.removeAnnotation(geotification)
removeRadiusOverlay(forGeotification: geotification)
updateGeotificationsCount()
}
func updateGeotificationsCount() {
title = "Geotifications: \(geotifications.count)"
navigationItem.rightBarButtonItem?.isEnabled = (geotifications.count < 20)
}
// MARK: Map overlay functions
func addRadiusOverlay(forGeotification geotification: Geotification) {
mapView.addOverlay(MKCircle(center: geotification.coordinate, radius: geotification.radius))
}
func removeRadiusOverlay(forGeotification geotification: Geotification) {
// Find exactly one overlay which has the same coordinates & radius to remove
guard let overlays = mapView?.overlays else { return }
for overlay in overlays {
guard let circleOverlay = overlay as? MKCircle else { continue }
let coord = circleOverlay.coordinate
if coord.latitude == geotification.coordinate.latitude &&
coord.longitude == geotification.coordinate.longitude &&
circleOverlay.radius == geotification.radius {
mapView.removeOverlay(circleOverlay)
break
}
}
}
// MARK: Other mapview functions
@IBAction func zoomToCurrentLocation(sender: AnyObject) {
mapView.zoomToLocation(mapView.userLocation.location)
}
// MARK: Put monitoring functions below
func startMonitoring(geotification: Geotification) {
// 1
if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
showAlert(
withTitle: "Error",
message: "Geofencing is not supported on this device!")
return
}
// 2
let fenceRegion = geotification.region
locationManager.startMonitoring(for: fenceRegion)
}
func stopMonitoring(geotification: Geotification) {
for region in locationManager.monitoredRegions {
guard
let circularRegion = region as? CLCircularRegion,
circularRegion.identifier == geotification.identifier
else { continue }
locationManager.stopMonitoring(for: circularRegion)
}
}
}
// MARK: AddGeotificationViewControllerDelegate
extension GeotificationsViewController: AddGeotificationsViewControllerDelegate {
func addGeotificationViewController(
_ controller: AddGeotificationViewController,
didAddGeotification geotification: Geotification
) {
controller.dismiss(animated: true, completion: nil)
// 1
geotification.clampRadius(maxRadius:
locationManager.maximumRegionMonitoringDistance)
add(geotification)
// 2
startMonitoring(geotification: geotification)
saveAllGeotifications()
}
}
// MARK: - Location Manager Delegate
extension GeotificationsViewController: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// 1
let status = manager.authorizationStatus
// 2
mapView.showsUserLocation = (status == .authorizedAlways)
// 3
if status != .authorizedAlways {
let message = """
Your geotification is saved but will only be activated once you grant
Geotify permission to access the device location.
"""
showAlert(withTitle: "Warning", message: message)
}
}
func locationManager(
_ manager: CLLocationManager,
monitoringDidFailFor region: CLRegion?,
withError error: Error
) {
guard let region = region else {
print("Monitoring failed for unknown region")
return
}
print("Monitoring failed for region with identifier: \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location Manager failed with the following error: \(error)")
}
}
// MARK: - MapView Delegate
extension GeotificationsViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "myGeotification"
if annotation is Geotification {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
let removeButton = UIButton(type: .custom)
removeButton.frame = CGRect(x: 0, y: 0, width: 23, height: 23)
removeButton.setImage(UIImage(systemName: "trash.fill"), for: .normal)
annotationView?.leftCalloutAccessoryView = removeButton
} else {
annotationView?.annotation = annotation
}
return annotationView
}
return nil
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKCircle {
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.lineWidth = 1.0
circleRenderer.strokeColor = .purple
circleRenderer.fillColor = UIColor.purple.withAlphaComponent(0.4)
return circleRenderer
}
return MKOverlayRenderer(overlay: overlay)
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
// Delete geotification
guard let geotification = view.annotation as? Geotification else { return }
stopMonitoring(geotification: geotification)
remove(geotification)
saveAllGeotifications()
}
}
4. AddGeotificationViewController.swift
import UIKit
import MapKit
protocol AddGeotificationsViewControllerDelegate: class {
func addGeotificationViewController(_ controller: AddGeotificationViewController, didAddGeotification: Geotification)
}
class AddGeotificationViewController: UITableViewController {
@IBOutlet var addButton: UIBarButtonItem!
@IBOutlet var zoomButton: UIBarButtonItem!
@IBOutlet weak var eventTypeSegmentedControl: UISegmentedControl!
@IBOutlet weak var radiusTextField: UITextField!
@IBOutlet weak var noteTextField: UITextField!
@IBOutlet weak var mapView: MKMapView!
weak var delegate: AddGeotificationsViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItems = [addButton, zoomButton]
addButton.isEnabled = false
}
@IBAction func textFieldEditingChanged(sender: UITextField) {
let radiusSet = !(radiusTextField.text?.isEmpty ?? true)
let noteSet = !(noteTextField.text?.isEmpty ?? true)
addButton.isEnabled = radiusSet && noteSet
}
@IBAction func onCancel(sender: AnyObject) {
dismiss(animated: true, completion: nil)
}
@IBAction private func onAdd(sender: AnyObject) {
let coordinate = mapView.centerCoordinate
let radius = Double(radiusTextField.text ?? "") ?? 0
let identifier = NSUUID().uuidString
let note = noteTextField.text ?? ""
let eventType: Geotification.EventType = (eventTypeSegmentedControl.selectedSegmentIndex == 0) ? .onEntry : .onExit
let geotification = Geotification(
coordinate: coordinate,
radius: radius,
identifier: identifier,
note: note,
eventType: eventType)
delegate?.addGeotificationViewController(self, didAddGeotification: geotification)
}
@IBAction private func onZoomToCurrentLocation(sender: AnyObject) {
mapView.zoomToLocation(mapView.userLocation.location)
}
}
5. Geotification.swift
import UIKit
import MapKit
import CoreLocation
class Geotification: NSObject, Codable, MKAnnotation {
enum EventType: String {
case onEntry = "On Entry"
case onExit = "On Exit"
}
enum CodingKeys: String, CodingKey {
case latitude, longitude, radius, identifier, note, eventType
}
var coordinate: CLLocationCoordinate2D
var radius: CLLocationDistance
var identifier: String
var note: String
var eventType: EventType
var title: String? {
if note.isEmpty {
return "No Note"
}
return note
}
var subtitle: String? {
let eventTypeString = eventType.rawValue
let formatter = MeasurementFormatter()
formatter.unitStyle = .short
formatter.unitOptions = .naturalScale
let radiusString = formatter.string(from: Measurement(value: radius, unit: UnitLength.meters))
return "Radius: \(radiusString) - \(eventTypeString)"
}
func clampRadius(maxRadius: CLLocationDegrees) {
radius = min(radius, maxRadius)
}
init(coordinate: CLLocationCoordinate2D, radius: CLLocationDistance, identifier: String, note: String, eventType: EventType) {
self.coordinate = coordinate
self.radius = radius
self.identifier = identifier
self.note = note
self.eventType = eventType
}
// MARK: Codable
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let latitude = try values.decode(Double.self, forKey: .latitude)
let longitude = try values.decode(Double.self, forKey: .longitude)
coordinate = CLLocationCoordinate2DMake(latitude, longitude)
radius = try values.decode(Double.self, forKey: .radius)
identifier = try values.decode(String.self, forKey: .identifier)
note = try values.decode(String.self, forKey: .note)
let event = try values.decode(String.self, forKey: .eventType)
eventType = EventType(rawValue: event) ?? .onEntry
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(coordinate.latitude, forKey: .latitude)
try container.encode(coordinate.longitude, forKey: .longitude)
try container.encode(radius, forKey: .radius)
try container.encode(identifier, forKey: .identifier)
try container.encode(note, forKey: .note)
try container.encode(eventType.rawValue, forKey: .eventType)
}
}
extension Geotification {
public class func allGeotifications() -> [Geotification] {
guard let savedData = UserDefaults.standard.data(forKey: PreferencesKeys.savedItems.rawValue) else { return [] }
let decoder = JSONDecoder()
if let savedGeotifications = try? decoder.decode(Array.self, from: savedData) as [Geotification] {
return savedGeotifications
}
return []
}
}
// MARK: - Notification Region
extension Geotification {
var region: CLCircularRegion {
// 1
let region = CLCircularRegion(
center: coordinate,
radius: radius,
identifier: identifier)
// 2
region.notifyOnEntry = (eventType == .onEntry)
region.notifyOnExit = !region.notifyOnEntry
return region
}
}
6. Utilities.swift
import UIKit
import MapKit
// MARK: Helper Extensions
extension UIViewController {
func showAlert(withTitle title: String?, message: String?) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
extension MKMapView {
func zoomToLocation(_ location: CLLocation?) {
guard let coordinate = location?.coordinate else { return }
let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 10_000, longitudinalMeters: 10_000)
setRegion(region, animated: true)
}
}
后记
本篇主要讲述了基于
Core Location
地理围栏的实现,感兴趣的给个赞或者关注~~~