In this post I’ll show you how to make an Observable Geocoder to use in your #SwiftUI projects.
Firstly we want to import CoreLocation
for the location services.
We’re going to construct a basic class, which subclasses ObservableObject
.
class Geocoder: ObservableObject {
/// ...
}
We’re going to add @Published public var
(published public variables),
for the items we want to expose, in my case it are the following variables placeMark
, location
, name
, iso
, country
, postalCode
, state
, subState
, city
, subCity
, street
, subStreet
, region
, timeZone
, inlandWater
, ocean
and areasOfInterest
.
Full Code:
import CoreLocation
class Geocoder: ObservableObject {
/// Placemark
@Published public var placeMark: CLPlacemark?
/// Location
@Published public var location: CLLocation?
/// The name of the placemark.
@Published public var name: String?
/// The abbreviated country or region name.
@Published public var iso: String?
/// The name of the country or region associated with the placemark.
@Published public var country: String?
/// The postal code associated with the placemark.
@Published public var postalCode: String?
/// The state or province associated with the placemark.
@Published public var state: String?
/// Additional administrative area information for the placemark.
@Published public var subState: String?
/// The city associated with the placemark.
@Published public var city: String?
/// Additional city-level information for the placemark.
@Published public var subCity: String?
/// The street address associated with the placemark.
@Published public var street: String?
/// Additional street-level information for the placemark.
@Published public var subStreet: String?
/// The geographic region associated with the placemark.
@Published public var region: CLRegion?
/// The time zone associated with the placemark.
@Published public var timeZone: TimeZone?
/// The name of the inland water body associated with the placemark.
@Published public var inlandWater: String?
/// The name of the ocean associated with the placemark.
@Published public var ocean: String?
/// The relevant areas of interest associated with the placemark.
@Published public var areasOfInterest: [String]?
/// Geocoder
private let geoCoder = CLGeocoder()
init () { }
/// Update to location
func update (to location: CLLocation) {
geoCoder.reverseGeocodeLocation(
location,
completionHandler: { (placemarks, _) -> Void in
// We're only using the first place mark.
if let placeMark = placemarks?[0] {
self.placeMark = placeMark
self.location = placeMark.location
self.name = placeMark.name
self.iso = placeMark.isoCountryCode
self.country = placeMark.country
self.postalCode = placeMark.postalCode
self.state = placeMark.administrativeArea
self.subState = placeMark.subAdministrativeArea
self.city = placeMark.locality
self.subCity = placeMark.subLocality
self.street = placeMark.thoroughfare
self.subStreet = placeMark.subThoroughfare
self.region = placeMark.region
self.timeZone = placeMark.timeZone
self.inlandWater = placeMark.inlandWater
self.ocean = placeMark.ocean
self.areasOfInterest = placeMark.areasOfInterest
// Send a notification that our `@Published` values have been changed.
self.objectWillChange.send()
}
}
)
}
}
Usage:
//
// _T.swift
//
//
// Created by Wesley de Groot on 25/11/2022.
//
import SwiftUI
class someView: View {
@State var isPresenting = false
@StateObject var geocoder: Geocoder = .init()
var body: some View {
NavigationView {
VStack {
SomeMapView(
mapType: .mutedStandard,
region: region,
) { annotation in
if let annotation {
self.isPresenting = true
geocoder.update(to: .init(
latitude: annotation.coordinate.latitude,
longitude: annotation.coordinate.longitude
))
} else {
// Deselected pin
self.isPresenting = false
}
}
.sheetWithDetents(
isPresented: $isPresenting,
detents: [.medium()]
) {
VStack {
Text("@Observed Geocoder demo")
Divider()
GroupBox("Pin location") {
VStack {
CustomRow(title: "Name", value: geocoder.name)
CustomRow(title: "ISO", value: geocoder.iso)
CustomRow(title: "Country", value: geocoder.country)
CustomRow(title: "Postal", value: geocoder.postalCode)
CustomRow(title: "State", value: geocoder.state)
CustomRow(title: "SubState", value: geocoder.subState)
CustomRow(title: "City", value: geocoder.city)
CustomRow(title: "SubCity", value: geocoder.subCity)
}
VStack {
CustomRow(title: "Street", value: geocoder.street)
CustomRow(title: "SubStreet", value: geocoder.subStreet)
CustomRow(title: "Region", value: "...")
CustomRow(title: "timeZone", value: geocoder.timeZone?.identifier)
CustomRow(title: "inlandWater", value: geocoder.inlandWater)
CustomRow(title: "ocean", value: geocoder.ocean)
CustomRow(title: "areasOfInterest",
value: geocoder.areasOfInterest?.joined(separator: "; ")
)
}
}
Spacer()
}
}
}
}
}
}
struct CustomRow: View {
let title: String
let value: String?
var body: some View {
HStack {
Text(title)
.font(.headline)
Spacer()
Text(value ?? "Unknown")
.font(.headline)
.foregroundColor(.secondary)
}
}
}