Tutorial: Making a Kids Maps App for my Daughter using Google Maps

Called How Long Until We’re There?

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '13.0'

target 'YourAppName' do
pod 'GoogleMaps', '7.1.0'
pod 'GooglePlaces', '7.1.0'
end
pod install
import UIKit
import GoogleMaps
import GooglePlaces

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("your_maps_api_key")
GMSPlacesClient.provideAPIKey("your_places_api+key")
return true
}
    <key>NSLocationWhenInUseUsageDescription</key>
<string>Location information is used to get time to destination.</string>
</dict>
</plist>
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate,
func getUserLocation(){
let manager = CLLocationManager()
locationManager.delegate = self
if (manager.authorizationStatus == .authorizedWhenInUse)
|| (manager.authorizationStatus == .authorizedAlways) {
print("location authorised")
locationManager.startUpdatingLocation()
}else{
print("location not authorised")
locationManager.requestWhenInUseAuthorization()
}
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.delegate = nil
locationManager.stopUpdatingLocation()
let location = locations.first!

print("latitude: \(location.coordinate.latitude), longitude: \(location.coordinate.longitude)")

let newLocation = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let newCam = GMSCameraUpdate.setTarget(newLocation)
mapView.animate(with: newCam)
mapView.animate(toZoom: 12)

marker.position = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
marker.icon = UIImage(named: smallCharacterImageArray[selectedCharacter])
marker.map = mapView

sourceCoordinates = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)

if didUserAskForTime {
let calls = NetworkCalls()
calls.delegate = self
//need to get new source before getting distance to destination
if hasDestinationBeenEntered {
calls.postRoute(startCoordinates: sourceCoordinates, destinationCoordinates: destinationCoordinates)
}
}
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
break
default:
print("location permission not granted")
}
}
let newLocation = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let newCam = GMSCameraUpdate.setTarget(newLocation)
mapView.animate(with: newCam)
mapView.animate(toZoom: 12)

marker.position = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
marker.icon = UIImage(named: smallCharacterImageArray[selectedCharacter])
marker.map = mapView
if hasDestinationBeenEntered {
calls.postRoute(startCoordinates: sourceCoordinates, destinationCoordinates: destinationCoordinates)
}
@IBAction func onWhereAmIGoingButton(_ sender: Any) {
if (hasDestinationBeenEntered) {
addMarkerAndMoveToCoordinates()
}else{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self

// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))
autocompleteController.placeFields = fields

// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.types = ["address"]
autocompleteController.autocompleteFilter = filter

// Display the autocomplete view controller.
present(autocompleteController, animated: true, completion: nil)
}
}
func getLatLongFromPlaceId () {
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.coordinate.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))

placesClient?.fetchPlace(fromPlaceID: placeID, placeFields: fields, sessionToken: nil, callback: {
(place: GMSPlace?, error: Error?) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let place = place {
print("The selected place coordinate is: \(place.coordinate)")
self.destinationCoordinates = place.coordinate
self.hasDestinationBeenEntered = true
self.addMarkerAndMoveToCoordinates()
}else{
print("hmm, here");
}
})
}
extension ViewController: GMSAutocompleteViewControllerDelegate {

// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("Place name: \(place.name ?? "placename")")
print("Place ID: \(place.placeID ?? "")")
print("Place attributions: \(String(describing: place.attributions))")
self.placeID = place.placeID ?? "nil"
self.getLatLongFromPlaceId()
self.placeName = (place.name ?? "")
dismiss(animated: true, completion: nil)
}

func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}

// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}

// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}

func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}

}
//
// NetworkCalls.swift
//
// Created by Andy O'Sullivan on 06/10/2022.
//

import Foundation
import CoreLocation

protocol NetworkServiceDelegate {
func didCompleteRouteRequest(result: String)
}
class NetworkCalls {

var delegate: NetworkServiceDelegate?

func postRoute(startCoordinates: CLLocationCoordinate2D, destinationCoordinates: CLLocationCoordinate2D){
print("postRoute called:")
print(startCoordinates)
print(destinationCoordinates)

let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let url = URL(string: "https://routes.googleapis.com/directions/v2:computeRoutes?key=your_routes_api_key&fields=routes.duration,routes.distanceMeters")
var request : URLRequest = URLRequest(url: url!)
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"


let options = ["origin":["location":["latLng":["latitude": startCoordinates.latitude, "longitude": startCoordinates.longitude ]]]
,
"destination":["location":["latLng":["latitude": destinationCoordinates.latitude, "longitude": destinationCoordinates.longitude ]]]]

do {
request.httpBody = try JSONSerialization.data(withJSONObject: options, options: []) // pass dictionary to data object and set it as request body
print("all good: \(String(describing: request.httpBody))")
} catch let error {
print(error.localizedDescription)
print("json error")
}

let jsonData = try! JSONSerialization.data(withJSONObject: options, options: [])
let dataString = String(data: jsonData, encoding: .utf8)!
print(dataString)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// Do something...
guard let httpResponse = response as? HTTPURLResponse, let receivedData = data
else {
print("error: not a valid http response")
return
}
switch (httpResponse.statusCode) {
case 200:
//success response.
print("200 success! \(String(describing: String(data: receivedData, encoding: .utf8)))")
if data != nil {
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
receivedData, options: []) as? [String:Any]



print((jsonResponse!["routes"]! as AnyObject)) //Response result
let routes = jsonResponse!["routes"]
print(routes!)


if let results = jsonResponse!["routes"] as! [Any]?{
for result in results {
print(result)
if let locationDictionary = result as? [String : Any] {
print(locationDictionary["duration"]!)
let temp = locationDictionary["duration"]
self.delegate?.didCompleteRouteRequest(result: temp as! String)
}

}

}
} catch let parsingError {
print("Error", parsingError)
}

}
//self.delegate?.didCompleteRouteRequest(result: "whatevs")
break
case 400:
print("400 no! \(String(describing: String(data: receivedData, encoding: .utf8)))")
break
default:
print("default no! \(httpResponse.statusCode) all: \(httpResponse)")
break
}
}
task.resume()
}

}

--

--

Creator of Boxapopa, the iOS game for young kids with zero ads, just fun! https://apps.apple.com/ie/app/boxapopa/id1550536188

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store