I am using a REST API (https://restcountries.eu/) and want to download the flag image (which is an .svg) and show it as a UIImage. I tried the standard way with:
func requestData(at url: URL, success: #escaping (_ data: Data) -> Void, failure: ((_ error: NetworkError) -> Void)? = nil) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if responseError != nil {
failure?(.failedRequest)
} else if let data = responseData {
success(data)
} else {
failure?(.corruptedData)
}
}
}
task.resume()
}
and the data downloads fine, but when I try to show the image with UIImage(data: data), the image is nil. Am I missing something?
Related
I'm trying to download a zip file to the user's phone storage in an iOS app. Is it possible to do this without NSURLSession?
Yes, there are multiple tools but you should still try and use URL session.
A very easy way to do this is using Data. But it blocks your thread so you need to work a bit with queues to make it work properly (Otherwise your app MAY crash).
A very simple, non-safe, thread-blocking way would be:
func saveFile(atRemoteURL remoteURL: URL, to localURL: URL) {
let data = try! Data(contentsOf: remoteURL)
try! data.write(to: localURL)
}
But doing it a bit more stable should look something like this:
private func downloadIteam(atURL url: URL, completion: ((_ data: Data?, _ error: Error?) -> Void)?) {
let queue = DispatchQueue(label: "downloading_file")
queue.async {
do {
let data = try Data(contentsOf: url)
completion?(data, nil)
} catch {
completion?(nil, error)
}
}
}
private func saveDataToDocuments(_ data: Data, to: String, completion: ((_ resultPath: URL?, _ error: Error?) -> Void)?) {
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(to)
let queue = DispatchQueue(label: "saving_file")
queue.async {
do {
let folderPath: String = path.deletingLastPathComponent().path
if !FileManager.default.fileExists(atPath: folderPath) {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
}
try data.write(to: path)
completion?(path, nil)
} catch {
completion?(nil, error)
}
}
}
public func downloadFileAndSaveItToDocuments(urlToRemoteFile: URL, completion: #escaping (_ file: (relativePath: String, fullPath: URL)?, _ error: Error?) -> Void) {
let fileName = urlToRemoteFile.lastPathComponent
let relativePath = "downloads/" + fileName
func finish(fullPath: URL?, error: Error?) {
DispatchQueue.main.async {
if let path = fullPath {
completion((relativePath, path), error)
} else {
completion(nil, error)
}
}
}
downloadIteam(atURL: urlToRemoteFile) { (data, error) in
guard let data = data else {
completion(nil, error)
return
}
saveDataToDocuments(data, to: relativePath) { (url, saveError) in
finish(fullPath: url, error: saveError ?? error)
}
}
}
I hope the code is self-documented enough.
I have implemented Service and Content for push notifications. When the image is not available the image view space is still showing as empty. I need only notification text when no image is available.
Here is my code
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if let urlImageString = content.userInfo["image"] as? String {
if let url = URL(string: urlImageString) {
URLSession.downloadImage(atURL: url) { [weak self] (data, error) in
if let _ = error {
return
}
guard let data = data else {
return
}
DispatchQueue.main.async {
self?.imageView.image = UIImage(data: data)
}
}
}
}
}
}
extension URLSession {
class func downloadImage(atURL url: URL, withCompletionHandler completionHandler: #escaping (Data?, NSError?) -> Void) {
let dataTask = URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
completionHandler(data, nil)
}
dataTask.resume()
}
}
and when i click view button of push notification it is showing like
You've to add "UNNotificationExtensionInitialContentSizeRatio" to 0 in your .plist file as the image below here.
UNNotificationExtensionInitialContentSizeRatio
I am fairly new to Swift development, and am wondering why it is taking so long for the image to show. It's a 4.44kb image whose URL is stored in a Firestore document. It takes a second before it shows, which isn't very good for the UX. As I said, the image (a barcode) is just 4.44kb, which makes me wonder why loading for example an image from Instagram or Facebook is faster than loading this image.
My code:
In my viewDidLoad:
dbRef.getDocument { (document, err) in
if let e = err {
print("Error retrieving document: \(e)")
} else {
let data = document?.data()
if let imageURL = data?[K.User.barcodeImage] as? String {
// let url = URL(fileURLWithPath: imageURL)
let url = NSURL(string: imageURL)
self.downloadImage(from: url as! URL)
let number = data![K.User.barcodeNumber] as! String
self.cardNumber.text = number
UserDefaults.standard.set(number, forKey: K.User.barcodeNumber)
UserDefaults.standard.synchronize()
}
}
}
The rest:
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
func downloadImage(from url: URL) {
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() { [weak self] in
self?.cardCode.image = UIImage(data: data)
}
}
}
Does this have to do with DispatchQueue? Thank you in advance!
The 1 second latency is not unreasonable but you can try enabling cache headers from firebase storage / GCS bucket to improve performance.
In case I want to check if the file exists on my iPhone, just use the following code:
let filePath = fileName.path
let fileManager = FileManager.default
if fileManager.fileExists (atPath: filePath) {
}
How can I check if there is a pdf / jpg / png file at the URL:
www.myname.com/files/file1.jpg or www.myname.com/files/file2.pdf etc.?
Could I ask for an example of such a function - but for files on internet web servers?
UPDATE
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
}
}
task.resume()
}
Is it possible to check in this function whether the file is of JPG or PNG type? If so - then we also return true, and if not, false?
Updated:
After the discussion on the comments section, the code is updated to work in more correct way.
You should check for the mimeType of the URLResponse object rather than checking whether the image could be represented as UIImageJPEGRepresentation/UIImagePNGRepresentation or not. Because it doesn't guarantee that the resource is actually a jpg/jpeg or png.
So the mimeType should be the most reliable parameter that needs to considered here.
enum MimeType: String {
case jpeg = "image/jpeg"
case png = "image/png"
}
func remoteResource(at url: URL, isOneOf types: [MimeType], completion: #escaping ((Bool) -> Void)) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let mimeType = response.mimeType else {
completion(false)
return
}
if types.map({ $0.rawValue }).contains(mimeType) {
completion(true)
} else {
completion(false)
}
}
task.resume()
}
Verify with this:
let jpegImageURL = URL(string: "https://vignette.wikia.nocookie.net/wingsoffire/images/5/54/Panda.jpeg/revision/latest?cb=20170205005103")!
remoteResource(at: jpegImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) // true
}
let pngImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/6/69/Giant_panda_drawing.png")!
remoteResource(at: pngImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //true
}
let gifImageURL = URL(string: "https://media1.tenor.com/images/f88f6514b1a800bae53a8e95b7b99172/tenor.gif?itemid=4616586")!
remoteResource(at: gifImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //false
}
Previous Answer:
You can check if the remote data can be represented as UIImageJPEGRepresentation or UIImagePNGRepresentation. If yes, you can say that remote file is either JPEG or PNG.
Try this:
func remoteResource(at url: URL, isImage: #escaping ((Bool) -> Void)) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data, let image = UIImage(data: data) {
if let _ = UIImageJPEGRepresentation(image, 1.0) {
isImage(true)
} else if let _ = UIImagePNGRepresentation(image) {
isImage(true)
} else {
isImage(false)
}
} else {
isImage(false)
}
}
task.resume()
}
Usage:
let imageURL = URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL®ion=1")!
remoteResource(at: imageURL) { (isImage) in
print(isImage) // prints true for your given link
}
It’s about getting data from an URL. If the data is nil the file don’t exist.
Getting data from an URL in Swift
If you want to know the file exists on a server, then it requires sending a HTTP request and receiving the response.
Please try with following code,
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
} else {
completion(false)
}
}
task.resume()
}
UPDATE
remoteFileExistsAt(url: URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL®ion=1")!) { (success) in
print(success)
}
So we have this function that retrieves JSON data and presents it in its completion block, what I'm trying to understand is why use the signature: ((Data) -> Void) instead of just (Data), is the void really necessary? Here is the function:
typealias JSONData = ((Data) -> Void)
func getJSONData(type: String, urlExtension: String, completion: #escaping JSONData) {
let request = URLRequest(url: URL(string:"\(baseURL)\(type)/\(urlExtension)?api_key=\(apiKey)®ion=US&append_to_response=videos,images,releases")! )
let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) in
if error == nil {
if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
if let data = data {
completion(data)
}
default:
print(httpResponse.statusCode)
}
}
} else {
DispatchQueue.main.async {
if let error = error {
print("Error: \(error.localizedDescription)") }
return
}
}
})
dataTask.resume()
}
Swift syntax dictates that you must declare closures with a return type after the ->.
You have two options:
typealias JSONData = (Data) -> Void
typealias JSONData = (Data) -> ()
I see Apple using #1 most frequently.