Attach image to notification given image URL - ios

I want to attach an image to my local notifications given an image URL. This is the extension to create an attachment:
import UserNotifications
extension UNNotificationAttachment {
static func create(identifier: String, image: UIImage, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let imageFileIdentifier = identifier+".png"
let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
guard let imageData = UIImagePNGRepresentation(image) else {
return nil
}
try imageData.write(to: fileURL)
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
return imageAttachment } catch {
print("error " + error.localizedDescription)
}
return nil
}
}
When I schedule a new notification, I use it like this:
// url of the image such as http://www.unsplash.com/image.png
let data = try? Data(contentsOf: url)
guard let myImage = UIImage(data: data!) else { return }
if let attachment = UNNotificationAttachment.create(identifier: key, image: myImage, options: nil) {
content.attachments = [attachment]
}
Creating a notification like this freezes the application for a few seconds because the app downloads the image synchronously. I have also tried to use DispatchQueue but it didn't change anything. What did I do wrong?

Your code downloads an image, parses it to create a UIImage, converts the image back to a block of PNG data, then writes this data to a temporary file.
You can skip the step where you create the UIImage and convert it back to a file.
Try using URLSession and URLDataTask:
let fileURL = ...
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
do {
try imageData.write(to: fileURL)
let attachment = UNNotificationAttachment.create(identifier: key, image: myImage, options: nil)
// call closure to call back with attachment and/or error
}
catch let ex {
// call closure with error
}
}
task.resume()
I've left out some error handling and other details, but this should give you the general idea of what's required to do it asynchronously. URLSessions use GCD to perform asynchronous networking.

Download the image asynchronously using Alamofire then try to show it.
let destination: DownloadRequest.DownloadFileDestination = {
_, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("image.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(url, to: destination).response {
response in
// do whatever you want with your image, for example if it is an audio file:
do {
self.player = try AVAudioPlayer(contentsOf: URL(string: "\(response.destinationURL!)")!)
self.player.volume = 1.0
self.player.play()
} catch {
print(error)
}
}

Related

Download file with right extension

Problem with downloading files.
I'm trying to download a file form url and push it into open/share modal. But da data downloads as Data and if I try saving it to File app it saves a file called Data.
I just need to download and share the file with the original extension. Like file.extension.
Here's the code use. I used Alamofire pod here:
AF.download(url).responseData { response in
if let preset = response.value {
let shareArray = [preset]
let activityViewController = UIActivityViewController(activityItems: shareArray , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
Also tried this code but the app crashed:
if let url = URL(string: downloadURL!) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
let shareArray = [localURL]
let activityViewController = UIActivityViewController(activityItems: shareArray , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
task.resume()
}
The issue there is that you are trying to share the temporary file returned. It has a dynamic UTI (Unified Type Identifier). You need to get the url response suggested file name and rename the file.
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension URL {
var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
}
let url = URL(string: "https://i.stack.imgur.com/varL9.jpg")!
URLSession.shared.downloadTask(with: url) { location, response, error in
guard let location = location,
let httpURLResponse = response as? HTTPURLResponse,
httpURLResponse.statusCode == 200 else { return }
let fileName = httpURLResponse.suggestedFilename ?? httpURLResponse.url?.lastPathComponent ?? url.lastPathComponent
let destination = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
do {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
print("location", location.typeIdentifier ?? "no UTI") // location dyn.ah62d4rv4ge81k5pu
try FileManager.default.moveItem(at: location, to: destination)
print("destination", destination.typeIdentifier ?? "no UTI") // destination public.jpeg
} catch {
print(error)
}
}.resume()
To complete the answer from #leo Dabus, there is a need to modify the destination URL further and append the file extension you desire by using
FileManager.default.moveItem(at:to:)
https://developer.apple.com/documentation/foundation/filemanager/1414750-moveitem
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// create destination URL with the original pdf name
print("fileDownload: urlSession")
guard let url = downloadTask.originalRequest?.url else { return }
print("fileDownload: urlSession \(url)")
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
// delete original copy
try? FileManager.default.removeItem(at: destinationURL)
// append a new extension type
let newURL = destinationURL.deletingPathExtension().appendingPathExtension("pdf")
// copy from temp to Document
try? FileManager.default.moveItem(at: destinationURL, to: newURL)
do {
try FileManager.default.copyItem(at: location, to: newURL)
myViewDocumentsmethod(PdfUrl:destinationURL)
print("fileDownload: downloadLocation", destinationURL)
} catch let error {
print("fileDownload: error \(error.localizedDescription)")
}
}

How to download and save an audio file and then play it in swift?

I am trying to download an audio file from the internet and save it onto the phone. This is the download function:
func download() {
if let audioUrl = downloadUrl {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("The file already exists at path")
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
print("File moved to documents folder")
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
}
Then, after I close and open the app, I use the following function to retrieve the url and play it using an AVPlayer:
func getUrl2() {
if let audioUrl = downloadUrl {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
if let u = self.destinationUrl {
let player = AVPlayer(url: u)
print(u)
print("Bouta play")
print(CMTimeGetSeconds(player.currentItem!.duration))
player.play()
}
}
}
The duration that keeps getting printed out is "nan". Is there a way to check if the audio file is actually downloading? Or could it be a problem with retrieving the file after the download? Thanks in advance.
First of all you have to check for the URL is not empty with the below logic:
if !link.isEmpty{
checkBookFileExists(withLink: link){ [weak self] downloadedURL in
guard let self = self else{
return
}
play(url: downloadedURL)
}
}
Then checkBookFileExists function will check if the file already saved or not before download it again:
func checkBookFileExists(withLink link: String, completion: #escaping ((_ filePath: URL)->Void)){
let urlString = link.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
if let url = URL.init(string: urlString ?? ""){
let fileManager = FileManager.default
if let documentDirectory = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create: false){
let filePath = documentDirectory.appendingPathComponent(url.lastPathComponent, isDirectory: false)
do {
if try filePath.checkResourceIsReachable() {
print("file exist")
completion(filePath)
} else {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
} catch {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
}else{
print("file doesnt exist")
}
}else{
print("file doesnt exist")
}
}
Then if the file doesn't exists you will download it with the below function:
func downloadFile(withUrl url: URL, andFilePath filePath: URL, completion: #escaping ((_ filePath: URL)->Void)){
DispatchQueue.global(qos: .background).async {
do {
let data = try Data.init(contentsOf: url)
try data.write(to: filePath, options: .atomic)
print("saved at \(filePath.absoluteString)")
DispatchQueue.main.async {
completion(filePath)
}
} catch {
print("an error happened while downloading or saving the file")
}
}
}
That function will save it and you can play it with:
func play(url: URL) {
print("playing \(url)")
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.prepareToPlay()
audioPlayer?.delegate = self
audioPlayer?.play()
let percentage = (audioPlayer?.currentTime ?? 0)/(audioPlayer?.duration ?? 0)
DispatchQueue.main.async {
// do what ever you want with that "percentage"
}
} catch let error {
audioPlayer = nil
}
}

How to creat the image download progressView by swift

I have a question,and I want to show the progressView when I download the Image to my local file
I write a function to download Image, and take other question as reference.
but I don't know how to use URLSessionDownloadTak or other download progress function in my function.
This is my download function code:
func ImageFromUrl(imageView:UIImageView,url:String,chatroomId:String) {
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(Image)/")
// create a name for your image
let fileName = url + ".jpg"
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName)
let urlString = URL(string: url)
if let image = UIImage(contentsOfFile: fileURL.path)
{
imageView.image = image
return
}
DispatchQueue.global().async {
let data = try? Data(contentsOf: urlString!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
if data != nil
{
if let image = UIImage(data: data!)
{
if !FileManager.default.fileExists(atPath: fileURL.path) {
if let jpegData = UIImageJPEGRepresentation(image, 0.001)
{
do {
try jpegData.write(to: fileURL, options: .atomic)
} catch {
debug(object: error)
}
}
} else {
debug(object:"file already exists")
}
DispatchQueue.main.async {
imageView.image = image//UIImage(data: data!)
}
}
}
}
}
If you want a simple solution instead of NSURLSession, I would suggest Alamofire. It has a simple method to do this kind of task.
For more information https://github.com/Alamofire/Alamofire
Alamofire.download(urlString)
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.result.value {
let image = UIImage(data: data)
}
}

Saving image and then loading it in Swift (iOS)

I am saving an image using saveImage.
func saveImage (image: UIImage, path: String ) -> Bool{
let pngImageData = UIImagePNGRepresentation(image)
//let jpgImageData = UIImageJPEGRepresentation(image, 1.0) // if you want to save as JPEG
print("!!!saving image at: \(path)")
let result = pngImageData!.writeToFile(path, atomically: true)
return result
}
New info:
Saving file does not work properly ("[-] ERROR SAVING FILE" is printed)--
// save your image here into Document Directory
let res = saveImage(tempImage, path: fileInDocumentsDirectory("abc.png"))
if(res == true){
print ("[+] FILE SAVED")
}else{
print ("[-] ERROR SAVING FILE")
}
Why doesn't the saveImage function save the image? Access rights?
Older info:
The debug info says:
!!!saving image at: file:///var/mobile/Applications/BDB992FB-E378-4719-B7B7-E9A364EEE54B/Documents/tempImage
Then I retrieve this location using
fileInDocumentsDirectory("tempImage")
The result is correct.
Then I am loading the file using this path
let image = UIImage(contentsOfFile: path)
if image == nil {
print("missing image at: \(path)")
}else{
print("!!!IMAGE FOUND at: \(path)")
}
The path is correct, but the message is "missing image at..". Is the file somehow inaccessible or not stored? What can be a reason for this behavior?
I am testing this code on iphone 4 with ios 7 and iphone 5 with ios 7 simulator.
Edit:
1. The fileInDocumentsDirectory function
func fileInDocumentsDirectory(filename: String) -> String {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileURL = documentsURL.URLByAppendingPathComponent(filename).absoluteString
return fileURL
}
This function will save an image in the documents folder:
func saveImage(image: UIImage) -> Bool {
guard let data = UIImageJPEGRepresentation(image, 1) ?? 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("fileName.png")!)
return true
} catch {
print(error.localizedDescription)
return false
}
}
To use:
let success = saveImage(image: UIImage(named: "image.png")!)
This function will get that image:
func getSavedImage(named: String) -> UIImage? {
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path)
}
return nil
}
To use:
if let image = getSavedImage(named: "fileName") {
// do something with image
}
iOS 13+ Swift 5.1
iOS 12 introduced some API Changes.
func saveImage(imageName: String, image: UIImage) {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileName = imageName
let fileURL = documentsDirectory.appendingPathComponent(fileName)
guard let data = image.jpegData(compressionQuality: 1) else { return }
//Checks if file exists, removes it if so.
if FileManager.default.fileExists(atPath: fileURL.path) {
do {
try FileManager.default.removeItem(atPath: fileURL.path)
print("Removed old image")
} catch let removeError {
print("couldn't remove file at path", removeError)
}
}
do {
try data.write(to: fileURL)
} catch let error {
print("error saving file with error", error)
}
}
func loadImageFromDiskWith(fileName: String) -> UIImage? {
let documentDirectory = FileManager.SearchPathDirectory.documentDirectory
let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)
if let dirPath = paths.first {
let imageUrl = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
let image = UIImage(contentsOfFile: imageUrl.path)
return image
}
return nil
}
Details
Xcode Version 10.2 (10E125), Swift 5
Solution
// save
extension UIImage {
func save(at directory: FileManager.SearchPathDirectory,
pathAndImageName: String,
createSubdirectoriesIfNeed: Bool = true,
compressionQuality: CGFloat = 1.0) -> URL? {
do {
let documentsDirectory = try FileManager.default.url(for: directory, in: .userDomainMask,
appropriateFor: nil,
create: false)
return save(at: documentsDirectory.appendingPathComponent(pathAndImageName),
createSubdirectoriesIfNeed: createSubdirectoriesIfNeed,
compressionQuality: compressionQuality)
} catch {
print("-- Error: \(error)")
return nil
}
}
func save(at url: URL,
createSubdirectoriesIfNeed: Bool = true,
compressionQuality: CGFloat = 1.0) -> URL? {
do {
if createSubdirectoriesIfNeed {
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(),
withIntermediateDirectories: true,
attributes: nil)
}
guard let data = jpegData(compressionQuality: compressionQuality) else { return nil }
try data.write(to: url)
return url
} catch {
print("-- Error: \(error)")
return nil
}
}
}
// load from path
extension UIImage {
convenience init?(fileURLWithPath url: URL, scale: CGFloat = 1.0) {
do {
let data = try Data(contentsOf: url)
self.init(data: data, scale: scale)
} catch {
print("-- Error: \(error)")
return nil
}
}
}
Usage
// save image (way 1)
let path = "photo/temp/album1/img.jpg"
guard let img = UIImage(named: "img"),
let url = img.save(at: .documentDirectory,
pathAndImageName: path) else { return }
print(url)
// get image from directory
guard let img2 = UIImage(fileURLWithPath: url) else { return }
// save image (way 2)
let tempDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(path)
guard let url2 = img2.save(at: tempDirectoryUrl) else { return }
print(url2)
Check results
open the iOS simulator directory
You should save image name with extension so your path should be like,
///var/mobile/Applications/BDB992FB-E378-4719-B7B7-E9A364EEE54B/Documents/tempImage.png
And second thing replace below line,
let result = pngImageData!.writeToFile(path, atomically: true)
with
let result = pngImageData!.writeToFile(path, atomically: false)
You need to set false as parameter of atomically.
atomically:
If true, the data is written to a backup file, and then—assuming no errors occur—the backup file is renamed to the name specified by path; otherwise, the data is written directly to path.
Hope this will help :)
Save image in local Xcode Documents directory
Pass in your image and the name you want to call it (you choose what you want fileName to be).
func saveImageLocally(image: UIImage, fileName: String) {
// Obtaining the Location of the Documents Directory
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// Creating a URL to the name of your file
let url = documentsDirectory.appendingPathComponent(fileName)
if let data = image.pngData() {
do {
try data.write(to: url) // Writing an Image in the Documents Directory
} catch {
print("Unable to Write \(fileName) Image Data to Disk")
}
}
}
Read
Use the same fileName as when you saved it
func getImageFromName(fileName: String) {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = documentsDirectory.appendingPathComponent(fileName)
if let imageData = try? Data(contentsOf: url) {
let image = UIImage(data: imageData) // HERE IS YOUR IMAGE! Do what you want with it!
} else {
print("Couldn't get image for \(fileName)")
}
}
Ashish's comment has a clue to the answer. If you read the docs on UIImage(contentsOfFile:) they say
path The path to the file. This path should include the filename
extension that identifies the type of the image data.
The imageNamed call is smart enough to try the .png and .jpg extensions, but the contentsOfFile call expects a full path including extension.
If you want to load image from server you can do like below
let url = URL(string: "http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg")
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
else { return }
DispatchQueue.main.async() { () -> Void in
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("apple.jpg")
print(paths)
fileManager.createFile(atPath: paths as String, contents: data, attributes: nil)
}}.resume()
You can actually use PHPhotoLibrary to do that.
Here is the code for saving the image and fetching the image url.
extension UIImage {
func saveToPhotoLibrary(completion: #escaping (URL?) -> Void) {
var localeId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAsset(from: self)
localeId = request.placeholderForCreatedAsset?.localIdentifier
}) { (isSaved, error) in
guard isSaved else {
debugPrint(error?.localizedDescription)
completion(nil)
return
}
guard let localeId = localeId else {
completion(nil)
return
}
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let result = PHAsset.fetchAssets(withLocalIdentifiers: [localeId], options: fetchOptions)
guard let asset = result.firstObject else {
completion(nil)
return
}
getPHAssetURL(of: asset) { (phAssetUrl) in
completion(phAssetUrl)
}
}
}
static func getPHAssetURL(of asset: PHAsset, completionHandler : #escaping ((_ responseURL : URL?) -> Void))
{
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
asset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
completionHandler(contentEditingInput!.fullSizeImageURL)
})
}
}
You have to create a directory in the Documents directory to be able to store a file.
Swift 5
func saveImage(image: UIImage) -> Bool{
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() 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("\(txtNom.text!).png")!)
print(directory)
print(data)
print("si se pudo")
return true
} catch {
print(error.localizedDescription)
return false
}
} // saveImage
I found the solution on StackOverFlow some time ago. I didn't remember the author
Assuming yourImage is UIImage()
let ciImage = yourImage!.ciImage
let context = CIContext()
let cgImage = context.createCGImage(ciImage!, from: ciImage!.extent)
let uiImage = UIImage(cgImage: cgImage!)
UIImageWriteToSavedPhotosAlbum(uiImage, self,
#selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
and this function
#objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
// we got back an error!
let ac = UIAlertController(title: "Save error", message: error.localizedDescription, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
} else {
let ac = UIAlertController(title: "Saved!", message: "Your altered image has been saved to your photos.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}

How to save a remote image with Swift?

I'm trying to display and save images with Swift. On first hit, it shows the remote image on imageview, on second hit it shows blank imageview instead of it should be local image which saved on first hit.
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
var checkImage = NSFileManager.defaultManager()
if (checkImage.fileExistsAtPath(imagePath)) {
let getImage = UIImage(contentsOfFile: imagePath)
self.image?.image = getImage
} else {
dispatch_async(dispatch_get_main_queue()) {
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
self.image?.image = getImage
}
}
Edit: This one worked for me.
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var dirPath = paths.stringByAppendingPathComponent("images/\(id)" )
var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
var checkImage = NSFileManager.defaultManager()
if (checkImage.fileExistsAtPath(imagePath)) {
let getImage = UIImage(contentsOfFile: imagePath)
self.image?.image = getImage
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil)
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue()) {
self.image?.image = getImage
return
}
}
}
To answer your main question, you're calling the wrong UIImage initializer. You should be calling UIImage(contentsOfFile: imagePath) in swift 2 and UIImage(contentsOf: imagePath) in swift 3.
Additionally, it looks like you're trying to do your remote fetch in the background with dispatch_async (or DispatchQueue in swift 3), but you're passing it the main queue, so you're actually blocking the main/UI thread with that. You should dispatch it to one of the background queues instead and then dispatch back to the main queue when you actually set the image in your UI:
Swift 3 :
DispatchQueue.global(qos: DispatchQoS.background.qosClass).async {
do {
let data = try Data(contentsOf: URL(string: self.remoteImage)!)
let getImage = UIImage(data: data)
try UIImageJPEGRepresentation(getImage!, 100)?.write(to: imagePath)
DispatchQueue.main.async {
self.image?.image = getImage
return
}
}
catch {
return
}
}
Swift 2 :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: self.remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue()) {
self.image?.image = getImage
return
}
}
#Rob's answer re: fetching your remote image and saving it is really the best way to do this.
Your code that dispatches NSData(contentsOfURL:) (now known as Data(contentsOf:)) to the main queue. If you're going to use that synchronous method to request remote image, you should do this on a background queue.
Also, you are taking the NSData, converting it to a UIImage, and then converting it back to a NSData using UIImageJPEGRepresentation. Don't round-trip it though UIImageJPEGRepresentation as you will alter the original payload and will change the size of the asset. Just just confirm that the data contained an image, but then write that original NSData
Thus, in Swift 3, you probably want to do something like:
DispatchQueue.global().async {
do {
let data = try Data(contentsOf: URL(string: urlString)!)
if let image = UIImage(data: data) {
try data.write(to: fileURL)
DispatchQueue.main.async {
self.imageView?.image = image
}
}
} catch {
print(error)
}
}
Even better, you should use NSURLSession because you can better diagnose problems, it's cancelable, etc. (And don't use the deprecated NSURLConnection.) I'd also check the statusCode of the response. For example:
func requestImage(_ url: URL, fileURL: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// check for fundamental network issues (e.g. no internet, etc.)
guard let data = data, error == nil else {
print("dataTask error: \(error?.localizedDescription ?? "Unknown error")")
return
}
// make sure web server returned 200 status code (and not 404 for bad URL or whatever)
guard let httpResponse = response as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else {
print("Error; Text of response = \(String(data: data, encoding: .utf8) ?? "(Cannot display)")")
return
}
// save image and update UI
if let image = UIImage(data: data) {
do {
// add directory if it doesn't exist
let directory = fileURL.deletingLastPathComponent()
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
// save file
try data.write(to: fileURL, options: .atomic)
} catch let fileError {
print(fileError)
}
DispatchQueue.main.async {
print("image = \(image)")
self.imageView?.image = image
}
}
}
task.resume()
}
Note, the just-in-time creation of the folder is only necessary if you haven't created it already. Personally, when I build the original path, I'd create the folder there rather than in the completion handler, but you can do this any way you want. Just make sure the folder exists before you write the file.
Regardless, hopefully this illustrates the main points, namely that you should save the original asset and that you should do this in the background.
For Swift 2 renditions, see previous revision of this answer.

Resources