I am writing a code in swift 3 for parse a query in json format result from http request.
the json format is:
JSON: {
base = stations;
coord = {
lat = "23.9";
lon = "42.89";
};
weather = (
{
description = mist;
icon = 50n;
id = 701;
main = Mist;
},
{
description = fog;
icon = 50n;
id = 741;
main = Fog;
}
);
wind = {
deg = "222.506";
speed = "1.72";
};}
My code is:
Alamofire.request(url).responseJSON { response in
if let a = response.result.value {
let jsonVar = JSON(a)
if let resDati = jsonVar["base"].string {
print(resDati as String) // <- OK
}
if let dati2 = jsonVar["weather"].array {
for item in dati2 {
print(" > \(item["main"])") // <- OK
}
}
} else {
print(Error.self)
}
}
The problem is on "coord" and "wind" data i have try:
if let dati4 = jsonVar["wind"].array {
for item in dati4 {
print("-- \(item)")
} }
I cannot print the data relatives to "wind" and "coord" in json format.
How can I resolve this.
Thank you.
The key wind contains a dictionary, not an array, you can get the deg and speed values using SwiftyJSON with this code:
if let wind = jsonVar["wind"].dictionary,
let deg = wind["deg"]?.double,
let speed = wind["speed"]?.double {
print(deg, speed)
}
coord works accordingly
if let coord = jsonVar["coord"].dictionary,
let lat = coord["lat"]?.double,
let lon = coord["lon"]?.double {
print(lat, lon)
}
Note: All values are of type Double, the json format is misleading.
Related
I'm trying to write an iOS app in Swift that will let the user take a photo, and then I'm going to overlay some additional data onto the image. I would like the images to include location data. I'm using AVCapturePhoto, and I can see in the documentation that it has some metadata variables, but I can't find any info on how to use them. When I take a photo now with my app, it has no location data in the EXIF info.
How can I set the capture sessions to embed the location data?
I haven't dealt with the new IOS 11 AVCapturePhoto object so this answer has to make a couple of assumptions about how to access data but in theory all of this should work.
Before adding location data you need to ask the user if you can use their location. Add the "Privacy - Location When In Use" tag to your Info.plist. Then add the following code somewhere in your initialisation.
// Request authorization for location manager
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
locationManagerStatus = CLLocationManager.authorizationStatus()
default:
locationManagerStatus = .denied
}
Now the following will create a dictionary with location information in it.
// create GPS metadata properties
func createLocationMetadata() -> NSMutableDictionary? {
guard CLLocationManager.authorizationStatus() == .authorizedWhenInUse else {return nil}
if let location = locationManager.location {
let gpsDictionary = NSMutableDictionary()
var latitude = location.coordinate.latitude
var longitude = location.coordinate.longitude
var altitude = location.altitude
var latitudeRef = "N"
var longitudeRef = "E"
var altitudeRef = 0
if latitude < 0.0 {
latitude = -latitude
latitudeRef = "S"
}
if longitude < 0.0 {
longitude = -longitude
longitudeRef = "W"
}
if altitude < 0.0 {
altitude = -altitude
altitudeRef = 1
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd"
gpsDictionary[kCGImagePropertyGPSDateStamp] = formatter.string(from:location.timestamp)
formatter.dateFormat = "HH:mm:ss"
gpsDictionary[kCGImagePropertyGPSTimeStamp] = formatter.string(from:location.timestamp)
gpsDictionary[kCGImagePropertyGPSLatitudeRef] = latitudeRef
gpsDictionary[kCGImagePropertyGPSLatitude] = latitude
gpsDictionary[kCGImagePropertyGPSLongitudeRef] = longitudeRef
gpsDictionary[kCGImagePropertyGPSLongitude] = longitude
gpsDictionary[kCGImagePropertyGPSDOP] = location.horizontalAccuracy
gpsDictionary[kCGImagePropertyGPSAltitudeRef] = altitudeRef
gpsDictionary[kCGImagePropertyGPSAltitude] = altitude
if let heading = locationManager.heading {
gpsDictionary[kCGImagePropertyGPSImgDirectionRef] = "T"
gpsDictionary[kCGImagePropertyGPSImgDirection] = heading.trueHeading
}
return gpsDictionary;
}
return nil
}
This is where I have to do some guessing as I haven't dealt with IOS 11 AVPhotoCapture. You will need a file data representation of your image data. I assume
AVCapturePhoto.fileDataRepresentation()
returns this. Also you will need the original file metadata. I'll take a guess that
AVCapturePhoto.metadata
contains this. With those assumptions the following function will give you a file data representation with additional location data. There may be newer IOS 11 methods to do this in a cleaner way.
func getFileRepresentationWithLocationData(photo : AVCapturePhoto) -> Data {
// get image metadata
var properties = photo.metadata
// add gps data to metadata
if let gpsDictionary = createLocationMetadata() {
properties[kCGImagePropertyGPSDictionary as String] = gpsDictionary
}
// create new file representation with edited metadata
return photo.fileDataRepresentation(withReplacementMetadata:properties,
replacementEmbeddedThumbnailPhotoFormat:photo.embeddedThumbnailPhotoFormat,
replacementEmbeddedThumbnailPixelBuffer:photo.previewPixelBuffer,
replacementDepthData:photo.depthData)
}
#adamfowlerphoto gave by far the best answer I could find on this subject!
AVCapturePhoto.fileDataRepresentation(withReplacementMetadata:replacementEmbeddedThumbnailPhotoFormat:replacementEmbeddedThumbnailPixelBuffer:replacementDepthData:)
was deprecated in iOS 12. For the new API you must implement the AVCapturePhotoFileDataRepresentationCustomizerProtocol with the method replacementMetadata(). Here is the updated code:
extension CameraViewController: AVCapturePhotoFileDataRepresentationCustomizer {
// create GPS metadata properties
func createLocationMetadata() -> NSMutableDictionary? {
if let location = locationManager.location {
let gpsDictionary = NSMutableDictionary()
var latitude = location.coordinate.latitude
var longitude = location.coordinate.longitude
var altitude = location.altitude
var latitudeRef = "N"
var longitudeRef = "E"
var altitudeRef = 0
if latitude < 0.0 {
latitude = -latitude
latitudeRef = "S"
}
if longitude < 0.0 {
longitude = -longitude
longitudeRef = "W"
}
if altitude < 0.0 {
altitude = -altitude
altitudeRef = 1
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd"
gpsDictionary[kCGImagePropertyGPSDateStamp] = formatter.string(from:location.timestamp)
formatter.dateFormat = "HH:mm:ss"
gpsDictionary[kCGImagePropertyGPSTimeStamp] = formatter.string(from:location.timestamp)
gpsDictionary[kCGImagePropertyGPSLatitudeRef] = latitudeRef
gpsDictionary[kCGImagePropertyGPSLatitude] = latitude
gpsDictionary[kCGImagePropertyGPSLongitudeRef] = longitudeRef
gpsDictionary[kCGImagePropertyGPSLongitude] = longitude
gpsDictionary[kCGImagePropertyGPSDOP] = location.horizontalAccuracy
gpsDictionary[kCGImagePropertyGPSAltitudeRef] = altitudeRef
gpsDictionary[kCGImagePropertyGPSAltitude] = altitude
if let heading = locationManager.heading {
gpsDictionary[kCGImagePropertyGPSImgDirectionRef] = "T"
gpsDictionary[kCGImagePropertyGPSImgDirection] = heading.trueHeading
}
return gpsDictionary;
}
return nil
}
func getFileRepresentationWithLocationData(photo : AVCapturePhoto) -> Data {
// get image metadata
var properties = photo.metadata
// add gps data to metadata
if let gpsDictionary = createLocationMetadata() {
properties[kCGImagePropertyGPSDictionary as String] = gpsDictionary
}
// create new file representation with edited metadata
return photo.fileDataRepresentation(with: self) ?? Data()
}
func replacementMetadata(for photo: AVCapturePhoto) -> [String : Any]? {
var properties = photo.metadata
// add gps data to metadata
if let gpsDictionary = createLocationMetadata() {
properties[kCGImagePropertyGPSDictionary as String] = gpsDictionary
}
return properties
}
}
To print the result you could use this:
static func printEXIFData(imageData: Data) {
var exifData: CFDictionary? = nil
imageData.withUnsafeBytes {
let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, imageData.count),
let source = CGImageSourceCreateWithData(cfData, nil) {
exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
print(exifData)
}
}
}
Don't try to convert your imageData into JPEG representation because it will discard the GPS metadata. (Source: https://stackoverflow.com/a/10339278/647644)
let data = jpegData(compressionQuality: 1.0) // NOOOOO
I am querying a JSON database of zombies and it returns them as a Dictionary. I don't know how to mutate it with SWIFT 3
Here's the query::
func getZombieAttackGroupFromDatabase () {
ref?.child("Attacker Group").child((self.userParty?.leadID)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get data
print("PULLING DATA")
if let value = snapshot.value as? NSDictionary{
// break data into an Array of Zombie Structs
// print(value)
for zombieID in value.allKeys {
print(value[zombieID])
let thisZombieID = zombieID
let thisZombieGroup = value[zombieID]["Group"]
}
} else {
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
this part: let thisZombieGroup = value[zombieID]["Group"] isn't being recognized. How do I access group? If i get that, i can modify to the other components.
Here's the return :
{
"-KrNSmv64Ia32g5nw1L9" = {
Group = 15;
Health = 250;
"Is Undead" = true;
Location = 1;
Number = 0;
};
"-KrNSmv64Ia32g5nw1LA" = {
Group = 11;
Health = 250;
"Is Undead" = true;
Location = 5;
Number = 1;
};
"-KrNSmv64Ia32g5nw1LB" = {
Group = 2;
Health = 250;
"Is Undead" = true;
Location = 3;
Number = 2;
};
"-KrNSmv776r9eO6t7CY0" = {
Group = 14;
Health = 250;
"Is Undead" = true;
Location = 0;
Number = 3;
};
"-KrNSmv776r9eO6t7CY1" = {
Group = 0;
Health = 250;
"Is Undead" = true;
Location = 4;
Number = 4;
};
}
As you can see, each of the Structs has a parent that is an automatically generated ID. I don't know how to access it.
How can I access each element from item 1? I need the parent auto-key "-KrNSmv64Ia32g5nw1L9" and each child value.
"-KrNSmv64Ia32g5nw1L9" = {
Group = 15;
Health = 250;
"Is Undead" = true;
Location = 1;
Number = 0;
Cast value to a proper Swift dictionary, not NSDictionary.
if let value = snapshot.value as? [String:Any].
You just have to iterate through the keys of the dictionaries, get the embedded dictionary using the key value and then parse the "zombie data".
for key in value.keys {
if let zombieData = value[key] as? [String:Any] {
zombieData
if let group = zombieData["Group"] as? Int, let health = zombieData["Health"] as? Int, let isUndead = zombieData["Is Undead"] as? Bool, let location = zombieData["Location"] as? Int, let number = zombieData["Number"] as? Int {
//use the parsed data
}
}
}
Try this:
for zombieID in value.allKeys {
print(value[zombieID])
let thisZombieID = zombieID
if let zombieValues = value[zombieID] as? [String:Any] {
let thisZombieGroup = zombieValues["Group"] as? Int
}
}
First, Thank you so much to Woof and David. It was a combination of both of your ideas that got it to work.
func getZombieAttackGroupFromDatabase () {
ref?.child("Attacker Group").child((self.userParty?.leadID)!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get data
print("PULLING DATA")
if let value = snapshot.value as? [String:Any]{
// break data into an Array of Zombie Structs
for zombieID in value.keys {
let thisZombieID = zombieID
print(thisZombieID)
let zombieValues = value[zombieID] as? [String:Any]
let thisZombieGroup = zombieValues?["Group"] as! String
print(thisZombieGroup)
}
} else {
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
I am trying to integrate Firebase and AutoCompleteTextField in Swift so that I have autocomplete in the textField. I'm having a problem trying to turn the dictionary into an array so that I can set it in autoCompleteTextField.autoCompleteStrings. This is the code I have for it.
func handleTextFieldInterfaces() {
let ref = FIRDatabase.database().reference().child("Airport")
ref.observeEventType(.Value, withBlock: { (snapshot) in
self.autoCompleteTextField.onTextChange = {[weak self] text in
if !text.isEmpty {
var fbosStuff = [String]()
if let snaps = snapshot.value as? [[String: AnyObject]] {
for places in snaps {
print(places)
let names = places["code"] as? String
fbosStuff.append(names!)
}
self!.autoCompleteTextField.autoCompleteStrings = fbosStuff
}
}
}
})
}
and the response from Firebase that I am trying to put into the textfield is.
Snap (Airport) {
"Long Beach" = {
FBOs = {
Atlantic = {
100LL = "7.0";
freq = "120.1";
fullname = "Atlantic, Long Beach, KLGB";
"jet-A" = "5.5";
"phone number" = "(800) 554-3591";
};
Signature = {
100ll = "7.0";
email = "lgb#signatureflight.com";
freq = "120.1";
fullname = "Signature, Long Beach, KLGB";
"jet-a" = "5.5";
phonenumber = "(800) 554-3591";
};
};
code = KLGB;
fieldname = Daughtery;
location = "Long Beach, California, USA";
};
"Santa Monica" = {
FBOs = {
"American Flyers" = {
100ll = "5.38";
freq = "123.3";
fullname = "American Flyers, Santa Monica, KSMO";
phonenumber = "(310) 390-2099";
};
Atlantic = {
100ll = "7.79";
freq = "122.95";
fullname = "Atlantic, Santa Monica, KSMO";
"jet-a" = "7.19";
phonenumber = "(310) 396-6770";
};
};
code = KSMO;
fieldname = "Santa Monica Muni Airport";
location = "Santa Monica, California, USA";
};
}
Try :-
Swift 2
for places in snaps {
print(places.0) // Will give you the key
let names = places["code"] as? String
fbosStuff.append(names!)
}
Swift 3
for places in snaps {
print(places.key) // Will give you the key
let names = places["code"] as? String
fbosStuff.append(names!)
}
I am trying to parse and get the values from this structure of JSON:
["mst_customer": 1, "data": {
0 = 2;
1 = 1;
2 = 1;
3 = "JAYSON TAMAYO";
4 = "581-113-113";
5 = 56;
6 = on;
7 = g;
8 = jayson;
9 = active;
"app_access" = on;
id = 2;
"mst_customer" = 1;
name = "Jayson Tamayo";
status = active;
territory = 1;
}, "status": OK, "staff_id": 2, "staff_name": Jayson Tamayo]
I use the following Swift code to get the values:
(data: Dictionary<String, AnyObject>, error: String?) -> Void in
if error != nil {
print(error)
} else {
if let feed = data["data"] as? NSDictionary ,let entries = data["data"] as? NSArray{
for elem: AnyObject in entries{
if let staff_name = elem["name"] as? String{
print(staff_name)
}
}
}
}
I am trying to get the name by using the keys name or staff_name. But I always get nil.
you want to access staff_name, which is not in "data" variable ... you can simply get that like
if error != nil {
print(error)
} else {
if let name = data["staff_name"] as? String{
print(name)
}
}
for elem: AnyObject in entries{
if let songName = elem["name"] as? String{
print(songName)
}
}
//replace above code with below code
if let songName : String = entries["name"] as? String{
print(songName)
}
This code does not add annotations to mapView. I saw in one answer that mapView function is called every time addAnotation is called so where's the problem? But when I move map they show up.
func addPlacesMarkers(location:CLLocation) {
self.communication.getJsonData(self.getPubsNearByCreator(location)) { (finalData, error) -> Void in
if error != nil {
print(error)
} else {
if let row: NSArray = finalData {
for var i = 0; i < row.count; i++ {
let lat = row[i]["lat"] as! String
let lng = row[i]["lng"] as! String
let title = row[i]["name"] as! String
let id = row[i]["id"] as! String
let point = CustomizedAnotation(id: Int(id)!, name: title)
point.title = title
point.coordinate.latitude = Double(lat)!
point.coordinate.longitude = Double(lng)!
let keyExists = self.places[Int(id)!] != nil
if keyExists == false {
self.places.updateValue(point, forKey: Int(id)!)
}
}
var finalPlaces :[MKPointAnnotation] = []
for place in self.places.values {
finalPlaces.append(place)
}
self.mView.addAnnotations(finalPlaces)
self.mView.showsPointsOfInterest = false
}
}
}
}
You can't modify the UI in a thread different from the main.
You should put your UI modification code inside a dispatch_async block like this:
dispatch_async(dispatch_get_main_queue()) {
//Your code that modify the UI
self.mView.addAnnotations(finalPlaces)
}