I'm new to Swift and Xcode, and have started porting a small app from Android.
It's not to big so making nested api-calls (like a chain) has worked.
Now I try doing the same in XCode but get error: Failed to construct URL.
I will put the code for the first function that works:
func apiTest(){
/*Setting up for HTTP requests to https://example.com */
let session = URLSession.shared
let url = URL(string: APIBaseUrl+"get")!
let task = session.dataTask(with: url) { data, response, error in
if error != nil || data == nil {
print("Client error!")
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
print("Server error!")
return
}
guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("OK?")
print(json)
let dict = json as! [String:Any]
print(dict)
print(dict["message"] ?? "Could not read response")
let expected:[String:Any] = ["message": "success"]
print(NSDictionary(dictionary: dict).isEqual(to: expected))
if(NSDictionary(dictionary: dict).isEqual(to: expected)){
//it's working
self.apiTestOk = true
/*Here: continue to a similar function*/
self.apiGetUserPrivateInfo(_id: self.id, _key: self.key)
}
//let message = json as! SimpleMessage //fel i runtime
//print (message.message) //fel i runtime
//let decoder = JSONDecoder()
//let message = try decoder.decode(SimpleMessage.self)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}
task.resume()
/*end http requests*/
}
So I call a similar function with
self.apiGetUserPrivateInfo(_id: self.id, _key: self.key)
That function start like this:
func apiGetUserPrivateInfo(_id: Int, _key: String){
//"get/userp/{id}/{key}"
let session = URLSession.shared
let u:String = APIBaseUrl+"get/userp/\(_id)/\(_key)"
print (u)
var components = URLComponents()
components.scheme = "https"
components.host = "rapport.se/api"
components.path = u
guard let url = components.url else {
preconditionFailure("Failed to construct URL") // here it fails
}
I wonder if it could be the "session" that cant be reused. Would be grateful for an answer.
I also used:
let url = URL(string: u)!
with same result.
The problem is not the session (and, by the way, you generally do want to “reuse” sessions, to avoid overhead), but rather how you’re building URLs, specifically how you’re using the URLComponents.
If you're manually specifying the path for URLComponents, it needs to start with the / character. But URL has methods for building a URL with a path:
guard let baseURL = URL(string: "https://rapport.se/api") else { ... }
let url = baseURL.appendingPathComponent(u)
That’s simpler and more robust way to build up a URL than URLComponents. By the way, this appendingPathComponent is generally a preferred way of composing a URL in general, e.g., instead of:
let url = URL(string: APIBaseUrl+"get")!
You might do:
let url = URL(string: APIBaseUrl)!.appendingPathComponent("get")
This gets you out of the world of worrying about “gee, did my APIBaseUrl end in a / or not”, especially if there’s any risk of some future programmer changing the APIBaseUrl in such a way that the trailing / is removed, suddenly breaking your code. Using URL methods like appendingPathComponent is a robust way of doing this.
If you're wondering when you would use URLComponents, it is most useful if you already have a URL with its path, but just need to add query items to a URL. For example, space characters or & need to be percent escaped when included in a URL, and URLComponents does that for us. Consider:
guard var components = URLComponents(string: "https://example.com") else { ... }
components.queryItems = [
URLQueryItem(name: "q", value: "War & Peace")
]
guard let url = components.url else { ... }
That would result in a URL where the query components would be percent escaped (i.e. the space replaced with %20 and the & replaced with %26):
https://example.com?q=War%20%26%20Peace
Related
U P D A T E D... The function with what works!
I would like to incorporate the yelp api into an app but can't successfully pass my authorization token on the URL string. Do I need to do something to connect the URLRequest to the URLSessoin call and its not using the header? Maybe the key value pairs is wrong? The below function returns:
error = {
code = "TOKEN_MISSING";
description = "An access token must be supplied in order to use this endpoint.";
};
I was able to use postman to get the yelp API call working, but only by clicking the "Header" section on postman and putting in Bearer and then my yelp key. I googled around a bit and found some links that indicate that you can add a header to the URLSession that I assume would work the way postman does but I haven't been able to get it to work.
I know there are some githubs with yelp API repos but I am trying to not install a large set of code that I don't understand into my app, when all I want is the JSON that I can see is coming through on postman. Can anyone help me understand how I would edit code similar to the Here example below so that I can get the Authorization/Bearer that yelp requires?
func getYelp() {
let appSecret = "Bearer <YELP APIKEY>"
let link = "https://api.yelp.com/v3/businesses/search?latitude=37.786882&longitude=-122.399972"
if let url = URL(string: link) {
// Set headers
var request = URLRequest(url: url)
request.setValue("Accept-Language", forHTTPHeaderField: "en-us")
request.setValue(appSecret, forHTTPHeaderField: "Authorization")
print("Attempting to get places around location from Yelp")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject // Added "as anyObject" to fix syntax error in Xcode 8 Beta 6
print("Printing all JSON/n/n//n--------------------------")
print(jsonResult)
print("Printing from results/n/n//n--------------------------")
if let description = ((jsonResult["search"] as? NSDictionary)?["context"] as? NSDictionary)?["href"] as? String {
} else {
print("JSON pull failed/n/n//n--------------------------")
}
} catch {
print("JSON Processing Failed/n/n//n--------------------------")
}
}
}
}
task.resume()
} else {
resultLabel.text = "Couldn't get results from Here"
}
}
You're mixing up between the headers and the url, you need to set your headers correctly
if let url = URL(string: "https://places.cit.api.here.com/places/v1/discover/around?at=37.776169%2C-122.421267&app_id=\(app_id)&app_code=\(app_code)") {
var request = URLRequest(url: url)
// Set headers
request.setValue("Accept-Language", forHTTPHeaderField: "en-us")
request.setValue("Authorization", forHTTPHeaderField: "Bearer " + token // Token here)
print("Attempting to get places around location")
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// ...
Lets say you have an api with "https://google.com" (this is just an example with fake keys)
and an api key that is "ApiKey: 92927839238293d92d98d98d92".
You would then take this information and do this.
let uri = URL(string:"https://google.com")
if let unwrappedURL = uri {
var request = URLRequest(url: unwrappedURL)request.addValue("92927839238293d92d98d98d92", forHTTPHeaderField: "ApiKey")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
// you should put in error handling code, too
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
// HERE'S WHERE YOUR DATA IS
print(json)
} catch {
print(error.localizedDescription)
}
}
}
dataTask.resume()
}
Please remember that you would replace the google.com with your GET address and the APIKey header with your own api key values.
Also, this will print out all the JSON like in PostMan.
If this works for you, then I also have a link on accessing the JSON Objects.
I am using URLSession to scrape JSON data from my website. My code was throwing various errors relating to casting types, so I added some print statements to my code and found that this function is for some reason accessing an older version of my site. I have since updated the website's data, and verified that the new data is displaying properly both through visiting the website myself and using Rested. However, the print statements in the code below yield old data. The code does not read data from the disk so I am not sure why this is happening.
I have removed the website's link from my code for privacy purposes, but otherwise the function can be found below.
func websiteToDisk() {
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
let defaultSession = URLSession(configuration: config)
let url = URL(string: someURL)
let task = defaultSession.dataTask(with: url!) { data, response, error in
do {
print("Getting information from website")
if let error = error {
print(error.localizedDescription)
} else if let data = data,
let response = response as? HTTPURLResponse,
response.statusCode == 200 {
//do {
let jsonDecoder = JSONDecoder()
print("about to dcode")
let decodedData = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]//try jsonDecoder.decode([String: [String]].self, from: data)
print(decodedData)
print("accessing dictionary")
print(decodedData!["busLoops"])
let toWrite = decodedData!["busLoops"] as! [String]
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let busLoopsURL = documentDirectoryURL.appendingPathComponent("busLoops").appendingPathExtension("json")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(toWrite)
try jsonData.write(to: busLoopsURL)
//} catch { print(error)}
}
}
catch { print(error)}
}
task.resume()
}
Try ignore local cache data
guard let url = URL(string: "http://....") else{
return
}
let request = NSMutableURLRequest(url: url)
request.cachePolicy = .reloadIgnoringCacheData
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, resp, error) in
}
task.resume()
I'm trying to make GET request. Url is configured without slash at the end of URL. Server receives url with "/" at the end so I get error 404. I can see response.url with slash, but urlRequest contains URL without it.
Can't understand, what's going on.
Meant to send:
http://someUrl.com/api
Sent:
http://someUrl.com/api/
Code sample
guard let url = URL(string: self.rootUrl + "/api") else {
print ("Can't make URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
let sessionConf = URLSessionConfiguration.default
let session = URLSession.init(configuration: sessionConf)
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /api")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
print (urlRequest)
let json = try? JSONSerialization.jsonObject(with: responseData) as? [String: Any]
if ( json == nil ) {
print ("error json")
print(response!)
} else { ....
Swift cached response for this request, set CachePolicy to ignore reload and try to do GET request to other URL and than try again.
I need to run this code in the background if possible. Im getting a JSON Request that sometimes takes a while to load(lag is on the server side of the URL, not the code itself.).
I want to run the code below in the background if possible. Any ideas?
var stockData: Data!
var concatTickersString = ""
for object in dataArray.reversed() {
concatTickersString = concatTickersString + "," + object.symbol
}
let url = URL(string: "https://www.alphavantage.co/query?function=BATCH_STOCK_QUOTES&symbols=" + concatTickersString + "&apikey=IX58FUCXKD695JY0")
do {
stockData = try Data(contentsOf: url!)
let json = try JSON(data: stockData)
if let jsonArray = json["Stock Quotes"].array {
for ticker in jsonArray.reversed() {
if(jsonArray.count != 0){
let stockTicker = ticker["1. symbol"].string!
let stockPrice = ticker["2. price"].string!
self.watchListArray.append(WatchlistData(tickerName: stockTicker, tickerPrice: Double(stockPrice)?.currency))
}
}
tableView.isHidden = false
}
} catch {
print(error)
}
Its the server of the JSON that takes long I dont think its necessarily the Data(contents of)
I tried using dispatch_async but im getting no luck.
The lag is caused by the fact that Data(contentsOf:) is a synchronous method. As the documentation says,
Important
Don't use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.
As you discovered through experimentation, placing this method in DispatchQueue.main.async does not make it asynchronous. Instead, follow the documentation's instruction.
This is the slightly modified example found at Fetching Website Data into Memory:
func startLoad() {
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
if let data = data,
let string = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
doSomething(with: string)
}
}
}
task.resume()
}
I am trying to translate Arabic to English using a web service. I get an error when constructing the URL. I've defined these cases:
enum MyErrors: Error {
case urlParsingError(String)
case nonDictonaryObjectReturned(Any)
}
And my code is:
func translateWord (text: String, ToLan:String) {
// the text is "مرحبا" and ToLan is "en"
do {
let jsonString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=apikey&text=\(text)&lang=\(ToLan)"
guard let url = URL(string: jsonString) else {
throw MyErrors.urlParsingError(jsonString)
}
let data = try Data(contentsOf: url, options: Data.ReadingOptions())
let jsonObject = try JSONSerialization.jsonObject(with: data,options: .allowFragments)
guard let dictionary = jsonObject as? [AnyHashable: Any] else {
throw MyErrors.nonDictonaryObjectReturned(jsonObject)
}
let result = dictionary["text"] as? [Any]
let translattedSTR = result?.first as? String
let encodedData = translattedSTR?.data(using: String.Encoding.utf8)!
print(encodedData!)
self.textresult.text = translattedSTR
} catch {
print("caught error \(error)")
}
but unfortunately, it prints
caught error urlParsingError(output url)
also, my the structure looks something like [this](https://translate.yandex.net/api/v1.5/tr.json/translate?key= apikey&text=مرحبا&lang=en)
You need to properly encode the values of the query parameters. One good solution is to use URLComponents to build your query.
Then these lines:
let jsonString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=myAPIkey&text=\(text)&lang=\(ToLan)"
guard let url = URL(string: jsonString) else {
throw MyErrors.urlParsingError(jsonString)
}
need to be replaced with:
let baseString = "https://translate.yandex.net/api/v1.5/tr.json/translate"
var comps = URLComponents(string: baseString)!
let keyQuery = URLQueryItem(name: "key", value: "myAPIKey")
let textQuery = URLQueryItem(name: "text", value: text)
let langQuery = URLQueryItem(name: "lang", value: ToLan)
comps.queryItems = [ keyQuery, textQuery, langQuery ]
guard let url = comps.url else {
throw MyErrors.urlParsingError("\(comps)")
}
The resulting url is now:
https://translate.yandex.net/api/v1.5/tr.json/translate?key=myAPIkey&text=%D9%85%D8%B1%D8%AD%D8%A8%D8%A7&lang=en
You have to encode your String to get a valid URL. You can do this by using String.addingPercentEncoding(withAllowedCharacters: ).
let jsonString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=trnsl.1.1.20170517T154730Z.927d87b76de60242.7a92e4612778a4838d40ab192df5297d2a1af4ed&text=\(text)&lang=\(ToLan)"
guard let encodedJsonString = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedJsonString) else {
throw MyErrors.urlParsingError(jsonString)
}
The encoded URL becomes:
https://translate.yandex.net/api/v1.5/tr.json/translate?key=trnsl.1.1.20170517T154730Z.927d87b76de60242.7a92e4612778a4838d40ab192df5297d2a1af4ed&text=%D9%85%D8%B1%D8%AD%D8%A8%D8%A7&lang=en