How to save a remote image with Swift? - ios

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.

Related

How to show image from url in ios swift [duplicate]

This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 3 years ago.
I am trying to load image from url in my ios app swift. I have written following code.
let imageURL = minHost + "\(userData["profileImage"])"
let url = URL(string: imageURL)!
let imageData = try? Data(contentsOf: url)
profileImage.image = UIImage(data: imageData!)
Now imageURL is having proper url, but imageData receives nil and because of this, last line through an error Fatal error: Unexpectedly found nil while unwrapping an Optional value
Instead of fetching image using Data(contentsOf:) method, use URLSession to perform network calls.
let imageURL = minHost + "\(userData["profileImage"])"
if let url = URL(string: imageURL) {
URLSession.shared.dataTask(with: url) {[weak self] (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.async {
self?.profileImage.image = UIImage(data: imageData)
}
}
}.resume()
}
Important Note: Avoid using forced unwrapping (!) unnecessarily. It might result in unwanted app crashes. Instead use guard or if-let to unwrap optionals.
Try this at Playground.
Loading image from the URL takes some time, and need to be executed at another Thread, different from the main thread.
import UIKit
let url = URL(string: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")!
var image = UIImage()
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
image = UIImage(data: data)!
}
}
}
image
you can try like this:
let url = URL(string: "image url here")
if url != nil {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url!) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self.profileImage.image = image
}
}
}
}
}
Try This
let url = URL(string:imageURL)
if let data = try? Data(contentsOf: url!)
{
profileImage.image = UIImage(data: data, scale: 1.0)!
}
Never do the downloading task on main thread. if you do, you will not able to access components in current visible screens properly. It should be always on the background thread.
if let url = URL(string: "https://....") {
DispatchQueue.global(qos: .background).async {
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self.profileImage.image = image
}
}
}
}
}

Swift Grand Central Dispatch Queues and UIImages

I know this type of question has been asked 1e7 times but I have come across a specific issue that I don't think has been covered/is blatantly obvious but I am too novice to fix it on my own.
I have the following code snippet within my cellForRowAt method in a TableViewController:
let currentDictionary = parser.parsedData[indexPath.row] as Dictionary<String,String>
let urlString = currentDictionary["media:content"]
if urlString != nil {
let url = NSURL(string: urlString!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url! as URL) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
cell.thumbnailImageView.image = UIImage(data: data!)
}
}
}
Which executes fine, downloads the images and assigns them to the UIImageView of each tableViewCell.
There is a finite delay when scrolling the table as the images are downloaded 'on the fly' so to speak.
What I want to do is pre-download all these images and save them in a data structure so they are fetched from URL's less frequently.
I have tried the following implementation:
var thumbnail = UIImage()
for item in parser.parsedData {
let currentDictionary = item as Dictionary<String,String>
let title = currentDictionary["title"]
let link = currentDictionary["link"]
let urlString = currentDictionary["media:content"]
let url = NSURL(string: urlString!)
if urlString != nil {
let url = NSURL(string: urlString!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url! as URL)
DispatchQueue.main.sync {
thumbnail = UIImage(data: data!)!
}
}
}
var newsArticle: News!
newsArticle = News(title: title!, link: link!, thumbnail: thumbnail)
news.append(newsArticle)
Where news is my data structure. This code also executes fine, however each thumbnail is a 0x0 sized image, size {0, 0} orientation 0 scale 1.000000, according to the console output.
Does anyone have any ideas how to download these images but not immediately assign them to a UIImageView, rather store them for later use?
The problem is that you create your newsArticle before the global dispatch queue even started to process your url. Therefore, thumbnail is still the empty UIImage() created in the very first line.
You'll have to create the thumbnail inside the inner dispatch closure, like:
for item in parser.parsedData {
guard let currentDictionary = item as? Dictionary<String,String> else { continue /* or some error handling */ }
guard let title = currentDictionary["title"] else { continue /* or some error handling */ }
guard let link = currentDictionary["link"] else { continue /* or some error handling */ }
guard let urlString = currentDictionary["media:content"] else { continue /* or some error handling */ }
guard let url = URL(string: urlString) else { continue /* or some error handling */ }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.sync {
if let thumbnail = UIImage(data: data) {
let newsArticle = News(title: title, link: link, thumbnail: thumbnail)
news.append(newsArticle)
}
}
}
}
}
By the way, your very first code (cellForRow...) is also broken: You must not reference the cell inside the dispatch closure:
DispatchQueue.main.async {
// Never do this
cell.thumbnailImageView.image = UIImage(data: data!)
}
Instead, reference the IndexPath, retrieve the cell inside the clousure, and go on with that cell. But as you already mentioned, there are many many entries on stackoverflow regarding this issue.

UIImageView, Load UIImage from remote URL

This problems it's driving me crazy...
I have this string url:
"verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg"
and I have to load this image in my imageView.
this is my code :
do {
let url = URL(fileURLWithPath: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")
let data = try Data(contentsOf: url)
self.imageView.image = UIImage(data: data)
}
catch{
print(error)
}
This throw the exception :
No such file or directory.
But if I search this url with a browser I can see the image correctly!
You are using wrong method to create URL. Try URLWithString instead of fileURLWithPath. fileURLWithPath is used to get image from local file path not from internet url.
or
do {
let url = URL(string: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")
let data = try Data(contentsOf: url)
self.imageView.image = UIImage(data: data)
}
catch{
print(error)
}
The method fileURLWithPath opens file from file system. The file address is prepended with file://. You can print the url string.
From Apple documentation about + (NSURL *)fileURLWithPath:(NSString *)path;
The path that the NSURL object will represent. path should be a valid
system path, and must not be an empty path. If path begins with a
tilde, it must first be expanded with stringByExpandingTildeInPath. If
path is a relative path, it is treated as being relative to the
current working directory.
Here is one of a few possible solutions:
let imageName = "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg"
func loadImage(with address: String) {
// Perform on background thread
DispatchQueue.global().async {
// Create url from string address
guard let url = URL(string: address) else {
return
}
// Create data from url (You can handle exeption with try-catch)
guard let data = try? Data(contentsOf: url) else {
return
}
// Create image from data
guard let image = UIImage(data: data) else {
return
}
// Perform on UI thread
DispatchQueue.main.async {
let imageView = UIImageView(image: image)
/* Do some stuff with your imageView */
}
}
}
loadImage(with: imageName)
It's best practice if you just send a completion handler to perform on main thread to loadImage(with:).
Here the url is not of the local system but of the server.
let url = URL(fileURLWithPath: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")
Here the url created is of file which is locally on the device.
Create url like this:-
url = URL(string: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")
Use below code snippet to loading an image into imageview
func imageDownloading() {
DispatchQueue.global().async {
let url = URL(string: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")!
do {
let data = try Data(contentsOf: url)
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
} catch {
print(error.localizedDescription)
}
}
}

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

Swift - Download a video from distant URL and save it in an photo album

I'm currently displaying a video in my app and I want the user to be able to save it to its device gallery/album photo/camera roll.
Here it's what I'm doing but the video is not saved in the album :/
func downloadVideo(videoImageUrl:String)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//All stuff here
print("downloadVideo");
let url=NSURL(string: videoImageUrl);
let urlData=NSData(contentsOfURL: url!);
if((urlData) != nil)
{
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0];
let fileName = videoImageUrl; //.stringByDeletingPathExtension
let filePath="\(documentsPath)/\(fileName)";
//saving is done on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
urlData?.writeToFile(filePath, atomically: true);
print("videoSaved");
})
}
})
}
I'va also look into this :
let url:NSURL = NSURL(string: fileURL)!;
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(url);
let assetPlaceHolder = assetChangeRequest!.placeholderForCreatedAsset;
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest!.addAssets([assetPlaceHolder!])
}, completionHandler: saveVideoCallBack)
But I have the error "Unable to create data from file (null)". My "assetChangeRequest" is nil. I don't understand as my url is valid and when I go to it with a browser, it download a quick time file.
If anyone can help me, it would be appreciated ! I'm using Swift and targeting iOS 8.0 min.
Update
Wanted to update the answer for Swift 3 using URLSession and figured out that the answer already exists in related topic here. Use it.
Original Answer
The code below saves a video file to Camera Roll. I reused your code with a minor change - I removed let fileName = videoImageUrl; because it leads to incorrect file path.
I tested this code and it saved the asset into camera roll. You asked what to place into creationRequestForAssetFromVideoAtFileURL - put a link to downloaded video file as in the example below.
let videoImageUrl = "http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4"
DispatchQueue.global(qos: .background).async {
if let url = URL(string: urlString),
let urlData = NSData(contentsOf: url) {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let filePath="\(documentsPath)/tempFile.mp4"
DispatchQueue.main.async {
urlData.write(toFile: filePath, atomically: true)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath))
}) { completed, error in
if completed {
print("Video is saved!")
}
}
}
}
}
Swift 3 version of the code from #Nimble:
DispatchQueue.global(qos: .background).async {
if let url = URL(string: urlString),
let urlData = NSData(contentsOf: url)
{
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let filePath="\(documentsPath)/tempFile.mp4"
DispatchQueue.main.async {
urlData.write(toFile: filePath, atomically: true)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath))
}) { completed, error in
if completed {
print("Video is saved!")
}
}
}
}
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: video.url!)}) {
saved, error in
if saved {
print("Save status SUCCESS")
}
}
following #Nimble and #Yuval Tal solution, it is much more preferable to use the URLSession dataTask(with:completionHandler:) method to download a file before writing it as stated in the warning section of NSData(contentsOf:) Apple documentation
Important
Don't use this synchronous initializer 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
a correct implementation could be :
let defaultSession = URLSession(configuration: .default)
var dataTask: URLSessionDataTask? = nil
func downloadAndSaveVideoToGallery(videoURL: String, id: String = "default") {
DispatchQueue.global(qos: .background).async {
if let url = URL(string: videoURL) {
let filePath = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).mp4")
print("work started")
self.dataTask = self.defaultSession.dataTask(with: url, completionHandler: { [weak self] data, res, err in
DispatchQueue.main.async {
do {
try data?.write(to: filePath)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: filePath)
}) { completed, error in
if completed {
print("Saved to gallery !")
} else if let error = error {
print(error.localizedDescription)
}
}
} catch {
print(error.localizedDescription)
}
}
self?.dataTask = nil
})
self.dataTask?.resume()
}
}
}
One more advantage is that you can pause, resume and terminate your download by calling the corresponding method on dataTask: URLSessionDataTask .resume() .suspend() .cancel()

Resources