I am writing an app for a Udacity portfolio project which would fetch photos from Flickr and display in a collectionView. When a button is pressed, it will refresh the photos in the collectionView. However I am unable to get new sets of photos despite the fact that the search parameters have different page number after every call. My code as follows:
func getPhotosURLFromFlickr(_ lat: AnyObject, lon: AnyObject, _ completionHandlerForGetPhotosURL: #escaping (_ imageURL: [String]?, _ error: NSError?) -> Void) {
taskForGetPagesFromFlickr(lat, lon: lon) { (parameters, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
if let parameters = parameters {
print(parameters)
self.taskForGetPhotos(parameters, { (imageURLArray, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
if let imageURLArray = imageURLArray {
completionHandlerForGetPhotosURL(imageURLArray, nil)
}
}
})
}
}
}
}
func taskForGetPagesFromFlickr(_ lat: AnyObject, lon: AnyObject, _ completionHandlerForGetPhotosURL: #escaping (_ parameters: [String: AnyObject]?, _ error: NSError?) -> Void) {
let parameters = [
Constants.ParametersKey.Method: Constants.Methods.PhotoSearch as AnyObject,
Constants.ParametersKey.FlickrAPIKey : Constants.APIInfo.APIKey as AnyObject,
Constants.ParametersKey.Format: Constants.ParametersValues.JSON as AnyObject,
Constants.ParametersKey.NoJSONCallback: Constants.ParametersValues.DisableJSONCallback as AnyObject,
Constants.ParametersKey.PerPage: Constants.ParametersValues.Fifteen as AnyObject,
Constants.ParametersKey.Extras: Constants.ParametersValues.MediumURL as AnyObject]
var parametersWithCoord = parameters
parametersWithCoord[Constants.ParametersKey.lat] = lat
parametersWithCoord[Constants.ParametersKey.lon] = lon
let url = flickrURLFromParameters(parametersWithCoord)
let request = NSMutableURLRequest(url: url)
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
self.convertDataWithCompletionHandler(data!, completionHandlerForConvertData: { (results, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
// print(results)
guard let photosDict = results?["photos"] as? [String: AnyObject] else {
let userInfo = [NSLocalizedDescriptionKey : "NoPhotosFound"]
completionHandlerForGetPhotosURL(nil, NSError(domain: "NoPhotosFound", code: 1, userInfo: userInfo))
return
}
guard let pages = photosDict[Constants.ParametersKey.Pages] as? Int else {
let userInfo = [NSLocalizedDescriptionKey : "NoPagesFound"]
completionHandlerForGetPhotosURL(nil, NSError(domain: "NoPagesFound", code: 1, userInfo: userInfo))
return
}
let randomPageIndex = Int(arc4random_uniform(UInt32(pages)))
var searchParameters = parametersWithCoord
searchParameters[Constants.ParametersKey.Page] = randomPageIndex as AnyObject?
print("Number of pages: \(pages)")
print("Random page index: \(randomPageIndex)")
completionHandlerForGetPhotosURL(searchParameters, nil)
}
})
}
}
task.resume()
}
func taskForGetPhotos(_ parameters: [String: AnyObject], _ completionHandlerForGetPhotosURL: #escaping (_ imageURL: [String]?, _ error: NSError?) -> Void) {
let url = flickrURLFromParameters(parameters)
let request = NSMutableURLRequest(url: url)
print(url)
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
self.convertDataWithCompletionHandler(data!, completionHandlerForConvertData: { (results, error) in
if let error = error {
completionHandlerForGetPhotosURL(nil, error as NSError)
} else {
// print(results)
guard let photosDict = results?["photos"] as? [String: AnyObject] else {
let userInfo = [NSLocalizedDescriptionKey : "NoPhotosFound"]
completionHandlerForGetPhotosURL(nil, NSError(domain: "NoPhotosFound", code: 1, userInfo: userInfo))
return
}
guard let photosArray = photosDict["photo"] as? [[String: AnyObject]] else {
let userInfo = [NSLocalizedDescriptionKey : "NoPhotosArrayFound"]
completionHandlerForGetPhotosURL(nil, NSError(domain: "NoPhotosArray", code: 1, userInfo: userInfo))
return
}
var imageURLArray: [String] = []
if photosArray.count != 0 {
for pics in photosArray {
guard let imageURLString = pics[Constants.ParametersValues.MediumURL] as? String else {
print("NoImageURLString Found")
return
}
imageURLArray.append(imageURLString)
}
}
print("ImageURLArray : \(imageURLArray)")
completionHandlerForGetPhotosURL(imageURLArray, nil)
}
})
}
}
task.resume()
}
When my refresh button is tapped, the above set of networking code runs, and if I tap the button twice, my print statements are as such:
FETCHING NEW COLLECTION...
Number of items 0 Number of pages: 5712
Random page index: 490
https://api.flickr.com/services/rest?page=490&method=flickr.photos.search&format=json&lon=103.7940514843148&api_key=APIKEY&per_page=15&lat=1.411935988414726&extras=url_m&nojsoncallback=1
ImageURLArray :
["https://farm3.staticflickr.com/2059/32542810620_37312d07c9.jpg",
"https://farm3.staticflickr.com/2330/32102051403_46e30a5eec.jpg",
"https://farm3.staticflickr.com/2104/32529883860_17558a0acf.jpg",
"https://farm3.staticflickr.com/2595/32778162441_db4a98d3cd.jpg",
"https://farm3.staticflickr.com/2466/32087213853_257910d32d.jpg",
"https://farm3.staticflickr.com/2029/32891916665_2d2d177e71.jpg",
"https://farm4.staticflickr.com/3685/32878992915_38baaf513e.jpg",
"https://farm1.staticflickr.com/631/32062787803_ed58defea5.jpg",
"https://farm3.staticflickr.com/2332/32873171215_c807db5364.jpg",
"https://farm3.staticflickr.com/2788/32825584606_7d2bff507c.jpg",
"https://farm3.staticflickr.com/2479/32052120503_1317f70f1a.jpg",
"https://farm3.staticflickr.com/2909/32023922664_d276f52369.jpg",
"https://farm1.staticflickr.com/376/32023912434_9b89fc3d7b.jpg",
"https://farm1.staticflickr.com/455/32018845514_22681384ae.jpg",
"https://farm3.staticflickr.com/2833/32734093131_0e8da333f4.jpg"]
FETCHING NEW COLLECTION...
Number of items 0 Number of pages: 5712
Random page index: 5383
https://api.flickr.com/services/rest?page=5383&method=flickr.photos.search&format=json&lon=103.7940514843148&api_key=APIKEY&per_page=15&lat=1.411935988414726&extras=url_m&nojsoncallback=1
ImageURLArray :
["https://farm3.staticflickr.com/2059/32542810620_37312d07c9.jpg",
"https://farm3.staticflickr.com/2330/32102051403_46e30a5eec.jpg",
"https://farm3.staticflickr.com/2104/32529883860_17558a0acf.jpg",
"https://farm3.staticflickr.com/2595/32778162441_db4a98d3cd.jpg",
"https://farm3.staticflickr.com/2466/32087213853_257910d32d.jpg",
"https://farm3.staticflickr.com/2029/32891916665_2d2d177e71.jpg",
"https://farm4.staticflickr.com/3685/32878992915_38baaf513e.jpg",
"https://farm1.staticflickr.com/631/32062787803_ed58defea5.jpg",
"https://farm3.staticflickr.com/2332/32873171215_c807db5364.jpg",
"https://farm3.staticflickr.com/2788/32825584606_7d2bff507c.jpg",
"https://farm3.staticflickr.com/2479/32052120503_1317f70f1a.jpg",
"https://farm3.staticflickr.com/2909/32023922664_d276f52369.jpg",
"https://farm1.staticflickr.com/376/32023912434_9b89fc3d7b.jpg",
"https://farm1.staticflickr.com/455/32018845514_22681384ae.jpg",
"https://farm3.staticflickr.com/2833/32734093131_0e8da333f4.jpg"]
You would realise that despite that the search page is different for both times, but the imageURLArray is actually the same. I can't seem to identify the reason why.
Some help is much appreciated pls, thanks!
Somehow I manage to solve my problem. I limit the randomPageIndex with a maximum number of 100 and it successfully refreshes the page.
Perhaps Flickr has some maximum search pages which I may not be aware of.
Related
After URLSession.shared.dataTask it's not either returning error or success.
The completion handler is not getting called. How can I check or how can I proceed further. There is no error the app is working as such, but without data on the screen which is displayed.
func getPromotionsData() {
ConnectionManager.sharedInstance()?.getPromotions(PROMOTIONS, withCompletion: {
result, error in
if let result = result {
print("result: \(result)")
}
var arrPromotions: [Any] = []
if let object = result?["promotions"] as? [Any] {
arrPromotions = object
}
self.dataSource = []
if let arrPromotions = arrPromotions as? [AnyHashable] {
self.dataSource = arrPromotions
}
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
})
}
func getPromotions(_ path: String?, withCompletion completion: #escaping (_ result: [AnyHashable : Any]?, _ error: Error?) -> Void) {
let strPath = "/\(API)/\(path ?? "").json"
let url = strPath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
makeRequest(BASE_URL, path: url, httpMethod: GET_METHOD, httpBody: nil, completion: completion)
}
func makeRequest(_ url: String?, path: String?, httpMethod: String?, httpBody httpBoday: Data?, completion: #escaping (_ result: [AnyHashable : Any]?, _ error: Error?) -> Void) {
let headers = [
"cache-control": "no-cache",
"Authorization": "Token f491fbe3ec54034d51e141e28aaee87d47bb7e74"
]
var request: URLRequest? = nil
if let url = URL(string: "\(url ?? "")\(path ?? "")") {
request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
}
request?.httpMethod = httpMethod ?? ""
request?.allHTTPHeaderFields = headers
let configuration = URLSessionConfiguration.default
configuration.httpCookieStorage = nil
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
if #available(iOS 11.0, *) {
configuration.waitsForConnectivity = false
}
let session = URLSession(configuration: configuration)
// let session = URLSession.shared
var task: URLSessionDataTask? = nil
print ("Request =======>",request)
if let req = request {
task = session.dataTask(with: request! , completionHandler: {
data, response, error in
var result: Any? = nil
if error != nil {
if let error = error {
print("\(error)")
}
if completion != nil {
completion(nil, error)
}
} else
{
var string: String? = nil
if let data = data {
string = String(data: data, encoding: .utf8)
}
string = self.string(byRemovingControlCharacters: string)
do {
if let data = string?.data(using: .utf8) {
result = try JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable : Any]
print ("Result ===============>",result)
}
} catch {
}
if completion != nil {
completion(result as! [AnyHashable : Any], error)
}
}
})
}
task?.resume()
}
Actually the completion block is an asynchronous process and i was waiting for the control to go back immediately after the process ends in debugging mode. It works now as expected
I have a project code as below, I wish it can get urls from func getUrls(), but Xcode returns error message like title says.
I have search and try some solution to fix it, but all not works. Should I write it with another way, or just fix this error ? Is the declaration for arrUrls was wrong, or somewhere need to make correction?
p.s. If you answer me by a comment, remember to teach me how to make an "Answered mark" for your answer.:) Thank you.
var arrUrls = [String]()
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
getUrls(url: url, completion: arrUrls) // Error Message: Cannot convert value of type '[String]' to expected argument type '([AnyObject]) -> Void'
...do something with array 'arrUrl'....but can't, because of the bug!
}
func getUrls(url : URL ,completion: #escaping (([AnyObject]) -> Void)) {
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : AnyObject] {
if let subjects = jsonResult["subjects"] as? [AnyObject]? {
for subject in subjects! {
if let content = subject["content"] as? [String : AnyObject] {
let s = String(describing: content["url"]!)
arrUrls.append(s)
}
}
}
}
completion(arrUrls as [AnyObject])
}catch {
print("json error: \(error)")
}
})
task.resume()
}
You need to call like this
getUrls(url: url) { (arr) in
}
if you edit the array inside the function then no need to return a completion value
func getUrls(url : URL ,completion: #escaping (() -> Void))
with
getUrls(url: url) {
// refresh ui here
DispatchQueue.main.async {
if firstItem = self.arrUrls.first , let url = URL(string:firstItem) {
webView.load(URLRequest(url: url))
}
}
change code to this
func getUrls(url : URL ,completion: #escaping (() -> Void)) {
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data) as? [String : Any] {
if let subjects = jsonResult["subjects"] as? [[String:Any]] {
for subject in subjects {
if let content = subject["content"] as? [String : Any] {
if let s = content["url"] as? String
{
self.arrUrls.append(s)
}
}
}
completion()
}
}
}catch {
completion()
}
})
task.resume()
}
I am creating an Event Manager App, It requires to input valid Event Code to check the Event Details. I am working on the Sign in page where in the user will input the event code. When the page is error free, I tried to clean, build and run the app, insert breakpoints to check the executions of my codes. But seems that I am encountering an issue, where in, whether I input valid code or not, It just loaded and after it loads, It goes back to a clear textfield, no error alerts or not event dispatch the Dashboard Page. I really can't figure out what's wrong with my codes. I am new in swift. I really need hep to fix it. Below are my codes for your reference and image of eventDetails from breakpoint. Thankyou
APIService.swift
typealias JSONDictionary = Dictionary<String, AnyObject>
class APIService: NSObject, URLSessionDataDelegate {
enum Path {
case SubmitEventCode
}
typealias APICallback = ((AnyObject?, NSError?) -> ())
let responseData = NSMutableData()
var statusCode: Int = -1
var callback: APICallback! = nil
var path: Path! = nil
func validatePasscode(eventcode: String!, callback: #escaping APICallback)
{
let url = PASSCODE_CHECKER_URL //https://hopprLab.com/API/events/PasscodeChecker
makeHTTPPostRequest(path: Path.SubmitEventCode, callback: callback, url: url)
}
func connection(_ connection: URLSession, didReceive response: URLResponse){
let httpResponse = response as! HTTPURLResponse
statusCode = httpResponse.statusCode
switch (httpResponse.statusCode) {
case 201, 200, 401:
self.responseData.length = 0
default:
print("ignore")
}
}
func connection(_ connection: URLSession, didReceive data: Data) {
self.responseData.append(data)
}
func connectionDidFinishLoading(_ connection: URLSession) {
let error: NSError? = nil
let json = try? JSONSerialization.jsonObject( with: responseData as Data, options:[]) as AnyObject
if ((data) != nil) {
callback(nil, error)
return
}
switch(statusCode, self.path!) {
case(200, Path.SubmitEventCode):
callback(self.handleValidatePasscode(json: json!) as AnyObject,nil)
default:
//UnknownError
callback(nil, nil)
}
}
func handleAuthError(json: AnyObject) -> NSError {
if let eventObj = json as? JSONDictionary {
//
if let messageObj: AnyObject = eventObj["error"] {
if let message = messageObj as? String {
return NSError(domain: "validatePasscode", code: 200, userInfo: ["error": message])
}
}
}
return NSError(domain: "validatePasscode", code: 200, userInfo: ["error": "unknown auth error"])
}
func handleValidatePasscode(json: AnyObject) -> Event? {
if let eventObj = json as? JSONDictionary{
if let eventid: AnyObject = eventObj["event_id"]{
if let eventJson = eventid as? JSONDictionary {
if let eventpass = Event.init(JSON: eventJson){
return eventpass
}
}
}
}
return nil
}
//private
func makeHTTPPostRequest(path: Path, callback: #escaping APICallback, url: String) {
self.path = path
self.callback = callback
var request = URLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "content-type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let dataTask = session.dataTask(with: request, completionHandler: {
(data: Data?, response: URLResponse?, error: Error?) -> Void in
if data != nil {
DispatchQueue.main.async {
callback(nil,nil)
}
}
})
dataTask.resume()
}
}
Event.swift
struct Event {
let id: String
let name: String
let location: String
let startDateTime: Date
let endDateTime: String
let deleteFlag: Bool?
let deleteDateTime: String?
let dateCreated: String?
let hasRaffle: Bool?
let registrationReq: Bool?
let participantCount: Int
let closedFlag: Bool?
let closedDateTime: String?
let reopenFlag: Bool?
let reopenDateTime: String?
init?(JSON: [String: AnyObject]) {
guard let eventID = JSON["event_id"] as? String,
let eventName = JSON["event_name"] as? String,
let eventLocation = JSON["event_location"] as? String,
let startDateTime = JSON["start_datetime"] as? String,
let endDateTime = JSON["end_datetime"] as? String,
let participantCount = JSON["participant_count"] as? Int else {
return nil
}
self.id = eventID
self.name = eventName
self.location = eventLocation
self.endDateTime = endDateTime
self.participantCount = participantCount
validatePasscode Function
func validateEventPasscode() {
//Show Loading
self.view.squareLoading.start(0.0)
let api = APIService()
api.validatePasscode(eventcode: eventCode) { (data, error) in
guard let eventDetails = self.event, error == nil else {
if let networkError = error {
if networkError != error {
_ = SCLAlertView(appearance: appearance).showError("Ooops", subtitle: "Please enter valid event code")
else {
_ = SCLAlertView(appearance: appearance).showError("Network Error", subtitle: "Network Error")
}
}
guard eventDetails.deleteFlag else {
_ = SCLAlertView(appearance: appearance).showError("Ooops!", subTitle: "Please enter a valid event passcode")
self.view.squareLoading.stop(0.0)
return
}
if eventDetails.closedFlag == true && eventDetails.reopenFlag == false {
_ = SCLAlertView(appearance: appearance).showError("Closed Event", subTitle: "Please check the status of your event and try again")
self.view.squareLoading.stop(0.0)
return
}
}
}
I'm attempting to follow a tutorial from Ray Wenderlich's website to learn to use Instruments. The sample code is written is Swift 2 I believe so before I'm allowed to run it, I have to migrate the code to the latest Swift version. I've gotten half-way through the conversion errors but I'm stumped on the following area of code:
class Flickr {
let processingQueue = OperationQueue()
func searchFlickrForTerm(_ searchTerm: String, completion : #escaping (_ results: FlickrSearchResults?, _ error : NSError?) -> Void){
let searchURL = flickrSearchURLForSearchTerm(searchTerm)
let searchRequest = URLRequest(url: searchURL)
NSURLConnection.sendAsynchronousRequest(searchRequest, queue: processingQueue) {response, data, error in
if error != nil {
completion(nil,error as! NSError)
return
}
var JSONError : NSError?
let resultsDictionary = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
if JSONError != nil {
completion(nil, JSONError)
return
}
switch (resultsDictionary!["stat"] as! String) {
case "ok":
print("Results processed OK")
case "fail":
let APIError = NSError(domain: "FlickrSearch", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey:resultsDictionary!["message"]!])
completion(results: nil, error: APIError)
return
default:
let APIError = NSError(domain: "FlickrSearch", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey:"Unknown API response"])
completion(nil, APIError)
return
}
let photosContainer = resultsDictionary!["photos"] as! NSDictionary
let photosReceived = photosContainer["photo"] as! [NSDictionary]
let flickrPhotos : [FlickrPhoto] = photosReceived.map {
photoDictionary in
let photoID = photoDictionary["id"] as? String ?? ""
let title = photoDictionary["title"] as? String ?? ""
let farm = photoDictionary["farm"] as? Int ?? 0
let server = photoDictionary["server"] as? String ?? ""
let secret = photoDictionary["secret"] as? String ?? ""
let flickrPhoto = FlickrPhoto(photoID: photoID, title: title, farm: farm, server: server, secret: secret)
return flickrPhoto
}
DispatchQueue.main.async(execute: {
completion(FlickrSearchResults(searchTerm: searchTerm, searchResults: flickrPhotos), nil)
})
}
}
fileprivate func flickrSearchURLForSearchTerm(_ searchTerm:String) -> URL {
let escapedTerm = searchTerm.addingPercentEscapes(using: String.Encoding.utf8)!
let URLString = "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=\(apiKey)&text=\(escapedTerm)&per_page=30&format=json&nojsoncallback=1"
return URL(string: URLString)!
}
}
I get the error on the following line of code:
NSURLConnection.sendAsynchronousRequest(searchRequest, queue: processingQueue) {response, data, error in
if error != nil {
completion(nil,error as! NSError)
return
}
I'm a little confused on how this should be amended using do, try, catch so any help would be appreciated just so I can get the app running to play around with Instruments.
Here is a link to the tutorial: https://www.raywenderlich.com/97886/instruments-tutorial-with-swift-getting-started
replace your Flickr class with this.
class Flickr {
let processingQueue = OperationQueue()
func searchFlickrForTerm(_ searchTerm: String, completion : #escaping (_ results: FlickrSearchResults?, _ error : NSError?) -> Void){
let searchURL = flickrSearchURLForSearchTerm(searchTerm)
let searchRequest = URLRequest(url: searchURL)
NSURLConnection.sendAsynchronousRequest(searchRequest, queue: processingQueue) {response, data, error in
guard let data = data, error == nil else {
completion(nil, error as NSError?)
return
}
guard let jsonObject = try? JSONSerialization.jsonObject(with: data,
options: JSONSerialization.ReadingOptions(rawValue: 0)),
let resultsDictionary = jsonObject as? Dictionary<String, Any>
else
{
return
}
switch (resultsDictionary["stat"] as! String) {
case "ok":
print("Results processed OK")
case "fail":
let APIError = NSError(domain: "FlickrSearch", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey:resultsDictionary["message"]!])
completion(nil, APIError)
return
default:
let APIError = NSError(domain: "FlickrSearch", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey:"Unknown API response"])
completion(nil, APIError)
return
}
let photosContainer = resultsDictionary["photos"] as! NSDictionary
let photosReceived = photosContainer["photo"] as! [NSDictionary]
let flickrPhotos : [FlickrPhoto] = photosReceived.map {
photoDictionary in
let photoID = photoDictionary["id"] as? String ?? ""
let title = photoDictionary["title"] as? String ?? ""
let farm = photoDictionary["farm"] as? Int ?? 0
let server = photoDictionary["server"] as? String ?? ""
let secret = photoDictionary["secret"] as? String ?? ""
let flickrPhoto = FlickrPhoto(photoID: photoID, title: title, farm: farm, server: server, secret: secret)
return flickrPhoto
}
DispatchQueue.main.async(execute: {
completion(FlickrSearchResults(searchTerm: searchTerm, searchResults: flickrPhotos), nil)
})
}
}
fileprivate func flickrSearchURLForSearchTerm(_ searchTerm:String) -> URL {
let escapedTerm = searchTerm.addingPercentEscapes(using: String.Encoding.utf8)!
let URLString = "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=\(apiKey)&text=\(escapedTerm)&per_page=30&format=json&nojsoncallback=1"
return URL(string: URLString)!
}
}
Sorry spacing is all messed up but the issue is you're not doing error handling correctly. When you do JSONSerialization it can throw so you have to wrap it in a do catch block or you can use ! to ignore the throw and crash if it throws an error or ? to return nil if it fails.
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")