My ultimate goal is to display annotations on a Map from MapKit. However, loading data from firebase does not work.
Here is the code:
var hotels = [Hotel]()
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self
checkLocationServices()
observe()
}
The function observe is the following:
func observe() {
Data().getAllHotel { (hotelA) -> (Void) in
if let hotel = hotelA {
print("hotel: ", hotel)
self.hotels.append(hotel)
let hotelAnnotation = MKPointAnnotation()
hotelAnnotation.title = "\(hotel.hotelName)"
hotelAnnotation.coordinate = CLLocationCoordinate2D(latitude: hotel.latitude, longitude: hotel.longitude)
self.map.addAnnotation(hotelAnnotation)
}
}
}
Note that the print in the if statement is not empty.
However the line hotelAnnotation.title causes a bug unexpected nil and I don't understand why.
Additionnally here is the getAllHotel function inside the Data class:
func getAllHotel(closure: HotelClosure?) {
let ref = Ref().databaseHotels
ref.observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? Dictionary<String, AnyObject> {
let hotel = self.fromDictToHotel(key: snapshot.key, dict: dict)
closure?(hotel)
} else {
closure?(nil)
}
}
}
func fromDictToHotel(key: String, dict: Dictionary<String, AnyObject>) -> Hotel? {
guard let hotelName = dict["name"] as? String else { return nil }
guard let latitude = dict["latitude"] as? Double else { return nil }
guard let longitude = dict["longitude"] as? Double else { return nil }
let hotelDescription = dict["description"] as? String
let hotelGrade = dict["grade"] as? Double
let hotelImageUrl = dict["hotelImageUrl"] as? String
let hotel = Hotel(hid: key, hotelName: hotelName, hotelDescription: hotelDescription, latitude: latitude, longitude: longitude, grade: hotelGrade, url: hotelImageUrl)
return hotel
}
Thanks in advance for your help!
I've been trying to filter data by a string array within firebase to return only the filtered values but the app crashes when I try to run the function. Is this the way it should be done? (The function isn't called if the array is empty)
func fetchFilteredActivities() {
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "category").queryEqual(toValue: filterList)
ref.observe(.childAdded) { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let uid = dictionary["uid"] as? String else { return }
guard let latitudeString = dictionary["latitude"] as? String else { return }
guard let longitudeString = dictionary["longitude"] as? String else { return }
let latitude = (latitudeString as NSString).doubleValue
let longitude = (longitudeString as NSString).doubleValue
Database.fetchUserWithUID(uid: uid, completion: { (user) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
var post = Post(postId: snapshot.key, user: user, dictionary: dictionary)
let postId = snapshot.key
post.id = snapshot.key
let coords = [CLLocation(latitude: latitude, longitude: longitude)]
self.addAnnotation(post: post, title: post.category, subtitle: nil, coords: coords)
})
}
}
The error:
libc++abi.dylib: terminating with uncaught exception of type NSException
I am working on a demo app to return a list of restaurant with the google places API.
I have a Restaurant class - 1 property is a custom type RestaurantDetails. This is where the challenge is:
Restaurant.swift
class Restaurant {
var id:String
var placeId:String
var name:String
var location: Location //Location + address + coordinate + distance
var phone:String?
let details : RestaurantDetails
init(id:String, placeId:String, name:String, location: Location, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init(dict:[String:Any]) {
let id = dict["id"] as! String
let placeId = dict["place_id"] as! String
let name = dict["name"] as! String
let address = dict["formatted_address"] as? String
let location = Location(address: address!, json: dict["geometry"] as! [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
self.init(id: id, placeId: placeId, name: name, location: location!, details: RestaurantDetails(json: dict)!)
}
}
the initialization of the details property fails each time
even using the breakpoint does not help to debug the error because the custom init? does not seem to fire. I do not see any syntax error though
I am usually using guard let control flow to parse the JSON format if the value exists. unsuccessful.
the breakpoint does not kick in on the RestaurantDetails.swift file
RestaurantDetails.swift
enum Price {
case cheap, expensive, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .cheap
case 2..<4 : self = .expensive
default: self = .default
}
}
var dollarSymbol : String {
switch self {
case .cheap: return "$"
case .expensive: return "$$"
default: return ""
}
}
}
enum Rating {
case low, fair, good, excellent, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .low
case 3..<4: self = .fair
case 4..<5: self = .good
case 6 : self = .excellent
default: self = .default
}
}
var starSymbol : String {
switch self {
case .low: return "⭐"
case .fair: return "⭐⭐"
case .good: return "⭐⭐⭐"
case .excellent: return "⭐⭐⭐⭐"
default: return ""
}
}
}
struct RestaurantDetails {
let price:Price
let rating:Rating // Rating (enum) (filter)
let openNow: Bool //(filter)
let types : [String]?
let photos : [NSDictionary]?
}
extension RestaurantDetails {
init?(json: [String: Any]) {
guard let price = json["price_level"] as? Double,
let rating = json["rating"] as? Double,
let openingHours = json["opening_hours"] as? NSDictionary,
let types = json["types"] as? [String],
let photos = json["photos"] as? [NSDictionary] else {
return nil
}
print("json details : \(json)")
self.price = Price(valueDouble: price)
self.rating = Rating(valueDouble: rating)
self.openNow = openingHours["open_now"] as! Bool != nil ?? false
self.types = types
self.photos = photos
}
}
I also have a Location.swift file that works perfectly
struct Location {
var address :String
var coordinate:(lat:Double, lng:Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
guard let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary else { return nil }
self.init(address: address, coordinate: (lat: latitude["lat"] as! Double, lng: longitude["lng"] as! Double))
}
}
As vadian described in his comment, the issue causing the crash is that location has a failable initializer, you are forcibly unwrapping it, and since initialization is in fact failing, you have now force-unwrapped a nil value.
The broader questions are: what do you, the developer, want to happen when some or all location data is not available, and what is the best way to go about doing that?
Options for handling incomplete or missing location data
Based on your description of the problem (“a demo app to return a list of restaurant with the google places api”), it is not clear whether or not restaurants without useable location data should enter your model. It is also not clear whether or not there is some location data you could do without. So I see three options:
Discard all restaurants with incomplete location data
Discard some restaurants with incomplete location data while keeping others
Keep all restaurants regardless of location data
To implement these options, you may want to use some common tools for dealing with optionals:
Nil-coalescing operator
a = b ?? c
Set a equal to b, unless b is nil, in which case set a equal to c.
Optional binding in guard and if
guard let a = b else { return }
Set a equal to b unless b is nil, in which case return from the current context. You can do work in the else branch prior to returning.
if let a = b { /* Do work using a */ } else { /* Do other work */ }
If b is not nil, enter the if branch and set a equal to b; if b is nil, enter the else branch.
How to do it
Discard all restaurants with incomplete location data
In this case, you could make a failable initializer for Restaurant, guarded by location being non-nil:
/// Initialization fails if `location` is `nil`
convenience init?(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
guard let location = location else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
Discard some restaurants with incomplete location data while keeping others
You may be able to recover from some kinds of missing location data. For example, it may be the case that you really just need the street address string and the latitude and longitude are inessential. You could rewrite your Location struct to reflect that:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)?
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
coordinate = nil
}
self.init(address: address, coordinate: coordinate)
}
}
Alternative, lat/long might be the important part, but it might sometimes be possible to recover them from the street address using another API:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
/* An API call that takes an address string and tries to return lat/long but might return nil */
coordinate = returnedCoordinateFromAPI
}
guard let coordinate = coordinate else { return nil }
self.init(address: address, coordinate: coordinate)
}
}
Keep all restaurants regardless of location data
This is the easiest case. If location is not essential, make it optional:
class Restaurant {
var id: String
var placeId: String
var name: String
var location: Location? //Location + address + coordinate + distance
var phone: String?
let details: RestaurantDetails
init(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init?(dict:[String:Any]) {
guard let id = dict["id"] as? String,
let placeId = dict["place_id"] as? String,
let name = dict["name"] as? String else { return nil }
// Use the address from the dictionary unless it is nil, in which case substitute an empty string
let address = dict["formatted_address"] as? String ?? ""
let location = Location(address: address, json: dict["geometry"] as? [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
guard let details = RestaurantDetails(json: dict) else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
}
I want to be able to access the results array, after all the data has been added from Firebase to my array. Every time I try this, I get nil array.
Objective is to have a list of location info objects in an array, loaded through Firebase.
My code snippet:
class func loadData(){
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observe(.childAdded,with: { (snapshot) in
print("inside closure")
let values = snapshot.value as? NSDictionary
let name = values?["Name"] as? String ?? ""
let rating = values?["Rating"] as? Int
let latitude = values?["Latitude"] as? Double
let longitude = values?["Longitude"] as? Double
let musicType = values?["Music"] as? String ?? ""
let loc = LocationInfo.init(name: name, rating: rating!, lat:
latitude!, long: longitude!, musicTyp: musicType)
resultsArray.append(loc)
})
}
Try something like this:
class func loadData(completion: #escaping (_ location: LocationInfo) -> Void) {
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observe(.childAdded,with: { (snapshot) in
print("inside closure")
let values = snapshot.value as? NSDictionary
let name = values?["Name"] as? String ?? ""
let rating = values?["Rating"] as? Int
let latitude = values?["Latitude"] as? Double
let longitude = values?["Longitude"] as? Double
let musicType = values?["Music"] as? String ?? ""
let loc = LocationInfo.init(name: name, rating: rating!, lat:
latitude!, long: longitude!, musicTyp: musicType)
completion(loc)
})
}
In your cycle add something like this:
func getArray(completion: #escaping (_ yourArray: [LocationInfo]) -> Void {
var resultsArray = [LocationInfo]()
let countOfLoadedItems = 0
for item in yourArrayForCycle { // or your own cycle. Implement your logic
loadData(completion: { location in
countOfLoadedItems += 1
resultsArray.append(location)
if countOfLoadedItems == yourArrayForCycle.count {
completion(resultsArray)
}
})
}
}
Then in function, where you wants your data:
getArray(completion: { result in
yourArrayToFill = result
// reload data etc..
})
Something like this. Adapt it to your solution.
Hope it helps
I am trying to parse the emergency data in into emergency struct but it never statifies the condition and get into else case.Here is my code and structure.Some thing i have written woring in first line.
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
for (emerId, emerData) in emergencyDict {
let emer = Emergency.init(emergency: emerData as NSDictionary)
emergency.append(emer)
}
}
else{
let emer = Emergency.init(emerg: "" as AnyObject)
emergency.append(emer)
}
struct Emergency{
var emer_id: String
var emer_name: String
var emer_phoneNo: String
init(emergency: NSDictionary) {
if emergency.object(forKey: "id") != nil {
emer_id = emergency.object(forKey: "id") as! String
}
else{
emer_id = ""
}
}
}
The problem you are having emergency as Array with type [Any] and if you remove the first object then you get Array of type [[String:Any]]. So try like this way.
if let array = snapshotValue["emergency"] as? [Any],
let emergencyArrar = Array(array.dropFirst()) as? [[String:Any]] {
print(emergencyArray)
for emergency in emergencyArray {
print(emergency)
}
}
You have written wrong in this line:
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
It should be:
if let emergencyDict = snapshotValue["emergency"] as? [[String:Any]]{
This question should belong to query from firebase database.
// you have to get the children in emergency,
// then get the value(dictionary) of each child
ref.child("emergency").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let keys = value?.allKeys // [1, 2, 3 ....]
for key in keys {
ref.child("emergency").child(key)..observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// Here is your dictionary
}
}
}) { (error) in
print(error.localizedDescription)
}