xxxxxxxxxxstruct 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() } } }}xxxxxxxxxximport SwiftUIimport 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) }}
xxxxxxxxxxstruct 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]}xxxxxxxxxxstruct Country: Codable, Hashable { let name: String let code: String let range: String static var none: Country { Country(name: "Select Country", code: "XX", range: "") }}
xxxxxxxxxxclass 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 }}