Getting latitude and longitude from a json file to create a map - json

I wanted to modify apple's earthquakes project to display a map together with the location magnitude and time. In the json file I can see the coordinates but for the life of me I cannot read them and use them as latitude and longitude for the map. I succeeded to display the map by using the address (title) but the format changes and there are too many possibilities to account for.
The earthquake project can be downloaded at https://developer.apple.com/documentation/coredata/loading_and_displaying_a_large_data_feed
I post the Quake.swift file below so you may have an idea of what I tried. I added a coordinates characteristic to their magnitude, place and time first as an array and then as a string but I always fail to read it and use it to display the map as latitude and longitude.
Thanks in advance for your help.
The json file is long so I post a few lines here to give you an idea of the format:
{"type":"FeatureCollection","metadata":{"generated":1648109722000,"url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.geojson","title":"USGS All Earthquakes, Past Month","status":200,"api":"1.10.3","count":9406},"features":[{"type":"Feature","properties":{"mag":4.5,"place":"south of the Fiji Islands","time":1648106910967,"updated":1648108178040,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/us7000gwsr","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us7000gwsr.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"reviewed","tsunami":0,"sig":312,"net":"us","code":"7000gwsr","ids":",us7000gwsr,","sources":",us,","types":",origin,phase-data,","nst":null,"dmin":5.374,"rms":1.03,"gap":102,"magType":"mb","type":"earthquake","title":"M 4.5 - south of the Fiji Islands"},"geometry":{"type":"Point","coordinates":[179.1712,-24.5374,534.35]},"id":"us7000gwsr"},
{"type":"Feature","properties":{"mag":1.95000005,"place":"2 km NE of Pāhala, Hawaii","time":1648106708550,"updated":1648106923140,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/hv72960677","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/hv72960677.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"automatic","tsunami":0,"sig":59,"net":"hv","code":"72960677","ids":",hv72960677,","sources":",hv,","types":",origin,phase-data,","nst":33,"dmin":null,"rms":0.109999999,"gap":136,"magType":"md","type":"earthquake","title":"M 2.0 - 2 km NE of Pāhala, Hawaii"},"geometry":{"type":"Point","coordinates":[-155.463333129883,19.2151660919189,34.9500007629395]},"id":"hv72960677"},
{"type":"Feature","properties":{"mag":1.75,"place":"4km SE of Calabasas, CA","time":1648106545420,"updated":1648109717670,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/ci39976447","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/ci39976447.geojson","felt":7,"cdi":3.1,"mmi":null,"alert":null,"status":"automatic","tsunami":0,"sig":49,"net":"ci","code":"39976447","ids":",ci39976447,","sources":",ci,","types":",dyfi,nearby-cities,origin,phase-data,scitech-link,","nst":33,"dmin":0.04554,"rms":0.27,"gap":56,"magType":"ml","type":"earthquake","title":"M 1.8 - 4km SE of Calabasas, CA"},"geometry":{"type":"Point","coordinates":[-118.61,34.1285,2.92]},"id":"ci39976447"},
The Quake.swift file:
import CoreData
import SwiftUI
import OSLog
// MARK: - Core Data
/// Managed object subclass for the Quake entity.
class Quake: NSManagedObject, Identifiable {
// The characteristics of a quake.
#NSManaged var magnitude: Float
#NSManaged var place: String
#NSManaged var time: Date
#NSManaged var coordinates: String
// A unique identifier used to avoid duplicates in the persistent store.
// Constrain the Quake entity on this attribute in the data model editor.
#NSManaged var code: String
/// Updates a Quake instance with the values from a QuakeProperties.
func update(from quakeProperties: QuakeProperties) throws {
let dictionary = quakeProperties.dictionaryValue
guard let newCode = dictionary["code"] as? String,
let newMagnitude = dictionary["magnitude"] as? Float,
let newPlace = dictionary["place"] as? String,
let newTime = dictionary["time"] as? Date,
let newCoordinates = dictionary["coordinates"] as? String
else {
throw QuakeError.missingData
}
code = newCode
magnitude = newMagnitude
place = newPlace
time = newTime
coordinates = newCoordinates
}
}
// MARK: - SwiftUI
extension Quake {
/// The color which corresponds with the quake's magnitude.
var color: Color {
switch magnitude {
case 0..<1:
return .green
case 1..<2:
return .yellow
case 2..<3:
return .orange
case 3..<5:
return .red
case 5..<Float.greatestFiniteMagnitude:
return .init(red: 0.8, green: 0.2, blue: 0.7)
default:
return .gray
}
}
/// An earthquake for use with canvas previews.
static var preview: Quake {
let quakes = Quake.makePreviews(count: 1)
return quakes[0]
}
#discardableResult
static func makePreviews(count: Int) -> [Quake] {
var quakes = [Quake]()
let viewContext = QuakesProvider.preview.container.viewContext
for index in 0..<count {
let quake = Quake(context: viewContext)
quake.code = UUID().uuidString
quake.time = Date().addingTimeInterval(Double(index) * -300)
quake.magnitude = .random(in: -1.1...10.0)
quake.place = "15km SSW of Cupertino, CA"
quake.coordinates = "-117.7153333,35.8655,7.59"
quakes.append(quake)
}
return quakes
}
}
// MARK: - Codable
/// creating or updating Quake instances.
struct GeoJSON: Decodable {
private enum RootCodingKeys: String, CodingKey {
case features
}
private enum FeatureCodingKeys: String, CodingKey {
case properties
}
private(set) var quakePropertiesList = [QuakeProperties]()
init(from decoder: Decoder) throws {
let rootContainer = try decoder.container(keyedBy: RootCodingKeys.self)
var featuresContainer = try rootContainer.nestedUnkeyedContainer(forKey: .features)
while !featuresContainer.isAtEnd {
let propertiesContainer = try featuresContainer.nestedContainer(keyedBy: FeatureCodingKeys.self)
// Decodes a single quake from the data, and appends it to the array, ignoring invalid data.
if let properties = try? propertiesContainer.decode(QuakeProperties.self, forKey: .properties) {
quakePropertiesList.append(properties)
}
}
}
}
/// A struct encapsulating the properties of a Quake.
struct QuakeProperties: Decodable {
// MARK: Codable
private enum CodingKeys: String, CodingKey {
case magnitude = "mag"
case place
case time
case code
case coordinates
}
let magnitude: Float // 1.9
let place: String // "21km ENE of Honaunau-Napoopoo, Hawaii"
let time: Double // 1539187727610
let code: String // "70643082"
let coordinates: String // [-117.7153333,35.8655,7.59]
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let rawMagnitude = try? values.decode(Float.self, forKey: .magnitude)
let rawPlace = try? values.decode(String.self, forKey: .place)
let rawTime = try? values.decode(Double.self, forKey: .time)
let rawCode = try? values.decode(String.self, forKey: .code)
let rawCoordinates = try? values.decode(String.self, forKey: .coordinates)
// Ignore earthquakes with missing data.
guard let magntiude = rawMagnitude,
let place = rawPlace,
let time = rawTime,
let code = rawCode,
let coordinates = rawCoordinates
else {
let values = "code = \(rawCode?.description ?? "nil"), "
+ "mag = \(rawMagnitude?.description ?? "nil"), "
+ "place = \(rawPlace?.description ?? "nil"), "
+ "time = \(rawTime?.description ?? "nil"), "
+ "coordinates = \(rawCoordinates?.description ?? "nil")"
let logger = Logger(subsystem: "com.example.apple-samplecode.Earthquakes", category: "parsing")
logger.debug("Ignored: \(values)")
throw QuakeError.missingData
}
self.magnitude = magntiude
self.place = place
self.time = time
self.code = code
self.coordinates = coordinates
}
// The keys must have the same name as the attributes of the Quake entity.
var dictionaryValue: [String: Any] {
[
"magnitude": magnitude,
"place": place,
"time": Date(timeIntervalSince1970: TimeInterval(time) / 1000),
"code": code,
"coordinates": coordinates
]
}
}

The coordinates are not on the same level as properties, they are in a sibling geometry. The basic pattern is
{
"features": [
{
"properties": {
"mag":1.9,
"place":"21km ENE of Honaunau-Napoopoo, Hawaii",
"time":1539187727610,"updated":1539187924350,
"code":"70643082"
},
"geometry" : {
"coordinates": [-122.8096695,38.8364983,1.96]
}
}
]
}
You have to decode the coordinates in GeoJSON by adding geometry to FeatureCodingKeys. And you have to extend the Core Data model to preserve the coordinates.
In the Core Data model add two properties
longitude - Double - non-optional, use scalar type
latitude - Double - non-optional, use scalar type
In Quake.swift
import CoreLocation
In the class Quake add
#NSManaged var latitude: CLLocationDegrees
#NSManaged var longitude: CLLocationDegrees
and replace update(from with
func update(from quakeProperties: QuakeProperties) throws {
let dictionary = quakeProperties.dictionaryValue
guard let newCode = dictionary["code"] as? String,
let newMagnitude = dictionary["magnitude"] as? Float,
let newPlace = dictionary["place"] as? String,
let newTime = dictionary["time"] as? Date,
let newLatitude = dictionary["latitude"] as? CLLocationDegrees,
let newLongitude = dictionary["longitude"] as? CLLocationDegrees
else {
throw QuakeError.missingData
}
code = newCode
magnitude = newMagnitude
place = newPlace
time = newTime
latitude = newLatitude
longitude = newLongitude
}
In the Quake extension add
var coordinate : CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
In GeoJSON extend FeatureCodingKeys
private enum FeatureCodingKeys: String, CodingKey {
case properties, geometry
}
and replace the while loop with
while !featuresContainer.isAtEnd {
let propertiesContainer = try featuresContainer.nestedContainer(keyedBy: FeatureCodingKeys.self)
// Decodes a single quake from the data, and appends it to the array, ignoring invalid data.
if var properties = try? propertiesContainer.decode(QuakeProperties.self, forKey: .properties),
let geometry = try? propertiesContainer.decode(QuakeGeometry.self, forKey: .geometry) {
let coordinates = geometry.coordinates
properties.longitude = coordinates[0]
properties.latitude = coordinates[1]
quakePropertiesList.append(properties)
}
}
Add the struct
struct QuakeGeometry: Decodable {
let coordinates : [Double]
}
In QuakeProperties add
var latitude : CLLocationDegrees = 0.0
var longitude : CLLocationDegrees = 0.0
and replace dictionaryValue with
var dictionaryValue: [String: Any] {
[
"magnitude": magnitude,
"place": place,
"time": Date(timeIntervalSince1970: TimeInterval(time) / 1000),
"code": code,
"latitude": latitude,
"longitude": longitude
]
}
Finally in DetailView.swift
import MapKit
and replace QuakeDetail with
struct QuakeDetail: View {
var quake: Quake
#State private var region : MKCoordinateRegion
init(quake : Quake) {
self.quake = quake
_region = State(wrappedValue: MKCoordinateRegion(center: quake.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.3, longitudeDelta: 0.3)))
}
var body: some View {
VStack {
QuakeMagnitude(quake: quake)
Text(quake.place)
.font(.title3)
.bold()
Text("\(quake.time.formatted())")
.foregroundStyle(Color.secondary)
Text("\(quake.latitude) - \(quake.longitude)")
Map(coordinateRegion: $region, annotationItems: [quake]) { item in
MapMarker(coordinate: item.coordinate, tint: .red)
}
}
}
}

Related

Executing function after data loads into view SwiftUI

I want the Snapshot function to be executed once my ContentView has loaded in all the network data. I have no problem with the API call, I just simply want the Snapshot function to run after all data has loaded into ContentView.
Expected result after pressing button:
Expected result after pressing button
Actual result after pressing button:
Actual result after pressing button
ContentView.swift
extension View {
func Snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct ContentView: View {
#ObservedObject var FetchResults = fetchResults()
var contentView: some View {
ContentView()
}
var body: some View {
Group {
if FetchResults.dataHasLoaded {
VStack {
Text(FetchResults.clout.postFound?.body ?? "n/a")
.padding()
.background(Color.blue)
.mask(RoundedRectangle(cornerRadius: 30, style: .continuous))
.shadow(color: Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)).opacity(0.3), radius: 10, x: 0, y:10)
.padding()
.frame(maxWidth: 300)
}
Button(action: {
let image = contentView.Snapshot()
print(image)
}, label:{Text("press me to print view")})
} else {
Text("loading data")
}
}
}
}
GotPost.swift -- my ViewModel running the API call
class fetchResults: ObservableObject {
#Published var clout = Cloutington()
#Published var dataHasLoaded = false
init() {
getData { clout in
self.clout = clout
}
}
private func getData(completion: #escaping (Cloutington) -> ()) {
let parameters = "{\r\n \"PostHashHex\": \"2f9633c2460f23d9d5690fe9fd058457ebeb01f6f75805aa426cdaab90a1f4b4\"\r\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://bitclout.com/api/v0/get-single-post")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = postData
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { (responseData, response, error) in
print(error)
print(response)
print(responseData)
if let resData = responseData {
let decoder = JSONDecoder()
do
{
let finalData = try decoder.decode(Cloutington.self, from: resData)
DispatchQueue.main.async {
completion(finalData)
self.dataHasLoaded = true
}
}
catch (let error)
{
print(error)
}
}
}
task.resume()
}
}
PostFound.swift -- model parsing JSON data
struct Cloutington: Decodable {
var postFound: PostFound?
enum CodingKeys: String, CodingKey {
case postFound = "PostFound"
}
}
struct PostFound: Decodable {
var id: String?
var postHashHex, posterPublicKeyBase58Check, parentStakeID, body: String?
var imageURLs: [String]?
// var recloutedPostEntryResponse: JSONNull?
var creatorBasisPoints, stakeMultipleBasisPoints: Int?
var timestampNanos: Double?
var isHidden: Bool?
var confirmationBlockHeight: Int?
var inMempool: Bool?
var profileEntryResponse: ProfileEntryResponse?
var likeCount, diamondCount: Int?
var isPinned: Bool?
var commentCount, recloutCount: Int?
var diamondsFromSender: Int?
enum CodingKeys: String, CodingKey {
case postHashHex = "PostHashHex"
case posterPublicKeyBase58Check = "PosterPublicKeyBase58Check"
case parentStakeID = "ParentStakeID"
case body = "Body"
case imageURLs = "ImageURLs"
case creatorBasisPoints = "CreatorBasisPoints"
case stakeMultipleBasisPoints = "StakeMultipleBasisPoints"
case timestampNanos = "TimestampNanos"
case isHidden = "IsHidden"
case confirmationBlockHeight = "ConfirmationBlockHeight"
case inMempool = "InMempool"
case profileEntryResponse = "ProfileEntryResponse"
case likeCount = "LikeCount"
case diamondCount = "DiamondCount"
case isPinned = "IsPinned"
case commentCount = "CommentCount"
case recloutCount = "RecloutCount"
case diamondsFromSender = "DiamondsFromSender"
}
}
// MARK: - ProfileEntryResponse
struct ProfileEntryResponse: Decodable {
var publicKeyBase58Check, username, profileEntryResponseDescription, profilePic: String?
var isHidden, isReserved, isVerified: Bool?
var coinPriceBitCloutNanos, stakeMultipleBasisPoints: Int?
enum CodingKeys: String, CodingKey {
case publicKeyBase58Check = "PublicKeyBase58Check"
case username = "Username"
case profileEntryResponseDescription = "Description"
case profilePic = "ProfilePic"
case isHidden = "IsHidden"
case isReserved = "IsReserved"
case isVerified = "IsVerified"
case coinPriceBitCloutNanos = "CoinPriceBitCloutNanos"
case stakeMultipleBasisPoints = "StakeMultipleBasisPoints"
}
} ```
Really appreciate the help 🙏
You're going to get more and better help in general if you follow the Swift API Design Guidelines for naming types and variables. That means using lower case for the initial letters of properties and methods, and upper case for the initial letters of types. I understand that you may come from a platform like C# where the conventions are different. But when you don't do things the Swift way (or worse, as in your code, where you do both!), you make it harder for us to understand your code.
Also, this is a bad pattern:
#ObservedObject var FetchResults = fetchResults()
The problem is that SwiftUI doesn't understand the ownership of the FetchResults object, and will recreate it each time the view is recreated. In this case, you should use #StateObject instead:
#StateObject var fetchResults = FetchResults()
Check out this article by Paul Hudson for guidance on when to use #StateObject and when to use #ObservableObject.
However, that won't fix the problem you posted about. Your code creates the snapshot by saying contentView.Snapshot(), which calls your contentView computed property, which creates a new ContentView from scratch, which creates a new, unloaded FetchResults (regardless of whether you use #ObservedObject or #StateObject).
You need to reuse the FetchResults that has already loaded its data, so just say self.snapshot() instead:
Button(
action: {
let image = self.snapshot()
print(image)
},
label: {
Text("press me to print view")
}
)

Missing Data with URLSession and JSONDecode in Swift4

Using Swift4, iOS11.1, Xcode9.1,
The following parseData method almost works. Everything seems to work fine (and a correct JSON-data set is fetched by this URLSession and JSONDecode).
However, what is really surprising to me is the fact that a normal Browser shows different JSON-data (i.e. 20x as much is fetched than compared to this iOS-URLSession approach). Why is that ??
In the code below, you can find two print statements. The first showing the actual URLRequest-string (including all query- and type-parameters). The second prints the number of fetched JSON data-sets.
If you use a Browser and fetch the JSON with the exact same URLRequest-string, then the number of datasets is 20 sets. With the URLSession it is only 1 set
Why this difference in JSON data-length delivered ??????????
Here is PRINT_LOG 1 :
https://maps.googleapis.com/maps/api/place/textsearch/json?query=Cham,%20Langackerstrasse&type=bus_station&key=AIzaSyDYtkKiJRuJ9tjkeOtEAuEtTLp5a0XR1M0
(API key no longer valid - after having found the solution, I did change the API-key for security reasons. The Question and its Answer are still of value tough).
Here is PRINT_LOG 2 :
count = 1
Here is my Code:
func parseData(queryString: String) {
// nested function
func createURLWithComponents() -> URL? {
let urlComponents = NSURLComponents()
urlComponents.scheme = "https";
urlComponents.host = "maps.googleapis.com";
urlComponents.path = "/maps/api/place/textsearch/json";
// add params
urlComponents.queryItems = [
URLQueryItem(name: "query", value: "Cham, Langackerstrasse"),
URLQueryItem(name: "type", value: "bus_station"),
URLQueryItem(name: "key", value: AppConstants.GOOGLE_MAPS_DIRECTIONS_API_KEY)
]
return urlComponents.url
}
let myUrl = createURLWithComponents()
var myRequest = URLRequest(url: myUrl!)
myRequest.httpMethod = "GET"
myRequest.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let myConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: myConfiguration, delegate: nil, delegateQueue: OperationQueue.main)
// Until here everything all right !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// i.e. the following print is correct !!!!!!!!!!!!!!!!!!!!!!!!!
// (and when this print is copied to a Browser then the data fetched has 20 JSON-entries...)
// PRINT_LOG 1
print(myRequest.description)
let myTask = session.dataTask(with: myRequest) { (data, response, error) in
if (error != nil) {
print("Error1 fetching JSON data")
}
else {
do {
//Decode retrived data with JSONDecoder and assing type of Station object
let stationData = try JSONDecoder().decode(Station.self, from: data!)
// Here is the puzzling thing to happen !!!!!!!!!!!!!!!!!!!!!!
// i.e. the following print reveals count = 1 !!!!!!!!!!!!!!!!!
// ??? Why not 20 as with the Browser ????????????????????
// PRINT_LOG 2
print("count = " + "\(String(describing: stationData.results.count))")
}
catch let error {
print(error)
}
}
}
myTask.resume()
}
For completeness, here is the matching Struct:
struct Station: Codable {
let htmlAttributions: [String]
let nextPageToken: String?
let results: [Result]
let status: String
struct Result: Codable {
let formattedAddress: String
let geometry: Geometry
let icon: String
let id: String
let name: String
let photos: [Photo]?
let placeID: String
let rating: Double?
let reference: String
let types: [String]
struct Geometry: Codable {
let location: Coordinates
let viewport: Viewport
struct Coordinates: Codable {
let lat: Double
let lng: Double
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
lat = try values.decode(Double.self, forKey: .lat)
lng = try values.decode(Double.self, forKey: .lng)
}
enum CodingKeys : String, CodingKey {
case lat
case lng
}
}
struct Viewport: Codable {
let northeast: Coordinates
let southwest: Coordinates
enum CodingKeys : String, CodingKey {
case northeast
case southwest
}
}
enum CodingKeys : String, CodingKey {
case location
case viewport
}
}
struct Photo: Codable {
let height: Int
let htmlAttributions: [String]
let photoReference: String?
let width: Int
enum CodingKeys : String, CodingKey {
case height
case htmlAttributions = "html_attributions"
case photoReference = "photo_reference"
case width
}
}
enum CodingKeys : String, CodingKey {
case formattedAddress = "formatted_address"
case geometry
case icon
case id
case name
case photos
case placeID = "place_id"
case rating
case reference
case types
}
}
enum CodingKeys : String, CodingKey {
case htmlAttributions = "html_attributions"
case nextPageToken = "next_page_token"
case results
case status
}
}
Thanks for any help on this.
Found it !!!
It was the missing Info.plist localization ! (i.e. I ran the App form XCode having the Language set to German under Run-->Options-->Appliation Language). My Info.plist was only partly localized (i.e. My file InfoPlist.strings had only a few entries yet - but not a complete translation). And this missing translation lead to this problem !!!!
Running it in pure English works fine and I get 20 entries as expected.

Convert string to Date/Int/Double using codable

I am getting a response from an API but the problem is that the API is sending values back as a string of dates and doubles. I am therefore getting the error "Expected to decode Double but found a string/data instead." I have structured my struct like this to solve the problem but this seems like a patch. Is there any better way to fix this issue? I feel like apple has thought of this and included something natively to address this.
struct SimpleOrder:Codable{
var createdDate:Date! {
return createdDateString.dateFromISO8601
}
var createdDateString:String
var orderId:String!
var priceVal:Double!
var priceString:String{
didSet {
priceVal = Double(self.priceString)!
}
}
private enum CodingKeys: String, CodingKey {
//case createdDate
case createdDateString = "time"
case orderId = "id"
case priceVal
case priceString = "price"
}
}
I don't know if this is relevant but this is how it is being used. I am getting the data as a string and converting it to data which is stored in the dataFromString variable
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 //This is irrelevant though because the date is returned as a string.
do{
let beer = try decoder.decode(SimpleOrder.self, from: dataFromString)
print ("beer is \(beer)")
}catch let error{
print ("error is \(error)")
}
As a result of using codable, I am getting an error when trying to get an empty instance of SimpleOrder. Before I was using codable, I had no issues using SimpleOrder() without any arguments.
Error: Cannot invoke initializer for type 'SimpleOrder' with no arguments
var singlePoint = SimpleOrder()
struct SimpleOrder: Codable {
var created: Date?
var orderId: String?
var price: String?
private enum CodingKeys: String, CodingKey {
case created = "time", orderId = "id", price
}
init(created: Date? = nil, orderId: String? = nil, price: String? = nil) {
self.created = created
self.orderId = orderId
self.price = price
}
}
extension SimpleOrder {
var priceValue: Double? {
guard let price = price else { return nil }
return Double(price)
}
}
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
Decoding the json data returned by the API:
let jsonData = Data("""
{
"time": "2017-12-01T20:41:48.700Z",
"id": "0001",
"price": "9.99"
}
""".utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
do {
let simpleOrder = try decoder.decode(SimpleOrder.self, from: jsonData)
print(simpleOrder)
} catch {
print(error)
}
Or initialising a new object with no values:
var order = SimpleOrder()

Swift 4 JSON Codable ids as keys

I would like to use Swift 4's codable feature with json but some of the keys do not have a set name. Rather there is an array and they are ids like
{
"status": "ok",
"messages": {
"generalMessages": [],
"recordMessages": []
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie",
"animalBreed": "Domestic Short Hair / Domestic Short Hair / Mixed (short coat)",
"animalGeneralAge": "Adult",
"animalSex": "Female",
"animalPrimaryBreed": "Domestic Short Hair",
"animalUpdatedDate": "6/26/2015 2:00 PM",
"animalOrgID": "12",
"animalLocationDistance": ""
where you see the 181 ids. Does anyone know how to handle the 181 so I can specify it as a key? The number can be any number and is different for each one.
Would like something like this
struct messages: Codable {
var generalMessages: [String]
var recordMessages: [String]
}
struct data: Codable {
var
}
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
//var 181: [data] //What do I place here
}
Thanks in advance.
Please check :
struct ResponseData: Codable {
struct Inner : Codable {
var animalID : String
var animalName : String
private enum CodingKeys : String, CodingKey {
case animalID = "animalID"
case animalName = "animalName"
}
}
var Status: String
var foundRows: Int
var data : [String: Inner]
private enum CodingKeys: String, CodingKey {
case Status = "status"
case foundRows = "foundRows"
case data = "data"
}
}
let json = """
{
"status": "ok",
"messages": {
"generalMessages": ["dsfsdf"],
"recordMessages": ["sdfsdf"]
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie"
},
"182": {
"animalID": "182",
"animalName": "Sophie"
}
}
}
"""
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let jsonData = try decoder.decode(ResponseData.self, from: data)
for (key, value) in jsonData.data {
print(key)
print(value.animalID)
print(value.animalName)
}
}
catch {
print("error:\(error)")
}
I don't think you can declare a number as a variable name. From Apple's doc:
Constant and variable names can’t contain whitespace characters,
mathematical symbols, arrows, private-use (or invalid) Unicode code
points, or line- and box-drawing characters. Nor can they begin with a
number, although numbers may be included elsewhere within the name.
A proper way is to have a property capturing your key.
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
var key: Int // your key, e.g., 181
}
I would suggest something like this:-
struct ResponseData: Codable {
struct AnimalData: Codable {
var animalId: String
var animalName: String
private enum CodingKeys: String, CodingKey {
case animalId = "animalID"
case animalName = "animalName"
}
}
var status: String
var foundRows: Int
var data: [AnimalData]
private enum CodingKeys: String, CodingKey {
case status = "status"
case foundRows = "foundRows"
case data = "data"
}
struct AnimalIdKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let animalsData = try container.nestedContainer(keyedBy: AnimalIdKey.self, forKey: .data)
self.foundRows = try container.decode(Int.self, forKey: .foundRows)
self.status = try container.decode(String.self, forKey: .status)
self.data = []
for key in animalsData.allKeys {
print(key)
let animalData = try animalsData.decode(AnimalData.self, forKey: key)
self.data.append(animalData)
}
}
}
let string = "{\"status\":\"ok\",\"messages\":{\"generalMessages\":[],\"recordMessages\":[]},\"foundRows\":2515989,\"data\":{\"181\":{\"animalID\":\"181\",\"animalName\":\"Sophie\"}}}"
let jsonData = string.data(using: .utf8)!
let decoder = JSONDecoder()
let parsedData = try? decoder.decode(ResponseData.self, from: jsonData)
This way your decoder initializer itself handles the keys.

how to map JSON object with MapKit using swift

I am working with a MapKit example using swift. The JSON object they are using is a object of arrays. I have a JSON file that is a array of objects. from the example I see they are pulling the properties they want by there location in the array. I need to use the property keys in my objects. How do I do this? Very first time with swift. thanks
here are example files
init(title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
class func fromJSON(json: [JSONValue]) -> Artwork? {
// 1
var title: String
if let titleOrNil = json[16].string {
title = titleOrNil
} else {
title = ""
}
let locationName = json[12].string
let discipline = json[15].string
// 2
let latitude = (json[18].string! as NSString).doubleValue
let longitude = (json[19].string! as NSString).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
// 3
return Artwork(title: title, locationName: locationName!, discipline: discipline!, coordinate: coordinate)
}
var subtitle: String {
return locationName
}
// MARK: - MapKit related methods
// pinColor for disciplines: Sculpture, Plaque, Mural, Monument, other
func pinColor() -> MKPinAnnotationColor {
switch discipline {
case "Sculpture", "Plaque":
return .Red
case "Mural", "Monument":
return .Purple
default:
return .Green
}
}
// annotation callout opens this mapItem in Maps app
func mapItem() -> MKMapItem {
let addressDict = [String(kABPersonAddressStreetKey): self.subtitle]
let placemark = MKPlacemark(coordinate: self.coordinate, addressDictionary: addressDict)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = self.title
return mapItem
}
Snippet of JSON file
[
198,
"9E5E1F28-22AF-459A-841E-5E89B022505E",
198,
1340413921,
"436621",
1340413921,
"436621",
"{\n}",
null,
null,
"1933",
"Bronze plaque mounted on a stone with an inscription marking the site of an artesian well. Located along Wilder Avenue near Artesian Way.",
"1922 Wilder Avenue",
"http://hiculturearts.pastperfect-online.com/34250images/004/193301-2.JPG",
"1933.01",
"Plaque",
"Site of Honolulu's Pioneer Artesian Well",
"Full",
"21.30006",
"-157.827969",
[
null,
"21.30006",
"-157.827969",
null,
false
],
null
],
snippet of JSON file i want to use
{
"id": "301B2",
"name": "Wrenhurst",
"lat": "35.815864",
"lng": "-78.918893",
"status": "Act 2Q12",
"minp": "632000",
"maxp": "678000",
"annStarts": "14",
"annClosings": "0",
"bldList": "Builder example",
"vdl": "0",
"futures": "0",
"lotSize": "95'",
It looks like your example is using SwiftyJSON
You can look at the docs to get more information on how to get data out of a JSONValue object, but specifically, if I understand your question, you need to know how to get values using keys rather than position.
OK, it depends on what values you are getting and whether or not those values can be null in your JSON file (aka nil in Swift)
I'm going to quote the example that you gave and then show you how to get that value from your JSON file
If you know for a fact that a value will never be null in your JSON file and will always be provided:
// The code from your example using array position
let locationName = json[12].string
// Would convert to:
let locationName = json["name"].string
// And in the case of lat / lng
let latitude = (json[18].string! as NSString).doubleValue
// This converts to:
let latitude = (json["lat"].string! as NSString).doubleValue
Simply use the key instead of the position to get at your value. However, this is not the only change you need. Instead of passing an array of JSONValue objects to your factory method, you need to specify a normal JSONValue (or just a JSON object)
So your factory method changes from this:
class func fromJSON(json: [JSONValue]) -> Artwork? {
// 1
var title: String
if let titleOrNil = json[16].string {
title = titleOrNil
} else {
title = ""
}
let locationName = json[12].string
let discipline = json[15].string
// 2
let latitude = (json[18].string! as NSString).doubleValue
let longitude = (json[19].string! as NSString).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
// 3
return Artwork(title: title, locationName: locationName!, discipline: discipline!, coordinate: coordinate)
}
To something like this:
// This class method should return an Artwork object or nil if one couldn't be created for any reason.
// In your example, I couldn't find any reason you would return nil, so... in this example, it simply returns and Artwork object
class func fromJSON(json: JSON) -> Artwork {
// 1
var title: String
if let titleOrNil = json["title"].string {
title = titleOrNil
} else {
title = ""
}
let locationName = json["name"].string
let discipline = json["discipline"].string
// 2
let latitude = (json["lat"].string! as NSString).doubleValue
let longitude = (json["lng"].string! as NSString).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
// 3
return Artwork(title: title, locationName: locationName!, discipline: discipline!, coordinate: coordinate)
}