I'm trying to make a network call to forecast.io for a weather forecast. My code looks like this:
class ViewController: UIViewController {
private let apiKey = "" // 32 letters and numbers
override func viewDidLoad() {
super.viewDidLoad()
let baseURL = NSURL(string: "https://developer.forecast.io/forecast/\(apiKey)/")
let forecastURL = NSURL(string: "37.8267,-122.423", relativeToURL: baseURL)
let weatherData = NSData(contentsOfURL: forecastURL!, options: nil, error: nil)
println(weatherData)
}
}
The println() just returns nil. When I log in to forecast.io I see I haven't yet made a call. What's wrong with my code?
The problem lies in url:
"https://developer.forecast.io/forecast/
should be
"https://api.forecast.io/forecast/
Related
I'm getting this error on line var delegate = WeatherManagerDelegate()
import Foundation
protocol WeatherManagerDelegate {
func didUpdateWeather(weather:WeatherModel)
}
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?&appid=d73ab8784f3b294976fc6189b3e6eba2&units=metric"
var delegate = WeatherManagerDelegate()
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String)
{
//Create URL
if let url = URL(string: urlString){
//Create a URL Session.
let session = URLSession(configuration: .default)
//Give session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{ //on selecting url 2nd option the seletors you get select from data onwards to error and press enter and it will be set to closure format.
print(error!)
return
}
if let safeData = data {
/* let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)*/
if let weather = parseJSON(weatherData: safeData) {
self.delegate.didUpdateWeather(weather:weather)
}
}
}
//Start task
task.resume()
}
}
func parseJSON(weatherData: Data) ->WeatherModel? {
let decoder = JSONDecoder()
do{
let decodeData = try decoder.decode(WeatherData.self, from: weatherData)
let name = decodeData.name
let temp = decodeData.main.temp
print(decodeData.main.temp_max)
print(decodeData.main.temp_min)
print(decodeData.sys.country)
print(decodeData.weather[0].description)
let id = decodeData.weather[0].id
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
print(weather.conditionName)
print(weather.temperatureString)
}
catch{
print(error)
return nil
}
}
}
and when I'm trying to make it an optional
var delegate = WeatherManagerDelegate?()
I'm getting this error
No exact matches in call to initializer
Replace
var delegate = WeatherManagerDelegate()
with
weak var delegate: WeatherManagerDelegate?
and update the calls to read self.delegate?.didUpdateWeather()
WeatherManager should not be responsible for creating its own delegate, that is something that should come from wherever it is begin used.
The weak attribute is almost always necessary when using delegates to avoid retain cycles.
Since weak can only be applied to class objects, you also need to indicate that in the protocol definition:
protocol WeatherManagerDelegate: AnyObject { ... }
I have a struct that I am using to call out to the iTunes API. But when ever I run it myURL variable is never getting set, it's always nil. Not sure what I am doing wrong:
let myDefaultSession = URLSession(configuration: .default)
var myURL: URL?
var myDataTask: URLSessionTask?
struct APIManager {
func getJSON(strURL: String) {
myURL = URL(string: strURL)
var dictReturn: Dictionary<String, Any> = [:]
//cancel data task if it is running
myDataTask?.cancel()
print(myURL!) //<----Always nil
}
}
Here's the string:
"https://itunes.apple.com/search?media=music&entity=song&term=The Chain"
You´re getting nil because the URL contains a space. You need to encode the string first and then convert it to an URL.
func getJSON(strURL: String) {
if let encoded = strURL.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let myURL = URL(string: encoded) {
print(myURL)
}
var dictReturn:Dictionary<String, Any> = [:]
//cancel data task if it is running
myDataTask?.cancel()
}
URL will be:
https://itunes.apple.com/search?media=music&entity=song&term=The%20Chain
I am retrieving posts in a data handler from a DB for a news feed and I run a php script and echo the JSON encoded data back to my application, at which point the data is parsed and stored in a model of a "post", there is a protocol that is used in the view controller to get the data once it has been downloaded. My problem is that I am getting the notorious "Unexpectedly found nil when unwrapping optional value" error when I pass the NSMutableArray of "post" objects to the function "itemsDownloaded" which is function of the protocol. I checked all the values being parsed and they exist, and I also checked the count of the array to make sure it has values. The exception is occurring on the line self.delegate.itemsDownloaded(posts)
The code to handle the data is this :
import Foundation
protocol PostDataHandlerProtocol: class {
func itemsDownloaded(items: NSArray)
}
class PostDataHandler: NSObject, NSURLSessionDataDelegate {
weak var delegate: PostDataHandlerProtocol!
var data : NSMutableData = NSMutableData()
//The path to the php script to be executed
let urlPath: String = "www.something.com/myphpscript.php"
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
let posts: NSMutableArray = NSMutableArray()
for(var i = 0; i < jsonResult.count; i++)
{
jsonElement = jsonResult[i] as! NSDictionary
let post = PostModel()
//The following insures none of the JsonElement values are nil through optional binding
let username = jsonElement["username"] as? String
let imagePath = jsonElement["user_imagePath"] as? String
let postID = (jsonElement["post_id"] as! NSString).integerValue
let postRep = (jsonElement["post_rep"] as! NSString).integerValue
let postType = jsonElement["post_type"] as? String
let postDate = jsonElement["post_date"] as? String
let comment = jsonElement["comment"] as? String
post.username = username
post.imagePath = imagePath
post.postID = postID
post.postRep = postRep
post.postType = postType
post.postDate = postDate
post.comment = comment
posts.addObject(post)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.itemsDownloaded(posts)
})
}
}
In my view controller, I create a new data handler let postHandler = PostDataHandler() and then once the view has loaded I call postHandler.downloadItems() and in the view controller declaration I conformed to the protocol and implemented itemsDownloaded:
func itemsDownloaded(items: NSArray) {
allPosts = items as! [PostModel]
indicator.stopAnimating()
self.tableVIew.reloadData()
}
Does anyone know why this is happening? I tried to look at the numerous postings regarding this error as I'm aware it's quite common, but couldn't find anything that helped me. Many of the postings say there should be a check to ensure it's not nil, the problem is I didn't think NSMutableArray can be optional, and also I checked all the values and they don't appear to be nil, so those answers did not help me. In the thread exceptions it says something related to a closure, which I think could be causing the issue, I'm just not exactly sure what or how.
None of the code that you showed ever set your PostDataHandler's delegate property to anything, so it is reasonable to suppose that it is still nil, which perfectly explains why you crash at runtime when you try to access it as if it were an actual object.
try to use dump method on variable filled with JSON. But it doesnt work and i dont understand why
import UIKit
class ViewController: UIViewController {
var hoge: JSON?
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.whitehouse.gov/v1/petitions.json")
var request = NSURLRequest(URL: url!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
hoge = JSON(data: data!)
let count = hoge!["results"][0]["body"].stringValue
println(count)
}
}
func res() {
dump(self.hoge)
}
}
can you please help me show what am i doing wrong ?
I have this same question as was asked and answered here: How to get data to return from NSURLSessionDataTask
The difference: How can I do this in Swift? I do not know Objective C at all so trying to interpret that answer is proving a bit futile for me..
So given the code below I have a FirstViewController which will call my HomeModel class to go and get the data using a NSURLSession call. Once the call is complete I want return the data to the FirstViewController so that I may go and set it in the view.
FirstViewController class looks like:
import UIKit
class FirstViewController: UIViewController
{
let homeModel = HomeModel()
override func viewDidLoad()
{
super.viewDidLoad()
// Go load the home page content
SetHomeContent()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData();
//TODO find a way to get result here... and set it to the textView
}
}
HomeModel class looks like:
import Foundation
class HomeModel
{
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
func GetHomeData()
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// At this stage I want the jsonResult to be returned to the FirstViewController
});
// do whatever you need with the task
task.resume()
}
I guess I want either a way to pass in a completion handler from the original all or something similar to C# 5 using async tasks to await the result..
Any help appreciated..
With the help and suggestion taken from Abizern I finally managed how to write up this block or closure if you want.
So what worked for me in the end:
The GetHomeData function I changed as follows:
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
// I changed the signiture from my original question
func GetHomeData(completionHandler: ((NSDictionary!) -> Void)?)
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// then on complete I call the completionHandler...
completionHandler?(jsonResult?);
});
task.resume()
}
Then I call the function like this:
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData(self.resultHandler)
}
func resultHandler(jsonResult:NSDictionary!)
{
let siteContent = jsonResult.objectForKey("SiteContent") as NSDictionary
let paraOne = siteContent.objectForKey("HomePageParagraphOne") as String
}
Change your GetHomeData() method so that it takes a block. When you call the method pass it the block that does what you want to with the data. In the completion block of the data task, call this passed in block.
You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let dataFuture = URLSession.shared.dataTaskFuture(for: url)
let data = try dataFuture.await().data
. . . parse data ...
}