I have some trouble with the code below.
Though it works, there is some timing problem.
First let me say what I expect, I suppose the completion handler should be run when the data download is complete and my image ready to use. But reality seems to be quite different. When I try it the completion handler is called right away (I can see 'All OK' in the console) as if everything was instantaneous. But the image gets actually displayed much later. What am I missing?
let imageURL = URL(string: myURLString)
session = URLSession.shared,
_ = session.dataTask(with: imageURL) {[weak self]
(data: Data?, response: URLResponse?, error: Error?) in
if error == nil {
print("All OK")
self?.theImage = UIImage(data: data!)
self?.theView.image = self?.theImage
} else {print(error!)}
DispatchQueue.main.async {
self?.activityIndicator.stopAnimating()
self?.theView.setNeedsDisplay()
}
}.resume()
Can you try this code?
The control should not be actually going inside the handler at first call. And I think there are a few mistakes in your code as well which I pointed out earlier, especially the main thread is required for updating UI.
let session : URLSession
let config = URLSessionConfiguration.default
var resultFromServer: Any?
let responseResultData = [String:Any]()
session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
session.dataTask(with: request) { (data, response, error ) in
if error != nil {
DispatchQueue.main.async(execute: {
session.invalidateAndCancel()
})
}else{
let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
do{
resultFromServer = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 || httpResponse.statusCode == 202 || httpResponse.statusCode == 204 || httpResponse.statusCode == 203 {
if let respArr = resultFromServer as? [Any]{
//resp is array
}else if let respdict = resultFromServer as? [String : Any] {
//resp is dict
}else{
//resp is something else maybe string, etc
}
}
else {
//error status code something like 500, 404, etc
}
}
catch let error as NSError {
DispatchQueue.main.async(execute: {
session.invalidateAndCancel()
})
}
}
session.finishTasksAndInvalidate()
}.resume()
Related
i have the following code in which i'm trying to download exchange rates into my app to use in currency conversion.
The data fetch seems to work ok, as does the json decoding model, but i'm unable to get the data through the Rates variable
import SwiftUI
struct ExchangeRates: Codable {
var conversionRates: [String: Double]?
init(conversionRates:[String:Double]) {
self.conversionRates = conversionRates
}
enum CodingKeys: String, CodingKey {
case conversionRates = "conversion_rates"
}
}
class DownloadingData:ObservableObject{
#Published var Rates:ExchangeRates = ExchangeRates.init(conversionRates: ["test" : 0])
init() {
datatask()
}
func datatask() {
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(String(describing: error))")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let rates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(rates) **// this works and prints out data**
DispatchQueue.main.async {
[weak self] in
self?.Rates = rates
print(self?.Rates) **// this works and prints out the data**
}
print(self.Rates) **// this doesnt print out anything**
}.resume()
print(Rates) **// this doesnt print out anything**
}
}
i can't seem to get the data into the Rates Variable
any guidance please
thanks
here is a sample of the console output :
ExchangeRates(conversionRates: Optional(["test": 0.0]))
Optional(test3.ExchangeRates(conversionRates: Optional(["BZD": 2.7002, "PHP": 68.7948, "PGK": 4.725, "BND": 1.8176, "HNL": 32.8885, "TND": 3.7553, "BDT": 115.2218, "SBD": 10.6866, "NIO": 47.4824, "XDR": 0.963, "IDR": 19213.9064, "XCD": 3.6453, "CAD": 1.7152, "UGX": 4778.6135,])
you could try this, using your ExchangeRates struct:
class DownloadingData: ObservableObject{
#Published var rates = ExchangeRates(conversionRates: ["test" : 0])
init() {
datatask()
}
func datatask() {
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(error)")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let exRates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(exRates)
DispatchQueue.main.async {
self.rates = exRates // <--- here
print(self.rates) // <--- here
}
// print(self.rates) // <--- NEVER here
}.resume()
}
}
EDIT-1: with completion closure:
class DownloadingData: ObservableObject{
#Published var rates = ExchangeRates(conversionRates: ["test" : 0])
init() {
datatask() { isDone in
print(self.rates) // <--- here OK
}
}
func datatask(completion: #escaping(Bool) -> ()) { // <--- here
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(error)")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let exRates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(exRates) // <--- here OK
DispatchQueue.main.async {
self.rates = exRates
print(self.rates) // <--- here OK
completion(true) // <--- here return completion
}
}.resume()
}
}
Declare rates – please with starting lowercase letter – as empty dictionary
#Published var rates = [String:Double]()
In the struct delete the init method and declare the dictionary also non-optional
struct ExchangeRates: Decodable {
let conversionRates: [String: Double]
enum CodingKeys: String, CodingKey {
case conversionRates = "conversion_rates"
}
}
in the DispatchQueue closure assign the value of conversionRates to rates
DispatchQueue.main.async { // no weak self needed
self.rates = rates.conversionRates
}
In the view enumerate the dictionary rates
I'm having a scenario like :
1) I want to create Http POST request and for this I'm having the data, please see this image:
2) As you can see in the above image, I have to create post a request with the mentioned body and also I'm getting response named: token. How to create post request and fetch this token response?.
3) That token response will allow me to login into myapp.
I'm newbie to this scenario. I have tried some code by my own but still getting confuse in how to combine my app delegate code with this POST Request Code.
Code
#IBAction func signinaction(_ sender: Any) {
self.username.resignFirstResponder()
self.password.resignFirstResponder()
if (self.username.text == "" || self.password.text == "") {
let alertView = UIAlertController(title: "Login failed",
message: "Wrong username or password." as String, preferredStyle:.alert)
let okAction = UIAlertAction(title: "Try Again!", style: .default, handler: nil)
alertView.addAction(okAction)
self.present(alertView, animated: true, completion: nil)
return
}
// Check if the user entered an email
if let actualUsername = self.username.text {
// Check if the user entered a password
if let actualPassword = self.password.text {
// Build the body message to request the token to the web app
self.bodyStr = "username=8870417698&password=1234&grant_type=password" + actualUsername + "&password=" + actualPassword
// Setup the request
let myURL = NSURL(string: "http://ezschoolportalapi.azurewebsites.net/token")!
let request = NSMutableURLRequest(url: myURL as URL)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = bodyStr.data(using: String.Encoding.utf8)!
let task = URLSession.shared.dataTask(with: request as URLRequest) {
(data, response, error) -> Void in
if data?.count != 0
{
do {
let tokenDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! NSDictionary
print(tokenDictionary)
// Get the token
let token:String = tokenDictionary["access_token"] as! String
// Keep record of the token
let userdefaults = UserDefaults()
let saveToken = userdefaults.set(token, forKey: "access_token")
userdefaults.synchronize()
// Dismiss login view and go to the home view controller
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
}
}
catch {
// Wrong credentials
// Reset the text fields
self.username.text = ""
self.password.text = ""
// Setup the alert
let alertView = UIAlertController(title: "Login failed",
message: "Wrong username or password." as String, preferredStyle:.alert)
let okAction = UIAlertAction(title: "Try Again!", style:.default, handler: nil)
alertView.addAction(okAction)
self.present(alertView, animated: true, completion: nil)
return
}
}
}
task.resume()
}
}
}
Question is how to combine this code with my above code :
let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.gotoMainvc()
if I use directly this code then in any of the case I'm able to switch over to my home screen it doesn't matter whether m using this POST Request code or not. Please Help.
You are hard coding the username and password in bodyStr update it to
self.bodyStr = "username=" + actualUsername + "&password=" + actualPassword + "&grant_type=password"
Update the statements inside do with
do {
let tokenDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! NSDictionary
print(tokenDictionary)
// Get the token
if let authToken = tokenDictionary["access_token"] as? String{
self.token = authToken
UserDefaults.standard.set(accessToken, forKey: "access_token")
UserDefaults.standard.synchronize()
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
}
}
}
You can implement a function like isUserLoggedIn which will return true if token is saved in userDefaults. You need to check whether the user has logged in in the appDelegate's applicationdidFinishLaunchingWithOptions.Like
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if isUserLoggedIn(){
//showHomeViewController
} else{
//showLoginViewController
}
return true
}
func isUserLoggedIn() -> Bool{
if let accessToken = UserDefaults.standard.object(forKey: "access_token") as? String {
if (accessToken.characters.count)! > 0{
return true
} else {
return false
}
}
else {
return false
}
}
let todoEndpoint: String ="http://localhost:16789/api/product"
guard let url = NSURL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(url: url as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest as URLRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET")
print(error)
return
}
do{
self.users = try JSONSerialization.jsonObject(with: responseData, options: []) as? Array}
catch{
print("Error parsing")
}
}
task.resume()
this is my Output my API
[{"ProductId":2,"Category":"Laptop","Brand":"Apple","ModelName":"MacBook Pro","SerialNumber":"Test1","DatePurchased":"2016-08-12T00:00:00","EmployeeName":"Vismita Shetty","DateAssigned":"2017-01-17T00:00:00"},{"ProductId":4,"Category":"Keyboard","Brand":"Logitech","ModelName":"K200","SerialNumber":"TestKeyboard1","DatePurchased":"2017-01-23T03:02:13.247","EmployeeName":"Vismita Shetty","DateAssigned":"2017-01-17T00:00:00"},{"ProductId":5,"Category":"Keyboard","Brand":"Logitech","ModelName":"K200","SerialNumber":"testkeyboard2","DatePurchased":"2017-01-23T03:03:26.07","EmployeeName":"Suraj Pangam","DateAssigned":"2017-01-17T00:00:00"}]
What I currently trying to do convert user into different variable which will be of type Array of dictionaries in swift. So that Each object can be accessed by key. I want to make it generic so that even some properties got changed it won't matter as it won't be accessed by providing property name. Like (users[indexPath.row] as? [String: AnyObject])?["EmployeeName"] as! String!.
Instead if i make it to dictionary i will be easily do it by running for (key,value) loop for array of dictionary.
Sorry for my English I'll do my best.
I have an issue trying to upload photos from the user's library.
First, I get user's photo with this method
func grabPhotos(){
let imgManager = PHImageManager.defaultManager()
let requestOptions = PHImageRequestOptions()
requestOptions.synchronous = false
requestOptions.deliveryMode = .FastFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: fetchOptions){
if fetchResult.count > 0{
for i in 0..<fetchResult.count{
let asset = fetchResult.objectAtIndex(i) as! PHAsset
if NSComparisonResult.OrderedSame == asset.creationDate!.compare(self.appDelegate.dateLastUpload!){
print("meme date")
}
else if NSComparisonResult.OrderedAscending == asset.creationDate!.compare(self.appDelegate.dateLastUpload!){
}
else {
imgManager.requestImageDataForAsset(asset, options: requestOptions, resultHandler: { (data, string, orientation, objects) in
self.Upload((UIImage(data: data!)?.CGImage)! , nomImage: "\(asset.creationDate)" )
})
}
}
}
else{
print("you got no photos")
}
}
}
as you can see, each time I get a photo I want to upload it to my server.
the upload part works well.
Here is the upload method
func clickUpload(image:CGImage,nomImage : String){
let url = NSURL(string: "http://192.168.1.20:1993/upload")
let image_photo = UIImage(CGImage: image)
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
let boundary = generateBoundaryString()
//define the multipart request type
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
if var image_data = UIImageJPEGRepresentation(image_photo,0.8){
let body = NSMutableData()
let fname = nomImage
let mimetype = "image/jpg"
//define the data post parameter
body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition:multipart/form-data; name=\"test\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("enctype=\"multipart/form-data".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("hi\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition:form-data; name=\"file\"; filename=\"\(fname)\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: \(mimetype)\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(image_data)
body.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
//request.setValue("multipart/form-data", forHTTPHeaderField: "content-Type")
request.HTTPBody = body
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
task.resume()
}
else {
print(« data nil")
}
}
Now problems come... It works well if I upload photos with reduced size, but I want to upload them in HighQualityFormat.
I got 170 photos on my device, and it uploads approximatively 80 photos before crashing with this message
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSAllocateMemoryPages(1504802) failed'
Could you guys help me to solve it or give me another way to achieve this?
Thank you all.
I have the following code snippet to load an image from an url:
let url = NSURL(string: imageUrl)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
In case that my variable imageUrl has a valid string value, what is the most secure way to protect this code against possible edge cases?
Following code seems not to be very handy:
if let url = NSURL(string: imageUrl) {
if let data = NSData(contentsOfURL: url) {
if let image = UIImage(data: data) {
// success -> do something with the image...
}
else {
// print error message
}
}
else {
// print error message
}
}
else {
// print error message
}
The best practice is not to use a synchronous method like contentsOfURL to load data from over the network.
The recommended way is NSURLSession which works asynchronously.
This is a simple example with a completion block and an enum with associated types,
it catches all possible errors
enum Result {
case Success(UIImage), Failure(NSString)
}
func loadImage(string : String, completion: (Result) -> ()) {
guard let url = NSURL(string: string) else {
completion(.Failure("Bad URL"))
return
}
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
completion(.Failure(error!.localizedDescription))
} else {
guard let image = UIImage(data: data!) else {
completion(.Failure("Could not load image data"))
return
}
completion(.Success(image))
}
}.resume()
}
Call it with:
loadImage("http://myserver.com/path/to/image.png") { result in
switch result {
case .Success(let image) :
// do something with the image
case .Failure(let error) :
print(error)
}
}