swift json help in displaying data on table view controller - json

// my url
// https://fetch-hiring.s3.amazonaws.com/hiring.json
/*
my json
[
{"id": 383, "listId": 4, "name": ""},
{"id": 472, "listId": 1, "name": ""},
{"id": 625, "listId": 2, "name": null}
]
*/
// my main vc class for table view controller
import UIKit
class HeadlLinesTableViewController: UITableViewController {
var parse = [HiringElement]()
override func viewDidLoad() {
// Do any additional setup after loading the view.
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://fetch-hiring.s3.amazonaws.com/hiring.json"
guard let url = URL(string: urlString) else { return }
// 2
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
// 3
//Decode data
self.Elements = try? JSONDecoder().decode(HiringElement.self, from: data)
print(data)
// 4
//Get back to the main queue
DispatchQueue.main.async {
self.tableView.reloadData()
}
// 5
}.resume() // fires of request
}
My model struct for my api this is something I used from quickTypeIo api generator
struct HiringElement: Codable {
let id, listID: Int
let name: String?
enum CodingKeys: String, CodingKey {
case id
case listID
case name
}
} typealias Hiring = [HiringElement]
And my table view controller method here I can't display data on and some some errors. I am using tableview controller so doesn't need tableview delegate or datasource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let articles = Elements else { return 0 }
return return parse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
as? newsTableViewCell else {
fatalError(" cell not found ") }
// here I have errors thanks
cell.titleLabel.text = parse[indexPath.row].name
print(cell.titleLabel.text)
return cell
}
}
Here is my table view cell class
import UIKit
class newsTableViewCell: UITableViewCell {
//var article:Article!
#IBOutlet weak var avator: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var newsLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}

I think you should work on the decoding part. Here is a solution:
struct HiringElement: Decodable {
let id, listId: Int
let name: String?
}
#propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
try? container.decode(_None.self)
}
}
}
}
Then write the following code in your HeadlLinesTableViewController.swift.
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
Then try decoding as:
guard let objects = try? JSONDecoder().decode(ArrayIgnoringFailure<HiringElement>.self, from: data) else { return }
self.elements = objects.wrappedValue
Hope it will solve your problems.

Related

Swift Table view Fail to Load the data

I am new in swift . I am trying the display the data into tableview controller from API . Here is the api link . https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=DEMO_KEY. I create model based on the json property . Here is the model code .
import Foundation
struct Welcome: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Codable {
let id, sol: Int
let camera: Camera
let imgSrc: String
let earthDate: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case id, sol, camera
case imgSrc = "img_src"
case earthDate = "earth_date"
case rover
}
}
// MARK: - Camera
struct Camera: Codable {
let id: Int
let name: String
let roverID: Int
let fullName: String
enum CodingKeys: String, CodingKey {
case id, name
case roverID = "rover_id"
case fullName = "full_name"
}
}
// MARK: - Rover
struct Rover: Codable {
let id: Int
let name, landingDate, launchDate, status: String
enum CodingKeys: String, CodingKey {
case id, name
case landingDate = "landing_date"
case launchDate = "launch_date"
case status
}
}
From this model I want to user Rover struct fields like id and status and display those property into table view cell .
Here is the network Manager code .
class NetworkManager{
func fetchData(completion: #escaping ([Rover]) -> Void) {
if let url = URL(string: "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=DEMO_KEY") {
URLSession.shared.dataTask(with: url) { data, urlResponse, error in
if let data = data {
do {
let result = try JSONDecoder().decode([Rover].self, from: data)
completion(result)
} catch let error {
print(error.localizedDescription)
}
}
}
.resume()
}
}
}
Here code for view controller .
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var posts = [Rover]()
private let networkManager = NetworkManager()
private var rowSelected = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUpUI ()
fetchData()
}
private func setUpUI () {
tableView.dataSource = self
tableView.delegate = self
}
private func fetchData() {
networkManager.fetchData { [weak self] array in
self?.posts = array
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
rowSelected = indexPath.row
performSegue(withIdentifier: "cell", sender: nil)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let post = posts[row]
cell.textLabel?.text = String(post.id)
cell.detailTextLabel?.text = post.status
return cell
}
}
Here is the console result when i run the code , it is saying The data couldn’t be read because it isn’t in the correct format.

How to decode custom type inside dictionary value with JSON?

my JSON:
https://www.cbr-xml-daily.ru/daily_json.js
my code:
struct CoinData: Decodable {
let Valute: [String: CoinInfo]
}
struct CoinInfo: Decodable {
let Name: String
let Value: Double
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
print(coinData)
}
}
func parseJSON(_ data: Data) -> [String: CoinInfo]? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CoinData.self, from: data)
return decodedData.Valute
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
In debug console following gets printed:
["PLN": CurrencyConverter.CoinInfo(Name: "X", Value: 19.6678), ...]
This way I can't reach Name and Value properties of a coin. What's wrong?
I am going to do for-loop to check if a key contains certain symbols. If it does - I will need to be able to access to both Name and Value
You don't actually need a for loop. Since coinData is a dictionary, you can use its subscript, together with optional binding to do this. For example, to check if the key "PLN" exists, and access its name and value:
if let coinInfo = coinData["PLN"] {
print(coinInfo.Name)
print(coinInfo.Value)
} else {
// "PLN" does not exist
}
StoyBoard
Code
import UIKit
import Alamofire
// MARK: - CoinData
struct CoinData: Codable {
let date, previousDate: String
let previousURL: String
let timestamp: String
let valute: [String: Valute]
enum CodingKeys: String, CodingKey {
case date = "Date"
case previousDate = "PreviousDate"
case previousURL = "PreviousURL"
case timestamp = "Timestamp"
case valute = "Valute"
}
}
// MARK: - Valute
struct Valute: Codable {
let id, numCode, charCode: String
let nominal: Int
let name: String
let value, previous: Double
enum CodingKeys: String, CodingKey {
case id = "ID"
case numCode = "NumCode"
case charCode = "CharCode"
case nominal = "Nominal"
case name = "Name"
case value = "Value"
case previous = "Previous"
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var getCoinData = [CoinData]()
var coinNameArr = [String]()
var coinDataArr = [Valute]()
#IBOutlet weak var tblDataList: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
getData()
}
func getData()
{
let url = "https://www.cbr-xml-daily.ru/daily_json.js"
AF.request(url, method: .get, encoding: URLEncoding.default).responseJSON { response in
let json = response.data
do{
let decoder = JSONDecoder()
self.getCoinData = [try decoder.decode(CoinData.self, from: json!)]
let response = self.getCoinData[0]
if response.valute.count != 0 {
self.coinNameArr.removeAll()
self.coinDataArr.removeAll()
for (coinName, coinData) in response.valute {
self.coinNameArr.append(coinName)
self.coinDataArr.append(coinData)
}
self.tblDataList.reloadData()
} else {
}
}catch let err{
print(err)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coinDataArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:coinTblCell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath as IndexPath) as! coinTblCell
cell.accessoryType = .disclosureIndicator
cell.tintColor = .black
let rowData = coinDataArr[indexPath.row]
cell.lblName.text = rowData.name
cell.lblValue.text = String(rowData.value)
return cell
}
}
class coinTblCell: UITableViewCell {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lblValue: UILabel!
}

How to parse Json from URL and display in Collection View in Xcode

Here is a sample of my Api code that I am trying to parse
{"#iot.nextLink" : "Google.com","value" : [ {
"phenomenonTime" : "2020-02-12T01:38:17.622Z",
"resultTime" : "2020-02-12T01:38:17.622Z",
"result" : 1009.3,
"Datastream#1" : "Yahoo.com",
"FeatureOfInterest#2" : "bing.com",
"#iot.id" : 4093015,
"#iot.selfLink" : "youtube.com"},
{"phenomenonTime" : "2020-02-12T01:23:11.397Z",
"resultTime" : "2020-02-12T01:23:11.397Z",
"result" : 1009.7,
"Datastream#1" : "walmart.com",
"FeatureOfInterest#2" : "bestbuy.com",
"#iot.id" : 4092867,
"#iot.selfLink" : "gmail.com"}, ...]}
I have created structures to parse the Json
struct Initial: Decodable {
let value : [Blue]}
struct Blue: Decodable {
let phenomenonTime : String
let result : Double}
I only want to display the PhenomenonTime value and the result value. Next, I did
var url1 = ""
override func viewDidLoad() {
let url = URL(string: url1)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let initial = try? JSONDecoder().decode(Initial.self, from: data)
print(initial?.value)
}catch {
print("Error")
}
}.resume()
}
Here is my code of me parsing the JSON. I can print the values
Optional([GTSeaLevelAPP.Blue(phenomenonTime: "2020-02-12T01:38:17.622Z", result: 1009.3),
GTSeaLevelAPP.Blue(phenomenonTime: "2020-02-12T01:23:11.397Z", result: 1009.7),...])
But, it doesn't allow me when I try to do
print(initial?.value.result)
An error comes up saying "Value of type '[Blue]' has no member 'result'". So, I don't know how to fix this issue of how to print just values or just phenomenonTime so I can put just put values in the collection view or just put phenomenonTime in another collection view. Also, I didn't know how to parse the Json so the collection view can see the array of just phenomenonTime.
extension ViewController:UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = pressureView.dequeueReusableCell(withReuseIdentifier: "check", for: indexPath) as? PressureCollectionViewCell
cell?.number.text=String(indexPath.row)
return cell!
}
Currently, I made it so it will just present a different number in each cell but I don't know how to get all the values into one array and all the phenomenonTime into one array and present them into the collectionview. When I try to refer to make the cell text display the values
cell?.number.text=initial?.value[indexPath.row]
It says:
"Use of unresolved identifier 'initial'"
So, what to do to fix it?
So, set your viewcontroller like:
class ViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .lightGray
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
var url1 = "your_url"
private var cellId: String = "12312"
private var phenomenonTimeArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.collectionView)
NSLayoutConstraint.activate([
self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
self.collectionView.register(PressureCollectionViewCell.self, forCellWithReuseIdentifier: self.cellId)
self.getDataFromServer()
}
func getDataFromServer() {
let url = URL(string: url1)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let initial = try? JSONDecoder().decode(Initial.self, from: data)
if let value = initial?.value {
self.phenomenonTimeArray = value.map { (blue) -> String in
return blue.phenomenonTime
}
self.collectionView.reloadData()
}
} catch {
print("Error")
}
}.resume()
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.phenomenonTimeArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as! PressureCollectionViewCell
cell.item = phenomenonTimeArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionView.frame.width, height: 30)
}
}
And your cell(design your cell as per requirement):
class PressureCollectionViewCell: UICollectionViewCell {
var item: String? {
didSet {
self.label.text = item
}
}
private lazy var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupViews()
}
required init?(coder: NSCoder) {
fatalError()
}
private func setupViews() {
self.addSubview(self.label)
NSLayoutConstraint.activate([
self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.label.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.label.topAnchor.constraint(equalTo: self.topAnchor),
self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}

My object array is nil while my data are correct

I try to display my data in a tableView using no framework to parse my data, but when I add my data to my table and debug it, it is nil at the output while my data I retrieve are well parses, have I forgotten something to do?
I use a structure for my parameters as this :
enum Types {
case School
case Hospital
case Station_Essence
case Restaurant
}
struct Adresse {
public var title: String
public var details: String?
public var type: Types
public var coordinate: [String: Any]
}
and in my ViewController, i proced as this :
class ListMapViewController: UIViewController {
#IBOutlet var TitleTableView: UITableView!
#IBOutlet var MapView: MKMapView!
var adresse: [Adresse]?
override func viewDidLoad() {
super.viewDidLoad()
self.TitleTableView.register(UINib(nibName: "ListMapTableViewCell", bundle: nil), forCellReuseIdentifier: "Adresse")
self.TitleTableView.delegate = self
self.TitleTableView.dataSource = self
guard let POI = URL(string: "https://moc4a-poi.herokuapp.com/") else {
return
}
let task = URLSession.shared.dataTask(with: POI) { (data, response, error) in
guard let dataResponse = data else { return }
if let json = try! JSONSerialization.jsonObject(with: dataResponse, options:[]) as? [[String: Any]] {
for data in json {
let title = data["title"] as! String
let details = data["details"] as? String
guard let type = data["type"] as? Int else { return }
let valueType = self.valueType(dataType: type)
guard let coordinates = data["coordinates"] as? [String: Any] else { return }
self.adresse?.append(Adresse(title: title, details: details, type: valueType, coordinate: coordinates))
}
}
print(self.adresse)
}
self.TitleTableView.reloadData()
task.resume()
}
private func valueType(dataType: Int) -> Types {
if(dataType == 1) {
return Types.School
} else if (dataType == 2) {
return Types.Hospital
} else if (dataType == 3) {
return Types.Station_Essence
} else {
return Types.Restaurant
}
}
}
extension ListMapViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.adresse?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Adresse", for: indexPath) as! ListMapTableViewCell
if let adresse = self.adresse?[indexPath.row] {
cell.draw(adresse: adresse)
}
return cell
}
}
extension ListMapViewController: UITableViewDelegate {
}
You have two big problems.
self.adresse is nil. You never assign it a value. So all of the self.adresse?... do nothing.
You call reloadData too soon. It needs to be done inside the completion block, after you update the data. And it needs to be on the main queue.
To fix #1, change var adresse: [Adresse]? to var adresse = [Adresse](). Then you can get rid of all the ? after uses of adresse.
To fix #2, add:
DispatchQueue.main.async {
self.TitleTableView.reloadData()
}
just after the print at the end of the completion block. Don't forget to remove the current call to reloadData.

Swift4 UITableView: JSON data loading correctly but throwing error on assigning value

I have some JSON data that looks like this
{
"fullName": "John Doe",
"imageUrl": "https://www.example.com/images/about/team/john.jpg",
"titles": [
"Founder & President",
"Advisor"
]
},
{
"fullName": "Jane Doe",
"imageUrl": "https://www.example.com/images/about/team/jane.jpg",
"titles": [
"Executive Vice President",
"Director of Advisor Services and Marketing"
]
},
The data gets loaded and parses correctly, but when I run the code I get an error. I think it has to do with the titles section having multiple titles and it doesn't know how to display the titles correctly.
Here is my code.
This is the initial structure.
import UIKit
class Managers: Codable {
let managers: [Manager]
init (managers: [Manager]) {
self.managers = managers
}
}
class Manager: Codable {
let imageUrl: String?
let fullName: String?
let titles: [titles]
init(imageUrl: String?, fullName: String?, titles: [titles]) {
self.imageUrl = imageUrl
self.fullName = fullName
self.titles = titles
}
struct titles: Codable {
let title: String
}
}
This is the Management cell that displays the layout of the image, name and title.
import UIKit
class ManagementCell: UITableViewCell {
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var fullNameLbl: UILabel!
#IBOutlet weak var titlesLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
This is the view controller
import UIKit
class ManagementViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
final let url = URL(string: "Data from above goes here")
private var managers = [Manager]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
tableView.tableFooterView = UIView()
}
func downloadJson() {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("downloaded")
do
{
let decoder = JSONDecoder()
let downloadedManagers = try decoder.decode([Manager].self, from: data)
self.managers = downloadedManagers
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Something went wrong after download")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return managers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ManagementCell") as? ManagementCell else { return UITableViewCell() }
cell.fullNameLbl?.text = managers[indexPath.row].fullName
cell.titlesLbl?.text = managers[indexPath.row].titles
if let imageURL = URL(string: managers[indexPath.row].imageUrl!) {
DispatchQueue.global().async {
let data = try? Data(contentsOf: imageURL)
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.imageUrl.image = image
cell.imageUrl.layer.cornerRadius = 60
cell.imageUrl.clipsToBounds = true
cell.imageUrl.contentMode = .scaleAspectFit
cell.imageUrl.backgroundColor = UIColor.lightGray
cell.imageUrl.layer.borderWidth = 5
cell.imageUrl.layer.borderColor = UIColor.clear.cgColor
}
}
}
}
return cell
}
This line of code is throwing an error:
cell.titlesLbl?.text = managers[indexPath.row].titles
error is Cannot assign value of type '[Manager.titles]' to type 'String?'
Does anyone have a workaround for this. I believe that I have to create a separate loop for the titles since it also has an array of options. Any suggestions will be very much appreciated.
On a separate note for the image if an image is missing is causes the app to crash. How do I set a generic image if an image in the JSON data isn't present?
Because managers[indexPath.row].titles returns an array of Strings. Peeking into your data, it looks like the first title in that array is the most recent title the manager holds. You can append an index or call .first to get that title:
cell.titlesLbl?.text = managers[indexPath.row].titles.first?.title
// or
cell.titlesLbl?.text = managers[indexPath.row].titles[0].title
// or to show all past titles, comma separated
cell.titlesLbl?.text = managers[indexPath.row].titles
.map({ $0.title })
.joined(separator: ", ")
The difference is that .first won't throw an error if the titles array is empty. You get a blank label instead. [0] will throw a index out of bounds error if your array is empty.