Handle location and geocoding with SwiftUI - swift

I want to get the current longitude and latitude of a location using Swift and display them.
If I update the scheme to have a default location then It just uses the default location the whole time and never updates with my current location, same when I try it on an actual device. Please Help.
struct prayer: Hashable {
var name: String
var time: String
var color: String
}
LocationFetcher
class LocationFetcher: NSObject, CLLocationManagerDelegate {
let manager = CLLocationManager()
var location: CLLocationCoordinate2D?
override init() {
super.init()
manager.delegate = self
}
func start() {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.first?.coordinate
} }
ContentView
struct ContentView: View {
#State var prayers = [
prayer(name: "Fajr", time: "", color: "Fajr"),
prayer(name: "Sunrise", time: "", color: "Sunrise"),
prayer(name: "Dhuhr", time: "", color: "Dhuhr"),
prayer(name: "Asr", time: "", color: "Asr"),
prayer(name: "Maghrib", time: "", color: "Maghrib"),
prayer(name: "Isha", time: "", color: "Isha")
]
#State var date = ""
#State var currentSalah = ""
#State var nextSalah = ""
#State var country = ""
let locationManager = LocationFetcher()
var body: some View {
ZStack {
Color("Background").ignoresSafeArea()
VStack {
Text(country)
.padding([.top, .bottom], 20)
.foregroundColor(Color.white.opacity(0.7))
.font(.title3)
Text(currentSalah)
.foregroundColor(.white)
.fontWeight(.light)
.padding(.bottom, 15)
.font(.title)
Text(nextSalah)
.foregroundColor(Color.white.opacity(0.7))
.padding(.bottom, 30)
Text(date)
.foregroundColor(.white)
.fontWeight(.light)
ForEach(prayers, id: \.self) { prayer in
HStack {
Image(systemName: "deskclock.fill")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.padding()
.foregroundColor(Color(prayer.color))
Text(prayer.name)
.font(.title)
.fontWeight(.regular)
.foregroundColor(Color(prayer.color))
Spacer()
Text(prayer.time)
.font(.title3)
.fontWeight(.medium)
.padding()
.foregroundColor(Color(prayer.color))
}
.padding(.all, 10)
}
Spacer()
}
}
.onAppear() {
locationManager.start()
getPrayerTime()
}
}
func getPrayerTime() {
let cal = Calendar(identifier: Calendar.Identifier.gregorian)
let date = cal.dateComponents([.year, .month, .day], from: Date())
let coordinates = Coordinates(latitude: locationManager.location?.latitude ?? 33.3152, longitude: locationManager.location?.longitude ?? 44.3661)
var params = CalculationMethod.ummAlQura.params
params.madhab = .hanafi
if let prayers = PrayerTimes(coordinates: coordinates, date: date, calculationParameters: params) {
let formatter = DateFormatter()
formatter.timeStyle = .short
formatter.timeZone = TimeZone(identifier: "America/New_York")!
self.prayers[0].time = formatter.string(from: prayers.fajr)
self.prayers[1].time = formatter.string(from: prayers.sunrise)
self.prayers[2].time = formatter.string(from: prayers.dhuhr)
self.prayers[3].time = formatter.string(from: prayers.asr)
self.prayers[4].time = formatter.string(from: prayers.maghrib)
self.prayers[5].time = formatter.string(from: prayers.isha)
self.updateDate()
self.updateLocationName()
self.updateLabel(prayerTimes: prayers)
}
}
func updateDate() {
let hijriCalendar = Calendar(identifier: .islamicCivil)
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en")
formatter.calendar = hijriCalendar
formatter.dateFormat = "-------- EEEE, MMM dd, yyyy -------- "
date = "\(formatter.string(from: Date()))"
}
func updateLabel(prayerTimes: PrayerTimes) {
guard let current = prayerTimes.currentPrayer() else { return }
currentSalah = "Time For \(current)"
guard let next = prayerTimes.nextPrayer() else { return }
let countdown = prayerTimes.time(for: next)
let diffTime = countdown.timeIntervalSince(Date())
let diffTimeMin = diffTime / 60
let diffTimeMinRem = Int(diffTimeMin.truncatingRemainder(dividingBy: 60))
let diffTimeHour = Int(diffTimeMin / 60)
let stringDiff = "\(next) is in \(diffTimeHour) hr, \(diffTimeMinRem) min"
print(nextSalah = stringDiff)
}
func updateLocationName() {
let location = CLLocation(latitude: locationManager.location?.latitude ?? 33.3152, longitude: locationManager.location?.longitude ?? 44.3661)
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
guard let placemark = placemarks?.first else { return }
country = placemark.country ?? ""
}
} }

You can try with this
class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate{
#Published var longitudeStorage : Double = 0.0
#Published var latitudeStorage : Double = 0.0
#Published var isTrue : Bool = false
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40, longitude: 120), span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100))
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
}
func requestAllowOnceLocationPermission() {
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latestLocation = locations.first else{
//Show an error
return
}
DispatchQueue.main.async {
self.region = MKCoordinateRegion(center: latestLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.015, longitudeDelta: 0.015))
//check region value
print(self.region.center.longitude)
self.longitudeStorage = Double(self.region.center.longitude)
self.latitudeStorage = Double(self.region.center.latitude)
print("Longitude --> \(self.longitudeStorage)")
print("Latitude --> \(self.latitudeStorage)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
I'm making a variable that will store the longitude and latitude that you get

Related

Make MapAnnotation slide to new location with animation

I want to want to slide the annotation over map to new position when coordinates are received. And the change each time coordinates are changed.
This is view I am using:
struct ContentView: View {
#StateObject var request = Requests()
#State private var mapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 56.946285, longitude: 24.105078), span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
var body: some View {
Map(coordinateRegion: $mapRegion, annotationItems: request.users){ user in
withAnimation(.linear){
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: user.latitude, longitude: user.longitude)) {
AnnotationView(requests: request, user: user)
}
}
}
}
struct AnnotationView: View{
#ObservedObject var requests: Requests
#State private var showDetails = false
let user: User
var body: some View{
ZStack{
if showDetails{
withAnimation(.easeInOut) {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.white)
.frame(width: 180, height: 50)
.overlay{
VStack{
Text(user.name)
.foregroundColor(.black)
.font(.title3)
Text(user.address)
.foregroundColor(.gray)
}
}
.offset(y: -50)
}
}
Circle()
.frame(width: 35, height: 35)
.foregroundColor(.white)
AsyncImage(url: URL(string: user.image)) { image in
image
.resizable()
.frame(width: 30, height: 30)
.clipShape(Circle())
.scaledToFit()
} placeholder: {
Image(systemName: "person.circle")
}
.onTapGesture {
self.showDetails.toggle()
}
}
}
}
}
And in view model I have a function that work over incoming data. The response is just received data from server.
#Published var users = User
func collectUsers(_ response: String){
if users.count != 0{
var data = response.components(separatedBy: "\n")
data.removeLast()
for newData in data{
var components = newData.components(separatedBy: ",")
var userID = components[0].components(separatedBy: " ")
userID.removeFirst()
components[0] = userID[0]
if let index = self.users.firstIndex(where: { $0.id == Int(components[0]) }){
self.getAddress(latitude: Double(components[1])!, longitude: Double(components[2])!) { address in
DispatchQueue.main.async {
self.users[index].address = address
self.users[index].latitude = Double(components[1])!
self.users[index].longitude = Double(components[2])!
}
}
}
}
}else{
var userData = response.components(separatedBy: ";")
userData.removeLast()
let users = userData.compactMap { userString -> User? in
let userProperties = userString.components(separatedBy: ",")
var idPart = userProperties[0].components(separatedBy: " ")
if idPart.count == 2{
idPart.removeFirst()
}
guard userProperties.count == 5 else { return nil }
guard let id = Int(idPart[0]),
let latitude = Double(userProperties[3]),
let longitude = Double(userProperties[4]) else { return nil }
var collectedUser = User(id: id, name: userProperties[1], image: userProperties[2], latitude: latitude, longitude: longitude, address: "")
return collectedUser
}
DispatchQueue.main.async {
self.users = users
}
}
}
func getAddress(latitude: Double, longitude: Double, completion: #escaping (String) -> Void){
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + subThoroughfare
completion(collectedAddress)
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
And also I dont understand why calling getAdress does not invoke changes on view each time coordinates are received.

Write and read ratings from firestore in Swift

I'm trying to have users submit star ratings and then have them stored under a collection within their document. I have the functions written which should do these things, getRatingsSubcollection, addTransactionRating, but I can't figure out how to place them in the structure so that they run for each user-submitted rating.
struct Rating: View {
#State var userCurrentRating: CGFloat = 0.0
var schools = ["North Avenue", "West Village", "Brittain"]
#State private var location = "West Village"
#State private var isUserRatingCurrent = true
#State private var currentRating: CGFloat = 0.0
var db: Firestore!
var body: some View {
NavigationView{
ScrollView {
VStack(spacing: 16) {
Divider()
Text("Choose a dining location:")
.bold()
Picker("Choose a dining location:", selection: $location) {
ForEach(schools, id: \.self) {
Text($0)
}
}
Text("Current Rating, \(currentRating)")
.bold()
if self.isUserRatingCurrent {
VStack {
RatingsViewCurrent(onDidRateCurrent: self.userDidRateCurrent, ratingCurrent: self.$userCurrentRating)
.frame(width: .infinity, height: 100, alignment: .center)
}
} else {
VStack {
RatingsViewCurrent(ratingCurrent: self.$currentRating)
.frame(width: .infinity, height: 100, alignment: .center)
if userCurrentRating == 0.0 {
Button("Rate") {
self.isUserRatingCurrent = false
}
}
}
}
}
}.navigationTitle("Add a Rating")
}
}
func userDidRateCurrent(_ ratingCurrent: Int) { print("ratingcurrent \(ratingCurrent)")
}
// [START restaurant_struct]
struct diningLocation {
let location: String
let currentRating: Float
let usualRating: Float
let totalRatings: Float
init(location: String, currentRating: Float, usualRating: Float, totalRatings: Float) {
self.location = location
self.currentRating = currentRating
self.usualRating = usualRating
self.totalRatings = totalRatings
}
}
let WestVillage = diningLocation(location: "West Village", currentRating: 4.65, usualRating: 2.9, totalRatings: 45.0)
// [END restaurant_struct]
struct Rating {
let rating: Float
}
func getRatingsSubcollection() {
// [START get_ratings_subcollection]
db.collection("RatingInformation")
.document("West Village")
.collection("ratings")
.getDocuments() { (querySnapshot, err) in
// ...
}
// [END get_ratings_subcollection]
}
// [START add_rating_transaction]
func addRatingTransaction(restaurantRef: DocumentReference, ratingCurrent: Float) {
let ratingRef: DocumentReference = restaurantRef.collection("ratings").document()
db.runTransaction({ (transaction, errorPointer) -> Any? in
do {
let restaurantDocument = try transaction.getDocument(restaurantRef).data()
guard var restaurantData = restaurantDocument else { return nil }
// Compute new number of ratings
let totalRatings = restaurantData["totalRatings"] as! Int
let newNumRatings = totalRatings + 1
// Compute new average rating
let usualRating = restaurantData["usualRating"] as! Float
let oldRatingTotal = usualRating * Float(totalRatings)
let newAvgRating = (oldRatingTotal + ratingCurrent) / Float(newNumRatings)
// Set new restaurant info
restaurantData["totalRatings"] = newNumRatings
restaurantData["usualRating"] = newAvgRating
// Commit to Firestore
transaction.setData(restaurantData, forDocument: restaurantRef)
transaction.setData(["rating": ratingCurrent], forDocument: ratingRef)
} catch {
// Error getting restaurant data
// ...
}
return nil
}) { (object, err) in
// ...
}
}
}

MapAnnotations Not Showing Up

I have a function that takes my coordnaties that are stored inside of my Firebase Storage, and turns them into MKPointAnnotations For Some Reason I keep on getting the error Type '()' cannot conform to 'View'
Here is my code for the function:
import SwiftUI
import MapKit
import CoreLocationUI
import Firebase
import FirebaseFirestore
struct Marker: Identifiable {
let id = UUID()
var coordinate : CLLocationCoordinate2D
}
struct MapView: View {
#StateObject private var viewModel = ContentViewModel()
//For GeoCoder
let geocoder = CLGeocoder()
#State private var result = "result of lat & long"
#State private var lat = 0.0
#State private var long = 0.0
#State private var country = "country name"
#State private var state = "state name"
#State private var zip = "zip code"
//For Map Annotations
#State var address = ""
#State var realLat = 0.00
#State var realLong = 0.00
#State var email = ""
//For TopBar
#State var goToAddress = ""
#State var filters = false
var body: some View {
let markers = [
Marker(coordinate: CLLocationCoordinate2D(latitude: realLat, longitude: realLong))
]
NavigationView {
VStack {
ZStack (alignment: .bottom) {
LocationButton(.currentLocation) {
viewModel.requestAllowOnceLocationPermission()
}
.foregroundColor(.white)
.cornerRadius(8)
.labelStyle(.iconOnly)
.symbolVariant(.fill)
.tint(.pink)
.padding(.bottom)
.padding(.trailing, 300)
getAnnotations { (annotations) in
if let annotations = annotations {
Map(coordinateRegion: $viewModel.region, showsUserLocation: true, annotationItems: MKPointAnnotation) { annotations in
MapAnnotation(coordinate: annotations.coordinate) {
Circle()
}
}
.ignoresSafeArea()
.tint(.pink)
} else {
print("There has been an error with the annotations")
}
}
}
}
}
}
func getAnnotations(completion: #escaping (_ annotations: [MKPointAnnotation]?) -> Void) {
let db = Firestore.firestore()
db.collection("annotations").addSnapshotListener { (querySnapshot, err) in
guard let snapshot = querySnapshot else {
if let err = err {
print(err)
}
completion(nil) // return nil if error
return
}
guard !snapshot.isEmpty else {
completion([]) // return empty if no documents
return
}
var annotations = [MKPointAnnotation]()
for doc in snapshot.documents {
if let lat = doc.get("lat") as? String,
let lon = doc.get("long") as? String,
let latitude = Double(lat),
let longitude = Double(lon) {
let coord = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let annotation = MKPointAnnotation()
annotation.coordinate = coord
annotations.append(annotation)
}
}
completion(annotations) // return array
}
}
func goToTypedAddress() {
geocoder.geocodeAddressString(goToAddress, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error ?? "")
}
if let placemark = placemarks?.first {
let coordinates:CLLocationCoordinate2D = placemark.location!.coordinate
print("Lat: \(coordinates.latitude) -- Long: \(coordinates.longitude)")
//added code
result = "Lat: \(coordinates.latitude) -- Long: \(coordinates.longitude)"
lat = coordinates.latitude
long = coordinates.longitude
}
})
print("\(lat)")
print("\(long)")
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
struct Item: Identifiable {
let id = UUID()
let text: String
}
//LocationButton
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40, longitude: 120), span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100))
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
}
func requestAllowOnceLocationPermission() {
locationManager.requestLocation()
}
func locationManager( _ _manager:CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let latestLocation = locations.first else {
// show an error
return
}
DispatchQueue.main.async{
self.region = MKCoordinateRegion(
center: latestLocation.coordinate,
span:MKCoordinateSpan(latitudeDelta:0.05, longitudeDelta:0.05))
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
The updated error is on line 50 now. Might be because of all of the new functions that I have. Also is the firebase function correct? I would like to make sure that it correct too.

Error with Geocoding - Error 1 and Error 8

I am pulling all of the users locations inside of Firebase storage, and when I try to geocode them into lat and long coordinates, I am getting the error 2022-07-26 15:40:39.676845-0400 Polygon[96779:3864577] [MKCoreLocationProvider] CLLocationManager(<CLLocationManager: 0x6000004c2c90>) for <MKCoreLocationProvider: 0x6000034f0870> did fail with error: Error Domain=kCLErrorDomain Code=1 "(null) and Error Error Domain=kCLErrorDomain Code=8 "(null)"
My code seems to be working fine, and when I take one of the addresses and geocode them in a different project, they work well. Please take a look at the code below and tell me of any errors or if I missed something?
import SwiftUI
import MapKit
import CoreLocationUI
import Firebase
struct Place: Identifiable {
let id = UUID()
var name: String
var coordinate: CLLocationCoordinate2D
}
struct MapView: View {
var empireStateBuilding =
Place(name: "Empire State Building", coordinate: CLLocationCoordinate2D(latitude: 40.748433, longitude: -73.985656))
#StateObject private var viewModel = ContentViewModel()
#State var address = ""
#State var addresses:[String] = []
let geocoder = CLGeocoder()
#State private var result = "result of lat & long"
#State private var lat = 0.0
#State private var long = 0.0
#State private var country = "country name"
#State private var state = "state name"
#State private var zip = "zip code"
var body: some View {
NavigationView {
ZStack (alignment: .bottom) {
Map(coordinateRegion: $viewModel.region, showsUserLocation: true)
.ignoresSafeArea()
.tint(.pink)
LocationButton(.currentLocation) {
viewModel.requestAllowOnceLocationPermission()
}
.foregroundColor(.white)
.cornerRadius(8)
.labelStyle(.iconOnly)
.symbolVariant(.fill)
.tint(.pink)
.padding(.bottom)
.padding(.trailing, 300)
Button {
print(address)
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error ?? "")
}
if let placemark = placemarks?.first {
if let coordinates:CLLocationCoordinate2D = placemark.location.coordinate {
print("Lat: \(coordinates.latitude) -- Long: \(coordinates.longitude)")
//added code
result = "Lat: \(coordinates.latitude) -- Long: \(coordinates.longitude)"
lat = coordinates.latitude
long = coordinates.longitude
}
}
})
} label: {
Image(systemName: "arrow.clockwise")
}
.padding(.leading, 300)
.padding(.bottom)
.cornerRadius(8)
Text("\(result)")
}
List(0..<addresses.count, id: \.self) {i in Text(addresses[i]) }
}
.onAppear(perform: {
downloadServerData()
})
}
func downloadServerData() {
let db = Firestore.firestore()
db.collection("UserInfo").addSnapshotListener {(snap, err) in
if err != nil{
print("There is an error")
return
}
for i in snap.documentChanges {
let documentId = i.document.documentID
if let address = i.document.get("address") as? String {
DispatchQueue.main.async {
addresses.append("\(address)")
print(address)
}
}
}
}
}
}
struct AnnotatedItem: Identifiable {
let id = UUID()
var name: String
var coordinate: CLLocationCoordinate2D
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
//LocationButton
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40, longitude: 120), span: MKCoordinateSpan(latitudeDelta: 100, longitudeDelta: 100))
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
}
func requestAllowOnceLocationPermission() {
locationManager.requestLocation()
}
func locationManager( _ _manager:CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let latestLocation = locations.first else {
// show an error
return
}
DispatchQueue.main.async{
self.region = MKCoordinateRegion(
center: latestLocation.coordinate,
span:MKCoordinateSpan(latitudeDelta:0.05, longitudeDelta:0.05))
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
struct MapView1_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}

SwiftUI - Get User's coordinates to pass in API call

This problem has been haunting me for months and I believe it comes down to my using the wrong structure and procedure.
I'm trying to do an API call to Yelp's API and passing in the variables for the user's lat/long. I'm able to grab the lat/long based on my current LocationManager, however when it seems as though the lat/long only becomes available AFTER the API call has been made, so the API is getting default 0.0 values for both lat/long.
I'm very much a beginner when it comes to this, but is there a way that I could set up a loading screen that grabs the lat/long in the background and by the time my ExploreView shows, the real location information has been established?
Below is my LocationManager and ExploreView
LocationManager
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
let geoCoder = CLGeocoder()
#Published var location: CLLocation? = nil
#Published var placemark: CLPlacemark? = nil
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
func geoCode(with location: CLLocation) {
geoCoder.reverseGeocodeLocation(location) { (placemark, error) in
if error != nil {
print(error!.localizedDescription)
} else {
self.placemark = placemark?.first
}
}
}
func startUpdating() {
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
self.location = location
self.geoCode(with: location)
}
}
ExploreView (The first view that shows upon launch)
import SwiftUI
import CoreLocation
import Foundation
struct ExploreView: View {
#ObservedObject var location = LocationManager()
#ObservedObject var fetcher: RestaurantFetcher
init() {
let location = LocationManager()
self.location = location
self.fetcher = RestaurantFetcher(locationManager: location)
self.location.startUpdating()
}
var body: some View {
ScrollView (.vertical) {
VStack {
HStack {
Text("Discover ")
.font(.system(size: 28))
.fontWeight(.bold)
+ Text(" \(location.placemark?.locality ?? "")")
.font(.system(size: 28))
.fontWeight(.bold)
Spacer()
}
HStack {
SearchBar(text: .constant(""))
}.padding(.top, 16)
HStack {
Text("Featured Restaurants")
.font(.system(size: 24))
.fontWeight(.bold)
Spacer()
NavigationLink(
destination: FeaturedView(),
label: {
Text("View All")
})
}.padding(.vertical, 30)
HStack {
Text("All Cuisines")
.font(.system(size: 24))
.fontWeight(.bold)
Spacer()
}
Spacer()
}.padding()
}
}
}
public class RestaurantFetcher: ObservableObject {
#Published var businesses = [RestaurantResponse]()
#ObservedObject var locationManager: LocationManager
let location = LocationManager()
var lat: String {
return "\(location.location?.coordinate.latitude ?? 0.0)"
}
var long: String {
return "\(location.location?.coordinate.longitude ?? 0.0)"
}
init(locationManager: LocationManager) {
let location = LocationManager()
self.locationManager = location
self.location.startUpdating()
load()
}
func load() {
print("\(location.location?.coordinate.latitude ?? 0.0)")
print("user latitude top of function")
//Returns default values of 0.0
let apikey = "APIKEY Here"
let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(lat)&longitude=\(long)&radius=40000")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let d = data {
print("\(self.location.location?.coordinate.longitude ?? 0.0)")
let decodedLists = try JSONDecoder().decode(BusinessesResponse.self, from: d)
// Returns actual location coordinates
DispatchQueue.main.async {
self.businesses = decodedLists.restaurants
}
} else {
print("No Data")
}
} catch {
print ("Caught")
}
}.resume()
}
}
Try the following modified code (I needed to make some replications, so pay attention - some typos possible).
The main idea is to subscribe for LocationManager updated location publisher to listen for explicit changes of location and perform next API load only after location is really updated and not nil.
struct ExploreView: View {
#ObservedObject var location: LocationManager
#ObservedObject var fetcher: RestaurantFetcher
init() {
let location = LocationManager() // << use only one instance
self.location = location
self.fetcher = RestaurantFetcher(locationManager: location)
self.location.startUpdating() // << do this only once
}
var body: some View {
ScrollView (.vertical) {
VStack {
HStack {
Text("Discover ")
.font(.system(size: 28))
.fontWeight(.bold)
+ Text(" \(location.placemark?.locality ?? "")")
.font(.system(size: 28))
.fontWeight(.bold)
Spacer()
}
HStack {
SearchBar(text: .constant(""))
}.padding(.top, 16)
HStack {
Text("Featured Restaurants")
.font(.system(size: 24))
.fontWeight(.bold)
Spacer()
NavigationLink(
destination: FeaturedView(),
label: {
Text("View All")
})
}.padding(.vertical, 30)
HStack {
Text("All Cuisines")
.font(.system(size: 24))
.fontWeight(.bold)
Spacer()
}
Spacer()
}.padding()
}
}
}
import Combine
public class RestaurantFetcher: ObservableObject {
#Published var businesses = [RestaurantResponse]()
private var locationManager: LocationManager
var lat: String {
return "\(locationManager.location?.coordinate.latitude ?? 0.0)"
}
var long: String {
return "\(locationManager.location?.coordinate.longitude ?? 0.0)"
}
private var subscriber: AnyCancellable?
init(locationManager: LocationManager) {
self.locationManager = locationManager
// listen for available location explicitly
subscriber = locationManager.$location
.debounce(for: 5, scheduler: DispatchQueue.main) // wait for 5 sec to avoid often reload
.receive(on: DispatchQueue.main)
.sink { [weak self] location in
guard location != nil else { return }
self?.load()
}
}
func load() {
print("\(locationManager.location?.coordinate.latitude ?? 0.0)")
print("user latitude top of function")
//Returns default values of 0.0
let apikey = "APIKEY Here"
let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(lat)&longitude=\(long)&radius=40000")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let d = data {
print("\(self.locationManager.location?.coordinate.longitude ?? 0.0)")
let decodedLists = try JSONDecoder().decode(BusinessesResponse.self, from: d)
// Returns actual location coordinates
DispatchQueue.main.async {
self.businesses = decodedLists.restaurants
}
} else {
print("No Data")
}
} catch {
print ("Caught")
}
}.resume()
}
}