xxxxxxxxxx
struct LocationFinderView: View {
@StateObject var locationService = LocationService()
@State private var code = ""
@State private var selectedCountry = Country.none
var body: some View {
NavigationStack {
VStack {
Picker("Select Country", selection: $selectedCountry) {
ForEach(locationService.countries, id: \.code) { country in
Text(country.name).tag(country)
}
}
.buttonStyle(.bordered)
if selectedCountry != .none {
Text(selectedCountry.range)
Text("Postal Code/Zip Range")
.font(.caption)
.foregroundColor(.secondary)
TextField("Code", text: $code)
.textFieldStyle(.roundedBorder)
.frame(width: 100)
Button("Get Location") {
Task {
await locationService.fetchLocation(for: selectedCountry.code, postalCode: code)
}
}
.buttonStyle(.borderedProminent)
.disabled(code.isEmpty)
if let errorString = locationService.errorString {
Text(errorString)
.foregroundColor(.red)
}
if let locationInfo = locationService.locationInfo {
Text(locationInfo.placeName)
Text(locationInfo.state)
if locationService.errorString == nil {
MapView(longitude: locationInfo.longitude, latitude: locationInfo.latitude)
.padding()
}
}
}
if locationService.locationInfo == nil {
Image("locationFinder")
}
Spacer()
}
.navigationTitle("Location Finder")
.onChange(of: selectedCountry) { _ in
code = ""
}
.onChange(of: code) { _ in
locationService.reset()
}
}
}
}
xxxxxxxxxx
import SwiftUI
import MapKit
struct MapView: View {
let longitude: Double
let latitude: Double
@State private var mapRegion: MKCoordinateRegion
init(longitude: Double, latitude: Double) {
self.longitude = longitude
self.latitude = latitude
self.mapRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: latitude,
longitude: longitude),
span: MKCoordinateSpan(
latitudeDelta: 0.2,
longitudeDelta: 0.2))
}
var body: some View {
Map(coordinateRegion: $mapRegion)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(longitude: -73.9894536, latitude: 40.7484445)
}
}
xxxxxxxxxx
struct Location: Codable {
var country: String
struct Place: Codable {
var placeName: String
var longitude: String
var state: String
var latitude: String
enum CodingKeys: String, CodingKey {
case placeName = "place name"
case longitude
case state
case latitude
}
}
var places: [Place]
}
xxxxxxxxxx
struct Country: Codable, Hashable {
let name: String
let code: String
let range: String
static var none: Country {
Country(name: "Select Country", code: "XX", range: "")
}
}
xxxxxxxxxx
class LocationService: ObservableObject {
@Published var countries: [Country] = []
let baseURL = "https://api.zippopotam.us"
struct LocationInfo {
let placeName: String
let state: String
let longitude: Double
let latitude: Double
}
@Published var locationInfo: LocationInfo?
@Published var errorString: String?
init() {
loadCountries()
}
func loadCountries() {
guard let url = Bundle.main.url(forResource: "Countries", withExtension: "json") else {
fatalError("Failed to locate Countries.json in the bundle")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load Countries.json from the bundle")
}
do {
countries = try JSONDecoder().decode([Country].self, from: data)
countries.insert(Country.none, at: 0)
} catch {
fatalError("Failed to decode Countries.json from data")
}
}
@MainActor
func fetchLocation(for countryCode: String, postalCode: String) async {
guard let urlString = (baseURL + "/" + countryCode + "/" + postalCode)
.trimmingCharacters(in: .whitespacesAndNewlines)
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: urlString)
else {
errorString = "Invalide code entered."
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let location = try JSONDecoder().decode(Location.self, from: data)
if let place = location.places.first {
locationInfo = LocationInfo(placeName: place.placeName,
state: place.state,
longitude: Double(place.longitude) ?? 0,
latitude: Double(place.latitude) ?? 0)
let range = -180.0...180.0
if !(range.contains(locationInfo!.latitude) &&
range.contains(locationInfo!.longitude)) {
errorString = "Invalid map coordinates"
}
}
} catch {
errorString = "Could not decode returned result."
}
}
func reset() {
locationInfo = nil
errorString = nil
}
}