I can't open "url" in "images".
Parsing doesn't work. Please help. Thank you.
"images": [
{
"url": "https://ktar.com/wp-content/uploads/2020/03/ap_27df5153e3bf48198ebcfd40900446d6.jpg",
"width": 1280,
"height": 853,
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"attribution": null
=======================================================
=======================================================
API
{
"path": "_news/2020-04-01-arizona-reports-124-new-coronavirus-cases-five-additional-deaths.md",
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"excerpt": "The Arizona health department reported 124 new cases of coronavirus and five additional deaths on Wednesday morning, a day after the state's \"stay at home\" order went into effect.",
"heat": 145,
"tags": [
"US"
],
"type": "article",
"webUrl": "https://ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/",
"ampWebUrl": "https://ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/amp/",
"cdnAmpWebUrl": "https://ktar-com.cdn.ampproject.org/c/s/ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/amp/",
"publishedDateTime": "2020-04-01T09:09:00-07:00",
"updatedDateTime": null,
"provider": {
"name": "KTAR News",
"domain": "ktar.com",
"images": null,
"publishers": null,
"authors": null
},
"images": [
{
"url": "https://ktar.com/wp-content/uploads/2020/03/ap_27df5153e3bf48198ebcfd40900446d6.jpg",
"width": 1280,
"height": 853,
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"attribution": null
}
],
"locale": "en-us",
"categories": [
"news"
],
"topics": [
"Coronavirus in US",
"Coronavirus",
"New Cases"
]
}
======================================================
======================================================
// SPORTNEWSVC.swift
// Hero
import Alamofire
import Kingfisher
import UIKit
class NewsVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var news = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
getArticles()
}
func getArticles() {
let parameters: Parameters = ["Subscription-Key": "3009d4ccc29e4808af1ccc25c69b4d5d"]
Alamofire.request("https://api.smartable.ai/coronavirus/news/US", parameters: parameters).responseData { (response) in
guard let data = response.data else { return }
do {
// let json = try JSONSerialization.jsonObject(with: data, options: [])
// print(json)
let topHeadlinesResponse = try JSONDecoder().decode(TopHeadlinesResponse.self, from: data)
self.news = topHeadlinesResponse.news
self.collectionView?.reloadData()
} catch {
print(error)
}
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return news.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ArticleCell", for: indexPath) as? ArticleCell else { return UICollectionViewCell ()}
let article = news[indexPath.item]
cell.populate(with: article)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height: CGFloat = 277
let width = (collectionView.frame.width / 2) - 2
let size = CGSize(width: width, height: height)
return size
}
}
======================================================
======================================================
// Article.swift
// Hero
//
import Foundation
struct Article: Decodable {
let headline: String
let url: String?
private enum CodingKeys: String, CodingKey {
case headline = "title"
case url
}
}
======================================================
======================================================
//
// ArticleCell.swift
// Hero
import UIKit
import Kingfisher
class ArticleCell: UICollectionViewCell {
#IBOutlet weak var articleImageView: UIImageView!
#IBOutlet weak var headlieLabel: UILabel!
func populate(with article: Article){
headlieLabel.text = article.headline
if let url = article.url {
let url = URL(string: url)
articleImageView.kf.setImage(with: url)
}
}
}
======================================================
======================================================
import Foundation
struct TopHeadlinesResponse: Decodable {
let news: [Article]
}
The idea is that "images" in the JSON response you get is an array and you need to reflect this fact in Codable structure. The Decoding magic needs you to follow JSON data structure and namings: so if you meet field 'width' inside 'image' dictionary - you need to have a field named 'width' inside an 'image' field (e.g. having image as a separate struct)
you can read more about Decodables here
struct NewsApiResponse : Decodable {
let status : String
let updatedDateTime : Date?
let news : [Article]
}
struct Article : Decodable {
let id : Int
let title : String
let excerpt : String
let webUrl : String
let publishedDateTime : Date?
let images : [Image]
}
struct Image : Decodable {
let url : String
let width : Int
let height : Int
let title : String
}
*struct TopHeadLinesResponse: Codable {
let path, title, excerpt: String
let heat: Int
let tags: [String]
let type: String
let webURL, ampWebURL, cdnAmpWebURL: String
let publishedDateTime: Date
let updatedDateTime: JSONNull?
let provider: Provider
let images: [Image]
let locale: String
let categories, topics: [String]
enum CodingKeys: String, CodingKey {
case path, title, excerpt, heat, tags, type
case webURL
case ampWebURL
case cdnAmpWebURL
case publishedDateTime, updatedDateTime, provider, images, locale, categories, topics
}
}
// MARK: - Image
struct Image: Codable {
let url: String
let width, height: Int
let title: String
let attribution: JSONNull?
}
// MARK: - Provider
struct Provider: Codable {
let name, domain: String
let images, publishers, authors: JSONNull?
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
extension UIImageView {
func loadImagee(_ urlString: String?, onSuccess: ((UIImage) -> Void)? = nil) {
self.image = UIImage()
guard let string = urlString else { return }
guard let url = URL(string: string) else { return }
self.sd_setImage(with: url) { (image, error, type, url) in
if onSuccess != nil, error == nil {
onSuccess!(image!)
}
}
}
}
// try using SD Web library code is above install SDweb instead of // king fisher
// check your struct also check this answer
https://stackoverflow.com/questions/60310054/im-having-troubles-displaying-an-image-from-json-in-a-table-view*
Related
I am new to swift . I am trying to retrieve the data form api. The data in json format. Here is the data in json format.
{
"photos": [
{
"id": 424926,
"sol": 1000,
"camera": {
"id": 22,
"name": "MAST",
"rover_id": 5,
"full_name": "Mast Camera"
},
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631200305217E01_DXXX.jpg",
"earth_date": "2015-05-30",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
}
]
}
Here is the online Json viewer .
NetworkMnager 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=Mykey") {
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()
}
}
}
I am trying to display the data id and status from rover (struct ) into simulator when i try to run the data , I am getting following error into console window of the xcode.The data couldn’t be read because it isn’t in the correct format. I am sure there is somethings wrong with the struct. Here is the struct code .
import Foundation
// MARK: - Welcome
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
}
}
Table view controller code .
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()
}
/* override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let destination = segue.destination as? SecondViewController
destination?.posts = posts
destination?.rowSelected = rowSelected
}
}*/
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
}
}
try using this example code to decode your json data:
(note I do not have a api_key, so I cannot test this).
EDIT-1:
here is the code I used for testing (sorry it is using SwiftUI), from this you should be
able to get your data decoded. Works for me.
class NetworkManager {
func fetchData(completion: #escaping ([Photo]) -> Void) { // <-- here
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(NASAResponse.self, from: data) // <-- here
completion(result.photos)
} catch let error {
print(error.localizedDescription)
}
}
}
.resume()
}
}
}
struct ContentView: View {
let networkManager = NetworkManager()
#State var photos: [Photo] = []
var body: some View {
List (photos) { photo in
HStack {
Text(String(photo.rover.id))
Text(photo.rover.status.rawValue)
}
}
.onAppear {
networkManager.fetchData { thePhotos in
photos = thePhotos
}
}
}
}
struct NASAResponse: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Identifiable, Codable {
let sol, id: Int
let earthDate: String
let camera: Camera
let imgSrc: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case sol, id
case earthDate = "earth_date"
case camera
case imgSrc = "img_src"
case rover
}
}
// MARK: - Camera
struct Camera: Identifiable, Codable {
let id, roverID: Int
let fullName: FullName
let name: CameraName
enum CodingKeys: String, CodingKey {
case id
case roverID = "rover_id"
case fullName = "full_name"
case name
}
}
enum FullName: String, Codable {
case chemistryAndCameraComplex = "Chemistry and Camera Complex"
case frontHazardAvoidanceCamera = "Front Hazard Avoidance Camera"
case mastCamera = "Mast Camera"
case navigationCamera = "Navigation Camera"
case rearHazardAvoidanceCamera = "Rear Hazard Avoidance Camera"
}
enum CameraName: String, Codable {
case chemcam = "CHEMCAM"
case fhaz = "FHAZ"
case mast = "MAST"
case navcam = "NAVCAM"
case rhaz = "RHAZ"
}
// MARK: - Rover
struct Rover: Identifiable, Codable {
let status: Status
let id: Int
let landingDate, launchDate: String
let name: RoverName
enum CodingKeys: String, CodingKey {
case status, id
case landingDate = "landing_date"
case launchDate = "launch_date"
case name
}
}
enum RoverName: String, Codable {
case curiosity = "Curiosity"
}
enum Status: String, Codable {
case active = "active"
}
EDIT-2:
To get all the "rover" into the post var, in your class ViewController: UIViewController, do this
for your tableView :
private func fetchData() {
networkManager.fetchData { thePhotos in
self.posts = thePhotos.map { $0.rover }
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
// 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.
I'm working on an app where I need to store a Country and City in a Firebase database.
Besides storing, I also need to retrieve that info and present to the user in a pickerView. Given that, I need to read the Country and City from the Database, check what is their index and set it in pickerView.
Countries and Cities are store in JSON
{
"Country": [
{
"name": "UK",
"cities": [
{
"name": "London"
},
{
"name": "Manchester"
},
{
"name": "Bristol"
}
]
},
{
"name": "USA",
"cities": [
{
"name": "New York"
},
{
"name": "Chicago"
}
]
},
{
"name": "China",
"cities": [
{
"name": "Beijing"
},
{
"name": "Shanghai"
},
{
"name": "Shenzhen"
},
{
"name": "Hong Kong"
}
]
}
]
}
My code to read JSON is
// Declared in Class
var countryList = [NSDictionary]()
var selectedRow = [NSDictionary]()
var selectedCity = ""
var selectedCountry = ""
func readJson() {
if let path = Bundle.main.path(forResource: "Countries", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let country = jsonResult["Country"] as? [NSDictionary] {
//handles the array of countries on your json file.
self.countryList = country
self.selectedRow = self.countryList.first?.object(forKey: "cities") as! [NSDictionary]
}
} catch {
print("error loading countries")
// handle error
}
}
}
The code above allows me to feed a UIPickerView with 2 sessions and the Country and the list of cities within that Country. From there, I can also identify which Country and City were selected.
AS part of my code I have a func that would allow me to identify what is indexes of the saved Country(countryIndex) and City(cityIndex) in UIPickerView so that I can set it and that's where my issues start
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
// countries is an array of [Country]
// var countries = [Country]()
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
//let cityIndex = 0
return (countryIndex, cityIndex)
} // courtesy of #flanker
This func was working perfectly fine when my Countries and Cities were stored to a [Country] but is not working with NSDictionary coming from JSON.
I have tried to
1) Change [Country] by [NSDictionary], "countries" by "countryList" and "name" by "Country"
Here I receive and error "NSDictionary has no member Country"
I also tried to leave just $0 == Country which hasn't worked as well.
2) Tried also "countryList.firstIndex(of: "USA")" but got the error below
Cannot convert value of type 'String' to expected argument type 'NSDictionary'
Anyone would be able to assist? How can I make the func indexOf work again?
Thanks
Updated according to #vadian's suggestion
My updated code is
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var countryLbl: UILabel!
var familyNames: [String] = []
var fontName = "Arial"
let fontCount = 0
var countryList = [Country]()
var selectedRow = [City]()
var selectedCity : City?
var selectedCountry : Country?
struct Root : Decodable {
let country : [Country] // better plural let countries
private enum CodingKeys : String, CodingKey { case country = "Country" }
}
struct Country : Decodable {
var name : String
var cities : [City]
}
struct City : Decodable {
var name : String
}
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
fontName = "HelveticaNeue"
}
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
return (countryIndex, cityIndex)
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if component == 0 {
return 80
} else {
return 300
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return countryList.count
} else {
return selectedRow.count
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var rowTitle = ""
let pickerLabel = UILabel()
pickerLabel.textColor = UIColor.blue
switch component {
case 0:
rowTitle = countryList[row].name
case 1:
rowTitle = selectedRow[row].name
default:
break
}
pickerLabel.text = rowTitle
pickerLabel.font = UIFont(name: fontName, size: 20.0)
pickerLabel.textAlignment = .center
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadAllComponents()
if component == 0 {
self.selectedCountry = self.countryList[row]
self.selectedRow = self.countryList[row].cities
pickerView.reloadComponent(1)
self.pickerView.selectRow(0, inComponent: 1, animated: true)
self.selectedCity = self.selectedRow[0]
} else {
self.selectedCity = self.selectedRow[row]
}
if let indexes = indexOf(city: self.selectedCity!.name, inCountry: self.selectedCountry!.name, in: countryList) {
//do something with indexes.countryIndex and indexes.cityIndex
print("This is the result \(indexes.cityIndex) and \(indexes.countryIndex)")
}
countryLbl.text = "The right answer is: \(self.selectedCountry?.name) and the city is \(self.selectedCity?.name)"
}
func readJson() {
let url = Bundle.main.url(forResource: "Countries", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(Root.self, from: data)
//handles the array of countries on your json file.
self.countryList = jsonResult.country
self.selectedRow = self.countryList.first!.cities
} catch {
print("error loading countries", error)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
readJson()
}
}
You have to decode the JSON in the bundle also into custom structs
struct Root : Decodable {
let country : [Country] // better plural let countries
private enum CodingKeys : String, CodingKey { case country = "Country" }
}
struct Country : Decodable {
let name : String
let cities : [City]
}
struct City : Decodable {
let name : String
}
var countryList = [Country]()
var selectedRow : [City]()
var selectedCity : City?
var selectedCountry : Country?
func readJson() {
let url = Bundle.main.url(forResource: "Countries", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(Root.self, from: data)
//handles the array of countries on your json file.
self.countryList = jsonResult.country
self.selectedRow = self.countryList.first!.cities
} catch {
print("error loading countries", error)
}
}
Then your method indexOf(city:inCountry:in:) works
As the file is in the application bundle consider to omit the root dictionary "Country" and decode [Country].self.
The usual side notes:
Do not use NS... collection types in Swift. You throw away the type information. Use native types.
.mutableContainers and .mutableLeaves are pointless in Swift. Apart from that ironically you assign the value to an immutable constant anyway.
A JSON dictionary in Swift 3+ is always value type [String:Any] not reference type[String:AnyObject].
This is the code I am using but I am unable to fetch the JSON.
Error message:
Expected to decode Dictionary<String, Any> but found an array instead.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(ItemList.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
JSON:
{
"page": 1,
"total_results": 2102,
"total_pages": 106,
"results": [{
"vote_count": 9052,
"id": 11,
"video": false,
"vote_average": 8.2,
"title": "Star Wars",
"popularity": 31.502792,
"poster_path": "/btTdmkgIvOi0FFip1sPuZI2oQG6.jpg",
"original_language": "en",
"original_title": "Star Wars",
"genre_ids": [
12,
28,
878
],
"backdrop_path": "/4iJfYYoQzZcONB9hNzg0J0wWyPH.jpg",
"adult": false,
"overview": "Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.",
"release_date": "1977-05-25"
}]
}
struct Results: Codable {
let id: Int
let title: String
let poster_path: String
struct ItemList: Codable {
let results: Results
}
}
You can create a Swift Struct for this purpose. Here is how you do it.
import Foundation
struct MovieStruct: Codable {
let page, totalResults, totalPages: Int?
let results: [Result]?
enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
}
struct Result: Codable {
let voteCount, id: Int?
let video: Bool?
let voteAverage: Double?
let title: String?
let popularity: Double?
let posterPath, originalLanguage, originalTitle: String?
let genreIDS: [Int]?
let backdropPath: String?
let adult: Bool?
let overview, releaseDate: String?
enum CodingKeys: String, CodingKey {
case voteCount = "vote_count"
case id, video
case voteAverage = "vote_average"
case title, popularity
case posterPath = "poster_path"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case releaseDate = "release_date"
}
}
After you have created the struct, you can do something like this to parse your data.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try? JSONDecoder().decode(MovieStruct.self, from: jsonData)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Note I have created the struct with the JSON which you have given. I hope it helps.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Result struct contains the contents of "results"
struct Result: Codable {
var results: [Movie]
}
Add variables to correspond to the item's fields
struct Movie: Codable {
var id: Int
var vote_count: Int
var title: String
//etc..
}
I got the same error when I try to get previous and song that play right now for every channel. I think something is wrong in my struct Root and playlist because data is not in the correct format.I am using Swift 4 and trying to learn JSON, but its hard. Previous struct is same as Song struct. How do I fix my error?
My code looks now:
import UIKit
struct Root : Decodable {
let copyright : String
let channels : [Channel]
let pagination : Pagination
}
struct Channel : Decodable {
let image : String
let imagetemplate : String
let color : String
let tagline : String?
let siteurl : String
let id : Int
let url : URL?
let statkey : String?
let scheduleurl : String
let channeltype : String
let name : String
}
struct Pagination : Decodable {
let page, size, totalhits, totalpages : Int
let nextpage : URL
}
struct Playlist : Decodable{
let id : Int
let name :String
let prev : [previoussong]
let song : [Song]
}
struct previoussong : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
struct Song : Decodable {
let title : String
let description : String
let artist : String
let composer :String
let conductor : String
let albumname : String
let recordlabel : String
let lyricist : String
let producer : String
let starttimeutc : String
let stopttimeutc : String
}
import UIKit
class ViewController: UIViewController,UITableViewDataSource {
var auth = SPTAuth.defaultInstance()!
var session:SPTSession!
var loginUrl: URL?
var tests = [Channel]()
//var pl = [Playlist]()
var stationsName:String?
#IBOutlet weak var tableV: UITableView!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var statLabel: UILabel!
#IBOutlet weak var songLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tableV.dataSource = self
downloadStations()
}
func downloadStations(){
let test2 = "http://api.sr.se/api/v2/channels?format=json"
let play = "http://api.sr.se/api/v2/playlists/rightnow?format=json"
let url = URL(string: test2)
URLSession.shared.dataTask(with: url!){ (data , response, error) in
do{
let root = try JSONDecoder().decode(Root.self, from: data!)
self.tests = root.channels
for eachStations in self.tests {
self.stationsName = eachStations.name
print(" " + self.stationsName!)
DispatchQueue.main.async {
self.tableV.reloadData()
}
}
}
catch{
print(error.localizedDescription)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style : .default, reuseIdentifier : nil)
cell.textLabel?.text = tests[indexPath.row].name
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The data you're getting from api.sr.se/api/v2/playlists/rightnow?format=json does not always have the correct structure. Notice that sometimes you will get json that starts with a copyright field, while other times you get something like this:
{
"playlist": {
"previoussong": {
"title": "Stir Fry",
"description": "Migos - Stir Fry",
"artist": "Migos",
"composer": "Quavious Marshall/Pharrell Williams",
"recordlabel": "Universal",
"starttimeutc": "\/Date(1521137326000)\/",
"stoptimeutc": "\/Date(1521137510000)\/"
},
...
When you attempt to decode into Root, the decoder looks in the json for something that looks like your Root class, something like this:
{
"copyright": "Copyright Sveriges Radio 2018. All rights reserved.",
"channels": [{
"id": 4540,
"name": "Ekot",
"playlists": {
"playlist": {
"channel": {
"id": 4540,
"name": "Ekot sänder direkt"
}
If it can't find something like that in the json, it says the data is missing. You will have to figure out a way to tell when to decode into Root, and when the data isn't appropriate for Root.