Creating a flexible API class in Swift - ios

I'm trying to design an API helper function for an app. The idea is that I'll be able to call the function from a viewController, using code such as:
let api = APIController();
api.request("get_product_list")
api.delegate = self
Here's the class so far:
import Foundation
protocol APIControllerProtocol {
func didReceiveAPIResults(originalRequest: String, status: Bool, data: String, message: String)
}
class APIController {
var delegate: APIControllerProtocol?
let url = "https://example.co.uk/api.php"
let session = NSURLSession.sharedSession()
let appID = "EXAMPLEAPPID";
let deviceID = "EXAMPLEDEVICE"
func request(req:String)-> Void {
let urlString = "\(url)?request=\(req)"
let combinedUrl = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: combinedUrl!)
request.HTTPMethod = "POST"
let stringPost="app_id=\(appID)&device_id=\(deviceID)"
let data = stringPost.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = 60
request.HTTPBody=data
request.HTTPShouldHandleCookies=false
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
let statusInt = jsonData["status"]! as! Int
let status = (statusInt == 1)
let data = String(jsonData["data"])
let message = String(jsonData["message"])
self.delegate?.didReceiveAPIResults(req,status: status,data: data,message: message)
} catch _ {
print("ERROR")
}
}
task.resume()
}
}
The difficulty I'm having is that the 'data' parameter might one of the following:
A string / number, such as the number of purchases a customer has made
An array of items, such as a list of products
A dictionary, such as the customer's details
I've set the data parameter to String as that allowed me to do some testing, but then converting it back into something usable for a tableView got very messy.
Are there any experts on here that can advise me the best way to do this? Perhaps showing me how I'd use the results in a cellForRowAtIndexPath method? Here's an example response from the API, in case it's useful:
{
"status":1,
"message":"",
"cached":0,
"generated":1447789113,
"data":[
{"product":"Pear","price":0.6},
{"product":"Apple","price":0.7},
{"product":"Raspberry","price":1.1}
]
}

One function doing too many things at once makes a really messy code. Also you don't want too many if statements or enums - your View controller will grow really fast.
Id suggest splitting request and parse logic. Your API class would be then responsible only for requests. It would return data to another class, that would be responsible for parsing. Then in the Parser class you could add methods like toDictionary() or toArray(), toArrayOfClasses() and so on. That would be the basic API structure.
If you want to expand it a little bit, you could add another class layer that would handle all that logic so your View Controller doesn't know if it uses API or another data source - this way you could easy implement new things in the future, like Core Data or migrate from your API class to some framework, maybe Parse.com - this layer gives you flexibility.
Example structure:
API - requests
Parser - parsing API response
DataManager - Send request to API and return parsed response.
or if you don't want this third point, you can just request & parse in the view controller.

Related

How to convert escaping closure code to async-await code that uses URLSession?

I’m trying to convert escaping closure code to async-await code in a certain file. I’m stuck with implementing the async-await part, specifically whether to still use a do block, with catch and resume, or to not use a do block, and assign the line of code with “try await URLSession.shared.dataTask(with: request)” (that's commented out in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift in this post below, and meant to be used in the solution attempts to this file) to a variable, then use that variable later similar to how the file File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift does, which is posted further below in this post.
*Note: I used async-await and codable to get the restaurant business data for a certain searched city (thats searched by the user) which is done in a different file (and function). The file I’m having trouble with though is for getting the selected restaurant business’s detail info (name, address, business hours, etc.), and I’m not using codable in this file because some of the data I get when doing this URL request, I get by using NSDictionary; not codable.
How do I correctly implement this async-await concept in my file? The file I’m implementing this in is File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift which is posted further below in this post.
*Update: Where I thought my problem lied when first writing up this question post: At the line of code “URLSession.shared.dataTask(with: request) { (data, response, error) in” in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, specifically I thought that I should use the form of code that didn’t use the completionHandler (which is commented as V2 in that file), and whether to use a do block after it, and catch, and resume after the do block.
I’ve posted some attempted solutions so far, which are incomplete, since I’m having the problems mentioned in the first paragraph of this post. I know they don’t work, but this is my thought process so far. These solution attempts are below the code files that I’m working with which are posted further below.
I used the following article for learning more about async-await before attempting to make this current implementation: https://www.avanderlee.com/swift/async-await/.
My code:
Code files that I’m working with:
File that I’m attempting to implement this escaping closure to async-await concept in:
File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift:
import Foundation
import UIKit
import CoreLocation
extension UIViewController {
func retrieveSelectedRestaurantDetailViewInfo(
selected_restaurant_business_ID: String) async throws -> SelectedRestaurantDetailViewInfoNotUsingCodable? {
// MARK: Make API Call
let apiKey = "API key"
/// Create URL
let baseURL =
"https://api.yelp.com/v3/businesses/\(selected_restaurant_business_ID)"
let url = URL(string: baseURL)
/// Creating Request
var request = URLRequest(url: url!)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
///Initialize session and task
//V1: Code for using escaping closure version code.
URLSession.shared.dataTask(with: request) { (data, response, error) in
//This version commented out right now, to show where I'm at with this proboem for clarity. This version is included in the solution attempts; both SoultionAttempt1 and SolutionAttempt2.
if let error = error {
completionHandler(nil, error)
}
//V2: Code for what I think is correct for using async-await version code. Not using the completionHandler here.
// URLSession.shared.dataTask(with: request)
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, hours, etc..*
//Commented out for now, because am going with version 2 below.
//V1: Uses escaping closure code version.
// completionHandler(selectedVenue, nil)
//V2: Used for Async/Await code version.
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
*Update: Below is the new file that shows the code that calls the function with the URLRequest in it in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, that uses the async-await concept, as mentioned in a response to a comment in this post:
File0.5-FileWithJustCodeThatCallsTheFunctionForMakingTheURLRequestInFile1.swift:
async let responseSelectedVenueDetailViewInfoNotUsingCodable = try await retrieveSelectedRestaurantDetailViewInfo(selected_restaurant_business_ID: venues.id)
File (file I'm referring to is below; is File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift) that uses async-await for getting the initial restaurant business data, after the user selects a city, which I’m using for reference for making the stated change from the escaping closure code to the async-await code in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift above:
File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift:
import UIKit
import Foundation
import CoreLocation
class YelpApiSelectedRestaurantDetailViewInfo {
private var apiKey: String
init(apiKey: String) {
self.apiKey = apiKey
}
func searchBusinessDetailViewInfo(selectedRestaurantBusinessID: String) async throws -> SelectedRestaurantDetailViewInfo {
var resultsForTheSelectedRestaurantDetailViewInfo: SelectedRestaurantDetailViewInfo
// MARK: Make URL Request.
var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/\(selectedRestaurantBusinessID)")
guard let url = urlComponents?.url else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let businessDetailViewInfoResults = try JSONDecoder().decode(SelectedRestaurantDetailViewInfo.self, from: data)
resultsForTheSelectedRestaurantDetailViewInfo = businessDetailViewInfoResults
return resultsForTheSelectedRestaurantDetailViewInfo
}
}
Solution Attempts:
*Note:
-Both of the below solution attempt code snippets start at the line of code: URLSession.shared.dataTask(with: request) { (data, response, error) in in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, and goes through the rest of that file, and ends at the same end point as that file (as file File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift).
-Also, the file that these solution attempts are to be implemented in is File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift.
SolutionAttempt1-DoesntUseDoBlock.swift:
///Initialize session and task
try await URLSession.shared.dataTask(with: request)
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, business hours, etc..*
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
*Note about the following solution attempt file: The solution attempt here in my opinion (SolutionAttempt2-DoesUseDoBlock.swift) may not have to include indentation for the do block, where the do block is within the scope of the “try await URLSession.shared.dataTask(with: request)” line of code, but I included the below solution attempt to have this indentation, as it would seem that the do block would need to be within the scope of the “try await URLSession.shared.dataTask(with: request)” line of code, and the original file version of File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, that uses the escaping closure (not the file being edited/worked on here) had the do block within the “URLSession.shared.dataTask(with: request) { (data, response, error) in” line of code’s scope, which is at the same position as the “try await URLSession.shared.dataTask(with: request)” line of code in this SolutionAttempt2-DoesUseDoBlock.swift file below.
SolutionAttempt2-DoesUseDoBlock.swift:
///Initialize session and task
try await URLSession.shared.dataTask(with: request)
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, business hours, etc.*
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
Thanks!

login user with GET request swift

I have created a screen with a text field called customer_number text field and another screen with a text field called password text field. I want to integrate my app with an existing API made by the backend developers. I am new to IOS Development and I don't know how to go about it. How do I make a get request and pass the login credentials for the user to login?
I want to get the customer number from the API and pass it to the app and enable the customer to log in.
I think this question is too big and complex to be replied exhaustively. You didn't tell us about the API. What kind of input does it take? What kind of response?
Supposing the simplest case. You API expects JSON objects as input and respond with another JSON object containing the information you request.
I usually do tasks like this using the NSURLRequest.
let js = ["Username":username, "Password":password]
let session = URLSession.init(configuration: .default)
let url = URL(...)
var req = URLRequest.init(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10)
req.httpMethod = "POST"
// Add some header key-value pairs
req.addValue(..., forHTTPHeaderField: ...)
...
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let responseData = data else { return }
let code = (response as! HTTPURLResponse).statusCode
// Checking for code == 200 states for authorised user. Generally log-in APIs should return some 4xx code if not allowed or non-authorised user.
if code == 200 {
// Now we try to convert returned data as a JSON object
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
// use your json object here, for example checking if contains the user number...
} catch {
// handle errors
}
}
}
task.resume()
I coded this very quickly, please check the correctness of al mechanism!

Working with AlamoFire and local JSON

I realise this sounds a bit counter intuitive but I need to work with a local JSON file while our REST api is being built. I'm also using SwiftyJson to parse it and I had this working in a very rudimentary way, I am now wanting to expand a bit on thee basics and start to flesh out some of the proper requests etc. so, in effect all I will have to do is swap the local JSON path for the HTTP one when its ready.
I appreciate this is probably a little noobish but I couldnt find any documentation on this particular scenario :\
My current attempts are below:
Job.swift:
class func endpointForjob() -> String {
DataManager.getJobsDataFromLocalFile { (data) -> Void in
// Get jobs from local jobs.json file (dummy data while we work on the networking)
let jsonData = JSON(data: data)
}
return jsonData //i know this wont work, just giving an idea of how i *think* this should work
// return "http://ourUrl.co/api/v1/job/"
}
DataManager.swift:
class func getJobsDataFromLocalFile(success: ((data: NSData) -> Void)) {
//1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//2
let filePath = NSBundle.mainBundle().pathForResource("jobs",ofType:"json")
var readError:NSError?
if let data = NSData(contentsOfFile:filePath!,
options: NSDataReadingOptions.DataReadingUncached,
error:&readError) {
success(data: data)
}
})
}
A quick and rather obvious solution is to just serve the JSON file from your localhost
class func endpointForjob() -> String {
return "http://localhost/jsonfile.json"
}

How can I process multiple links of JSON data?

The code works perfectly. The problem is that, after trying for a while, I cannot figure out how to make my program process a second link of different JSON data.
Here is my viewDidLoad where everything goes on:
override func viewDidLoad() {
super.viewDidLoad()
var err: NSError?
let urlPath: String = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/" + searchFieldDataPassed + "?api_key=(removed my private api key for obvious reasons"
var url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
if let httpResponse = response as? NSHTTPURLResponse { switch httpResponse.statusCode { case 200..<300: println("OK") default: println("Not OK") } }
// parse JSON using NSJSONSerialization if you've got data
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary,
let include = jsonResult.objectForKey(self.searchFieldDataPassed) as? NSDictionary {
if let summLevel = include[ "summonerLevel" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.summonerLevel.text = "\(summLevel.integerValue)"
println("summoner level: \(summLevel.integerValue)")
}
}
if let profIconId = include[ "profileIconId" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.profileIconId.text = "\(profIconId.integerValue)"
println("profile icon id: \(profIconId.integerValue)")
}
}
if let idNum = include [ "id" ] as? NSNumber {
dispatch_async(dispatch_get_main_queue()) {
self.idNumber = idNum
println("id number: \(self.idNumber)")
}
}
}
// spawn off another network call here if you like
}
task.resume()
}
That is from my secondViewController where all the processing goes on for JSON and then is displayed.
Here is the JSON data that I'm processing (for the first JSON parsing):
{"soon2challenger":{"id":43993167,"name":"soon2challenger","profileIconId":844,"summonerLevel":30,"revisionDate":1435549418000}}
All of that works fine, now, I want to process this JSON data which actually takes the id from the first parsed JSON data and uses it in the link to process more data, which I would like to output, part of it, to the screen.
Second JSON data:
{"summonerId":43993167,"playerStatSummaries":[{"playerStatSummaryType":"AramUnranked5x5","wins":25,"modifyDate":1423007927000,"aggregatedStats":{"totalChampionKills":676,"totalTurretsKilled":20,"totalAssists":991}},{"playerStatSummaryType":"CAP5x5","wins":15,"modifyDate":1429065922000,"aggregatedStats":{"totalChampionKills":312,"totalMinionKills":4885,"totalTurretsKilled":31,"totalNeutralMinionsKilled":511,"totalAssists":216}},{"playerStatSummaryType":"CoopVsAI","wins":28,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":266,"totalMinionKills":2802,"totalTurretsKilled":50,"totalNeutralMinionsKilled":385,"totalAssists":164,"maxChampionsKilled":0,"averageNodeCapture":0,"averageNodeNeutralize":0,"averageTeamObjective":0,"averageTotalPlayerScore":49,"averageCombatPlayerScore":0,"averageObjectivePlayerScore":49,"averageNodeCaptureAssist":0,"averageNodeNeutralizeAssist":0,"maxNodeCapture":0,"maxNodeNeutralize":0,"maxTeamObjective":0,"maxTotalPlayerScore":49,"maxCombatPlayerScore":0,"maxObjectivePlayerScore":49,"maxNodeCaptureAssist":0,"maxNodeNeutralizeAssist":0,"totalNodeNeutralize":0,"totalNodeCapture":0,"averageChampionsKilled":0,"averageNumDeaths":0,"averageAssists":0,"maxAssists":0}},{"playerStatSummaryType":"CoopVsAI3x3","wins":15,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":140,"totalMinionKills":1114,"totalTurretsKilled":9,"totalNeutralMinionsKilled":449,"totalAssists":91}},{"playerStatSummaryType":"OdinUnranked","wins":1,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":31,"totalAssists":45,"maxChampionsKilled":10,"averageNodeCapture":4,"averageNodeNeutralize":4,"averageTeamObjective":0,"averageTotalPlayerScore":843,"averageCombatPlayerScore":268,"averageObjectivePlayerScore":575,"averageNodeCaptureAssist":3,"averageNodeNeutralizeAssist":1,"maxNodeCapture":6,"maxNodeNeutralize":7,"maxTeamObjective":2,"maxTotalPlayerScore":1468,"maxCombatPlayerScore":529,"maxObjectivePlayerScore":939,"maxNodeCaptureAssist":5,"maxNodeNeutralizeAssist":2,"totalNodeNeutralize":22,"totalNodeCapture":25,"averageChampionsKilled":5,"averageNumDeaths":5,"averageAssists":8,"maxAssists":19}},{"playerStatSummaryType":"RankedSolo5x5","wins":116,"losses":120,"modifyDate":1433630047000,"aggregatedStats":{"totalChampionKills":1699,"totalMinionKills":33431,"totalTurretsKilled":219,"totalNeutralMinionsKilled":6501,"totalAssists":1969}},{"playerStatSummaryType":"RankedTeam3x3","wins":0,"losses":0,"modifyDate":1377726216000,"aggregatedStats":{}},{"playerStatSummaryType":"RankedTeam5x5","wins":3,"losses":0,"modifyDate":1383784473000,"aggregatedStats":{"totalChampionKills":28,"totalMinionKills":636,"totalTurretsKilled":6,"totalNeutralMinionsKilled":101,"totalAssists":41}},{"playerStatSummaryType":"Unranked3x3","wins":9,"modifyDate":1421882181000,"aggregatedStats":{"totalChampionKills":90,"totalMinionKills":1427,"totalTurretsKilled":11,"totalNeutralMinionsKilled":428,"totalAssists":105}},{"playerStatSummaryType":"URF","wins":4,"modifyDate":1435024847000,"aggregatedStats":{"totalChampionKills":68,"totalMinionKills":642,"totalTurretsKilled":14,"totalNeutralMinionsKilled":182,"totalAssists":55}},{"playerStatSummaryType":"Unranked","wins":566,"modifyDate":1435549418000,"aggregatedStats":{"totalChampionKills":8419,"totalMinionKills":128213,"totalTurretsKilled":960,"totalNeutralMinionsKilled":26117,"totalAssists":7812}}]}
Heres the link of the second JSON data I want to parse (just adding it, could be useful, but not sure):
https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/43993167/summary?season=SEASON2015&api_key=(took-out-my-private-api-key-for-obvious-reasons)
The link doesn't work because I have to keep my api key private to myself, but the JSON data that it displays is right above the link, which is the what it would result if you were to use the link with the api key.
Just to restate, I would like to process the second part (above of this) of JSON data, but I do not understand how to process multiple links of JSON. I have the first JSON data parsed, but am unable to parse the second JSON data.
I believe Apple is deprecating NSURLConnection. Take a look at NSURLSession. Using it, you can pass in a completion block that takes three arguments: NSData?, NSURLResponse?, and NSError?. The data object contains the JSON you can pass into the JSON serializer. After that, if you need to make another network call, just call it from inside the completion block with another NSURLSession data task. Alamofire is a great framework, but sometimes you don't need everything it provides, and it adds complexity into your app that if something goes wrong or doesn't behave the way you intend/understand, you may not fully understand why. If you want to keep it simple and under your control, use NSURLSession.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { data, response, error in
// cast response as NSHTTPURLResponse and switch on statusCode if you like
// parse JSON using NSJSONSerialization if you've got data
// spawn off another network call here if you like
}
task.resume() // or in Swift 2, task?.resume()
First, i would totally prefer using some common frameworks for http requests - expecially if youre new in swift. For example here with alamofire.
https://github.com/Alamofire/Alamofire
There is also a version with integrated SwiftyJSON, so you are able to parse JSON Responses very easily.
https://github.com/SwiftyJSON/Alamofire-SwiftyJSON
So if you want to make a request, use this:
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { (_, _, json, _) in
var json = JSON(json)
// get the id out (depends on your structure of JSON):
let id = json["id"].int
}
Now you are able to perform a second Request (with the same Code) - Read the Documentation, how to make different Requests (like with POST) and add Parameters.
If you want to use Segues, so you want to load more data from the ID in another ViewController, you can use Segues to push the data to a second ViewController, and Load the new Content from JSON when the new ViewController is initialised.
Check out this how to send data through segues:
Sending data with Segue with Swift

How can I initialize an object with data from two separate Alamofire requests?

Im trying to make a GET request to Foursquare's Photos From a Venue and
Foursquare's Explore at the same time. Right now (correct me if I'm wrong) I have two methods to make the request via Alamofire and convert the response to a JSON object using SwiftyJSON.
I can successfully update the UITableViewCell's labels to reflect the data using makeRequest() below, but can't update the UIImage for the background photo of each respective cell.
My problem is A) getting a usable photo URL, and B) Initializing "pin" while providing data from two separate requests. In order to initialize pin, I need to set all of the values. Im geting 90% of the values from one request, and 10% (the photo URL that I need to get) from another request. How do I initialize "pin" with data from two separate requests?
makeImageRequest:
func makeImageRequest() {
let venueID = "43695300f964a5208c291fe3"
let firstURL = "https://api.foursquare.com/v2/venues/\(venueID)/photos"
Alamofire.request(.GET, firstURL, parameters: [
"client_id" : foursquareClientID,
"client_secret" : foursquareClientSecret,
"v" : "20140806",
"m" : "foursquare",
"limit" : "10"
])
.responseJSON { (request, response, data, error) in
println(request)
println(response)
println(error)
let jsonObj = JSON(data!)
self.pins = []
for obj in jsonObj["response"]["photos"]["items"].arrayValue {
let photoURL = obj["prefix"]["suffix"].stringValue
println("the photo url is\(photoURL)")
let pin = Pin(title: obj["venue"]["name"].stringValue, openUntil: obj["venue"]["hours"]["status"].stringValue, address: obj["venue"]["location"]["address"].stringValue, ratingSignals: obj["venue"]["ratingSignals"].stringValue, ratingImage: UIImage(named:"Score8-5")!, venueImage: UIImage(named: "FloraGrubTestImage.jpg")!)
self.pins.append(pin)
}
self.tableView.reloadData()
}
}
and makeRequest
func makeRequest(searchString: String) {
let secondURL = "https://api.foursquare.com/v2/venues/explore"
Alamofire.request(.GET, secondURL, parameters: [
"client_id" : foursquareClientID,
"client_secret" : foursquareClientSecret,
"ll" : "37.71987,-122.470089",
"query" : searchString,
"radius" : "1000",
"limit" : "10",
"v" : "20140806",
"m" : "foursquare"
])
.responseJSON { (request, response, data, error) in
println(request)
println(response)
println(error)
let jsonObj = JSON(data!)
self.pins = []
println(jsonObj)
for obj in jsonObj["response"]["groups"][0]["items"].arrayValue {
let pin = Pin(title: obj["venue"]["name"].stringValue, openUntil: obj["venue"]["hours"]["status"].stringValue, address: obj["venue"]["location"]["address"].stringValue, ratingSignals: obj["venue"]["ratingSignals"].stringValue, ratingImage: UIImage(named:"Score8-5")!, venueImage: UIImage(named: "FloraGrubTestImage.jpg")!)
self.pins.append(pin)
}
self.tableView.reloadData()
}
}
I have a separate bindData() method in my UITableViewCell class. Does this make sense? Can anyone help me?
UPDATE: An engineer I work with suggested that I make another request inside of the same method makeRequest and not bother with breaking it out into two separate methods. I also read a tutorial online that suggests a response router of some kind. Any suggestions on how I can refactor this code into one method?
UPDATE #2: I have renamed this question as I realize that the original question was not my real problem
The easiest way would be to execute these request serially. First retrieve all the pins, and then retrieve all the photos. This is essentially what the other engineer advised.
If you fire off these requests in parallel, it will require a extra work to merge the responses, since you don't know which request will return first.
Now to merge the pins you need a way to uniquely identify each pin. If they have some ID, you can use that. Otherwise you have to rely on the sort order and index. Assuming each request returns the same pins in the same order.
After the first request returns, you have an array of pins. Then in the second request callback you can retrieve the matching pin from this array and update it with the new data.

Resources