I'm making a weather app using OpenweatherAPI. I download data from web, but when I want to pass it to another class it show as nil. Here is the code:
So that's the class I'm getting data from the API
var currentTemp: Double {
if _currentTemp == nil {
_currentTemp = 120.0
}
return _currentTemp
}
func downloadWeatherDetails(completed: #escaping DownloadComplete) {
Alamofire.request(CURRENT_WEATHER_URL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, Any> {
if let main = dict["main"] as? Dictionary<String, Any> {
if let currentTemperature = main["temp"] as? double_t {
let kelvinToCelsiusPreDivision = (currentTemperature - 273.15)
let kelvinToCelsius = Double(round((10 * kelvinToCelsiusPreDivision) / 10))
self._currentTemp = kelvinToCelsius
print(self._currentTemp)
}
}
}
completed()
} }
And the mainVC - ViewController:
func updateMainUI() {
currentTempLabel.text = "\(currentWeather.currentTemp)"
}
I'm of course calling updateMainUI in ViewDidLoad, but IMHO I think the method that updates UI is probably called before getting the data from the JSON.
The Label on the App shows 120 - so it is considered as nil...
Sorry about parenthesis if there is something wrong.
Thanks for help in advice :)
EDIT:
Ohh and I forgot to add.. The data from API is perfectly fine, so the call to API is working :)
// I Hope that helps
func downloadWeatherDetails(completed: #escaping DownloadComplete) {
//Download Current Weather Data
Alamofire.request(CURRENT_WEATHER_URL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let main = dict["main"] as? Dictionary<String, AnyObject> {
if let currentTemperature = main["temp"] as? Double {
let kelvinToFarenheitPreDivision = (currentTemperature * (9/5) - 459.67)
let kelvinToFarenheit = Double(round(10 * kelvinToFarenheitPreDivision/10))
self._currentTemp = kelvinToFarenheit
print(self._currentTemp)
}
}
}
completed()
}
}
Related
I'm trying to fetch data from web and trying paging using AZTableView library. I'm facing the above error. Here's my code
My Modal class
class JobsNearBy: NSObject {
var jobId: Int?
var title: String?
var companyName: String? }
Fetch data code
I fetch 10 rows from the web first time put them in object and append on array and return.
func jobsNearByFetch(pageNumber: Int, success:#escaping (_ status:Bool, _ jobsNearByArray:Any) -> (), failure:#escaping (_ message: Error) -> ()) {
let headers: HTTPHeaders = ["Accept": "application/json",
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhb"]
let url = "http://thedemo.net/demo/stdinaus/api/jobs-near-me?page=\(pageNumber)&latitude=27.6947033&longitude=85.3310636"
Alamofire.request(url, headers: headers).responseJSON { response in
guard let jobsResponse = response.result.value as? [String:Any] else{
print("Error: \(String(describing: response.result.error))")
failure((response.result.error! as Error))
return
}
// print("response: \(jobsResponse)")
let jobsNearByObj:JobsNearBy = JobsNearBy()
var jobsNearByArray:Array = [JobsNearBy]()
let dict = jobsResponse as NSDictionary
let status = dict["status"] as? Int
let meta = dict["meta"] as! NSDictionary
let lastPage = meta["last_page"] as? Int
let dataArray = dict["data"] as! NSArray
for dataDict in dataArray{
let dataCompanyName = dataDict as! NSDictionary
let jobId = dataDict as! NSDictionary
let title = dataDict as! NSDictionary
if let companyName = dataCompanyName["company_name"],
let jobId = jobId["jobId"],
let title = title["title"]{
jobsNearByObj.companyName = companyName as? String
jobsNearByObj.jobId = jobId as? Int
jobsNearByObj.title = title as? String
jobsNearByArray.append(jobsNearByObj)
}
}
success(true, jobsNearByArray)
}
}
Code in AZTableViewController
override func fetchData() {
super.fetchData()
if Util.isConnectedToInternet(){
self.showLoading(view: self.view, text: "Loading..")
APIHandler.sharedInstance.jobsNearByFetch(pageNumber: 1, success: { (status, jobsArray) in
self.stopLoading(fromView: self.view)
self.arrayOfJobs.removeAll()
self.arrayOfJobs.append(jobsArray as! JobsNearBy)
self.didfetchData(resultCount: self.arrayOfJobs.count, haveMoreData: true)
}) { (failure) in
self.stopLoading(fromView: self.view)
print("Failure")
}
}else{
Util.showAlert(title:"Oops", message:"No internet connection..", view:self)
}
}
override func fetchNextData() {
super.fetchNextData()
if Util.isConnectedToInternet(){
self.showLoading(view: self.view, text: "Loading..")
APIHandler.sharedInstance.jobsNearByFetch(pageNumber: 2, success: { (status, jobsArray) in
self.stopLoading(fromView: self.view)
self.arrayOfJobs.append(jobsArray as! JobsNearBy)
if self.arrayOfJobs.count < 10{
self.didfetchData(resultCount: self.arrayOfJobs.count, haveMoreData: true)
}else{
self.didfetchData(resultCount: self.arrayOfJobs.count, haveMoreData: false)
}
}) { (failure) in
self.stopLoading(fromView: self.view)
print("Failure")
}
}else{
Util.showAlert(title:"Oops", message:"No internet connection..", view:self)
}
}
I think I've made mistake on append line but unable to solve this. Please someone help me with the above error.
Your completion handler for jobsNearByFetch returns an array of JobsNearBy, which you put into jobsArray
Then, you have a force cast of jobsArray to JobsNearBy, but it is an array, not a single instance of the object so the downcast fails and because it is a forced downcast your app crashes.
You could fix it by using as! [JobsNearBy], but it is better to change the signature of the completion closure to indicate that it returns [JobsNearBy] instead of Any; then you don't need to downcast anything:
You shouldn't use Any when you can determine what the actual type is. Also, you shouldn't use NSDictionary when working in Swift if you can avoid it. Also, avoid force downcasting and unwrapping whenever possible.
Stylistically the boolean success parameter and a separate failure closure is a bit odd too; you would typically have a single closure that returns an optional Error - If error is nil then the operation was successful.
I would have:
func jobsNearByFetch(pageNumber: Int, completion:#escaping ( _ jobsNearByArray:[JobsNearBy]?, error:Error?) -> ()) {
This way you can use a single trailing closure.
You also need to look at your jobsNearByFetch as there are some return paths that don't call a closure.
Finally, you should look at the Codeble protocol as it can eliminate the JSON parsing code altogether.
I am using Swift 3 with XCode 8
I am pretty new to IOS development and using Swift. I am currently having a problem where some required code does not run after the asynchronous call has successfully been completed.
In my constants file:
typealias DownloadComplete = () -> ()
In my WeatherVC.swift file:
var currentWeather = CurrentWeather()
override func viewDidLoad() {
super.viewDidLoad()
TableView.delegate = self
TableView.dataSource = self
currentWeather.downloadWeatherDetails{
//setup UI to load downloaded data
print("Done 2")
self.updateMainUI()
}
}
In my CurrentWeather.swift class:
func downloadWeatherDetails(completed: #escaping DownloadComplete){
//Alamofire download
let currentWeatherURL = URL(string: CURRENT_WEATHER_URL)!
Alamofire.request(currentWeatherURL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let name = dict["name"] as? String{
self._cityName = name.capitalized
print(self._cityName)
}
if let weather = dict["weather"] as? [Dictionary<String, AnyObject>]{
if let main = weather[0]["main"] as? String{
self._weatherType = main.capitalized
print(self._weatherType)
}
}
if let main = dict["main"] as? Dictionary<String, AnyObject>{
if let currentTemperature = main["temp"] as? Double {
let kelvinToCelsius = currentTemperature - 273.15
self._currentTemp = kelvinToCelsius
print(self._currentTemp)
}
}
}
print("Done 1")
}
completed() //Make sure to tell download is done
}}
When Executing the code, "Done 2" is being printed out first, before "Done 1", when I want it to be the other way around.
How can I fix this? (FYI: Following Weather App Tutorial on Udemy)
Update: It was just simply moving completed inside the responseJSON closure.
This question already has answers here:
How to return value from Alamofire
(5 answers)
Closed 5 years ago.
I am new with iOS programming. I am trying to make a piece of code in my function be synchronized, but it doesn't seem to work:
func fetchLocationsList(searchText:String)->Array<String> {
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any]
else { print ("error in json response")
return}
guard let list = jsonResponse["list"] as? NSArray else {return}
let lockQueue = DispatchQueue(label:"Et.My-Weather-App.queue1")
_ = lockQueue.sync{
for index in 0..<list.count {
print ("index is: \(index)")
guard let listElement = list[index] as? [String:Any] else {return}
let id = listElement["id"] as! Int
print ("id is: \(id)")
let cityName = listElement["name"] as! String
print ("cityName is: \(cityName)")
let sys = listElement["sys"] as! [String:Any]
let country = sys["country"] as! String
print ("country is: \(country)")
let element = "\(cityName), \(country), \(id)"
print ("\(element)")
self.resultsArray.append(element)
}
}
}
if self.resultsArray.count==0 {
print ("results array is also zero!")
}
return self.resultsArray
}
When I run it, I see that the line "results array is also zero!" is printed before the "for" loop fills the resultArray with elements, so the returned resultArray is always empty!
What am I doing wrong?
I suggest you do this as async tasks are a pain and this works quite well.
func fetchLocationsList(searchText:String, completion: #escaping (_ results:Array<String>)->()){
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any] else { print ("error in json response"); return}
guard let list = jsonResponse["list"] as? Array<Dictionary<String,Any>> else { return }
var array = Array<String>() // create an array to store results.
for item in list {
let id = item["id"] as! Int
let cityName = item["name"] as! String
let sys = item["sys"] as! Dictionary<String,Any>
let country = sys["country"] as! String
let element = "\(cityName), \(country), \(id)"
array.append(element) // add to that array.
}
completion(array) //send the array via the completions handler.
}
}
So in your viewDidLoad or whatever.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchLocationsList(searchText: "Whatever this string is") { (results) in
self.resultsArray.append(contentsOf: results)
// Then do anything else you need to do after this function has completed within this closure.
}
}
I have made the following function in Swift 3:
func parseJSON() {
var JsonResult: NSMutableArray = NSMutableArray()
do {
JsonResult = try JSONSerialization.jsonObject(with: self.data as Data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement:NSDictionary=NSDictionary()
let locations: NSMutableArray = NSMutableArray()
for i in 0 ..< JsonResult.count
{
jsonElement = JsonResult[i] as! NSDictionary
let location = Parsexml()
if let title = jsonElement["Title"] as? String,
let body = jsonElement["Body"] as? String,
let userId = jsonElement["UserId"] as? Int,
let Id = jsonElement["Id"] as? Int
{
location.title = title
location.body = body
location.userId = userId
location.id = Id
}
locations.add(location)
}
DispatchQueue.main.async { () -> Void in
self.delegate.itemsDownloaded(items: locations)
}
When i call this function from another method, i get the following error:
Could not cast value of type '__NSArrayI' (0x105d4fc08) to 'NSMutableArray' (0x105d4fcd0).
It points me towards the element here:
JsonResult = try JSONSerialization.jsonObject(with: self.data as Data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSMutableArray
Where it exits with a SIGBRT..
What have i missed here?
You are trying to convert an NSArray into an NSMutable array which is what the warning is complaining about.
Take the array it provides you, and then convert it into a mutable one.
let jsonArray = try JSONSerialization.jsonObject(with: self.data as Data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
jsonResult = jsonArray.mutableCopy() as! NSMutableArray
Unrelated, but you may also want to user a lower case value for the JsonResult to fit with normal iOS style guidelines. It should instead be jsonResult.
Another way to improve your code:
You are not mutating your JsonResult, so you have no need to declare it as NSMutableArray:
var JsonResult = NSArray()
do {
JsonResult = try JSONSerialization.jsonObject(with: self.data as Data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
And some steps to improve your code...
enum MyError: Error {
case NotArrayOfDict
}
func parseJSON() {
do {
guard let jsonResult = try JSONSerialization.jsonObject(with: self.data as Data) as? [[String: Any]] else {
throw MyError.NotArrayOfDict
}
let locations: NSMutableArray = NSMutableArray()
for jsonElement in jsonResult {
let location = Parsexml()
if let title = jsonElement["Title"] as? String,
let body = jsonElement["Body"] as? String,
let userId = jsonElement["UserId"] as? Int,
let Id = jsonElement["Id"] as? Int
{
location.title = title
location.body = body
location.userId = userId
location.id = Id
}
locations.add(location)
}
DispatchQueue.main.async { () -> Void in
self.delegate.itemsDownloaded(items: locations)
}
} catch let error {
print(error)
}
}
as! casting sometimes crashes your app, use it only when you are 100%-sure that the result is safely converted to the type. If you are not, using guard-let with as? is safer.
Use Swift types rather than NSSomething as far as you can.
Specifying .allowFragments is not needed, as you expect the result as an Array.
And if you can modify some other parts of your code, you can write your code as:
func parseJSON() {
do {
//If `self.data` was declared as `Data`, you would have no need to use `as Data`.
guard let jsonResult = try JSONSerialization.jsonObject(with: self.data) as? [[String: Any]] else {
throw MyError.NotArrayOfDict
}
var locations: [Parsexml] = [] //<-Use Swift Array
for jsonElement in jsonResult {
let location = Parsexml()
if let title = jsonElement["Title"] as? String,
let body = jsonElement["Body"] as? String,
let userId = jsonElement["UserId"] as? Int,
let Id = jsonElement["Id"] as? Int
{
location.title = title
location.body = body
location.userId = userId
location.id = Id
}
locations.append(location)
}
DispatchQueue.main.async { () -> Void in
self.delegate.itemsDownloaded(items: locations)
}
} catch let error {
print(error)
}
}
I am very beginner in swift and I am trying to fetch some JSON Data from an api and then creating an array from that Data.
Alamofire.request(.GET, URL)
.responseJSON
{
response in
let JSON = response.result.value
let response = JSON as! NSDictionary
let Data = response.objectForKey("data")!
for slot in timeSlot as! NSDictionary
{
let json = slot.value
let availability = json as! NSDictionary
let myavailable = availability.objectForKey("available")!
let slotTime = availability.objectForKey("time")!
if (myavailable as! NSNumber == 1)
{
self.fetchSlot.append(slotTime as! String)
}
}
for x in self.mySlot
{
for c in self.fetchSlot
{
if (c == x)
{
self.availableSlot.append(x)
}
}
}
}
Now I am trying to print the "self.availableslot" in the same viewDid load function.
But it always returning an empty array, because it is compiling before all the JSON data is fetched.
Please if there is any way how can I use reload method or completion handler kind of things to get my job done.
func performAlamoFireRequest(completion: () -> Void) {
Alamofire.request(.GET, URL).responseJSON {
response in
let JSON = response.result.value
let response = JSON as! NSDictionary
let Data = response.objectForKey("data")!
for slot in timeSlot as! NSDictionary {
let json = slot.value
let availability = json as! NSDictionary
let myavailable = availability.objectForKey("available")!
let slotTime = availability.objectForKey("time")!
if (myavailable as! NSNumber == 1) {
self.fetchSlot.append(slotTime as! String)
}
}
for x in self.mySlot {
for c in self.fetchSlot {
if (c == x) {
self.availableSlot.append(x)
}
}
}
completion()
}
}
performAlamoFireRequest() {
//code to perform after here
}