I have the Url saved in the info.plist as such:
BASE_URL <-> String <-> $(BASE_URL)
and in my project's Build Settings, I added a user-defined setting as such:
BASE_URL http://data.nba.net
After setting this up, when I try to get the website into the url variable, the variable returns "". As I debug the issue, I don't see the website stored under that variable.
I am new to Swift and still learning so any comments on the way I have setup my structs will be appreciated as well.
import UIKit
struct sports_content: Decodable {
let sports_meta_expanded: sports_meta
let teams_expanded: teams
}
struct sports_meta: Decodable {
let date_time: String?
let season_meta_list: season_meta
}
struct season_meta: Decodable {
let calendar_date: Date
let season_year: Int?
let stats_season_year: Int?
let stats_season_id: Int?
let stats_season_stage: Int?
let roster_season_year: Int?
let schedule_season_year: Int?
let standings_season_year: Int?
let season_id: Int?
let display_year: String
let display_season: String
let season_stage: Int?
}
struct next: Decodable {
let url: String
}
struct teams: Decodable {
let season_year: year
let team_data: [team]
}
struct year: Decodable {
let season_year: Int?
}
struct team: Decodable {
let is_nba_team: Bool
let team_name: String
let team_nickname: String
let team_code: String
let team_abbrev: String
let city: String
let state: String
let team_short_name: String
let team_id: Int?
let conference: String
let division_id: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.infoDictionary?["BASE_URL"] as? String ?? ""
guard let convertedURL = URL(string: url) else {
return
}
URLSession.shared.dataTask(with: convertedURL) { (data, response, error) in
guard let data = data else {
return
}
do{
let dataSet = try JSONDecoder().decode(sports_content.self, from: data)
print(dataSet)
} catch {
print("JSONSerialization error:", error)
}
}.resume()
}
}
A build setting is used at build / compile time and not necessarily at run time.
To get your URL into the infoDictionary, you need to add it to the Info.plist file. Double click on your Info.plist to get the view open in your Xcode, then click "Add Value" under the Editor menu, then you can add BASE_URL as the key and your URL as the value.
Try using $(BASE_URL) as the value in your Info.plist and see if your build setting gets added in at build time. :)
Related
I'm building a Swift app and testing in an Xcode Playground. Calling the NYTimes Search API and trying to store its response in a struct. The code executes cleanly and no errors appear (I am using a do, try, catch), but I cannot print any properties from the resulting object (print(json.status)).
My hunch is that something is fishy with this line but I'm not sure what since no errors are printing from the catch statement
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
Form the URL endpoint to make the API call:
func APICall() {
let APIKey = "MY_API_KEY_GOES_HERE_BUT_IT'S_A_SECRET"
let searchTerm = "A Super Bowl Sideshow: See the Ageless Man!"
// Remove the spaces and convert them to percents
guard let encodedSearchTerm = searchTerm.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
else {
print("Error encoding search term in URL")
return
}
let url = "https://api.nytimes.com/svc/search/v2/articlesearch.json?q=" + encodedSearchTerm + "&api-key=" + APIKey
getData(from: url)
}
Data Task:
func getData(from url: String) {
//I believe something is wrong with the following line but I'm not sure what it is
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("Error loading data")
return
}
var result: NYTSearchResponse?
do {
result = try JSONDecoder().decode(NYTSearchResponse.self, from: data)
} catch {
print(error)
}
guard let json = result else {
print("Error assigning result to json")
return
}
//Try to print these from the resulting object but these commands do not print
print(json.status)
print(json.response.docs[0].abstract)
})
task.resume()
}
My NYTSearchResponse struct which mirrors the NYT API JSON response. It's pretty complicated, but I pasted the json response into https://app.quicktype.io/ to build the struct.
// MARK: - Welcome
struct NYTSearchResponse: Codable {
let status, copyright: String
let response: Response
}
// MARK: - Response
struct Response: Codable {
let docs: [Doc]
let meta: Meta
}
// MARK: - Doc
struct Doc: Codable {
let abstract: String
let webURL: String
let snippet, leadParagraph, printSection, printPage: String
let source: String
let multimedia: [Multimedia]
let headline: Headline
let keywords: [Keyword]
let pubDate: Date
let documentType, newsDesk, sectionName, subsectionName: String
let byline: Byline
let typeOfMaterial, id: String
let wordCount: Int
let uri: String
enum CodingKeys: String, CodingKey {
case abstract
case webURL = "web_url"
case snippet
case leadParagraph = "lead_paragraph"
case printSection = "print_section"
case printPage = "print_page"
case source, multimedia, headline, keywords
case pubDate = "pub_date"
case documentType = "document_type"
case newsDesk = "news_desk"
case sectionName = "section_name"
case subsectionName = "subsection_name"
case byline
case typeOfMaterial = "type_of_material"
case id = "_id"
case wordCount = "word_count"
case uri
}
}
// MARK: - Byline
struct Byline: Codable {
let original: String
let person: [Person]
let organization: String?
}
// MARK: - Person
struct Person: Codable {
let firstname: String
let middlename: String?
let lastname: String
let qualifier, title: String?
let role, organization: String
let rank: Int
}
// MARK: - Headline
struct Headline: Codable {
let main: String
let kicker, contentKicker: String?
let printHeadline: String
let name, seo, sub: String?
enum CodingKeys: String, CodingKey {
case main, kicker
case contentKicker = "content_kicker"
case printHeadline = "print_headline"
case name, seo, sub
}
}
// MARK: - Keyword
struct Keyword: Codable {
let name, value: String
let rank: Int
let major: String
}
// MARK: - Multimedia
struct Multimedia: Codable {
let rank: Int
let subtype: String
let caption, credit: String?
let type, url: String
let height, width: Int
let legacy: Legacy
let subType, cropName: String
enum CodingKeys: String, CodingKey {
case rank, subtype, caption, credit, type, url, height, width, legacy, subType
case cropName = "crop_name"
}
}
// MARK: - Legacy
struct Legacy: Codable {
let xlarge: String?
let xlargewidth, xlargeheight: Int?
}
// MARK: - Meta
struct Meta: Codable {
let hits, offset, time: Int
}
I moved the code out of the playground and it works.
I am parsing a json data and trying to create a Model but can't figure out how to achieve the title and extract properties from the json data (which I have provided), as pageids property is dynamic. Please tell me how can I create Model to extract the title property from the page using id (stored in pageids property)
link for jsonData https://en.wikipedia.org/w/api.php?exintro=&titles=canterbury%20bells&indexpageids=&format=json&pithumbsize=500&explaintext=&redirects=1&action=query&prop=extracts%7Cpageimages
I tried little a bit, below is my code but I don't think that's correct
var ID = ""
struct Document:Codable {
let batchcomplete:String
let query:Query
}
struct Query:Codable {
let normalized:[Normalized]
let pages:Pages
var pageids:[String]{
didSet{
ID = oldValue[0]
}
}
}
struct Normalized:Codable {
let from:String
let to:String // it is a name of an flower
}
struct Pages:Codable {
let id:[Pages2]
enum CodingKeys:CodingKey {
case id = "\(ID)"
}
}
struct Pages2:Codable {
let title:String // this is an official name of flower
let extract:String // this is a body
let thumbnail:Thumbnail
}
struct Thumbnail:Codable {
let source:String //this is an url for photo
}
The model to map your JSON will be something like this:
struct Document: Codable {
let batchcomplete: String
let query: Query
}
struct Query: Codable {
let normalized: [Normalized]
var pageids: [String]
let pages: [String: Page]
}
struct Normalized: Codable {
let from: String
let to: String
}
struct Page: Codable {
let title: String
let extract: String
let thumbnail: Thumbnail
}
struct Thumbnail: Codable {
let source: String
}
and you have access to each page using pageids array and pages dictionary:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(Document.self, from: Data(jsonString.utf8))
decoded.query.pageids.forEach { id in
guard let page = decoded.query.pages[id] else { return }
print(page.title)
}
} catch {
print(error)
}
However I would prefer to make a small change to the model in order to make access to pages easier. That will require to customly implement the decoding of Query struct:
struct Query: Decodable {
let normalized: [Normalized]
let pages: [Page]
enum CodingKeys: String, CodingKey {
case normalized
case pageids
case pages
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
normalized = try container.decode([Normalized].self, forKey: .normalized)
let pageids = try container.decode([String].self, forKey: .pageids)
let pagesDict = try container.decode([String: Page].self, forKey: .pages)
pages = pageids.compactMap { pagesDict[$0] }
}
}
Then, access to each page would be as simple as a loop:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(Document.self, from: Data(jsonString.utf8))
decoded.query.pages.forEach { page in
print(page.title)
}
} catch {
print(error)
}
This question already has answers here:
Split a String into an array in Swift?
(40 answers)
Closed 3 years ago.
I'm writing an app that pulls data from the Timezonedb API to get a list of available timezones:
https://timezonedb.com/references/list-time-zone
I'm trying to parse the zoneName which is currently formatted as "Europe/Andorra". My question is how do I split the JSON string to just display city name i.e "Andorra" in a tableview?
Here's the response I'm getting back:
{
"status":"OK",
"message":"",
"zones":[
{
"countryCode":"AD",
"countryName":"Andorra",
"zoneName":"Europe\/Andorra",
"gmtOffset":7200,
"timestamp":1464453737
},
{
"countryCode":"AE",
"countryName":"United Arab Emirates",
"zoneName":"Asia\/Dubai",
"gmtOffset":14400,
"timestamp":1464460937
},
{"
countryCode":"AF",
"countryName":"Afghanistan",
"zoneName":"Asia\/Kabul",
"gmtOffset":16200,
"timestamp":1464462737
}]}
Here's my code:
Model:
struct TimeZones: Codable {
let status, message: String?
let zones: [Zone]?
}
struct Zone: Codable {
let countryCode, countryName, zoneName: String?
let gmtOffset, timestamp: Int?
}
Here's the networking code:
var cities: [Zone] = []
func getAvailableTimeZones() {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
let task = session.dataTask(with: url) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let content = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let timeZones = try decoder.decode(TimeZones.self, from: content)
if let zones = timeZones.zones {
self.cities.append(contentsOf: zones)
}
} catch let err {
print("Err", err)
}
}
// Execute the HTTP request
task.resume()
}
TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchResultsCell", for: indexPath)
cell.textLabel?.text = cities[indexPath.row].zoneName
return cell
}
Any help appreciated. Thank you.
A solution is to add CodingKeys and a computed property.
And there is no reason to declare the struct members as optional
struct TimeZones: Decodable {
let status, message: String
let zones: [Zone]
}
struct Zone: Decodable {
let countryCode, countryName, zoneName: String
let gmtOffset, timestamp: Int
private enum CodingKeys: String, CodingKey { case countryCode, countryName, zoneName, gmtOffset, timestamp}
lazy var zoneCountry : String = {
return zoneName.components(separatedBy: "/").last!
}()
}
and use it
cell.textLabel?.text = cities[indexPath.row].zoneCountry
To get the desired output with the minimal change you just have to update cellForRowAt() method like:
let zoneName = cities[indexPath.row].zoneName.components(separatedBy: "/").last ?? ""
cell.textLabel?.text = zoneName
I'm trying to receive data from a JSON link in Swift Playground on Mac, I've struct all the data, but I'm having issue trying to decode all of the data, receiving the error: "Referencing instance method 'decode(_:from:)' on 'Array' requires that 'Bicimia' conform to 'Decodable'"
I've already tries to add the Codable/Decodable option, and tried to change the URLSession respectively, but nothing has changed.
struct Bicimia {
let network: Network
}
struct Network {
let company: [String]
let href, id: String
let location: Location
let name: String
let stations: [Station]
}
struct Location {
let city, country: String
let latitude, longitude: Double
}
struct Station {
let emptySlots: Int
let extra: Extra
let freeBikes: Int
let id: String
let latitude, longitude: Double
let name, timestamp: String
}
struct Extra {
let extraDescription: String
let status: Status
}
enum Status {
case online
}
let url = "https://api.citybik.es/v2/networks/bicimia"
let urlOBJ = URL(string: url)
URLSession.shared.dataTask(with: urlOBJ!) {(data, response, error) in
do {
let res = try JSONDecoder().decode([Bicimia].self, from: data!)
print(res)
}
catch {
print(error)
}
}.resume()
To be Decodable all properties should be Decodable down the chain:
struct Bicimia: Decodable {
let network: Network // should be decodable
}
struct Network: Decodable {
let company: [String]
let href, id: String
let location: Location // should be decodable
let name: String
let stations: [Station] // should be decodable
}
struct Location: Decodable {
let city, country: String
let latitude, longitude: Double
}
struct Station: Decodable {
let emptySlots: Int
let extra: Extra // should be decodable
let freeBikes: Int
let id: String
let latitude, longitude: Double
let name, timestamp: String
}
struct Extra: Decodable {
let extraDescription: String
let status: Status // should be decodable
}
enum Status: String, Decodable {
case online
}
Note that enums can not be Decodable alone, because they should know what is the raw value, or you should manually decode them in decode function.
I am trying to parse some json but seem to be getting nil in the outputs.
I am not sure where I am going wrong and could use some help trying to figure this out.
struct albumInfo: Decodable {
var name: String?
var artist: String?
var url: String?
var playcount: String?
var listeners: String?
var releasedate: String?
var summary: String?
}
class SearchVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Choice = "album"
Album = "Believe"
Artist = "Cher"
let tryURL = "\(BASE_URL)\(Choice!).getinfo&api_key=\(API_KEY)&artist=\(Artist!)&album=\(Album!)&format=json"
print(tryURL)
guard let url = URL(string: tryURL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}
}.resume()
}
Here is the data as shown with the tryUrl.
First of all please conform to the naming convention that struct names start with a capital letter.
There are two major issues:
The root object is a dictionary with one key album containing the dictionary with keys name, listeners etc.
The key summary is in another dictionary for key wiki.
The structure of the JSON is very easy to identify. The body within each pair of braces ({}) represents a separate struct.
Further there is no key releasedate so this struct member has to be declared as optional, all other members can be declared as non-optional and as constants (let). url can be declared as URL for free.
Change your structs to
struct Root : Decodable {
let album : AlbumInfo
}
struct AlbumInfo: Decodable {
let name: String
let artist: String
let url: URL
let playcount: String
let listeners: String
let releasedate: String?
let wiki : Wiki
}
struct Wiki: Decodable {
let content: String
let published: String
let summary: String
}
and decode Root
let albumDescription = try JSONDecoder().decode(Root.self, from: data)
print(albumDescription.album.artist)
The first key of your response is "album", you need to parse that first.
The classes do not correspond to json, I guess you should use the following approach (new classes implement your decode, encode protocol):
class JsonInfo {
var album : albumInfo
}
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.album.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}