Best practice to safely load image from url - error-handling

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

Related

im having difficulty getting downloaded json response into my data model and accessing it in code

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

Import & Export PDF & Word Doc SwiftUI

I was not able to import or export PDF nor .doc documents to my app. This code below saves selected pdf inside iCloud or phone memory but when I try to view the pdf file it always converts to a document with only text "Hello World".
I guess I need to change allowedContentTypes in .fileImporter and contentType inside .fileExporter but after some research I could not find working example on the web.
#State private var document: MessageDocument = MessageDocument(message: "Hello, World!")
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [UTType.pdf],
allowsMultipleSelection: false
) { result in
do {
guard let selectedFile: URL = try result.get().first else { return }
//trying to get access to url contents
if (CFURLStartAccessingSecurityScopedResource(selectedFile as CFURL)) {
guard let message = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return }
document.message = message
//done accessing the url
CFURLStopAccessingSecurityScopedResource(selectedFile as CFURL)
}
else {
print("Permission error!")
}
} catch {
// Handle failure.
print(error.localizedDescription)
}
}
.fileExporter(
isPresented: $isExporting,
document: document,
contentType: UTType.data,
defaultFilename: "Message"
) { result in
if case .success = result {
// Handle success.
} else {
// Handle failure.
}
}
}
}
MessageDocument
import SwiftUI
import UniformTypeIdentifiers
struct MessageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var message: String
init(message: String) {
self.message = message
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
message = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
return FileWrapper(regularFileWithContents: message.data(using: .utf8)!)
}
}

Download APNG File

I am getting some issues related to APNG file, APNG file animation working perfect if i put APNG files in resource bundle , But when i have download same APNG file from assets server and saving APNG file into resource directory and then load using MSSticker like this way. after loading it showing only first frame.if anyone wanna try to check APNG file please have a look to this.
let imagePath = Bundle.main.path(forResource: imgName, ofType: ".png")
let pathurl = URL(fileURLWithPath: imagePath!)
do {
try cell.stickerview.sticker = MSSticker(contentsOfFileURL: pathurl, localizedDescription: "anything that you want")
}
catch {
fatalError("Failed to create sticker: \(error)")
}
Here i am saving image & getting saved image url from resource directory:
static func saveImage(image: UIImage , name:String) -> Bool? {
guard let data = UIImagePNGRepresentation(image) else {
return false
}
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
return false
}
do {
try data.write(to: directory.appendingPathComponent(name)!)
return true
} catch {
print(error.localizedDescription)
return false
}
}
static func getSavedImageUrl(named: String) -> URL? {
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
return URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named)
}
return nil
}
I have written the extension in custom MSSticker class
extension MSStickerView {
func downloadedFrom(url: URL , name: String) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { () -> Void in
// self.sticker = image
_ = GameUtil.saveImage(image: image, name: name)
if let pathurl = GameUtil.getSavedImageUrl(named: name) {
do {
try self.sticker = MSSticker(contentsOfFileURL: pathurl, localizedDescription: "Raid")
}
catch {
fatalError("Failed to create sticker: \(error)")
}
}
self.startAnimating()
}
}.resume()
}
func downloadedFrom(link: String , name: String) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url ,name: name)
}
I think problem is this UIImagePNGRepresentation. Why convert Data to UIImage and then use UIImagePNGRepresentation.
Try saving data directly.
static func saveData(data: Data , name:String) -> Bool? {
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
return false
}
do {
try data.write(to: directory.appendingPathComponent(name)!)
return true
} catch {
print(error.localizedDescription)
return false
}
}
And ignore image just pass data.
_ = GameUtil.saveImage(data: data, name: name)

session.dataTask synchronization issue

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

Formating json object into Array of dictionary in swift

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.