How to output loading message as NSURL retrieves URL's html? - ios

I am using Swift's NSURL function to retrieve HTML output from a PHP script that interacts with a MySQL database with variables passed with POST and secured with an SSL certificate.
Everything is all fine and great except for the occasional prolonged loading which resulting in a blank table view. Is there any way to run a function while I am waiting for the response string? I am completely in the dark on this one.
Here is the code I am using:
let myUrl = NSURL(string: "http://www.casacorazon.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
}
}
}
task.resume()

Yes. You can add a UIActivityIndicatorView to display a loading symbol. And any code you place after task.resume() will run while the network call is happening. Depending if you're already on the main thread or not, you might need an additional dispatch block after it (the same way you do it in your completion callback):
dispatch_async(dispatch_get_main_queue()) {
// make your UIActivityIndicator visible here
}

Related

how to handle Alamofire request when network speed is slow or server is down?

I am new to the Alamofire which I am using to make a request to rest api.
Now while making request there can be two issues and I want to know how to handle those issues using Alamofire.
1) What if user have submit data and Internet is slow and it takes longer to get response from server. In such case how one should insure that whether a request is successful or not. Or we can show some message to user that Internet is slow so he can wait for long response.
2) What if internet speed is ok but the server is down or it is taking longer to send a response how should we handle these situations in our application. And maintain integrity of data.
Following is an example of how I am using Alamofire to make request.
static func getUserListFromServer(completion: #escaping(Bool,[Users]?,String?)->() ){
Alamofire.request(APPURL.getFeedbackUserList, method: .get, parameters: nil, encoding: URLEncoding.queryString, headers: nil).responseJSON { (responseJson) in
responseJson.result.ifSuccess {
do {
// Decode data to object
let jsonDecoder = JSONDecoder()
let root = try jsonDecoder.decode(Root.self, from: responseJson.data!)
if let users = root.users{
completion(true,users,nil)
}else{
completion(false,nil,"No users found.")
}
}
catch let err {
print(err.localizedDescription)
completion(false,nil,err.localizedDescription)
}
}
responseJson.result.ifFailure {
completion(false,nil,responseJson.result.error?.localizedDescription)
}
}
}
You're actually implementing alamofire correctly I would already be about the connection and server problem if network speed is slow. Try using a different endpoint from a different server to test the speed.If its faster then the problem is on your server. Since your implementation is correct then definitely it is server problem. alamofire has nothing to do on handling the issue if the problem is on the server or connection.
You can increase timeout for slow response API calls.
static func getUserListFromServer(completion: #escaping(Bool,[Users]?,String?)->() ){
let request = NSMutableURLRequest(url: APPURL.getFeedbackUserList)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 10 // change time out according to your need
let values = ["key": "value"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values, options: [])
Alamofire.request(request as! URLRequestConvertible).responseJSON { (responseJson) in
responseJson.result.ifSuccess {
do {
// Decode data to object
let jsonDecoder = JSONDecoder()
let root = try jsonDecoder.decode(Root.self, from: responseJson.data!)
if let users = root.users{
completion(true,users,nil)
}else{
completion(false,nil,"No users found.")
}
}
catch let err {
print(err.localizedDescription)
completion(false,nil,err.localizedDescription)
}
}
responseJson.result.ifFailure {
completion(false,nil,responseJson.result.error?.localizedDescription)
}
}
}

Run JSON Request in the background Swift 4

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()
}

Data in HTTPBody with a PUT method fails, while it works with a POST?

first of all i would like to say i got the exact same problem as the following question: How to add data to HTTPBody with PUT method in NSURLSession?. But it wasn't answered so i made my own question.
We have written a node API for a school assignment. We've tested the whole API. (The chances of being something wrong there are slim.)
After that i went working on a iOS client to CRUD users.
Making a user is going perfectly, but whenever i try to edit a user something strange happens. The data on the server arrives as undefined.
I use the following code to save a user:
func saveUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "POST"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
keep in mind, this is working perfectly (don't know if it is intended to be used like this)
To edit a user i use the following code:
func editUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/\(user.id!)")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "PUT"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
(The original code is a bit longer but i removed parts that had nothing to do with the actual posting of the data)
I have really no idea what i'm doing wrong, could be something small and stupid. Please help.
edit after input from #fiks
To be clear, the problem i am having is that I fill the "postString" the same way in the editUser method as I do in the saveUser method.(At least I think I do)
However in the saveUser method the postString seems to be correctly passed through to the API (it creates a new user with the given values).
The editUser method does not pass the values through.
If I put a console log on the server it shows all values are "undefined".
To test if the postString was correct on the iOS part I printed both strings out. Both of them outputted email=user#test.com&password=test&role=admin
From what I see in the postman request, you are sending a x-www-form-urlencoded request.
You have to specify it in the code. See example: POST request using application/x-www-form-urlencoded
Regarding Charles: since you are using https, you have to enable proxy for the host. More info here: https://www.charlesproxy.com/documentation/proxying/ssl-proxying/

After HTTP POST request UIView change delays

I have use this code to make HTTP POST request:
let myURL = NSURL(string: serverURL)
let request = NSMutableURLRequest(URL: myURL!)
request.HTTPMethod = "POST"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print(error)
} else {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(responseString)
self.view.backgroundColor = UIColor.redColor()
}
}
task.resume()
I have experience that the view's backgroundColor changes after several seconds from the print of the responseString.
Why is that and how can I make it simultaniously?
Thanks.
Always perform UI changes on main thread, so change your view's backgrondColor on the main thread like this way.
dispatch_async(dispatch_get_main_queue(),{
self.view.backgroundColor = UIColor.redColor()
}
You need to perform UI changes on the main thread in order to make it faster, Because i think http requests are made asynchronously for example downloading an image. So in your code you can do as follows
dispatch_async(dispatch_get_main_queue(),{
self.view.backgroundColor = UIColor.redColor()
}

Returning a block inside another block sometimes delayed

I’m trying to migrate to NSURLSession from NSURLConnection. I’ve used delegate methods to return the JSON object IN NSURLConnection previously. But now I’m looking to implement blocks to return the JSON object. I have my own block to return the JSON object since I have NSURLSession Network Aactivity in a separate class common to all service calls. I ahve use its delegate methods and everything is working fine but when I use its in-built block to get the object and return it using my own block thats when things get messy.
I've searched through web and found people using GCD to get the mainqueue and invoking the return block. I tried that too but sometimes it gets delayed too. What is the best practice to do this?
Here's my code
let urlString = baseURL + methodName
let postString = NSString(bytes: postData.bytes, length: postData.length, encoding: NSUTF8StringEncoding)
let request : NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "POST"
request.HTTPBody = postString?.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = requestTimeOutInterval
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
NSURLSession.sharedSession().finishTasksAndInvalidate()
do {
let responseDict: NSMutableDictionary? = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary
dispatch_async(dispatch_get_main_queue()) {
completionHandler(responseDict, nil, true)
}
} catch let err as NSError {
dispatch_async(dispatch_get_main_queue()) {
completionHandler(nil, nil, true)
}
}
})
task.resume()
finishTasksAndInvalidate() acts on session not on Task. Once completion handler is called the task will be marked complete and you dont have to worry about it.

Resources