Skip to content

Instantly share code, notes, and snippets.

@pmarquees
Created October 2, 2024 21:54
Show Gist options
  • Save pmarquees/3d6d43c3ad8db6093c69c65adc956762 to your computer and use it in GitHub Desktop.
Save pmarquees/3d6d43c3ad8db6093c69c65adc956762 to your computer and use it in GitHub Desktop.
import SwiftUI
import MapKit
import CoreLocation
@main
struct ISSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
TabView {
ISSMapView()
.tabItem {
Image(systemName: "map")
Text("Map")
}
AstronautListView()
.tabItem {
Image(systemName: "person.3")
Text("Astronauts")
}
}
}
}
// MARK: - First Tab: ISS Map View
struct ISSMapView: View {
@ObservedObject var issLocationManager = ISSLocationManager()
@ObservedObject var userLocationManager = UserLocationManager()
@State private var region = MKCoordinateRegion()
@State private var isRegionSet = false
var issAnnotation: ISSAnnotation {
ISSAnnotation(coordinate: issLocationManager.issLocation)
}
var body: some View {
ZStack {
Map(coordinateRegion: $region, showsUserLocation: true, annotationItems: [issAnnotation]) { annotation in
MapAnnotation(coordinate: annotation.coordinate) {
Text("🛰")
.font(.largeTitle)
}
}
.onAppear {
setRegion()
}
.onChange(of: issLocationManager.issLocation) { _ in
updateRegion()
}
VStack {
Spacer()
HStack {
VStack(alignment: .leading) {
Text("Latitude: \(String(format: "%.2f", issLocationManager.issLocation.latitude))")
Text("Longitude: \(String(format: "%.2f", issLocationManager.issLocation.longitude))")
if let _ = userLocationManager.userLocation {
let distance = getDistance()
Text("Distance: \(String(format: "%.0f", distance)) km")
} else {
Text("Distance: Calculating...")
}
}
.padding()
.foregroundColor(.white)
.background(Color.black.opacity(0.5))
.cornerRadius(10)
Spacer()
}
.padding()
}
}
}
func setRegion() {
region = MKCoordinateRegion(
center: issLocationManager.issLocation,
span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100)
)
isRegionSet = true
}
func updateRegion() {
withAnimation {
region.center = issLocationManager.issLocation
}
}
func getDistance() -> Double {
if let userLocation = userLocationManager.userLocation {
let issLocation = CLLocation(latitude: issLocationManager.issLocation.latitude, longitude: issLocationManager.issLocation.longitude)
let distanceMeters = issLocation.distance(from: userLocation)
let distanceKm = distanceMeters / 1000
return distanceKm
}
return 0.0
}
}
class ISSLocationManager: NSObject, ObservableObject {
@Published var issLocation = CLLocationCoordinate2D(latitude: 0, longitude: 0)
private var timer: Timer?
override init() {
super.init()
fetchISSLocation()
startTimer()
}
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
self.fetchISSLocation()
}
}
func fetchISSLocation() {
let url = URL(string: "http://api.open-notify.org/iss-now.json")!
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data {
if let issResponse = try? JSONDecoder().decode(ISSResponse.self, from: data) {
DispatchQueue.main.async {
self.issLocation = CLLocationCoordinate2D(latitude: issResponse.iss_position.latitude, longitude: issResponse.iss_position.longitude)
}
}
}
}.resume()
}
}
struct ISSResponse: Codable {
let iss_position: ISSPosition
}
struct ISSPosition: Codable {
let latitude: Double
let longitude: Double
private enum CodingKeys: String, CodingKey {
case latitude, longitude
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let latString = try container.decode(String.self, forKey: .latitude)
let lonString = try container.decode(String.self, forKey: .longitude)
if let lat = Double(latString), let lon = Double(lonString) {
latitude = lat
longitude = lon
} else {
throw DecodingError.dataCorruptedError(forKey: .latitude, in: container, debugDescription: "Invalid coordinates")
}
}
}
struct ISSAnnotation: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
class UserLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var userLocation: CLLocation?
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
// Permissions are already handled
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
userLocation = locations.last
}
}
// MARK: - Second Tab: Astronaut List View
struct AstronautListView: View {
@ObservedObject var astronautsManager = AstronautsManager()
var body: some View {
NavigationView {
List(astronautsManager.astronauts) { astronaut in
HStack {
Text(astronaut.name)
Spacer()
Text(astronaut.craft)
}
}
.navigationTitle("\(astronautsManager.astronauts.count) Astronauts in Space")
}
}
}
class AstronautsManager: ObservableObject {
@Published var astronauts = [Astronaut]()
init() {
fetchAstronauts()
}
func fetchAstronauts() {
let url = URL(string: "http://api.open-notify.org/astros.json")!
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data {
if let astroResponse = try? JSONDecoder().decode(AstronautResponse.self, from: data) {
DispatchQueue.main.async {
self.astronauts = astroResponse.people
}
}
}
}.resume()
}
}
struct AstronautResponse: Codable {
let people: [Astronaut]
}
struct Astronaut: Codable, Identifiable {
let id = UUID()
let name: String
let craft: String
private enum CodingKeys: String, CodingKey {
case name, craft
}
}
extension CLLocationCoordinate2D: Equatable {
public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment