版本记录
版本号 | 时间 |
---|---|
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的集成,感兴趣的给个赞或者关注~~~
网友评论