How to extract street, city, etc. from GMSPlace Address Components

Viewed 13525

I'm using Google Places API for iOS and can successfully retrieve nearby places and present the address as a string. What I'm trying to do is extract address components such as city to store in a database.

The documentation indicates a GMSPlace has an addressComponents property (an array), but I can't seem to figure how to use this property.

The code below provides sets the entire address to the text of a label, but I can't get beyond that:

Edit ---- added code that shows how I'm trying to access Address Components

venueLabel.isHidden = false
            venueLabel.text = selectedPlace?.name
            addressLabel.isHidden = false
            addressLabel.text = selectedPlace?.formattedAddress
            let addressComponents = selectedPlace?.addressComponents
            for component in addressComponents! {
                let city = component["city"] //Error Type GMSPaceAddressComponent has no subscript members
                print(city)
            }
10 Answers

Swift 4

    var keys = [String]()
    place.addressComponents?.forEach{keys.append($0.type)}
    var values = [String]()
    place.addressComponents?.forEach({ (component) in
        keys.forEach{ component.type == $0 ? values.append(component.name): nil}
    })
    print(values) 

For the latest Google Places API (July 2019) this is what could help you. Actually, Google now putting a couple of types for each element. So the "type" is now deprecated and "types" is new filed.

You can do something like this:

 for addressComponent in (self.mySelectedPlace?.addressComponents)! {
            for type in (addressComponent.types){

                switch(type){
                    case "country":
                        var country = addressComponent.name

                    case "postal_code":
                         var postCode = addressComponent.name

                default:
                    break
                }

            }
        }

It's working in Swift 5 and iOS 13.3

1. Create a function for showing GMSAutocompleteViewController

    func googlePlacesSearchVC() {
    
        let acController = GMSAutocompleteViewController()
        acController.delegate = self
        // Specify the place data types to return.
        let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
      UInt(GMSPlaceField.formattedAddress.rawValue)  |
               UInt(GMSPlaceField.addressComponents.rawValue))!
    
        acController.placeFields = fields
        // Specify a filter.
        let filter = GMSAutocompleteFilter()
        filter.country = "IN"
        acController.autocompleteFilter = filter

        acController.secondaryTextColor = .darkGray
        acController.primaryTextColor = .lightGray
        acController.primaryTextHighlightColor = .black
        acController.tableCellBackgroundColor = .whiteThree
        acController.tableCellSeparatorColor = .lightGray
        // Display the autocomplete view controller.
        present(acController, animated: true) {

            let views = acController.view.subviews
            let subviewsOfSubview = views.first!.subviews
            let subOfNavTransitionView = subviewsOfSubview[1].subviews
            let subOfContentView = subOfNavTransitionView[1].subviews
            let searchBar = subOfContentView[0] as! UISearchBar
            searchBar.searchTextField.placeholder = "places_picker_hint_add_address".localized
            searchBar.searchBarStyle = .minimal
            searchBar.searchTextField.font = UIFont.screenTitle16Pt
            searchBar.searchTextField.backgroundColor = UIColor.white
            searchBar.searchTextField.leftView?.tintColor = .darkGray
            searchBar.delegate?.searchBar?(searchBar, textDidChange: "")
        }
   }

2. Call that function where ever you need, for ex:-

   override func viewDidLoad() {
      super.viewDidLoad()

      googlePlacesSearchVC()
   }

3. Call GMSAutoCompleteViewControllerDelegates Methods. Here will get all details like Street, City, etc. from GMSPlace Address Components

extension ViewController {

      // Handle the user's selection.
      func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
        
            // Show HouseAndFlat
            if place.name?.description != nil {
                
                yourTxtField.text = place.name?.description ?? ""
            }

            // Show latitude
            if place.coordinate.latitude.description.count != 0 {
                var latitude = place.coordinate.latitude
            }
            // Show longitude
            if place.coordinate.longitude.description.count != 0 {
                selectedLongitude = place.coordinate.longitude
            }

            // Show AddressComponents
            if place.addressComponents != nil {
                
                for addressComponent in (place.addressComponents)! {
                   for type in (addressComponent.types){

                       switch(type){
                           case "sublocality_level_2":
                               yourTxtField.text = addressComponent.name
                           case "sublocality_level_1":
                                yourTxtField.text = addressComponent.name
                            case "administrative_area_level_2":
                                yourTxtField.text = addressComponent.name

                            case "administrative_area_level_1":
                                 yourTxtField.text = addressComponent.name
                            case "country":
                                yourTxtField.text = addressComponent.name

                            case "postal_code":
                                 yourTxtField.text = addressComponent.name
                       default:
                           break
                       }
                   }
               }
            }
            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)
      }
}

I have found an answer from this link

I am using this extension:

extension Array where Element == GMSAddressComponent {
    func valueFor(placeType: String, shortName: Bool = false) -> String? {
        // bug in the google or apple framework. This cast must stay.
        // Withou it crashing.
        guard let array = self as? NSArray else { return nil }
        let result = array
            .compactMap { $0 as? GMSAddressComponent }
            .first(where: {
                $0.types
                    .first(where: { $0 == placeType }) == placeType
            })
        return shortName ? result?.shortName : result?.name
    }
}

Usage:

let place: GMSPlace
// ...
place.addressComponents?.valueFor(placeType: kGMSPlaceTypeRoute, shortName: true)

I will extend @Ramis answer, in any case you want to check for multiple types if one of them exist, e.x

addressComponents?.valueFor(placeTypes: kGMSPlaceTypePostalTown, kGMSPlaceTypeLocality)

implementation

extension Array where Element == GMSAddressComponent {
    func valueFor(placeTypes: String..., shortName: Bool = false) -> String? {
        let array = self as NSArray
        let result = array
            .compactMap { $0 as? GMSAddressComponent }
            .first(where: {
                placeTypes.contains($0.types.first(where: { placeTypes.contains($0) }) ?? "")
            })
        return shortName ? result?.shortName : result?.name
    }
}

I could not find this anywhere for Objective-C, for those who follow behind me:

for (int i = 0; i < [place.addressComponents count]; i++)
{
    NSLog(@"name %@ = type %@", place.addressComponents[i].name, place.addressComponents[i].type);
}
var keys = [String]()
    place.addressComponents?.forEach{keys.append($0.type)}
    var values = [String]()
    place.addressComponents?.forEach({ (component) in
        keys.forEach{ component.type == $0 ? values.append(component.name): nil}

         print("All component type \(component.type)")

       if component.type == "locality"{
            print("This is city name \(component.name)")
        }
    })
    print(values)

I found this, I hope I help you

  let arrays : NSArray = place.addressComponents! as NSArray
        for i in 0..<arrays.count {

            let dics : GMSAddressComponent = arrays[i] as! GMSAddressComponent
            let str : NSString = dics.type as NSString

            if (str == "country") {
                print("Country: \(dics.name)")
                self.pais = dics.name
            }

            else if (str == "administrative_area_level_2") {
                print("City: \(dics.name)")
                self.ciudad = dics.name
            }
Related