Map specific element to array from array of codable classes - ios

Gallery is the array of ProviderGallery in this below model. I am trying to fetch specific variable of ProviderGallery to array without using forloop. The below code i used for loop to fetch the desired element.
Model :
struct ProviderProfileData : Codable{
let message : String?
let gallery : [ProviderGallery]?
}
struct ProviderGallery : Codable {
let id : Int?
let file_name : String?
let thumb : String?
let mime_type : String?
let duration : String?
let size : String?
}
JSONDecoder :
do {
let decoder = JSONDecoder()
let providerProfileDetails = try decoder.decode(ProviderProfileData.self, from: data)
print("data \(providerProfileDetails)")
// Here i am getting desired value into array using forloop
if let gallery = providerProfileDetails.data.gallery {
var thumbArray = [String]()
for i in 0..<gallery.count{
thumbArray.append(gallery[i].thumb ?? "")
}
print("thumbs \(thumbArray)")
}
}catch let error {
print("Error \(error.localizedDescription)")
}

Using compactmap to remove all nil value or using map if you want to replace nil value:
let thumArray = gallery.compactMap({ return $0.thumb })
let thumArray = gallery.map({ return $0.thumb ?? ""})

Related

How to archive data in swift?

I am trying to archive data and want to store it in userdefault but app getting crash.
Also tried this
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: selectedPoductDetails, requiringSecureCoding: false)
selectedPoductDetails is dict of type [String: SelectedProductDetail]
import Foundation
class SelectedProductDetail {
let product: String
var amount: Double
var title: String
init(product: String, amount: Double, title: String ) {
self.product = product
self.amount = amount
self.title = title
}
}
May i know why its not working and possible solution for same?
For this case you can use UserDefaults
struct ProductDetail: Codable {
//...
}
let encoder = JSONEncoder()
let selectedProductDetails = ProductDetail()
// Set
if let data = try? encoder.encode(selectedProductDetails) {
UserDefaults.standard.set(data, forKey: "selectedProductDetails")
}
// Get
if let selectedProductDetailsData = UserDefaults.standard.object(forKey: "selectedProductDetails") as? Data {
let selectedProductDetails = try? JSONDecoder().decode(ProductDetail.self, from: selectedProductDetailsData)
}
As mentioned in the comments to use NSKeyedArchiver the class must adopt NSSecureCoding and implement the two required methods.
The types in your class are JSON compatible, so adopt Codable and archive the data with JSONEncoder (or PropertyListEncoder). You could even use a struct and delete the init method
struct SelectedProductDetail: Codable {
let product: String
var amount: Double
var title: String
}
var productDetails = [String: SelectedProductDetail]()
// populate the dictionary
do {
let data = try JSONEncoder().encode(productDetails)
UserDefaults.standard.set(data, forKey: "productDetails")
} catch {
print(error)
}
And load it
do {
guard let data = UserDefaults.standard.data(forKey: "productDetails") else { return }
productDetails = try JSONDecoder().decode([String: SelectedProductDetail].self, from: data)
} catch {
print(error)
}
Note:
UserDefaults is the wrong place for user data. It's better to save the data in the Documents folder

Creating a struct that conforms to the encodable protocol gives me an error due to a timestamp being a variable. Is there a way to fix this?

import Firebase
import UIKit
//I followed the information you gave me. I am unsure if I have done that correctly or as you were expecting it. But, it gives the same error for codable. "Type post doesn't conform to protocol decodable".
import Firebase
import UIKit
struct Post: Codable {
var caption: String
var likes: Int
var imageUrl: String
var ownerUid: String
var postId: String
var ownerImageUrl: String
var ownerUsername: String
var didLike = false
var hashtags: [String]
var activity: [String]
var video: String
var videoURL: URL
var videoFileExtension: String?
var music: String
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }?
enum CodingKeys: String, CodingKey {
case caption
case likes
case imageUrl
case ownerUid
case timestamp
case postId
case ownerImageUrl
case ownerUsername
case didLike
case hashtags
case activity
case video
case videoURL
case videoFileExtension
case music
}
init(postId: String, dictionary: [String: Any]) {
self.postId = dictionary["postId"] as? String ?? ""
self.caption = dictionary["caption"] as? String ?? ""
self.likes = dictionary["likes"] as? Int ?? 0
self.imageUrl = dictionary["imageUrl"] as? String ?? ""
self.ownerUid = dictionary["ownerUid"] as? String ?? ""
self.ownerImageUrl = dictionary["ownerImageUrl"] as? String ?? ""
self.ownerUsername = dictionary["ownerUsername"] as? String ?? ""
self.hashtags = dictionary["hashtags"] as? [String] ?? [String]()
self.activity = dictionary["activity"] as? [String] ?? [String]()
self.video = dictionary["video"] as? String ?? ""
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
self.music = dictionary["music"] as? String ?? ""
if let asDouble = dictionary["timestamp"] as? Double { self.timestampDate = Date(timeIntervalSince1970: asDouble) } else { self.timestampDate = Date() }
}
//Here I am using JSONEncoder to be called in other parts of the code and to //help process the data to firebase
var dictionary: [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: [.mutableContainers, .allowFragments]) as? [String: Any]) ?? [:]
}
}
The compiler will not synthesize Codable for you since you have a coding key for a computed property. This is not supported, auto synthesis only works with stored properties. If you remove timestamp from your CodingKeys enum it should work fine, but your encoded JSON won’t contain the timestamp. If you need that in your output or parse it from input you will have to implement Codable yourself.
Upon the initial question:
A way to do that would be to keep the Date as private, and use Timestamp as a computed value:
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }
This need a little changes in the CodingKeys, because timestamp doesn't exists for it, but timestampDate does now:
enum CodingKeys: String, CodingKey {
...
case timestampDate = "timestamp"
}
Now, there are still a few issues.
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
This shouldn't work, since you are getting JSON, and URL isn't really a JSON value.
Instead:
let videoURLString = dictionary["videoURL"] as? String ?? ""
self.videoURL = URL(fileURLWithPath: videoURLString)
Now, you might have an issue with the Date value, you need to tell the encoder what's the logic:
var dictionary: [String: Any] {
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let data = try encoder.encode(self)
let dict = try JSONSerialization.jsonObject(with: data)
return dict as? [String: Any] ?? [:]
} catch {
print("Error: \(error)")
return [:]
}
}
I used as reference date 1970, depending on your settings, you might change it when encoding or decoding.
Also, I did proper do/try/catch, please don't write try?. If there is an error, you won't see it, you are just ignoring them.
Now, it's unrelated, but in the init(postId:dictionary:) you don't read postId value. Did you meant self.postId = dictionary["postId"] as? String ?? postId ?
Instead of using dictionary["someValue"], why not use dictionary[CodingKeys.someValue.rawValue], avoiding you any typo error?

how to add Json value into model Array to display into tableview in swift

I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong

Nested Json data fetch and append problem using swiftyjson library swift

Getting data append problem in nested json by using swiftjson library swift.
I have created two struct but getting an error while appending the final list. Error in getting when appending data. Have i created struct well.
My struct
struct GistModel {
var comments : Int!
var commentsUrl : String!
var descriptionField : String!
var owner : Owner!
}
struct Owner{
var login : String!
}
JSON DATA result:
{
url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2",
forks_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/forks",
commits_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/commits",
id: "7e624eed62b3a317541791d719dcacf2",
node_id: "MDQ6R2lzdDdlNjI0ZWVkNjJiM2EzMTc1NDE3OTFkNzE5ZGNhY2Yy",
git_pull_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
git_push_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
html_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2",
files: 
 {
GistTest2: 
 {
filename: "GistTest2",
type: "text/plain",
language: null,
raw_url: "https://gist.githubusercontent.com/MasamMahmood/7e624eed62b3a317541791d719dcacf2/raw/7302f0d923e9e08b0e502ad9df762a1b2aa072e1/GistTest2",
size: 29
}
},
public: true,
created_at: "2019-02-01T18:41:39Z",
updated_at: "2019-02-01T19:01:16Z",
description: "Gist Test 2",
comments: 0,
user: null,
comments_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/comments",
owner: 
 {
login: "MasamMahmood",
id: 36441313,
node_id: "MDQ6VXNlcjM2NDQxMzEz",
avatar_url: "https://avatars3.githubusercontent.com/u/36441313?v=4",
gravatar_id: "",
url: "https://api.github.com/users/MasamMahmood",
html_url: "https://github.com/MasamMahmood",
followers_url: "https://api.github.com/users/MasamMahmood/followers",
following_url: "https://api.github.com/users/MasamMahmood/following{/other_user}",
gists_url: "https://api.github.com/users/MasamMahmood/gists{/gist_id}",
starred_url: "https://api.github.com/users/MasamMahmood/starred{/owner}{/repo}",
subscriptions_url: "https://api.github.com/users/MasamMahmood/subscriptions",
organizations_url: "https://api.github.com/users/MasamMahmood/orgs",
repos_url: "https://api.github.com/users/MasamMahmood/repos",
events_url: "https://api.github.com/users/MasamMahmood/events{/privacy}",
received_events_url: "https://api.github.com/users/MasamMahmood/received_events",
type: "User",
site_admin: false
},
truncated: false
}
Swift:
switch response.result{
case .success(let value):
let json = JSON(value)
print(json)
for subJson in json.arrayValue {
let comm = subJson["comments"].intValue
let commurl = subJson["comments_url"].stringValue
let desc = subJson["description"].string
//let age = subJson["owner"]["login"].string
for item in subJson{
let login = subJson["owner"]["login"].string
// do something
}
let user = GistModel(comments: comm, commentsUrl: commurl, descriptionField: desc, login: login)//, owner: login)
self.DataList.append(user)
print(user)
}
I am newbie Getting error on append the list. "Use of unresolved identifier 'login'".
If you are willing to move to standard json handling using Codable then this will work. First let structs implement Decodable
struct GistModel: Decodable {
let comments: Int
let commentsUrl: String
let description: String //Changed the name here
let owner: Owner
}
struct Owner: Decodable {
let login: String
}
And encoding is done like this
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode([GistModel].self, from: data)
print(result[0].owner.login)
print(result[0].comments)
print(result[0].commentsUrl)
} catch {
print(error)
}

I am trying to call a function from my main ViewController and use it to load JSON data in my Second ViewController

I am making a weather app for iOS, and I am trying to call a variable from my main ViewController, to be used in my second ViewController, I am using API data from OpenWeatherMap to display weather data, this works fine on the Main ViewController, and I want it displayed on the second one, as an 'Advanced details' section, that will display the max/min temp, and air pressure etc. (things people don't care about).
I have made an instance of my Main ViewController 'var myCustomViewController: ViewController = ViewController (nibName: nil, bundle: nil)' which works fine, and I am able to call labels, variables, TextBoxes etc from the Main Viewcontroller, but I cannot call the variable 'weatherData', which is the decoder for my JSON data.
In my second View Controller, I want to say 'if let gmain = (myCustomViewController.weatherData.main.tempmax' for example, to get the maximum temperature, but it does not recognise 'weatherData'. I'm very stuck on this and would appreciate any help. Thank you.
Structs in my Main ViewController below:
struct Coordinate : Decodable {
let lat, lon : Double?
}
struct Weather : Decodable {
var id : Int?
var main, myDescription, icon : String?
enum CodingKeys : String, CodingKey {
case id = "id"
case main = "main"
case icon = "icon"
case myDescription = "description"
}
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct MyWeather : Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather : [Weather]?
let sys : Sys?
let main : Main?
let wind : Wind?
let dt : Date?
}
Main ViewController code below:
let text: String = userValue.text!
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
let weatherData = try decoder.decode(MyWeather.self, from: data)
if (self.MainLabel != nil)
{
if let gmain = (weatherData.weather?.first?.main) { //using .first because Weather is stored in an array
print(gmain)
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
}
}
}
if (self.LocationLabel != nil)
{
if let gmain = weatherData.name {
print(gmain)
DispatchQueue.main.async {
self.LocationLabel.text! = "Current Weather in: " + String (gmain)
}
}
}
Second ViewController code below:
var myCustomViewController: ViewController = ViewController (nibName: nil, bundle: nil) //Inheriting from other viewController
if (self.descriptionLabel != nil)
{
if let gmain = (myCustomViewController.weatherData { //NOT RECOGNISING weatherData <<<<<
print(gmain)
DispatchQueue.main.async {
self.descriptionLabel.text! = String (gmain)
}
}
}
}
In your MainViewController code, you have the following line:
let weatherData = try decoder.decode(MyWeather.self, from: data)
Is this inside a method, like viewDidLoad? If so, then weatherData is not a property of MainViewController, and is not accessible by a subclass. You have to define it as a property outside of any function scope (e.g. at the top of the file, right after the opening brackets):
var weatherData = ...
Then call it as such:
do {
weatherData = ...

Resources