Optional wrapper around API key swift - ios

I'm attempting to test a server response using an API key taken from a .plist file. When I print out the key from the from the request header, it has an optional wrapper: Optional([App-Token: apikey1234567])). I'm not getting a response, which I think is because the key is being passed in this form. A little new to Swift. How can I pass this so it isn't optional? Relevant code below:
var key: String!
let baseURL: String = ("https://apiurl.com/").stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
override init() {
path = NSBundle.mainBundle().pathForResource("APIkey", ofType: "plist")
dict = NSDictionary(contentsOfFile: path!)
key = dict!.objectForKey("APIkey") as! String
super.init()
}
func updateJSON() {
var session = NSURLSession.sharedSession()
var request = NSMutableURLRequest(URL: NSURL(string: baseURL)!)
request.HTTPMethod = "GET"
request.setValue(key, forHTTPHeaderField:"App-Token")
// This prints out the 'Optional([App-Token: apikey1234567]))'
println("\(request.allHTTPHeaderFields))")
var dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
var error: NSError?
if (error != nil) {
println("\(error)")
} else {
self.jsonObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? NSDictionary
println("\(self.jsonObject)")
}
})
dataTask.resume()
}
UPDATE: I was getting nil because jsonObject was being cast as an NSDictionary. When I changed its type to AnyObject the response came back.

The functions valueForHTTPHeaderField and allHTTPHeaderFields - return optionals, it doesn't mean the key you put in is optional. from Swift NSURLRequest file:
func valueForHTTPHeaderField(field: String) -> String?
var allHTTPHeaderFields: [String : String]?
Check value of key by adding print after setting it in init:
var key: String!
let baseURL: String = ("https://apiurl.com/").stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
override init() {
path = NSBundle.mainBundle().pathForResource("APIkey", ofType: "plist")
dict = NSDictionary(contentsOfFile: path!)
key = dict!.objectForKey("APIkey") as! String
println("On init Key = \(key)") // Check key value here is it optional?
super.init()
}
Also: dataTaskWithRequest returns optional so at least optional chain:
dataTask?.resume()
You also have an new error:NSError created in completion of task when it already returns an error, it appears you mean to create this error for JSON parse? best to name it different to avoid any ambiguity. Some var can be let because never mutated -session, request, dataTask.
func updateJSON() {
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: baseURL)!)
request.HTTPMethod = "GET"
request.setValue(key, forHTTPHeaderField:"App-Token")
// This prints out the 'Optional([App-Token: apikey1234567]))'
println("\(request.allHTTPHeaderFields))")
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
println("\(error)")
} else {
var jsonError: NSError?
self.jsonObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: & jsonError) as? NSDictionary
println("\(self.jsonObject)")
}
})
dataTask?.resume()
}
(NB: I only have machine with xcode 7 and swift 2 installed at this moment, but i don't think any of these things have changed since Swift 1.2)

Related

iOS - Swift : fetching data from database in main thread, not in background

In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}
You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}

How to parse JSON using init()

I can't display the json Array by using its object
showing this error :
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value"
class sample{
var jarray:[[String:Any]]!
init(url: String) {
let urll = URL(string: url)
var request = URLRequest(url: urll!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request, completionHandler: {(Data,response,Error) in
do
{
let jsonresponse = try JSONSerialization.jsonObject(with: Data!, options: [])
let jsonarray = jsonresponse as? [[String:Any]]
self.jarray = jsonarray!
print(self.jarray)
DispatchQueue.main.async {
}
}
catch let parsingerror
{
print("error",parsingerror)
}
})
task.resume()
}
}
First of all: Handle always errors and unwrap optionals safely.
Second of all Data and Error (capitalized) are reserved words, use always lowercased parameter labels in closures (and uppercased class names).
Many lines in your code are redundant.
class Sample {
var jarray = [[String:Any]]()
init(url: String) {
guard let urll = URL(string: url) else { return }
let task = URLSession.shared.dataTask(with: urll) { data, _ , error in
if let error = error { print(error); return }
do
{
// if error is nil then data is guaranteed to be non-nil
if let jsonarray = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
self.jarray = jsonarray
print(self.jarray)
DispatchQueue.main.async {
}
}
}
catch {
print("error", error)
}
}
task.resume()
}
}
Note: It's bad practice to run asynchronous tasks in init methods
Avoid using force unwrapping unnecessarily. I might result in unwanted crashes in your app. In your code,
Check if Data is nil. If it is, the below line will result in runtime exception.
let jsonresponse = try JSONSerialization.jsonObject(with: Data!, options: [])
In the below line of code, check whether jsonarray is nil.
self.jarray = jsonarray!
If not, then add the line where your app is crashing.
Try replacing your code with:
class sample {
var jarray: [[String:Any]]?
init(url: String) {
if let urll = URL(string: url) {
URLSession.shared.dataTask(with: urll) { (data, response, error) in
do {
if let data = data {
let jsonresponse = try JSONSerialization.jsonObject(with: data, options: [])
self.jarray = jsonresponse as? [[String:Any]]
print(self.jarray)
DispatchQueue.main.async {
}
}
} catch {
print("error",error)
}
}.resume()
}
}
}
Also, don't use reserved words as variable names like you did for Data and Error.
Most importantly - Never use forced unwrapping (!) with server response. API response might not be as expected always. Try handling that.
JSONSerialization is now old-fashioned way. Apple introduced Codable protocol that handles for you serialisation and deserialisation of objects.
Example:
struct Photo: Codable
{
//String, URL, Bool and Date conform to Codable.
var title: String
var url: URL
var isSample: Bool
//The Dictionary is of type [String:String] and String already conforms to Codable.
var metaData: [String:String]
//PhotoType and Size are also Codable types
var type: PhotoType
var size: Size
}
And in the response from the server:
if let jsonData = jsonString.data(using: .utf8)
{
let photoObject = try? JSONDecoder().decode(Photo.self, from: jsonData)
}

Can't get value from Google-Books JSON API

I'm using a bar code scanner to get data on a scanned book using the google books api. I successfully call the API and get a JSON object back.
I'm trying to get the book title which follows the path items.volumeInfo.title.
When I call valueForPath on the JSON object returned by the API and attempt to print it (the title), I end up printing:
Optional((
"A Dance with Dragons"
))
I can't seem to figure out how to actually get the string out of the printed optional. I tried as! String and jsonResult.valueForKeyPath("items.volumeInfo.title")!, but the first simply complained to me and the second only removed the optional and outside set of parentheses.
func getBookInfo(isbn: String) {
var url: String = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn;
var request: NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil) {
println(jsonResult.valueForKeyPath("items.volumeInfo.title"))
//self.json.setValue(jsonResult.valueForKeyPath("items.volumeInfo.title")!, forKey: "title")
} else {
GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
}
})
}
The response you get from the API is an array of titles.
I suggest using if let to unwrap the Optional value you get from KVC, and typecasting the result as a Swift array of Strings.
Swift 1
func getBookInfo(isbn: String) {
var url: String = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn
var request: NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary {
if let arrayOfTitles = jsonResult.valueForKeyPath("items.volumeInfo.title") as? [String] {
let titles = ", ".join(arrayOfTitles)
println(titles)
} else {
// error: no title found
}
} else {
GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
}
})
}
getBookInfo("0553283685") // prints "Hyperion"
Swift 2
For this version we're using NSURLSession because NSURLConnection is now deprecated.
func getBookInfo(isbn: String) {
let urlString = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn
if let url = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {data, _, error -> Void in
if let error = error {
print(error.localizedDescription)
} else {
if let data = data,
jsonResult = try? NSJSONSerialization.JSONObjectWithData(data, options: []),
arrayOfTitles = jsonResult.valueForKeyPath("items.volumeInfo.title") as? [String] {
let titles = arrayOfTitles.joinWithSeparator(", ")
print(titles)
} else {
GlobalConstants.AlertMessage.displayAlertMessage("Error fetching data from barcode, please try again.", view: self)
}
}
}).resume()
}
}
getBookInfo("0553283685") // prints "Hyperion"
Swift 3
Same as Swift 2 with some syntax changes. I've also added the "authors" example, and I'm now using guard. Just for the sake of showing something different from the previous example.
func getBookInfo(isbn: String) {
guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=isbn:\(isbn)") else {
print("the url is not valid")
return
}
URLSession.shared().dataTask(with: url, completionHandler: {data, response, error -> Void in
guard error == nil else {
print(response)
print(error!.localizedDescription)
return
}
guard let data = data else {
print("no error but no data")
print(response)
return
}
guard let jsonResult = try? JSONSerialization.jsonObject(with: data, options: []) else {
print("the JSON is not valid")
return
}
if let arrayOfTitles = jsonResult.value(forKeyPath: "items.volumeInfo.title") as? [String] {
print(arrayOfTitles)
}
if let arrayOfAuthors = jsonResult.value(forKeyPath: "items.volumeInfo.authors") as? [[String]] {
print(arrayOfAuthors)
}
}).resume()
}
getBookInfo(isbn: "0553283685")

JSON Data is not returning as a string in swift

I am trying to return data from JSON object as a string but whenever I try it returns nil if someone could help please find the below code. I need to return currentWeather as a String with its value currently I am not able to return the data as a string only as an optional.
let urlPath = "http://api.openweathermap.org/data/2.5/weather?q=London,uk&units=metric"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if((error) != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
let jsonDictionary = jsonResult as NSDictionary
let mainDictionary = jsonResult.valueForKey("main") as NSDictionary
let currentWeather = mainDictionary.valueForKey("humidity") as? NSString
println(currentWeather)
})
task.resume()
I tried the code. The 'humidity' is an Int, not String. So you should use
let currentWeather = mainDictionary.valueForKey("humidity") as Int

fatal error: unexpectedly found nil while unwrapping an Optional value

I am attempting to parse a JSON document and enter its information into a UICollectionView. I had tested the parsing before working on the UICollectionViewDelegate/Flowlayout/DataSource etc. It was returning the correct information, however now I am getting this error. Any idea what I am doing wrong here?
class ViewModel {
let urlString = "https://s3.amazonaws.com/nxtapnxtap/clubsinfo.json"
var clubNames = [String]()
var smImg = [UIImage]()
var lgImg = [String]()
func fetchItems(success: () -> ()) {
let url = NSURL(string: urlString)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = session.dataTaskWithURL(url!) { (data, response, error) in
var jsonError: NSError?
let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as NSDictionary // Error here --> fatal error: unexpectedly found nil while unwrapping an Optional value
if let unwrappedError = jsonError {
println("jsonError: \(unwrappedError)")
} else {
self.clubNames = json.valueForKeyPath("entry.name.label") as [String]
self.smImg = json.valueForKeyPath("entry.smimg.label") as [UIImage]
self.lgImg = json.valueForKeyPath("entry.lgimg.label") as [String]
success()
}
}
task.resume()
}
The object coming back from JSONObjectWithData is nil. You are trying to force cast it as an NSDictionary. You need to check if it can be cast to an NSDictionary before you act on it:
func fetchItems(success: () -> ()) {
let url = NSURL(string: urlString)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = session.dataTaskWithURL(url!) { (data, response, error) in
var jsonError: NSError?
if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as? NSDictionary {
self.clubNames = json.valueForKeyPath("entry.name.label") as [String]
self.smImg = json.valueForKeyPath("entry.smimg.label") as [UIImage]
self.lgImg = json.valueForKeyPath("entry.lgimg.label") as [String]
success()
} else if let unwrappedError = jsonError {
println("jsonError: \(unwrappedError)")
}
}
task.resume()
}

Resources