How to download image from iCloud without re-saving it? - swift

I ran into a problem when trying to pick photo from gallery (app crashes when I pick image uploaded on iCloud).
So my first solution was to check if image is on device and if not, then download it from iCloud.
I used this code
let manager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.resizeMode = .exact
requestOptions.deliveryMode = .highQualityFormat;
// Request Image
manager.requestImageData(for: asset, options: requestOptions, resultHandler: { (data, str, orientation, info) -> Void in
// Do somethign with Image Data
})
I get imageData in this situation, but if I want to save it, it saves into new photo. So I get copy of image in Photos instead getting one original image that stores on device.
So my question is how to download image without re-saving it.
Basically I want same behaviour as in photos app (when photo is not on device, it downloads photo and save it into same picture instead creating copy of it)

I faced this issue, here is a workaround for it:
Set isNetworkAccessAllowed to false:
requestOptions.isNetworkAccessAllowed = false
That's because you need to make sure that we will not fetch it from the iCloud. At this point, if the image is not downloaded, the data in resultHandler should be nil:
manager.requestImageData(for: PHAsset(), options: requestOptions, resultHandler: { (data, str, orientation, info) -> Void in
if let fetchedData = data {
// the image is already downloaded
} else {
// the image is not downloaded...
// now what you should do is to set isNetworkAccessAllowed to true
// and make a new request to get it from iCloud.
}
})
Furthermore, you could leverage PHImageResultIsInCloudKey:
manager.requestImageData(for: PHAsset(), options: requestOptions, resultHandler: { (data, str, orientation, info) -> Void in
if let keyNSNumber = info?[PHImageResultIsInCloudKey] as? NSNumber {
if keyNSNumber.boolValue {
// its on iCloud...
// read the "keep in mind" below note,
// now what you should do is to set isNetworkAccessAllowed to true
// and make a new request.
} else {
//
}
}
})
As per mentioned in PHImageResultIsInCloudKey documentation, keep in mind that:
If true, no image was provided, because the asset data must be
downloaded from iCloud. To download the data, submit another request,
and specify true for the isNetworkAccessAllowed option.

Related

How to use SDWebImage to download and save CGImage

I'm trying to use SDWebImage to download and image from an external url and return it. I do not want to set it on a view. This is the code I'm using, but it's not working. I'm returning nil. But I know the url I'm passing in works because I can see it in the browser. What am I doing wrong?
func downloadImage() -> CGImage {
var myImage: CGImage?
let myUrl = URL(string: "my-url-here.com")
SDWebImageDownloader.shared.downloadImage(with: myUrl, completed: { (image, data, error, true) in
print("Completed")
if image != nil {
myImage = image?.cgImage
}
})
return myImage!
}
I also tried this version, also with no luck:
func downloadImage() -> CGImage {
var myImage: CGImage?
let myUrl = URL(string: "my-url-here.com")
SDWebImageManager.shared.loadImage(with: myUrl, options: .continueInBackground, progress: { (received, expected, nil) in
print(received, expected)
}, completed: { (downloadedImage, data, error, SDImageCacheType, true, imageUrlString) in
DispatchQueue.main.async {
if downloadedImage != nil {
myImage = downloadedImage?.cgImage
}
}
})
return myImage!
}
SDWebImage is an asynchronous library. You can’t just return the results. Generally one would use an #escaping closure to supply the results to the caller. E.g.
func downloadImage(completion: #escaping(CGImage?) -> Void) {
let url = URL(string: "https://my-url-here.com")!
SDWebImageDownloader.shared.downloadImage(with: url) { image, _, _, _ in
completion(image?.cgImage)
}
}
And you’d use it like:
downloadImage { image in
guard let image = image else { return }
// use image here
}
// but not here
But let’s step back and look at the whole pattern. You say you want to “save” the result. If you’re talking about saving it to persistent storage, you would not want to use CGImage (or UIImage or whatever) at all. That’s computationally inefficient (converting asset to image and then back to Data so you can save it), space inefficient (you have to load the whole asset into memory at the same time), and likely introduces problems (e.g. if you download a JPG, convert to CGImage and then try to recreate a JPG, the resulting asset will be slightly different, bigger, and/or with new JPG artifacts). If you’re just pre-downloading assets, just use a simple networking library like Alamofire or URLSession.

get Image from PHAsset issue to detect updates on images

I'm currently managing a PHAsset with multiple Pictures (from an Album my application is creating), I'm able to display it without any problem.
For each picture of my PHAsset, I'm calculating the hash of each pictures in my app.
But If I'm modifying the picture out of my app (through the Photos, I'm changing the color of the picture for example) I'm not able to detect the update, if I'm calculating the hash one more time of the PHAsset pictures while my app is resumed, the update is not detected, the hashes remains the same...
Here is the code to get image from PHAsset:
var img: UIImage?
let manager = PHImageManager.default()
let options = PHImageRequestOptions()
options.version = .original
options.isSynchronous = true
manager.requestImageData(for: asset, options: options) { (data, _, _, _) in
if let data = data{
img = UIImage(data: data)
}
}
return img
I'm using this function while adding the picture (to calculate the hash), and when I need to check the hash of the PHasset's pictures, I'm retrieving the PHAssetCollection by using
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
but no changes are detected...may be I need to ask for a refresh of the PHAssetCollection to retrieve the changes on the pictures ?
Am I missing something ?
Just to be clear, I'm calculating the hash of the Image with the following:
//function described just above
let img = getSavedImageFromPHAsset(asset: myAsset)!
let img_hash = img.sha256()
//I'm comparing the hashes with this value
func sha256(data: Data) -> Data {
var digestData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes {digestBytes in
data.withUnsafeBytes {messageBytes in
CC_SHA256(messageBytes, CC_LONG(data.count), digestBytes)
}
}
return digestData
}
I assumed that as soon as an image linked to the PHAsset has been changed, I'm able to detect its modification (I can see the picture updated on my app's screen, but hash calculated is still the same)
Try the following:
options.version = .current
instead of
options.version = .original
Your code will always return the original, unedited photo/video. .current returns the same photo that you see in the iOS Photos App.

Read Gallery Video in Custom Size

How could I get a video from Gallery(Photos) in custom format and size.
for example I want to read a video in 360p.
I used below code to get video data but apple said it doesn't guarantee to read it in lowest quality.
It's a PHAsset extension, so self refering to a PHAsset object.
var fileData: Data? = nil
let manager = PHImageManager.default()
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .fastFormat
manager.requestAVAsset(forVideo: self, options: options) {
(asset: AVAsset?, audioMix: AVAudioMix?, _) in
if let avassetURL = asset as? AVURLAsset {
guard let video = try? Data(contentsOf: avassetURL.url) else {
print("reading video failed")
return
}
fileData = video
}
}
There is a simple reason it can't be guaranteed: The file in 360p might not be on the device or in the cloud. So the Photos framework will deliver a format nearest to what you request. If you want exactly 360p, I would recommend you reencode the video you get from the photos framework yourself.

using a local image from the device in order to classify it successfully

I am writing a visual recognition application that uses VisualRecognition.classify in order to classify images. I have configured my Swift environment and haven't been able to classify images when including a URL from the internet:
I have now created an application that uses the camera and photo library to allow users to take photos and have them classified. I am running into issues when passing along an fileURL from the device to the VisualRecognition service though.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
imageView.image = image
imagePicker.dismiss(animated: true, completion: nil)
let visualRecongnition = VisualRecognition(version: version, apiKey: apiKey)
let imageData = image.jpegData(compressionQuality: 0.01)
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentURL.appendingPathComponent("tempImage.jpg")
try? imageData?.write(to: fileURL, options: [])
visualRecongnition.classify(imageFile: fileURL, success: { (classifiedImages) in
print(classifiedImages)
}) // getting error here " Missing argument for parameter 'completionHandler' in call"
}else {
print("There was an error picking Image")
}
}
I have even attempted to include the NSURL directly into the classify call as I have done with the working external URL, but still run into the same error. Would really like to see how to use a local image from the device in order to classify it successfully
The problem is that your call to classify does not correspond to the signature of the classify method. In this line:
visualRecongnition.classify(imageFile: fileURL, success: { (classifiedImages) in
change success to completionHandler, and add a second parameter in the closure (even if you ignore it), like this:
visualRecongnition.classify(imageFile: fileURL, completionHandler: { classifiedImages,_ in
You must first request permission to use the camera and library. Open your Info.plist file in Source code mode, and add the following lines:
<key>NSCameraUsageDescription</key>
<string>Ask for permission to use camera.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Ask for permission to use Photo Library</string>
If you also want to be able to write images to the Camera Roll add this too:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Ask for permission to save images to Photo library</string>

Swift: image cache not working as expected

I am trying to use image cache in swift but nothing works as expected. I have tried NSCache and AlamofireImage but both didn't work for me. I did not get any error with code but when I tried to fetch image from cache it always returns nil.
I guess the image is not saved in cache. Can someone here share some links with example on how to use image caching in swift or ios?
What you can do is download images in background thread. When image is downloaded save it an Object and update you'r main thread too. For displaying image next time you can directly load image from the cache.
Below I'm sharing my code
var imageCache = NSMutableDictionary()
if let img = imageCache.valueForKey("key") as? UIImage{
imageView.image = img
}
else{
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(NSURL(string: imgURL!)!, completionHandler: { (data, response, error) -> Void in
if(error == nil){
let img = UIImage(data: data!)
imageCache.setValue(img, forKey: "key") // Image saved for cache
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Update your image view
})
}
})
task.resume()
}
Hope so this may help you
You can use this as a disk and memory cache, if you implemented downloading image mechanism. You can specify cache size, expire time
https://github.com/huynguyencong/DataCache
Or you can use Kingfisher as a image caching library