Get the CodingKeys key value - ios

In Swift, using Codable struct and CodingKeys emum,
If i have a Coordinate object, how can i get the latitude and longitude CodingKeys values, as an array ["1","2"]
struct Coordinate: Codable {
var latitude: Bool?
var longitude: Bool?
var elevation: Bool?
enum CodingKeys: String, CodingKey {
case latitude = "1"
case longitude = "2"
case elevation = "3"
}
}
And how to get all the CodingKeys values, only for the variables that are true ?
for example if longitude and elevation are set to true, I will get the array ["2,"3"]

CaseIterable will be helpful
struct Coordinate: Codable {
var latitude: Bool?
var longitude: Bool?
var elevation: Bool?
enum CodingKeys: String, CodingKey, CaseIterable {
case latitude = "1"
case longitude = "2"
case elevation = "3"
}
var allKeys: [String] {
CodingKeys.allCases.map { $0.stringValue }
}
}

If I understand correctly, something like this might work:
let myCoordinates = Coordinate(latitude: true, longitude: false, elevation: true)
var myArray: [String] = []
if myCoordinates.latitude ?? false {
myArray.append("1")
}
if myCoordinates.longitude ?? false {
myArray.append("2")
}
if myCoordinates.elevation ?? false {
myArray.append("3")
}
Also, thanks! I didn’t know Codable existed and now I think I might use it on my project!

Related

Swift Codable JSON parse error with JSONDecoder

I am trying to handle a JSON with Codable but there's a parsing error when I decode it with JSONDecoder().decode.
{
"data": [
{
"id": "90",
"symbol": "BTC",
"name": "Bitcoin",
"nameid": "bitcoin",
"rank": 1,
"price_usd": "50513.75",
"percent_change_24h": "3.03",
"percent_change_1h": "-0.50",
"percent_change_7d": "-9.91",
"price_btc": "1.00",
"market_cap_usd": "942710364520.73",
"volume24": 70745042591.75044,
"volume24a": 107034995571.4168,
"csupply": "18662452.00",
"tsupply": "18662452",
"msupply": "21000000"
},
{
"id": "80",
"symbol": "ETH",
"name": "Ethereum",
"nameid": "ethereum",
"rank": 2,
"price_usd": "4052.44",
"percent_change_24h": "10.17",
"percent_change_1h": "-0.78",
"percent_change_7d": "17.75",
"price_btc": "0.084812",
"market_cap_usd": "466734637594.73",
"volume24": 53134000887.50444,
"volume24a": 87082811090.79503,
"csupply": "115173595.00",
"tsupply": "115173595",
"msupply": ""
}
],
"info": {
"coins_num": 5949,
"time": 1621022046
}
}
The json is like above and I coded all my models like below.
class CoinModel: Codable {
var data: [Coin]?
var info: CoinInfo?
enum CodingKeys: String, CodingKey {
case data
case info
}
}
class CoinInfo: Codable {
var coinsNum: Int?
var time: TimeInterval?
enum CodingKeys: String, CodingKey {
case coinsNum = "coins_num"
case time = "time"
}
}
class Coin: Codable{
var csupply: String?
var id: String?
var marketCapUsd: String?
var msupply: Int?
var name: String?
var nameid: String?
var percentChange1h: String?
var percentChange24h: String?
var percentChange7d: String?
var priceBtc: String?
var priceUsd: String?
var rank: Int?
var symbol: String?
var tsupply: Int?
var volume24: Double?
var volume24a: Double?
enum CodingKeys: String, CodingKey {
case csupply = "csupply"
case id = "id"
case marketCapUsd = "market_cap_usd"
case msupply = "msupply"
case name = "name"
case nameid = "nameid"
case percentChange1h = "percent_change_1h"
case percentChange24h = "percent_change_24h"
case percentChange7d = "percent_change_7d"
case priceBtc = "price_btc"
case priceUsd = "price_usd"
case rank = "rank"
case symbol = "symbol"
case tsupply = "tsupply"
case volume24 = "volume24"
case volume24a = "volume24a"
}
}
And my network function work like below.
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(.failure(error!))
return
}
guard let data = data else {
completion(.failure(error ?? NSError()))
return
}
let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
guard let model = try? JSONDecoder().decode(CoinModel.self, from: data) else {
completion(.success([]))
return
}
completion(.success(model.data ?? []))
}.resume()
As I said before, json object is not nil and I got like below.
[({
csupply = "435032301.00";
id = 33285;
"market_cap_usd" = "435337535.24";
msupply = "";
name = "USD Coin";
nameid = "usd-coin";
"percent_change_1h" = "0.01";
"percent_change_24h" = "0.19";
"percent_change_7d" = "0.16";
"price_btc" = "0.000023";
"price_usd" = "1.00";
rank = 100;
symbol = USDC;
tsupply = 435032301;
volume24 = "11520659193.03477";
volume24a = "13684311331.83874";
}
)
, "info": {
"coins_num" = 5952;
time = 1621160044;
}]
I can handle the JSON with JSONSerialization but I could not find any reason for parse error in the JSONDecoder way. I think I did not see the problem. Thanks for any advice and helps.
Edit:
I solved it with several changes in my Codable class, I made a mistake when I describing model for id, csupply and etc.
var csupply: String?
var msupply: String?
var tsupply: String?
You have several mistakes on defining object. For example you have defined id, msupply is a integer but they are string. You have defined the volume24 and volume24a is string but they are not. You need to fix all of these probably thats why you can't parse it.
All wrong defined parameters are id, mSupply, volume24, volume24a, tSupply for coin object and for the info object you need to convert time to Int aswell.

Argument type does not conform to expected type MKAnnotation

I'm using this article as reference https://www.thorntech.com/2016/01/how-to-search-for-location-using-apples-mapkit/
to build my app
I try to use matchingItem when searchBarText are equal then append to matchingItems.
I currently have a error message that
Argument type 'ServiceLocation' does not conform to expected type 'MKAnnotation'
which means I need to change the variable type for matchingItem.
I am not sure what is the best variable types when you want store ServiceLocation later for using as MapView.
Any Suggestion?
var matchingItems = [MKAnnotation]()
var handleMapSearchDelegate:HandleMapSearch? = nil
var allServiceLocations : [ServiceLocation] = []
func updateSearchResults(for searchController: UISearchController) {
matchingItems = []
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
for location in allServiceLocations{
if(location.locationName == searchBarText){
matchingItems.append(location)
}
}
ServiceLocation.swift
struct ServiceLocation: Codable {
var id: Int
var locationType : LocationType
var locationName: String
var latitude: Double
var longitude: Double
var active: Bool
var home : Bool
var numAvailableChargers : Int
var acronym : String?
var eta: Int?
}
So what the compiler is telling you is that you have an array of MKAnnotation but you're trying to stuff a ServiceLocation into it which is an unrelated type. As Swift is strongly typed, this is just invalid (thing square peg - round hole situation).
What you need to do is map your ServiceLocation to MKAnnotation, e.g. like so:
var matchingItems = [MKAnnotation]()
var handleMapSearchDelegate:HandleMapSearch? = nil
var allServiceLocations : [ServiceLocation] = []
func updateSearchResults(for searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
matchingItems = allServiceLocations.filter({ $0.locationName == searchBarText })
.map({
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(exactly: $0.latitude)!, longitude: CLLocationDegrees(exactly: $0.longitude)!)
annotation.title = $0.locationName
annotation.subtitle = $0.locationType
return annotation
})
}
A few options:
You could make ServiceLocation an annotation, by making it a NSObject subclass that conforms to MKAnnotation. So, first make it a class:
class ServiceLocation: NSObject, Codable {
var id: Int
var locationType: LocationType
var locationName: String
var latitude: Double
var longitude: Double
var active: Bool
var home: Bool
var numAvailableChargers: Int
var acronym: String?
var eta: Int?
init(id: Int, locationType: LocationType, locationName: String, latitude: Double, longitude: Double, active: Bool, home: Bool, numAvailableChargers: Int, acronym: String?, eta: Int?) {
self.id = id
self.locationType = locationType
self.locationName = locationName
self.latitude = latitude
self.longitude = longitude
self.active = active
self.home = home
self.numAvailableChargers = numAvailableChargers
self.acronym = acronym
self.eta = eta
super.init()
}
}
Second, add MKAnnotation protocol conformance with a few computed properties:
extension ServiceLocation: MKAnnotation {
var coordinate: CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: latitude, longitude: longitude) }
var title: String? { locationName }
var subtitle: String? { "\(numAvailableChargers) chargers" }
}
Alternatively, you could create an annotation class that has your original service location struct as a property:
class ServiceLocationAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: serviceLocation.latitude, longitude: serviceLocation.longitude) }
var title: String? { serviceLocation.locationName }
var subtitle: String? { "\(serviceLocation.numAvailableChargers) chargers" }
let serviceLocation: ServiceLocation
init(serviceLocation: ServiceLocation) {
self.serviceLocation = serviceLocation
super.init()
}
}
There are other permutations on this idea, but the key is to just make an annotation that has your struct as a property, and then, rather than adding the ServiceLocation to the map’s annotations, add a ServiceLocationAnnotation.
Obviously, make the subtitle whatever you want, but hopefully this illustrates the idea.

Type 'MyWeather' does not conform to protocol 'Encodable' error

I'm trying to make an iOS app that uses the OpenWeatherMap API to check the current weather, but I'm getting an error saying 'Type 'MyWeather' does not conform to protocol 'Encodable''. I am new to Swift Programming and it's probably a simple mistake. I would appreciate any help, thank you.
My code below:
struct MyWeather: Codable {
let name: String?
let location: String?
let temp: URL?
let wind: Int?
//THE NAMES OF THE JSON STUFF IN THE LINK
private enum CodingKeys: String, CodingKey {
case weather
case name
case location
case temp
case wind
//THE NAMES OF THE JSON STUFF IN THE LINK
}
}
class ViewController: UIViewController {
#IBAction func ShowWeatherInfo(_ sender: Any) {
guard let APIUrl = URL(string: "http://api.openweathermap.org/data/2.5/weather?q=Crowland&appid=APIKEY&units=Metric") else { return }
URLSession.shared.dataTask(with: APIUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let weatherData = try decoder.decode(MyWeather.self, from: data)
You need to remove this
case weather
as there is no var for it also use CodingKeys only if you'll change key name , the Codable for the official json is
struct MyWeather: Codable {
let cod: String
let message: Double
let cnt: Int
let list: [List]
let city: City
}
struct City: Codable {
let id: Int
let name: String
let coord: Coord
let country: String
}
struct Coord: Codable {
let lat, lon: Double
}
struct List: Codable {
let dt: Int
let main: MainClass
let weather: [Weather]
let clouds: Clouds
let wind: Wind
let sys: Sys
let dtTxt: String
let rain, snow: Rain?
enum CodingKeys: String, CodingKey {
case dt, main, weather, clouds, wind, sys
case dtTxt = "dt_txt"
case rain, snow
}
}
struct Clouds: Codable {
let all: Int
}
struct MainClass: Codable {
let temp, tempMin, tempMax, pressure: Double
let seaLevel, grndLevel: Double
let humidity: Int
let tempKf: Double
enum CodingKeys: String, CodingKey {
case temp
case tempMin = "temp_min"
case tempMax = "temp_max"
case pressure
case seaLevel = "sea_level"
case grndLevel = "grnd_level"
case humidity
case tempKf = "temp_kf"
}
}
struct Rain: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
struct Sys: Codable {
let pod: Pod
}
enum Pod: String, Codable {
case d = "d"
case n = "n"
}
struct Weather: Codable {
let id: Int
let main: MainEnum
let description: Description
let icon: String
}
enum Description: String, Codable {
case brokenClouds = "broken clouds"
case clearSky = "clear sky"
case fewClouds = "few clouds"
case lightRain = "light rain"
case moderateRain = "moderate rain"
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
struct Wind: Codable {
let speed, deg: Double
}

How to create a collection of objects from a JSON

I'm learning Swift and want to make an app that will show the GPS coordinates of buses on a map. The bus lat and lon come from a JSON (excerpt below):
{
"result":[
{
"Lat":52.276408,
"Lon":21.167618,
"Time":"2018-08-24 11:50:05",
"Lines":"225",
"Brigade":"4"
},
{
"Lat":52.222656,
"Lon":21.102633,
"Time":"2018-08-24 11:51:03",
"Lines":"225",
"Brigade":"2"
},
{
"Lat":52.2100185,
"Lon":21.2054211,
"Time":"2018-08-24 11:51:08",
"Lines":"119",
"Brigade":"2"
},
{
"Lat":52.1676735,
"Lon":21.2222606,
"Time":"2018-08-24 11:51:07",
"Lines":"213",
"Brigade":"3"
}
]
}
I was thinking of creating a Bus class
class Bus {
var latitude : Double = 1.11
var longitude : Double = 2.22
var lines : Int = 0
init (lat: Double, lon: Double, line: Int) {
latitude = lat
longitude = lon
lines = line
}
}
But I'm trying to figure out how to create a collection of these bus objects from the JSON, which (in full) contains around 1000 objects (amount varies throughout the day).
Could someone point me in the right direction? I don't need the fully-coded solution, just some pointers on how should I approach this.
I will most likely be using the SwiftyJSON CocoaPod for JSON parsing, together with Alamofire for getting it.
Thank you!
you can use Below Codable class as Larme said in the comments.
import Foundation
class MyModelClass: Codable {
let result: [Result]
init(result: [Result]) {
self.result = result
}
}
class Result: Codable {
let lat, lon: Double
let time, lines, brigade: String
enum CodingKeys: String, CodingKey {
case lat = "Lat"
case lon = "Lon"
case time = "Time"
case lines = "Lines"
case brigade = "Brigade"
}
init(lat: Double, lon: Double, time: String, lines: String, brigade: String) {
self.lat = lat
self.lon = lon
self.time = time
self.lines = lines
self.brigade = brigade
}
}
If you wanted to use ObjectMapper then you can use below class
import Foundation
import ObjectMapper
class MyModelClass: Mappable {
var result: [Result]?
required init?(map: Map){
}
func mapping(map: Map) {
result <- map["result"]
}
}
class Result: Mappable {
var lat: NSNumber?
var lon: NSNumber?
var time: String?
var lines: String?
var brigade: String?
required init?(map: Map){
}
func mapping(map: Map) {
lat <- map["Lat"]
lon <- map["Lon"]
time <- map["Time"]
lines <- map["Lines"]
brigade <- map["Brigade"]
}
}
write List model like this
class BusListModel {
var list = [Bus]()
var longitude : Double = 2.22
var lines : Int = 0
init (With dict:[String:Any]) {
if let result = dict["result"] as? [[String:Any]]{
for busDetail in result{
let model = Bus(lat: **valueHere**, lon: **valueHere**, line: **valueHere**)
list.append(model)
}
}
}
}
Thank you all for the answers, especially #dahiya_boy
I customised the code posted by #dahiya_boy and ended up with exactly what I need.
Below is the code. I created a jsonString for example purposes.
import Foundation
class MyModelClass: Codable {
let busArray: [Bus]
enum CodingKeys: String, CodingKey {
case busArray = "result"
}
init(busArray: [Bus]) {
self.busArray = busArray
}
}
class Bus: Codable {
let lat, lon: Double
let time, lines, brigade: String
enum CodingKeys: String, CodingKey {
case lat = "Lat"
case lon = "Lon"
case time = "Time"
case lines = "Lines"
case brigade = "Brigade"
}
init(lat: Double, lon: Double, time: String, lines: String, brigade: String) {
self.lat = lat
self.lon = lon
self.time = time
self.lines = lines
self.brigade = brigade
}
}
var jsonString = """
{
"result":[
{
"Lat":52.276408,
"Lon":21.167618,
"Time":"2018-08-24 11:50:05",
"Lines":"225",
"Brigade":"4"
},
{
"Lat":52.222656,
"Lon":21.102633,
"Time":"2018-08-24 11:51:03",
"Lines":"225",
"Brigade":"2"
},
{
"Lat":52.2100185,
"Lon":21.2054211,
"Time":"2018-08-24 11:51:08",
"Lines":"119",
"Brigade":"2"
},
{
"Lat":52.1676735,
"Lon":21.2222606,
"Time":"2018-08-24 11:51:07",
"Lines":"213",
"Brigade":"3"
}
]
}
"""
if let jsonData = jsonString.data(using: .utf8) {
let decodedJSON = try! JSONDecoder().decode(MyModelClass.self, from: jsonData)
print("Latitude: \(decodedJSON.busArray[0].lat), Longitude: \(decodedJSON.busArray[0].lon), Line: \(decodedJSON.busArray[0].lines)")
}
This prints the following console output:
Latitude: 52.276408, Longitude: 21.167618, Line: 225

Initializer for conditional binding must have Optional type, not 'Date'

Xcode is yelling error
But I don't know what happen. I've been searching and I think it might be something about casting and optional. The first one gives Initializer for conditional binding must have Optional type, not 'Date' and the second and third gives Initializer for conditional binding must have Optional type, not 'Double'
for article in (topic.articleArrays ?? nil)!{
if let articleId = article.id,
let articleHeadline = article.headline,
let articleSummary = article.summary,
let articleCity = article.city,
let articleState = article.state,
let articleDateretrieved = article.dateRetrieved,
let articlePublisher = article.publisher,
let articleLatitude = article.latitude,
let articleLongitude = article.longitude,
let articleRawBaseUrl = article.rawBaseUrl,
let articleRawUrl = article.rawUrl {
editedArticles?.append(NewsArticle(id: articleId, headline: articleHeadline, publisher: articlePublisher, summary: articleSummary, rawUrl: articleRawUrl, rawBaseUrl: articleRawBaseUrl, retrieved_date: articleDateretrieved, city: articleCity, state: articleState, latitude: articleLatitude, longitude: articleLongitude))
}
}
The struct for ediedArticles is NewsArticle which I've listed below
struct NewsArticle {
var id: String
var headline: String
var publisher: String
var summary: String
var rawUrl: String
var rawBaseUrl: String
var retrieved_date: Date
var city: String
var state: String
var latitude: Double
var longitude: Double
init(id: String, headline: String, publisher: String, summary: String, rawUrl: String, rawBaseUrl: String, retrieved_date: Date, city: String, state: String, latitude: Double, longitude: Double) {
self.id = id
self.headline = headline
self.publisher = publisher
self.summary = summary
self.rawUrl = rawUrl
self.rawBaseUrl = rawBaseUrl
self.retrieved_date = retrieved_date
self.city = city
self.state = state
self.latitude = latitude
self.longitude = longitude
}
}
topic.articlesArray have different data structure type which is SavedArticle (CoreData)
var articleArrays: [SavedArticle]? {
return self.articles?.allObjects as? [SavedArticle]
}
and have SavedArticle-CoreDataClass
var dateRetrieved: Date {
get {
return retrieved_date as Date
}
set(newDate) {
retrieved_date = newDate as NSDate
}
}
// TODO: Figured it out how to stored corrdinates in [Double]
convenience init?(id: String, headline: String, publisher: String, summary: String, retrieved_date: Date, city: String, state: String, latitude: Double, longitude: Double) {
guard let context = NaberCoreDataHandler.sharedInstance.managedContext else { return nil }
self.init(entity: SavedArticle.entity(), insertInto: context)
self.id = id
self.headline = headline
self.publisher = publisher
self.summary = summary
self.dateRetrieved = retrieved_date
self.city = city
self.state = state
self.latitude = latitude
self.longitude = longitude
}
with a SavedArticle-CoreDataProperties of following
#NSManaged public var city: String?
#NSManaged public var headline: String?
#NSManaged public var id: String?
#NSManaged public var publisher: String?
#NSManaged public var rawBaseUrl: String?
#NSManaged public var rawUrl: String?
#NSManaged public var retrieved_date: NSDate
#NSManaged public var state: String?
#NSManaged public var summary: String?
#NSManaged public var latitude: Double
#NSManaged public var longitude: Double
#NSManaged public var topics: SavedTopic?
It would be awesome if someone can help me figured it out what's the problem. I've beent rying for the whole day and nothing helps. Thank you! :)
if let (and guard let) can only be used to unwrap optional values. You can either assign those values with regular let statements on a separate line, or just pass them into the function directly since they don't need to be unwrapped.
article.dateRetrieved, article.latitude, and article.longitude are not Optionals. They are not declared with ? after their type names, so they can never be nil. There is therefore no need -- and in fact it is an error -- to try to unwrap them with an "if let" statement.
Remove the three lines of code that the compiler is complaining about.
When you create the NewsArticle and append it to editedArticles, you can pass those properties of article directly to the constructor of NewsArticle.

Resources