Google Search bar Swift Uikit

Dr. Beynis (PhD)
6 min readMar 10, 2023

--

We implement a search bar on GoogleMaps that returns potential locations. Upon selection GoogleMaps updates to the chosen location. To implement this functionality, we ‘recycle’ and reuse API keys and project from an older article: https://medium.com/@mtxsaalis/google-maps-and-google-places-with-swift-uikit-79a84c49707c (See steps 1–3).

Outside the ViewController class we import

import GoogleMaps
import GooglePlaces

In the ViewController class we declare the following variables

    var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?




var locationManager: CLLocationManager!
var currentLocation: CLLocation?
var mapView: GMSMapView!
var placesClient: GMSPlacesClient!
var preciseLocationZoomLevel: Float = 15.0
var approximateLocationZoomLevel: Float = 10.0

Our search bar will be at the top of the view. It has autocomplete for GMS places, UIsearchcontroller and a text View UI.

In the piece of codes above, we also set the location manager and placeClient that returns the place(s).

In the ViewController class in viewDidLoad()

We set the views of the search bar and results with the following:

        resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self

searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController

let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))

subView.addSubview((searchController?.searchBar)!)
//view.addSubview(subView)
searchController?.searchBar.sizeToFit()
searchController?.hidesNavigationBarDuringPresentation = false

// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true

The mapView set up is similar to the previous article’s, except that we add a subview to it. The search bar is the subview. We also eliminate the gesture recognizer. Now the app will see it when one clicks on the search bar.

        mapView.gestureRecognizers?.removeAll()

mapView.addSubview(subView)

view.addSubview(mapView)

The entire viewDidLoad() is here:

override func viewDidLoad() {
super.viewDidLoad()



resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self

searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController

let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))

subView.addSubview((searchController?.searchBar)!)
//view.addSubview(subView)
searchController?.searchBar.sizeToFit()
searchController?.hidesNavigationBarDuringPresentation = false

// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true

// Do any additional setup after loading the view.
// Create a GMSCameraPosition that tells the map to display the
// coordinate -33.86,151.20 at zoom level 6.
// Initialize the location manager.

locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self

placesClient = GMSPlacesClient.shared()

// A default location to use when location permission is not granted.
let defaultLocation = CLLocation(latitude: -33.869405, longitude: 151.199)

// Create a map.
let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude,
longitude: defaultLocation.coordinate.longitude,
zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.settings.myLocationButton = true
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true

mapView.gestureRecognizers?.removeAll()


mapView.addSubview(subView)

view.addSubview(mapView)
mapView.isHidden = true


}


}

We have the following extension from the GoogleMaps platform’s documentation for Place Autocomplete

extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController?.isActive = false
// Do something with the selected place.
print("Place name: \(place.name)")
print("Place address: \(place.formattedAddress)")
print("Place attributions: \(place.attributions)")
}

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

// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}

func didUpdateAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

We modify it extension func resultsController by inserting the following chunck:

func updateLoc(finished: () -> Void) {

locationManager(CLLocationManager(), didUpdateLocations: [CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)])
finished()
}
updateLoc{
mapView.translatesAutoresizingMaskIntoConstraints = true
print("EEEEEEEEENNNNNNDDDDDD")

}

The extension looks like this now:

extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController?.isActive = false
// Do something with the selected place.
print("Place name: \(place.name)")
print("Place address: \(place.formattedAddress)")
print("Place attributions: \(place.attributions)")
print("Place coord: \(place.placeID)")





func updateLoc(finished: () -> Void) {

locationManager(CLLocationManager(), didUpdateLocations: [CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)])
finished()
}
updateLoc{
mapView.translatesAutoresizingMaskIntoConstraints = true
print("EEEEEEEEENNNNNNDDDDDD")

}


}

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

// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}

func didUpdateAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

Note that we have this extension as well from the previous project:

// Delegates to handle events for the location manager.
extension ViewController: CLLocationManagerDelegate {


// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Locationsssssssssssssss have chaaaaannnngggeeeddd: \(location)")

let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
zoom: zoomLevel)

if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}


}


// Populate the array with the list of likely places.

// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// Check accuracy authorization
let accuracy = manager.accuracyAuthorization
switch accuracy {
case .fullAccuracy:
print("Location accuracy is precise.")
case .reducedAccuracy:
print("Location accuracy is not precise.")
@unknown default:
fatalError()
}

// Handle authorization status
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
@unknown default:
fatalError()
}
}

// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error here: \(error)")
}



}

This is it, the complete ViewController file is below:

//
// ViewController.swift
// GoogleMapSearchPlaces
//
// Created by CHRISTIAN BEYNIS on 2/22/23.
//

import UIKit
import GoogleMaps
import GooglePlaces




class ViewController: UIViewController{

var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?




var locationManager: CLLocationManager!
var currentLocation: CLLocation?
var mapView: GMSMapView!
var placesClient: GMSPlacesClient!
var preciseLocationZoomLevel: Float = 15.0
var approximateLocationZoomLevel: Float = 10.0



override func viewDidLoad() {
super.viewDidLoad()



resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self

searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController

let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))

subView.addSubview((searchController?.searchBar)!)
//view.addSubview(subView)
searchController?.searchBar.sizeToFit()
searchController?.hidesNavigationBarDuringPresentation = false

// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true

// Do any additional setup after loading the view.
// Create a GMSCameraPosition that tells the map to display the
// coordinate -33.86,151.20 at zoom level 6.
// Initialize the location manager.

locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self

placesClient = GMSPlacesClient.shared()

// A default location to use when location permission is not granted.
let defaultLocation = CLLocation(latitude: -33.869405, longitude: 151.199)

// Create a map.
let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude,
longitude: defaultLocation.coordinate.longitude,
zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.settings.myLocationButton = true
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true

// Add the map to the view, hide it until we've got a location update.




mapView.gestureRecognizers?.removeAll()

//mapView.translatesAutoresizingMaskIntoConstraints = false

//mapView.topAnchor.constraint(equalTo: view.topAnchor, constant:100)
//mapView.heightAnchor.constraint(equalToConstant: 600).isActive = true
//mapView.widthAnchor.constraint(equalToConstant: 400).isActive = true
mapView.addSubview(subView)
//view.addSubview(subView)
//mapView.topAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor, constant: -40).isActive = true



view.addSubview(mapView)
mapView.isHidden = true


}


}


// Delegates to handle events for the location manager.
extension ViewController: CLLocationManagerDelegate {


// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Locationsssssssssssssss have chaaaaannnngggeeeddd: \(location)")

let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
zoom: zoomLevel)

if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}


}


// Populate the array with the list of likely places.

// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// Check accuracy authorization
let accuracy = manager.accuracyAuthorization
switch accuracy {
case .fullAccuracy:
print("Location accuracy is precise.")
case .reducedAccuracy:
print("Location accuracy is not precise.")
@unknown default:
fatalError()
}

// Handle authorization status
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
@unknown default:
fatalError()
}
}

// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error here: \(error)")
}



}


extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController?.isActive = false
// Do something with the selected place.
print("Place name: \(place.name)")
print("Place address: \(place.formattedAddress)")
print("Place attributions: \(place.attributions)")
print("Place coord: \(place.placeID)")





func updateLoc(finished: () -> Void) {

locationManager(CLLocationManager(), didUpdateLocations: [CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)])
finished()
}
updateLoc{
mapView.translatesAutoresizingMaskIntoConstraints = true
print("EEEEEEEEENNNNNNDDDDDD")

}


}

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

// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}

func didUpdateAutocompletePredictions(forResultsController resultsController: GMSAutocompleteResultsViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

github id: Chrisb5a

if you are interested in lauching your business or learning new skills feel free to reach out.

--

--

Dr. Beynis (PhD)
Dr. Beynis (PhD)

Written by Dr. Beynis (PhD)

I am Christian Beynis. I went from obtaining a physics PhD to trying out different career paths learning new skills and becoming a freelancer. github/chrisb5a

No responses yet