Parse Wiki Nearby - swift

I'm new to Swift, and trying to figure out how to parse the JSON returned by Wiki Nearby. With the help of https://www.youtube.com/watch?v=sqo844saoC4 here's where I am right now:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url)
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("failed to convert \(error.localizedDescription) ")
}
guard let json = result else {
return
}
print(type(of: json.query.geosearch))
print(json.query.geosearch.self)
})
task.resume()
}
}
struct Response: Codable {
let batchcomplete: String
let query: Query
}
struct Query: Codable {
let geosearch: [Geosearch]?
}
struct Geosearch: Codable {
let pageid: Int?
let title: String?
}
Following Need help parsing wikipedia json api in SWIFT I believe that geosearch is a dictionary. The code runs, but how do I retrieve the title or pageid from geosearch (print(json.query.geosearch.title) gives an error)?
Sorry - probably something really basic... any pointers would be appreciated.
Philipp

try the following code. It uses a completion closure to "wait" until the results are fetched before
returning them. Since geosearch is an array you need to loop through it. Note, the json response is parsed without errors. Works for me.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url) { response in
if let result = response,
let geosearch = result.query.geosearch {
print("-----> result: \(result) \n")
for geo in geosearch {
print("geo.title: \(geo.title)")
}
}
}
}
func getData(from url: String, completion: #escaping(Response?) -> ()) {
if let theURL = URL(string: url) {
let task = URLSession.shared.dataTask(with: theURL) { data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
completion(nil)
return
}
do {
let result = try JSONDecoder().decode(Response.self, from: data)
completion(result)
} catch {
print("failed to convert \(error) ")
completion(nil)
}
}
task.resume()
}
}
}

You need to access the item of array to get the pageid or title like:
print(json.query.geosearch?[0].title ?? "")
Since the json returns an array
{
"batchcomplete": "",
"query": {
"geosearch": [
{
"pageid": 18618509,
"ns": 0,
"title": "Wikimedia Foundation",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
},
{
"pageid": 42936625,
"ns": 0,
"title": "Foxcroft Building",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
}
]
}
}

print(json.query.geosearch.self) //This will store the repsone in the let geosearch: [Geosearch]?
//You can check the individual result for the 1st and 2nd object
print(json.query.geosearch[0].pageid)//18618509
print(json.query.geosearch[0].title) //"Wikimedia Foundation"
print(json.query.geosearch[1].pageid)//42936625
print(json.query.geosearch[1].title) //"Foxcroft Building"

Related

Does Swift task run first or print() first when I tap my UIButton?

I am trying to understand what is going on in my code here.
I have a simple API call to open weahter API and that whenever the user taps the UIButton, it should call the api and get the data back from open weather.
Everything works as intended however, when I have my UIButton pressed, the print statement executed first before the Task closure. I'm trying to understand the race condition here
This is my code in viewController:
#IBAction func callAPIButton(_ sender: UIButton) {
Task {
let weatherData = await weatherManager.fetchWeather(cityName: "Seattle")
}
}
Here's the code for fetching the API:
struct WeatherManager{
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=someAPIKeyHere"
func fetchWeather(cityName: String) -> WeatherModel? {
let urlString = "\(weatherURL)&q=\(cityName)"
let requestResult = performRequest(urlString: urlString)
return requestResult
}
func performRequest(urlString: String) -> WeatherModel? {
var weatherResult : WeatherModel? = nil
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error != nil {
return
}
if let safeData = data {
weatherResult = parseJSON(weatherData: safeData)
}
})
task.resume()
}
return weatherResult
}
func parseJSON(weatherData: Data) -> WeatherModel?{
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherResponse.self, from: weatherData)
print("this is in decodedData: \(decodedData)")
let temp = decodedData.main.temp
let name = decodedData.name
let weather = WeatherModel(conditionId:300, cityName: name, temperature: temp)
return weather
} catch {
print("Something is wrong here: " + error.localizedDescription)
}
return nil
}
}
Here's my Model:
struct WeatherModel{
let conditionId: Int
let cityName: String
let temperature: Double
var temperatureString: String{
return String(format: "%.1f", temperature)
}
var conditionName: String {
switch conditionId {
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
}
}
Desired result:
This is in weatherData: WeatherResponse(name: "Seattle", weather: [Awesome_Weather_App.WeatherAPI(description: "overcast clouds", icon: "04d")], main: Awesome_Weather_App.MainAPI(temp: 287.81, pressure: 1018.0, humidity: 44.0, temp_min: 284.91, temp_max: 290.42, feels_like: 286.48), sys: Awesome_Weather_App.SysAPI(sunrise: 1.6712886e+09, sunset: 1.6713243e+09))
This is what I am getting instead:
This is in weatherData: nil
this is in decodedData: WeatherResponse(name: "Seattle", weather: [Awesome_Weather_App.WeatherAPI(description: "overcast clouds", icon: "04d")], main: Awesome_Weather_App.MainAPI(temp: 287.81, pressure: 1018.0, humidity: 44.0, temp_min: 284.91, temp_max: 290.42, feels_like: 286.48), sys: Awesome_Weather_App.SysAPI(sunrise: 1.6712886e+09, sunset: 1.6713243e+09))
Thank you in advance
Everything works as intended
No, it doesn't. I don't know why you claim such a thing; your code isn't working at all.
The problem is that you are trying to return weatherResult from performRequest. But performRequest gets its weatherResult value asynchronously, so this attempt is doomed to failure; you will always be returning nil, because the return weatherResult happens before session.dataTask ever even starts to find out what weatherResult is.
You cannot just synchronously return the results of an asynchronous request. You have two basic options for asynchronous requests.
Use the older “completion handler” pattern with Result types:
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather"
let appId = "someAPIKeyHere"
func fetchWeather(
cityName: String,
completion: #escaping (Result<WeatherModel, Error>) -> Void
) {
guard var components = URLComponents(string: weatherURL) else {
completion(.failure(URLError(.badURL)))
return
}
components.queryItems = [
URLQueryItem(name: "appid", value: appId),
URLQueryItem(name: "q", value: cityName)
]
guard let url = components.url else {
completion(.failure(URLError(.badURL)))
return
}
performRequest(url: url, completion: completion)
}
func performRequest(
url: URL,
queue: DispatchQueue = .main,
completion: #escaping (Result<WeatherModel, Error>) -> Void
) {
let session = URLSession.shared // note, do not create a new URLSession for every request or else you will leak; use shared instance
let task = session.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let response = response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode
else {
queue.async { completion(.failure(error ?? URLError(.badServerResponse))) }
return
}
do {
let weatherResult = try parseJSON(weatherData: data)
queue.async { completion(.success(weatherResult)) }
} catch {
queue.async { completion(.failure(error)) }
}
}
task.resume()
}
func parseJSON(weatherData: Data) throws -> WeatherModel {
let decoder = JSONDecoder()
let response = try decoder.decode(WeatherResponse.self, from: weatherData)
print("this is in decodedData: \(response)")
return WeatherModel(conditionId: 300, cityName: response.name, temperature: response.main.temp)
}
}
Then, rather than:
let weather = weatherManager.fetchWeather(cityName: …)
You would
weatherManager.fetchWeather(cityName: …) { result in
switch result {
case .failure(let error):
print(error)
case .success(let weather):
// do something with the `weather` object here
}
}
// note, do not do anything with `weather` here, because the above
// runs asynchronously (i.e., later).
Use the newer async-await pattern of Swift concurrency:
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather"
let appId = "someAPIKeyHere"
func fetchWeather(cityName: String) async throws -> WeatherModel {
guard var components = URLComponents(string: weatherURL) else {
throw URLError(.badURL)
}
components.queryItems = [
URLQueryItem(name: "appid", value: appId),
URLQueryItem(name: "q", value: cityName)
]
guard let url = components.url else {
throw URLError(.badURL)
}
return try await performRequest(url: url)
}
func performRequest(url: URL) async throws -> WeatherModel {
let session = URLSession.shared // note, do not create a new URLSession for every request or else you will leak; use shared instance
let (data, response) = try await session.data(from: url)
guard
let response = response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode
else {
throw URLError(.badServerResponse)
}
return try parseJSON(weatherData: data)
}
func parseJSON(weatherData: Data) throws -> WeatherModel {
let decoder = JSONDecoder()
do {
let response = try decoder.decode(WeatherResponse.self, from: weatherData)
print("this is in decodedData: \(response)")
return WeatherModel(conditionId: 300, cityName: response.name, temperature: response.main.temp)
} catch {
print("Something is wrong here: " + error.localizedDescription)
throw error
}
}
}
And then you can do things like:
Task {
do {
let weather = try await weatherManager.fetchWeather(cityName: …)
// do something with `weather` here
} catch {
print(error)
}
}
Note, a few changes in the above unrelated to the asynchronous nature of your request:
Avoid creating URLSession instances. If you do, you need to remember to invalidate them. Instead, it is much easier to use URLSession.shared, eliminating this annoyance.
Avoid building URLs with string interpolation. Use URLComponents to build safe URLs (e.g., ones that can handle city names like “San Francisco”, with spaces in their names).

How to save data from JSON to model

I have an API, I parse it with alomofire, and I want to save parsed json to Model
This is my request and I want to save chats to ChatInstanceModel.
This is my NetworkingManager class
func getChatList(completionHandler: (#escaping (ChatInstanceModel) -> Void)) {
guard let url = URL(string: urlOfIntanceAPI) else { return }
let headers: HTTPHeaders = [
"Authorization" : "FastToken 8af34fc7c2557517d8a3e747e497de6491a16350df6fa0af8afcbbe72035484efae69d46efa8a47feb75be3250d025b01f42b3b5ad723e5a32afc887c1f6879629b86655ce49e19cada8e289b38d79061160f538c6fa59388076396de1cd2886e2c2e8c5f706ad2bb79a23f034b889c835dd288e44c0cc257d048a22093e51226a60b198bf72aa40a11829771147b4d5"
]
AF.request(url, method: .get, headers: headers).responseData { (responseData) in
switch responseData.result {
case .success(let data):
guard let chats = try? JSONDecoder().decode(ChatInstanceModel.self, from: data) else { return }
completionHandler(chats)
case .failure(let error):
print(error)
}
}
}
This is my model:
struct ChatInstanceModel: Codable {
let title: String
let avatar: String?
let unread_client: Int?
}
This is api which I want to save
{
"pk": 3,
"title": "None",
"avatar": null,
"unread_client": 0,
"unread_manager": 0,
"is_online": false,
"is_typing": false,
"last_seen": null,
"manager": null,
"last_update": null
}
Here is an example of how you can save the ChatInstanceModel instance after the getting the response from API using the completionHandler.
class ViewController: UIViewController {
var chats: ChatInstanceModel?
override func viewDidLoad() {
super.viewDidLoad()
getChatList { (chats) in
self.chats = chats
}
}
}
That's just a simple example for your problem statement currently specified.
Please find an example:
import Alamofire
// Your Model
struct ChatInstanceModel: Codable {
let title: String
let avatar: String?
let unread_client: Int?
}
// Your View Controller
class ViewController: UIViewController {
// Your chat instance
var chats: ChatInstanceModel? = nil
override func viewDidLoad() {
super.viewDidLoad()
getChatList { (chats) in
self.chats = chats
print(self.chats?.title)
}
}
// Get chat list from api
func getChatList(completionHandler: (#escaping (ChatInstanceModel) -> Void)) {
guard let url = URL(string: "<API_URL>") else { return }
let headers: HTTPHeaders = [
"Authorization" : "<TOKEN>"
]
let request = AF.request(url, method: .get, headers: headers)
request.responseDecodable(of: ChatInstanceModel.self) { (dataResponse) in
switch dataResponse.result {
case .success(let data):
completionHandler(data)
case .failure(let error):
print("Error", error.localizedDescription)
}
}
}
}

Is there a way to use my array of type Music, in another scope?

I'm attempting to print/dump and array of type Music outside of a function it's created in. I can successfully dump the musicItems array inside of the getMusicData function but when I set the musicItems array outside of the scope, it won't print anything. What am I doing wrong with the scope here? I have a feeling it's super simple but I just can't figure it out. Thanks in advance for taking the time to read this.
edit: It's giving me "0 elements" in the console when I attempt to dump the musicItems array in the ViewController class. Well, the function is in the same class as well so I guess I don't know what to call the first array. The parent array?
struct MusicResults: Decodable {
let results: [Music]?
}
struct Music: Decodable {
let trackName: String?
let collectionName: String?
let artworkUrl30: String?
}
class ViewController: UITableViewController, UISearchBarDelegate {
var musicItems: [Music] = []
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData()
dump(musicItems)
}
Here is the function.
func getMusicData() {
var musicItems: [Music] = []
guard let searchTerm = searchString else {return}
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
// print(results.trackName!)
musicItems.append(results)
}
//dump(musicItems)
self.musicItems = musicItems
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
Fixed Code
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData {
music in
dump(music)
}
function:
func getMusicData(completionHandler: #escaping (_ music: [Music]) -> ()) {
...
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
musicItems.append(results)
}
completionHandler(musicItems)
...
Your 'getMusicData' function is asynchronous which means that when it executes, it queues data task in a background queue and proceeds the execution and since there are no more institutions it simply returns control to its calling site - 'musicButton()' action, which in its turn executes the next instruction - prints the 'musicItems' array which might (and most likely, is) still not populated as the network call haven’t yet completed. One of the options that you have here is to pass a completion block to your 'getMusicData' function, that runs it after data task gets the results.
Another option is to use Property Observers
var musicItems: [Music] = [] {
didSet {
dump(self.musicItems)
/// This is where I would do the...
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
and then
func getMusicData() {
guard let searchTerm = searchString else { print("no search"); return }
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { print("url error"); return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { print(err ?? "unknown"); return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
if let results = music.results {
self.musicItems.append(contentsOf: results)
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}

Decode simple API array Swift

I am struggling to decode a specific object returned by an API, I can't figure out the correct struct.
What's wrong?
Here is the returned API JSON:
{
"votes": [
{
"votesId": "1",
"vote_nb": "6",
"current_time": "0",
"trend_up": "0",
"trend_down": "0",
"position_hold": "0",
"position_buy": "0",
"position_sell": "0"
}]
}
And my code to decode and fetch:
struct VoteData : Codable {
let votes : [Votes]
}
struct Votes : Codable {
let vote_nb : String
}
func fetchData(completion: #escaping (VoteData?, Error?) -> Void) {
let url = URL(string: "...")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, error ?? APIError.unknownNetworkError)
return
}
do {
let result = try JSONDecoder().decode(VoteData.self, from: data); completion(result, nil)
let vote_nb = result.votes.vote_nb // Value of type '[Votes]' has no member 'vote_nb'
print("VOTE NUMBER: ", vote_nb)
} catch let parseError {
completion(nil, parseError)
}
}
task.resume()
}
First select a particular member of the array, then check that property of THE member.
Change this line:
let vote_nb = result.votes.vote_nb
For this:
let vote_nb = result.votes[0].vote_nb

Using decoded data from an API into an algorithm

I successfully fetched and decoded data from an API and now have access to all the data I need to be used in the algorithm I want to write in my App.
The issue is that I don't know how to access this data after I decoded it, I can print it immediately after it's decoded but I have no idea how to use it in another function or place in my app.
Here is my Playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError : Error {
case FoundNil(String)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
print(marketData.data[0].open)
print(marketData.data[1].open)
print("Average=", (marketData.data[0].open + marketData.data[1].open) / 2)
//completion(marketData, nil)
throw MyError.FoundNil("data")
}
} catch {
print(error)
}
}
task.resume()
}
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
How can I use .data[0], .data[1], ..., somewhere else?
You data will be available in your fecthData() call. Probably what you want is your items variable, where you're printing it. But make sure to call the completion in your fetchData implementation.
WARNING: Untested code.
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case FoundNil(String)
case DecodingData(Data)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, MyError.FoundNil("data"))
}
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
completion(marketData, nil)
} else {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
} catch {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
}
task.resume()
}
fetchData() { items, error in
if let error == error {
switch(error) {
case .foundNil(let whatsNil):
print("Something is nil: \(whatsNil)")
case .decodingData(let data):
print("Error decoding: \(data)")
}
} else {
if let items = items {
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
print(items)
} else {
print("No items to show!")
}
}
}
I don't understand what is your real issue, because you have written everything you need here, but as far I understand , to pass data
just uncomment this line completion(marketData, nil)
and in
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
items is an object of your struct Response. You can pass this anywhere in your other class , by just creating an another variable like:
var items : Response!
for example :
class SomeOtherClass : NSObject{
var items : Response!
func printSomeData()
{
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
}
}
and in fetchData method write this:
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let otherObject = SomeOtherClass()
otherObject.items = items
otherObject.printSomeData()
}