美文网首页android开发技巧
地图和定位相关研究(二) —— Google Maps的集成(二

地图和定位相关研究(二) —— Google Maps的集成(二

作者: 刀客传奇 | 来源:发表于2020-03-14 15:58 被阅读0次

    版本记录

    版本号 时间
    V1.0 2020.03.14 星期六

    前言

    定位和地图是很多App中必有的功能,这里单独抽出来模块一起学习和探讨。感兴趣的可以多指正,大家一起进步。对于苹果地图框架MapKit我已经单独列出来了 - MapKit框架详细解析 - 会随时更新。感兴趣的可以看下。这里只说下三方地图SDK的相关集成和其他相关问题。
    1. 地图和定位相关研究(一) —— Google Maps的集成(一)

    源码

    1. Swift

    首先看下工程目录

    接着看下sb中的内容

    下面就是源码了

    1. AppDelegate.swift
    
    import UIKit
    import GoogleMaps
    
    let googleApiKey = "ENTER_KEY_HERE"
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
      var window: UIWindow?
      
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        GMSServices.provideAPIKey(googleApiKey)
        return true
      }
    }
    
    2. MapViewController.swift
    
    import UIKit
    import GoogleMaps
    
    class MapViewController: UIViewController {
      @IBOutlet private weak var addressLabel: UILabel!
      @IBOutlet private weak var mapView: GMSMapView!
      @IBOutlet private weak var mapCenterPinImage: UIImageView!
      @IBOutlet private weak var pinImageVerticalConstraint: NSLayoutConstraint!
      private var searchedTypes = ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]
      private let locationManager = CLLocationManager()
      private let dataProvider = GoogleDataProvider()
      private let searchRadius: Double = 1000
    }
    
    // MARK: - Lifecycle
    extension MapViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
    
        locationManager.delegate = self
          
        if CLLocationManager.locationServicesEnabled() {
          locationManager.requestLocation()
          mapView.isMyLocationEnabled = true
          mapView.settings.myLocationButton = true
        } else {
          locationManager.requestWhenInUseAuthorization()
        }
        
        mapView.delegate = self
      }
      
      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard
          let navigationController = segue.destination as? UINavigationController,
          let controller = navigationController.topViewController as? TypesTableViewController
          else {
            return
        }
        controller.selectedTypes = searchedTypes
        controller.delegate = self
      }
    }
    
    // MARK: - Actions
    extension MapViewController {
      @IBAction func refreshPlaces(_ sender: Any) {
        fetchPlaces(near: mapView.camera.target)
      }
      
      func fetchPlaces(near coordinate: CLLocationCoordinate2D) {
        mapView.clear()
        
        dataProvider.fetchPlaces(
          near: coordinate,
          radius: searchRadius,
          types: searchedTypes
        ) { places in
          places.forEach { place in
            let marker = PlaceMarker(place: place, availableTypes: self.searchedTypes)
            marker.map = self.mapView
          }
        }
      }
      
      func reverseGeocode(coordinate: CLLocationCoordinate2D) {
        let geocoder = GMSGeocoder()
        
        geocoder.reverseGeocodeCoordinate(coordinate) { response, error in
          self.addressLabel.unlock()
          
          guard
            let address = response?.firstResult(),
            let lines = address.lines
            else {
              return
          }
          
          self.addressLabel.text = lines.joined(separator: "\n")
          
          let labelHeight = self.addressLabel.intrinsicContentSize.height
          let topInset = self.view.safeAreaInsets.top
          self.mapView.padding = UIEdgeInsets(
            top: topInset,
            left: 0,
            bottom: labelHeight,
            right: 0)
          
          UIView.animate(withDuration: 0.25) {
            self.pinImageVerticalConstraint.constant = (labelHeight - topInset) * 0.5
            self.view.layoutIfNeeded()
          }
        }
      }
    }
    
    // MARK: - TypesTableViewControllerDelegate
    extension MapViewController: TypesTableViewControllerDelegate {
      func typesController(_ controller: TypesTableViewController, didSelectTypes types: [String]) {
        searchedTypes = controller.selectedTypes.sorted()
        dismiss(animated: true)
        fetchPlaces(near: mapView.camera.target)
      }
    }
    
    // MARK: - CLLocationManagerDelegate
    extension MapViewController: CLLocationManagerDelegate {
      func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        guard status == .authorizedWhenInUse else {
          return
        }
        
        locationManager.requestLocation()
        mapView.isMyLocationEnabled = true
        mapView.settings.myLocationButton = true
      }
      
      func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else {
          return
        }
        
        mapView.camera = GMSCameraPosition(
          target: location.coordinate,
          zoom: 15,
          bearing: 0,
          viewingAngle: 0)
        fetchPlaces(near: location.coordinate)
      }
      
      func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
      }
    }
    
    // MARK: - GMSMapViewDelegate
    extension MapViewController: GMSMapViewDelegate {
      func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
        reverseGeocode(coordinate: position.target)
      }
      
      func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
        addressLabel.lock()
        
        if gesture {
          mapCenterPinImage.fadeIn(0.25)
          mapView.selectedMarker = nil
        }
      }
      
      func mapView(_ mapView: GMSMapView, markerInfoContents marker: GMSMarker) -> UIView? {
        guard let placeMarker = marker as? PlaceMarker else {
          return nil
        }
        guard let infoView = UIView.viewFromNibName("MarkerInfoView") as? MarkerInfoView else {
          return nil
        }
        
        infoView.nameLabel.text = placeMarker.place.name
        infoView.addressLabel.text = placeMarker.place.address
        
        return infoView
      }
      
      func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
        mapCenterPinImage.fadeOut(0.25)
        return false
      }
      
      func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
        mapCenterPinImage.fadeIn(0.25)
        mapView.selectedMarker = nil
        return false
      }
    }
    
    3. TypesTableViewController.swift
    
    import UIKit
    
    protocol TypesTableViewControllerDelegate: class {
      func typesController(_ controller: TypesTableViewController, didSelectTypes types: [String])
    }
    
    class TypesTableViewController: UITableViewController {
      private let possibleTypesDictionary = ["bakery": "Bakery", "bar": "Bar", "cafe": "Cafe", "grocery_or_supermarket": "Supermarket", "restaurant": "Restaurant"]
      
      private var sortedKeys: [String] {
        return possibleTypesDictionary.keys.sorted()
      }
      
      weak var delegate: TypesTableViewControllerDelegate?
      var selectedTypes: [String] = []
    }
    
    // MARK - Actions
    extension TypesTableViewController {
      @IBAction func donePressed(_ sender: AnyObject) {
        delegate?.typesController(self, didSelectTypes: selectedTypes)
      }
    }
    
    // MARK - UITableView Delegate & Datasource
    extension TypesTableViewController {
      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return possibleTypesDictionary.count
      }
      
      override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TypeCell", for: indexPath)
        let key = sortedKeys[indexPath.row]
        let type = possibleTypesDictionary[key]
        cell.textLabel?.text = type
        cell.imageView?.image = UIImage(named: key)
        cell.accessoryType = selectedTypes.contains(key) ? .checkmark : .none
        return cell
      }
      
      override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let key = sortedKeys[indexPath.row]
        if selectedTypes.contains(key) {
          selectedTypes = selectedTypes.filter({$0 != key})
        } else {
          selectedTypes.append(key)
        }
        
        tableView.reloadData()
      }
    }
    
    4. GoogleDataProvider.swift
    
    import UIKit
    import CoreLocation
    
    typealias PlacesCompletion = ([GooglePlace]) -> Void
    typealias PhotoCompletion = (UIImage?) -> Void
    
    class GoogleDataProvider {
      private var photosDictionary: [String: UIImage] = [:]
      private var placesTask: URLSessionDataTask?
      private var session: URLSession {
        return URLSession.shared
      }
    
      func fetchPlaces(
        near coordinate: CLLocationCoordinate2D,
        radius: Double,
        types:[String],
        completion: @escaping PlacesCompletion
      ) -> Void {
        var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(coordinate)&radius=\(radius)&rankby=prominence&sensor=true&key=\(googleApiKey)"
        let typesString = types.count > 0 ? types.joined(separator: "|") : "food"
        urlString += "&types=\(typesString)"
        urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? urlString
        
        guard let url = URL(string: urlString) else {
          completion([])
          return
        }
        
        if let task = placesTask, task.taskIdentifier > 0 && task.state == .running {
          task.cancel()
        }
        
        placesTask = session.dataTask(with: url) { data, response, _ in
          guard let data = data else {
            DispatchQueue.main.async {
              completion([])
            }
            return
          }
          let decoder = JSONDecoder()
          decoder.keyDecodingStrategy = .convertFromSnakeCase
          guard let placesResponse = try? decoder.decode(GooglePlace.Response.self, from: data) else {
            DispatchQueue.main.async {
              completion([])
            }
            return
          }
          
          if let errorMessage = placesResponse.errorMessage {
            print(errorMessage)
          }
          
          DispatchQueue.main.async {
            completion(placesResponse.results)
          }
        }
        placesTask?.resume()
      }
    }
    
    5. GooglePlace.swift
    
    import UIKit
    import CoreLocation
    
    struct GooglePlace: Codable {
      let name: String
      let address: String
      let types: [String]
      
      private let geometry: Gemoetry
      var coordinate: CLLocationCoordinate2D {
        return CLLocationCoordinate2D(latitude: geometry.location.lat, longitude: geometry.location.lng)
      }
    
      enum CodingKeys: String, CodingKey {
        case name
        case address = "vicinity"
        case types
        case geometry
      }
    }
    
    extension GooglePlace {
      struct Response: Codable {
        let results: [GooglePlace]
        let errorMessage: String?
      }
      
      private struct Gemoetry: Codable {
        let location: Coordinate
      }
      
      private struct Coordinate: Codable {
        let lat: CLLocationDegrees
        let lng: CLLocationDegrees
      }
    }
    
    6. PlaceMarker.swift
    
    import UIKit
    import GoogleMaps
    
    class PlaceMarker: GMSMarker {
      let place: GooglePlace
      
      init(place: GooglePlace, availableTypes: [String]) {
        self.place = place
        super.init()
        
        position = place.coordinate
        groundAnchor = CGPoint(x: 0.5, y: 1)
        appearAnimation = .pop
        
        var foundType = "restaurant"
        let possibleTypes = availableTypes.count > 0 ? availableTypes : ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]
        for type in place.types {
          if possibleTypes.contains(type) {
            foundType = type
            break
          }
        }
        icon = UIImage(named: foundType+"_pin")
      }
    }
    
    7. Metadata.swift
    
    import Foundation
    
    enum Metadata: Codable, Equatable {
      case string(String)
      case int(Int)
      case double(Double)
      case bool(Bool)
      case object([String: Metadata])
      case array([Metadata])
      case null
      
      init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(String.self) {
          self = .string(value)
        } else if let value = try? container.decode(Int.self) {
          self = .int(value)
        } else if let value = try? container.decode(Double.self) {
          self = .double(value)
        } else if let value = try? container.decode(Bool.self) {
          self = .bool(value)
        } else if let value = try? container.decode([String: Metadata].self) {
          self = .object(value)
        } else if let value = try? container.decode([Metadata].self) {
          self = .array(value)
        } else if container.decodeNil() {
          self = .null
        } else {
          throw DecodingError.dataCorrupted(DecodingError.Context(
            codingPath: decoder.codingPath,
            debugDescription: "Invalid JSON"))
        }
      }
      
      func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let string):
          try container.encode(string)
        case .int(let int):
          try container.encode(int)
        case .double(let double):
          try container.encode(double)
        case .bool(let bool):
          try container.encode(bool)
        case .object(let object):
          try container.encode(object)
        case .array(let array):
          try container.encode(array)
        case .null:
          try container.encodeNil()
        }
      }
      
      var stringValue: String? {
        switch self {
        case .string(let string):
          return string
        default:
          return nil
        }
      }
      
      var intValue: Int? {
        switch self {
        case .int(let int):
          return int
        default:
          return nil
        }
      }
      
      var doubleValue: Double? {
        switch self {
        case .double(let double):
          return double
        default:
          return nil
        }
      }
      
      var boolValue: Bool? {
        switch self {
        case .bool(let bool):
          return bool
        default:
          return nil
        }
      }
      
      var array: [Metadata]? {
        switch self {
        case .array(let array):
          return array
        default:
          return nil
        }
      }
      
      var object: [String : Metadata]? {
        switch self {
        case .object(let object):
          return object
        default:
          return nil
        }
      }
    }
    
    8. CLLocationCoordinate+Extensions.swift
    
    import CoreLocation
    
    extension CLLocationCoordinate2D: CustomStringConvertible {
      public var description: String {
        let lat = String(format: "%.6f", latitude)
        let lng = String(format: "%.6f", longitude)
        return "\(lat),\(lng)"
      }
    }
    
    9. MarkerInfoView.swift
    
    import UIKit
    
    class MarkerInfoView: UIView {
      @IBOutlet weak var nameLabel: UILabel!
      @IBOutlet weak var addressLabel: UILabel!
    }
    
    10. UIView+Extensions.swift
    
    import UIKit
    
    extension UIView {
      func lock() {
        if let _ = viewWithTag(10) {
          //View is already locked
        }
        else {
          let lockView = UIView(frame: bounds)
          lockView.backgroundColor = UIColor(white: 0.0, alpha: 0.75)
          lockView.tag = 10
          lockView.alpha = 0.0
          let activity = UIActivityIndicatorView(style: .medium)
          activity.color = .white
          activity.hidesWhenStopped = true
          activity.center = lockView.center
          lockView.addSubview(activity)
          activity.startAnimating()
          addSubview(lockView)
          
          UIView.animate(withDuration: 0.2) {
            lockView.alpha = 1.0
          }
        }
      }
      
      func unlock() {
        if let lockView = viewWithTag(10) {
          UIView.animate(withDuration: 0.2, animations: {
            lockView.alpha = 0.0
          }, completion: { finished in
            lockView.removeFromSuperview()
          })
        }
      }
      
      func fadeOut(_ duration: TimeInterval) {
        UIView.animate(withDuration: duration) {
          self.alpha = 0.0
        }
      }
      
      func fadeIn(_ duration: TimeInterval) {
        UIView.animate(withDuration: duration) {
          self.alpha = 1.0
        }
      }
      
      class func viewFromNibName(_ name: String) -> UIView? {
        let views = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
        return views?.first as? UIView
      }
    }
    

    后记

    本篇主要讲述了Google Maps的集成,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:地图和定位相关研究(二) —— Google Maps的集成(二

        本文链接:https://www.haomeiwen.com/subject/aajwshtx.html