Url works in Postman but not in Swift app - swift

I have a url that works in Postman and in browser, but not in app.
I have an if let url = URL(string: urlString) line, but apparently the URL(string: urlString) is returning nil so it doesn't enter the block. It doesn't actually throw an error so I can't actually search for an error.
I've tried looking at other people's similar problems but haven't found a solution. Any help would be appreciated. If you could point me to another post with a potential solution I'd appreciate that too. Thank you.
Here is my code. I've used this many times before with no problems.
func performRequest<T: Codable>(urlString: String, returnType: T.Type, completion: #escaping (Result<T, Error>) -> Void ) {
print("\n\(#function)")
if let url = URL(string: urlString) { // <--- FAILS RIGHT HERE
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, _, error) in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(T.self, from: data)
completion(.success(decodedData))
} catch let decodingErr {
completion(.failure(decodingErr))
}
}
task.resume()
} else {
print("Something went wrong with the url")
}
}

There was a space in my url that Swift wasn't accepting. Apparently Postman and browsers can still make the call with a space, but not Swift. Thank you to #workingdog for solving this.

Related

Is Combine always required to make serial HTTP requests?

(note: I'm a beginner to programming) I just have a semantic question regarding Combine. I was under the impression that Combine was always required for serial HTTP requests but while experimenting I found that the following worked just fine:
// this function works even though it depends on the output of the first HTTP request
#available(iOS 15.0, *)
func displayPictures(completion: #escaping ([(AsyncImage<Image>, UUID)]) -> Void) throws {
do {
try getPictures { urls in
var tempImageArr = [(AsyncImage<Image>, UUID)]()
for url in urls {
guard let url = URL(string: url) else {
print("Invalid URL")
return
}
let image = AsyncImage(url: url)
let id = UUID()
tempImageArr.append((image, id))
}
completion(tempImageArr)
}
} catch {
throw NetworkError.failedToGetPictures
}
}
func getPictures(completion: #escaping ([String]) -> Void) throws {
guard let url = URL(string: "https://randomuser.me/api/?results=10&inc=picture") else {
print("Invalid URL")
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
let decoder = JSONDecoder()
let decodedResponse = try! decoder.decode(RandomUserModel.self, from: data!)
completion(decodedResponse.pictures)
}.resume()
}
I'm hoping someone can clear up my confusion.
Thanks everyone.

Making HTTP GET request with Swift 5

I am obviously missing something very fundamental/naïve/etc., but for the life of me I cannot figure out how to make simple GET requests.
I'm trying to make an HTTP GET request with Swift 5. I've looked at these posts/articles: one, two, but I can't get print() statements to show anything. When I use breakpoints to debug, the entire section within the URLSession.shared.dataTask section is skipped.
I am looking at the following code (from the first link, above):
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
HTTP_Request()
I am running this in a MacOS Command Line Project created through XCode.
I would greatly appreciate any help I can get on this, thank you.
Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,
func httpRequest() {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
That should at least give you some indication of the problem.
A few other considerations:
If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.
For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:
func httpRequest(completion: #escaping () -> Void) {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
defer {
DispatchQueue.main.async {
completion()
}
}
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
var finished = false
httpRequest {
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: .distantFuture)
}
In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.
If playground, you have to set needsIndefiniteExecution.
By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.
In this case, you should just use https and avoid that consideration altogether.
Make sure the response get print before exiting the process, you could try to append
RunLoop.main.run()
or
sleep(UINT32_MAX)
in the end to make sure the main thread won't exit. If you want to print the response and exit the process immediately, suggest using DispatchSemaphore:
let semphare = DispatchSemaphore(value: 0)
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
semphare.signal()
}
task.resume()
}
HTTP_Request()
_ = semphare.wait(timeout: .distantFuture)
This works for me many times I suggest you snippet for future uses!
let url = URL(string: "https://google.com")
let task = URLSession.shared.dataTask(with: ((url ?? URL(string: "https://google.com"))!)) { [self] (data, response, error) in
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: [])
print(jsonResponse)
guard let newValue = jsonResponse as? [String:Any] else {
print("invalid format")
}
}
catch let error {
print("Error: \(error)")
}
task.resume()
}

Why is Xcode complaining when I wrap this let in an if statement?

I have the following working code in my app:
func downloadJSON(completed: #escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://example.com/ExampleData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
self.defaults.set(posts.meta.version, forKey: "jsonVersion")
print("URLSession did not fail")
print("JSON Version Set to \(posts.meta.version)")
} catch {
print("Can't decode JSON: \(error)")
}
} else {
print("downloadJSON completed")
completed(nil)
}
}.resume()
}
I am wanting to change that URL to a different one based on a UserDefaults setting. So I wrapped the let url in an if statement like this:
if devModeStatus == true {
let url = URL(string: "https://otherexample.com/Example2Data.json")!
} else if devModeStatus == false {
let url = URL(string: "http://example.com/ExampleData.json")!
} else {
print("Invalid Dev Status encountered!")
return
}
However when I do that, Xcode complains about "Use of unresolved identifier 'url'; did you mean 'erfl'?" on the line that says URLSession.shared.dataTask(with: url) { (data, response, error) in
I'm not sure why it is complaining about this change. I use that same if/else logic else where to print a status message at first load of this view, so I know the variable is correct.
Your url declaration dies within those if, else-if scopes. You need to declare your url first then modify it. Also, since devModeStatus is a boolean value, the else case will never be reached, so no need for third path. Update your code as following:
let url: URL
if devModeStatus {
url = URL(string: "https://otherexample.com/Example2Data.json")!
} else {
url = URL(string: "http://example.com/ExampleData.json")!
}

How to prevent an app crash or freeze due to a slow connection when retrieving a remote photo in Swift?

I want to display avatar image in my table view cell by the url string. and it will crash when the phone is not connect to the internet, so I added Reachability swift to it. but now I face another problem, when the user leaving the wifi zone or the internet connection is not stable, the app will freeze, I'm not able to tap anything until I walk back the strong wifi zone. why?
let imageData:NSData = try! NSData(contentsOf: imageUrl)
this code will crash so I try add do & catch but still not working. is it because the app can't connect to the url string and get the data so that the app will be freeze? how to prevent an app crash or freeze due to a slow connection when retrieving a remote photo?
if Reachability.shared.isConnectedToNetwork(){
if let crew = user!["crew"] as? [String:Any], let crewAva = crew["crew_avatar"] as? String {
let imageUrlString = crewAva
let imageUrl:URL = URL(string: imageUrlString)!
DispatchQueue.main.async(execute: {
do{
let imageData:NSData = try NSData(contentsOf: imageUrl)
let image = UIImage(data: imageData as Data)
self.avaImg.image = image
}
catch{
print("error")
}
})
}
}else{
print("Not reachable")
}
From the NSData documentation:
init?(contentsOf url: URL)
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 NSURLSession class. See URL Session Programming Guide for details.
Solution
func getDataFromUrl(url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}.resume()
}
func downloadImage(url: URL) {
getDataFromUrl(url: url) { data, response, error in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? url.lastPathComponent)
DispatchQueue.main.async() {
self.avaImg.image = UIImage(data: data)
}
}
}
override func viewWillAppear(_ animated: Bool) {
if let crew = user!["crew"] as? [String:Any], let crewAva = crew["crew_avatar"] as? String {
let imageUrlString = crewAva
let url = URL(string: imageUrlString)!
downloadImage(url: url)
}
}
Try only updating the UI on the main thread.
if Reachability.shared.isConnectedToNetwork(){
if let crew = user!["crew"] as? [String:Any], let crewAva = crew["crew_avatar"] as? String {
let imageUrlString = crewAva
let imageUrl:URL = URL(string: imageUrlString)!
let imageData:NSData = try NSData(contentsOf: imageUrl)
let image = UIImage(data: imageData as Data)
DispatchQueue.main.async(execute: {
do{
self.avaImg.image = image
}
catch{
print("error")
}
})
}
}else{
print("Not reachable")
}

Making an API call in Swift 2.0

I've been scouring examples looking to pull some ideas together, I've come up with this although I'm not getting any output. It never enters the do which leads me to believe I have an issue with my call.
Can anyone shed some light on this for me or lead me to an appropriate location with more information on API calls in swift 2.0? Examples of this are quite sparse.
let url : String = "http://www.fantasyfootballnerd.com/service/nfl-teams/json/test/"
let request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
print("Start")
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult: NSDictionary! = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print("In method")
if (jsonResult != nil) {
// process jsonResult
print("Data added")
} else {
print("No Data")
// couldn't load JSON, look at error
}
}
catch {
print("Error Occured")
}
}
You're missing just one thing. You need to start the request:
// call this after you configure your session
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// process results
}.resume()