How to get particular values from JSON by using Swift 4? - ios

I need to get below JSON people of name and age and show on Tableview using Swift 4. Also,Provide some links for JSON best way of JSON parsing without using pods.
[
{
"id":"01",
"list":"class A",
"People":[
{
"name":"Jack",
"age":"18",
"employed":"Yes"
}
]
},
{
"id":"01",
"list":"class B",
"People":[
{
"name":"Siom",
"age":"17",
"employed":"Yes"
}
]
}
]
I am trying by using below Code, Please correct my code:
private func readJson() {
do {
if let file = Bundle.main.url(forResource: "test", withExtension: "json") {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let object = json as? [String: AnyObject] {
// json is a dictionary
if let person = object["People"] as? [String: AnyObject] {
if let name = person["name"] as? String {
print("Name List\(name)")
}
}
//print(object)
} else if let object = json as? [Any] {
// json is an array
print(object)
} else {
print("JSON is invalid")
}
} else {
print("no file")
}
} catch {
print(error.localizedDescription)
}
}

Use Swift's Decodable protocol to parse your JSON into objects, like so:
struct Root: Decodable {
let id: String
let list: String
let people: [People]
// This is a special nested enumeration named CodingKeys that conforms to the CodingKey protocol
// Here we tell decodable not to look for "people" in our json data, instead look for "People" key
// Also notice that we did not provide rawValues for 'id' and 'list', because our json data has same keys
enum CodingKeys: String, CodingKey {
case id
case list
case people = "People"
}
}
struct People: Decodable {
let name: String
let age: String
let employed: String
// In this Decodable struct, we do not provide CodingKeys, because our json dictionary of people has same keys, so this can be left alone
}
let data = """
[{"id":"01","list":"class A","People":[{"name":"Jack","age":"18","employed":"Yes"}]},{"id":"01","list":"class B","People":[{"name":"Siom","age":"17","employed":"Yes"}]}]
""".data(using: .utf8)!
// Since our JSON is an array, here [Root].self tells decoder to look for an array in our json data, if there was a dictionary, instead Root.self will be used, without braces
let root = try JSONDecoder().decode([Root].self, from: data)
Now lets create a People array to be used as UITableView dataSource:
// Loops through the root array and return peoples array, flattened into a single array
let peoples = root.flatMap{ $0.people }
Finally, here is a quick UITableViewController that shows our array of peoples
class ViewController: UITableViewController {
let peoples: [People]
private let cellId = "CellId"
init(category: Category? = nil) {
let data = """
[{"id":"01","list":"class A","People":[{"name":"Jack","age":"18","employed":"Yes"}]},{"id":"01","list":"class B","People":[{"name":"Siom","age":"17","employed":"Yes"}]}]
""".data(using: .utf8)!
let root = try! JSONDecoder().decode([Root].self, from: data)
self.peoples = root.flatMap{ $0.people }
super.init(style: .plain)
self.tableView.tableFooterView = UIView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.peoples.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: self.cellId)
if (cell == nil) {
cell = UITableViewCell(style:.value1, reuseIdentifier: self.cellId)
}
let people = self.peoples[indexPath.row]
cell.textLabel!.text = people.name
cell.detailTextLabel!.text = people.age
return cell
}
}
Here is what you get:

Related

Swift, fetch TableView from JSON and save it into array

i'm trying to do some simple things here, but i'm struggling a bit as i'm a beginner.
So basically i have a JSON file link to use to populate my tableview with 2 sections. Each cell have a "Favourite button".
i've tried many way to save my array with UserDefault with no luck.
What i wish to achieve:
When the "Favourite button" is pressed, i wish to move the cell to the other section
Save and retrieve those 2 array using UserDefault
I'm open to hear any suggestion also any other differs approach, as i'm sure there is some better one.
I will upload here some code, but also the link for the full project in Git so you can check better (https://github.com/Passeric/UserTableview.git)
I really appreciate any help.
The JSON i'm using:
{
"User": [
{
"Name": "John",
"Age": "34"
},{
"Name": "Sara",
"Age": "19"
}.......]}
My Struct:
class User: Codable {
let user: [UserDetails]
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(user: [UserDetails]) {
self.user = user
}
}
class UserDetails: Codable {
let name: String
let age: String
enum CodingKeys: String, CodingKey {
case name = "Name"
case age = "Age"
}
init(name: String, age: String) {
self.name = name
self.age = age
}
}
And my ViewController:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var arrayDefault = [UserDetails]()
var arrayFavourite = [UserDetails]()
var sec: [Int:[UserDetails]] = [:]
var Default = UserDefaults.standard
#IBOutlet weak var myTableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.myTableview.dataSource = self
self.myTableview.delegate = self
DownloadJSON()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
sec = [0 : arrayFavourite, 1 : arrayDefault]
return (sec[section]?.count)!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dataRef = arrayDefault[indexPath.row]
let cel: MyCell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cel.NameLabel.text = dataRef.name
cel.AgeLabel.text = dataRef.age
let imgFav = UIImage(systemName: "star")
let b = imgFav?.withRenderingMode(.alwaysOriginal)
cel.FavButton.setImage(b, for: .normal)
return cel
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func DownloadJSON() {
let urlll = URL(string: "https://pastebin.com/raw/Wufivqmq")
guard let downloadURL = urlll else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
return
}
print("downloaded")
do
{
let decoder = JSONDecoder()
let downloadeduser = try decoder.decode(User.self, from: data)
self.arrayDefault.removeAll()
self.arrayDefault.append(contentsOf: downloadeduser.user)
DispatchQueue.main.async {
print("data saved")
self.myTableview.reloadData()
}
} catch {
print(error)
}
}.resume()
}
#IBAction func RefreshButton(_ sender: Any) {
// Here i wish to refresh the table view (so the current saved array) with the updated JSON
// For example if i update the "Age" in the original JSON
// But i don't want to lose the user preference for the "Favourite"
}
}
As you can see is not a big deal thing, but i can't figure out how to properly save and retrive the array, and then move the cell (by the index path) to the other array.
This is the link for the full project: https://github.com/Passeric/UserTableview.git
Thanks again to anyone who will help.❤️
I would make a few changes:
Clean up your data model. Currently you are storing the data as both two arrays and a dictionary. There are minor pros and cons to each approach, but in the grand scheme of things it doesn't really matter which one you use, just pick one. Personally, as a beginner, I would go with two arrays.
Don't change your data model in the data source. Currently you are creating the sec dictionary in the tableView:numberOfRowsInSection function. If you want to keep using sec then create it as the same time you load the data initially. Or, as mentioned above, just remove sec completely.
Assuming you've made the above changes, moving a user to the favorites section is as simple as removing the user from the default list, adding it to the favorites list, and telling the list that the data has changed:
func makeFavorite(index:IndexPath) {
let user = arrayDefault[index.row]
arrayDefault.remove(at: index.row)
arrayFavourite.append(user)
//This updates the entire list. You might want to use moveRow(at:to:) in order to animate the change
tableView.reloadData()
}
When it comes to converting your data model to/from json, you're already most of the way there by conforming to Codable. To load from json:
if let jsonData = UserDefaults.standard.string(forKey: "userKey").data(using: .utf8) {
let decoder = JSONDecoder()
if let user = try? decoder.decode(User.self, from: jsonData) {
//Do something with user here...
}
}
And to output to json:
let encoder = JSONEncoder()
if let data = try? encoder.encode(user),
let jsonString = String(decoding: data, as: .utf8) {
UserDefaults.standard.setValue(jsonString, forKey: "userKey")
}
Do the same thing with the list of favorites (using a different key).

Parsing multi-level JSON with Codable

I'm trying to parse to following JSON into a tableView : https://www.pathofexile.com/api/trade/data/items
I succeeded in parsing the first array, but I'm unable to parse the key "entries"...
Here's my code, with the data structure I defined :
import UIKit
struct ItemCategories: Codable {
var result: [ItemCategory]
}
struct ItemCategory: Codable {
var label: String
var entries: [Item]
}
struct Item: Codable {
// empty struct
}
class ViewController: UITableViewController {
let urlString = "https://www.pathofexile.com/api/trade/data/items"
var categories = [ItemCategory]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Path of Data"
navigationController?.navigationBar.prefersLargeTitles = true
parse()
}
func parse() {
guard let url = URL(string: urlString) else { return }
guard let data = try? Data(contentsOf: url) else { return }
let decoder = JSONDecoder()
guard let jsonItemCategories = try? decoder.decode(ItemCategories.self, from: data) else { return }
categories = jsonItemCategories.result
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
var categoryName = categories[indexPath.row].label
if categoryName == "" { categoryName = "Unknown" }
cell.textLabel?.text = categoryName
cell.textLabel?.textColor = .systemOrange
let numberOfItemsInCategory = String(categories[indexPath.row].entries.count)
cell.detailTextLabel?.text = numberOfItemsInCategory + " items"
return cell
}
}
The struct Item is empty, because when I try to add variable corresponding to the keys in the JSON, then the whole parsing fail (the tableView displays nothing).
When the struct Item is empty, then the parsing succeed and the tableView is able to display the different categories. It even display the number of items for each "entries" thanks to :
let numberOfItemsInCategory = String(categories[indexPath.row].entries.count)
cell.detailTextLabel?.text = numberOfItemsInCategory + " items"
Can someone explain why ? Ideally I would like to display the content of "entries" when the rows are tapped, but I can't figure out how for the moment.
Thanks for you help :)
screenshot
#Laurent Delorme Your Struct Item should be like below, try with this,
struct Item: Codable {
let name: String?
let type: String?
let text: String?
let flags: FlagsRepresentation?
enum CodingKeys: String, CodingKey {
case name
case type
case text
case flags
}
}
struct FlagsRepresentation: Codable {
let unique: Bool?
enum CodingKeys: String, CodingKey {
case unique
}
}

How to check and delete duplicates values in TableView?

I have a database on Firebase and a tableview.
I have a list of brands, models, and year for motorcycles and I want to retrieve the list of brands on the tableview.
The problem is the DB has duplicates values. There is more than one motorcycle from Suzuki, there is more one models of SV 650, etc.
How can I check duplicates values, put it in a new array, and retrieve it in the tableview?
This is my TableViewController file:
import UIKit
import FirebaseAuth
import FirebaseDatabase
class SelectionMarqueViewController: UITableViewController {
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
loadMarques()
}
func loadMarques() {
var ref : DatabaseReference?
ref = Database.database(url: "https://myride-test.firebaseio.com/").reference()
ref?.observe(.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
let post = Post(MarqueText: MarqueText)
self.posts.append(post)
print(self.posts)
self.tableView.reloadData()
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath)
cell.textLabel?.text = posts[indexPath.row].Marque
return cell
}
}
And this one is the file with the Post func:
import Foundation
class Post {
var Marque: String
init(MarqueText: String) {
Marque = MarqueText
}
}
Here my Firebase Database:
Actually the tableview shows the complete list of brands in the DB, and so, many times the same brands.
On the DB and code:
"Marque" correspond to the brand.
You can implement Hashable
class Post : Hashable {
var marque: String
init(marqueText: String) {
marque = marqueText
}
// Equatable for contains
static func == (lhs:Post,rhs:Post) -> Bool {
return lhs.marque == rhs.marque
}
// Hashable for Set
var hashValue:Int {
return marque.hashValue
}
}
and use
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
let post = Post(MarqueText: MarqueText)
self.posts.append(post)
self.posts = Array(Set(self.posts))
print(self.posts)
self.tableView.reloadData()
}
Or simply
let marqueText = dict["Marque"] as! String
if !self.posts.map { $0.marqueText}.contains(marqueText) {
let post = Post(marqueText:marqueText)
self.posts.append(post)
self.tableView.reloadData()
}
Check and append if the marque is not available in the datasource of the tableview.
func appendMarqueAndReloadIfNeeded(_ marque: String) {
if self.posts.map({ $0.Marque }).contains(marque) {
// Do nothing
} else {
self.posts.append(Post(MarqueText: marque))
self.tableView.reloadData()
}
}
Then you call it inside observe:
///....
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
self.appendMarqueAndReloadIfNeeded(MarqueText)
}
///....

How do i parse the json array in tableview in swift 4 using decoder

I am very new to Swift 4 and I am not able to get an example on parsing JsonArray using JSONDecoder.
Below is the JSON Response which I am trying to parse from past many days.
{
"News": [ // how can i use this news as key in swift and parse it
{
"intId": 354,
"Guid": "4829b85d-56ed-46ed-b489-ddbaf0eaeb05",
"stTitle": "Hyatt place pune CsR Thrive Activity - 2017",
"dtUpdatedDate": "2017-06-01T11:25:00"
},
{
"intId": 115,
"Guid": "bcc1272c-6a47-4878-9091-5af224be494c",
"stTitle": "CIRCULAR W.R.T. BOMBAY HIGH COURT INJUNCTION AGAINST NOVEX",
"dtUpdatedDate": "2014-06-26T17:29:00"
},
{
"intId": 120,
"Guid": "274275db-9aa9-45d3-a00a-0f2eed662e7e",
"stTitle": "Extension of FSSAI deadline.",
"dtUpdatedDate": "2014-08-08T16:07:00"
}
]
}
Below is my Swift code:
import UIKit
/* This is struct i have created to parse jsonArray and JsonObject*/
struct JsonFromWeb:Codable {
let News: [jsonstruct]
}
struct jsonstruct:Codable {
let stTitle:String
let dtNewsDate:String
}
class ViewController:UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet var tableview: UITableView!
// below is the array for storing the response values
var arradata = [jsonstruct]()
override func viewDidLoad() {
super.viewDidLoad()
getdata() // funcction call to load the json api
}
func getdata() { // function getData to load the Api
let url = URL(string : "http://www.hrawi.com/HrawiService.svc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
if (error == nil) {
print(url!)
let news = try JSONDecoder().decode(JsonFromWeb.self, from: data!)
self.arradata = news.News
}
} catch {
print("Error In Json Data")
}
}.resume()
}
//Tableview to set the JSON Data in UITableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arradata.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : TableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
//lblname to set the title from response
cell.lblname.text = "News Title \(arradata[indexPath.row].stTitle)"
cell.lblcapital.text = "news Date \(arradata[indexPath.row].dtNewsDate)"
return cell
}
}
Use these structs:
struct JSONFromWeb: Codable {
let news: [JsonStruct]
enum CodingKeys: String, CodingKey {
case news = "News"
}
}
struct JsonStruct: Codable {
let intID: Int
let guid, stTitle, dtUpdatedDate: String
enum CodingKeys: String, CodingKey {
case intID = "intId"
case guid = "Guid"
case stTitle, dtUpdatedDate
}
}
Note the use of coding keys to conform to the camel case naming convention in Swift. Also, the first letter in a struct name should be uppercased: JsonStruct. arradata should then be declared as [JsonStruct].
Now you can decode the json like so:
do {
let jsonFromWeb = try JSONDecoder().decode(JSONFromWeb.self, from: data)
//This web call is asynchronous, so you'll have to reload the table view
DispatchQueue.main.async {
self.arradata = jsonFromWeb.news
self.tableview.reloadData()
}
} catch {
print(error)
}
Change this to
struct jsonstruct:Codable {
let stTitle:String
let dtUpdatedDate:String
}
let news = try JSONDecoder().decode(JsonFromWeb.self, from:ata!)
DispatchQueue.main.async {
self.arradata = news.News
self.tableview.reloadData()
}
You better start struct names with capital , and vars with small , i left it as not to confuse you with your current code

Taking a Snapshot of Firebase Data and Storing the Data

I have the following JSON Firebase database:
{ "fruits": {
"apple": {
"name": "Gala",
"url": "//s-media-cache-ak0.pinimg.com/564x/3e/b1/e7/3eb1e756d66856975d6e43ebb879200a.jpg",
"fruitArray": [1, 2]
},
"orange": {
"name": "Tangerine",
"url": "//userscontent2.emaze.com/images/0ba588c8-42d9-45e9-a843-d19e5720515a/e430f9a827f139e9f99f2826175dd0a9.jpg",
"fruitArray": []
}
}
}
the following Fruit class:
class Fruit {
var name: String
var url: String
var fruitArray: [Int]
var ref: FIRDatabaseReference?
init(name: String, url: String, fruitArray: [Int]) {
self.name = name
self.url = url
self.fruitArray = fruitArray
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String: Any]
name = snapshotValue["name"] as! String
url = snapshotValue["url"] as! String
if snapshotValue["fruitArray"] == nil {
fruitArray = [0]
} else {
fruitArray = snapshotValue["fruitArray"] as! [Int]
}
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"url": url,
"fruitArray": fruitArray
]
}
And the following FruitTableViewController Code:
class FruitTableViewController: UITableViewController {
// MARK: Properties
var fruits: [Fruit] = []
override func viewDidLoad() {
super.viewDidLoad()
let ref = FIRDatabase.database().reference(withPath: "fruits")
ref.queryOrdered(byChild: "name").observe(.value, with: { snapshot in
var addedFruits: [Fruit] = []
for fruit in snapshot.children {
let newFruit = Fruit(snapshot: fruit as! FIRDataSnapshot)
addedFruit.append(newFruit)
}
self.fruits = addedFruits
self.tableView.reloadData()
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? FruitTableViewCell
let fruit = fruits[indexPath.row]
let imgURL = NSURL(string: fruit.url)
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.icon.image = UIImage(data: data as! Data)
}
cell.nameLabel.text = fruit.name
return cell
}
For some reason, the Firebase snapshot is not working. I've tried almost everything with no luck.
It's not a TableViewCell issue (I think) because I checked the FruitViewCell and Storyboard and everything is in order. My hunch is that it has something to do with the way I'm changing the URL to a string as well as the array. I've used this exact code for a different iOS project and it worked but the difference between the two projects is that this one has an array and link within the JSON while the other one didn't.
I've seen that there are other ways to take a snapshot but I'm going to use the fruit data throughout the app and thus it's easier for me to have a Fruit object, but I wouldn't mid if someone were to suggest an alternate way of taking a snapshot that works. Any help is appreciated!
First of all change your viewDidLoad with this code as addedFruites is not needed at all
override func viewDidLoad() {
super.viewDidLoad()
let ref = FIRDatabase.database().reference().child("fruits")
ref.observe(.value, with: { snapshot in
if snapshot.exists() {
for fruit in snapshot.children {
let newFruit = Fruit(snapshot: fruit as! FIRDataSnapshot)
self.fruits.append(newFruit)
}
self.tableView.reloadData()
}
})
}
check firebase rules for read and write is properly set or not.. I think here is an issue because may be you did not set that rules.

Resources