Progress of a Alamofire request - ios

Will it be possible to show progress for
Alamofire.request(.POST, URL, parameters: parameter, encoding: .JSON)
.responseJSON { response in
// Do your stuff
}
I get my images/documents as a base64 string then I convert it as files in mobile
Can I show a progress bar with percentage?
I am using Alamofire, Swift 2

The way you monitor progress in Alamofire is using the progress closure on a Request. More details on usage can be found in the README. While that example in the README demonstrates usage on a download request, it still works on a data request as well.
The one important note is that you do not always get perfect reporting back from the server for a data request. The reason is that the server does not always report an accurate content length before streaming the data. If the content length is unknown, the progress closure will be called, but the totalExpectedBytesToRead will always be -1.
In this situation, you can only report accurate progress if you know the approximate size of the data being downloaded. You could then use your approximate number in conjunction with the totalBytesRead value to compute an estimated download progress value.

Alamofire 4.0 and Swift 4.x
func startDownload(audioUrl:String) -> Void {
let fileUrl = self.getSaveFileUrl(fileName: audioUrl)
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileUrl, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(audioUrl, to:destination)
.downloadProgress { (progress) in
self.surahNameKana.text = (String)(progress.fractionCompleted)
}
.responseData { (data) in
// at this stage , the downloaded data are already saved in fileUrl
self.surahNameKana.text = "Completed!"
}
}

To make a download with progress for Swift 2.x users with Alamofire >= 3.0:
let url = "https://httpbin.org/stream/100" // this is for example..
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, url, parameters: params, encoding: ParameterEncoding.URL,destination:destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
// Here you can update your progress object
print("Total bytes read on main queue: \(totalBytesRead)")
print("Progress on main queue: \(Float(totalBytesRead) / Float(totalBytesExpectedToRead))")
}
}
.response { request, _, _, error in
print("\(request?.URL)") // original URL request
if let error = error {
let httpError: NSError = error
let statusCode = httpError.code
} else { //no errors
let filePath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
print("File downloaded successfully: \(filePath)")
}
}

Related

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

How to cancel a URL session request

I am upload multiple image to server using convert image to base64 and send image in a API as a parameter. But when we call api again and again then how to stop api calling on button click. I am using below code to call API.
Thanks in advance
let urlPath: String = "URL"
let url: URL = URL(string: urlPath)!
var request1 = URLRequest(url: url)
request1.httpMethod = "POST"
let stringPost="imgSrc=\(image)"
let data = stringPost.data(using: String.Encoding.utf8)
// print("data\(data)")
request1.httpBody=data
request1.timeoutInterval = 60
let _:OperationQueue = OperationQueue()
let task = session.dataTask(with: request1){data, response, err in
do
{
if data != nil
{
print("data\(String(describing: data))")
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
{
DispatchQueue.main.async
{
print("json\(jsonResult)")
}
}
}
catch let error as NSError
{
DispatchQueue.main.async
{
print("error is \(error)")
print("error desc \(error.localizedDescription)")
}
}}
task.resume()
Make the object task as a global variable, then you can cancel it anywhere by:
task.cancel()
Alternatively, if the object session is a URLSession instance, you can cancel it by:
session.invalidateAndCancel()
If you don't want to allow API call again if there is any previous download is on progress, you can do as follows,
Make your task(URLSessionDataTask type) variable as global variable in the class as follows,
let task = URLSessionDataTask()
Then on your button action do as below by checking the task download status,
func uploadButtonPressed() {
if task.state != .running {
// Make your API call here
} else {
// Dont perform API call
}
}
You can make use following states like running which is provide by URLSessionDataTask class and do action accordingly as per your need,
public enum State : Int {
case running
case suspended
case canceling
case completed
}
You can check result of your task. And if everything is alright you can
task.resume()
but if not
task.cancel()

Alamofire cancel download on destination error

I am working on a project and trying to figure out if my download destination may fail.
As an example, if the destination URL or path fails to be clear because it already exists, I would like to handle this and prevent the request to be made.
Is there a simple way to stop a download request while in the destination closure ?
Alamofire
.download(
.GET,
url,
destination:{
(temporaryURL, response) in
let manager = NSFileManager.defaultManager()
let directoryURL = manager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
let localPath = directoryURL.URLByAppendingPathComponent(NSBundle.mainBundle().bundleIdentifier ?? "Default", isDirectory:true).URLByAppendingPathComponent(pathComponent!)
if (manager.fileExistsAtPath(localPath!.path!)) {
do {
try manager.removeItemAtURL(localPath!)
} catch let error {
print(error)
==>
HOW CAN I GET THIS TO STOP THE DOWNLOAD
AND CALL RESPONSE WITH AN ERROR
<==
}
}
return localPath!
}
)
.validate(statusCode: 200..<300)
.response {
...
};
As a bonus, I'd love to be able to send this to the response closure as an error, which would make error handling way easier.

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

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
}

Alamofire POST request with progress

I'm using Alamofire to do a POST request.
As this POST request can take a while and I want to keep track of the progress and display it as a ProgressView.
Alamofire.request(.POST, ApiLink.create_post, parameters: parameters, encoding: .JSON)
.progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) -> Void in
println("ENTER .PROGRESSS")
println("\(totalBytesRead) of \(totalBytesExpectedToRead)")
self.progressView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
}
.responseJSON { (_, _, mydata, _) in
println(mydata)
}
However, I've noticed that the .progress block only get called after the post request has ended instead of getting called multiple times to actually keep track of the progress.
println("ENTER .PROGRESSS") gets called only once (at the end)
How can I make .progress works with Alamofire.request POST ?
Also : My parameters include a base64 encoded image string. I'm using a back-end Ruby on Rails to process the image. It's that process that is taking quite some time.
What you can do instead is first use the ParameterEncoding enum to generate the HTTPBody data. Then you can pull that data out and pass it off to the Alamofire upload method. Here's a modified version of your same function that compiles in a playground and instead uses the upload function.
struct ApiLink {
static let create_post = "/my/path/for/create/post"
}
let parameters: [String: AnyObject] = ["key": "value"] // Make sure this has your image as well
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: ApiLink.create_post)!)
mutableURLRequest.HTTPMethod = Method.POST.rawValue
let encodedURLRequest = ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
let data = encodedURLRequest.HTTPBody!
let progressView = UIProgressView()
Alamofire.upload(mutableURLRequest, data)
.progress { _, totalBytesRead, totalBytesExpectedToRead in
println("ENTER .PROGRESSS")
println("\(totalBytesRead) of \(totalBytesExpectedToRead)")
progressView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
}
.responseJSON { _, _, mydata, _ in
println(mydata)
}
This will certainly have progress updates as #mattt originally mentioned in his comment above.
As #cnoon said you can use upload method to track progress with some modifications. Here is what exactly worked with me:
let jsonData = NSJSONSerialization.dataWithJSONObject(jsonObject, options: .PrettyPrinted)
Alamofire.upload(.POST, "API URL String", headers: ["Content-Type": "application/json"], data: jsonData)
.validate()
.responseJSON(completionHandler: { (response: Response<AnyObject, NSError>) in
//Completion handler code
})
.progress({ (bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) in
//Progress handler code
})
Note that you must set the "Content-Type" http header field value to "application/json" if the data is formatted as json to be decoded correctly at the backend.
There's a separate method in Alamofire to upload. Please check their documentation here.
They have sub-sections
Uploading with Progress
Uploading MultipartFormData
Which describes uploading.

Resources