I want to get all images from the phone.for create multiple selections in photos library.I am using below code to get all images from the phone. it's working perfectly but when on the phone have 1000 images its take more time to get all images and show on view.How to reduce the time of getting all images from the phone.
// get all images from photos
func getAllImagesFromPhotos() -> [Photos]?
{
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: true)]
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
var allImages = [Photos]()
for index in 0..<fetchResult.count
{
let asset = fetchResult.object(at: index) as PHAsset
imgManager.requestImage(for: asset, targetSize: CGSize(width:200,height:200), contentMode: .aspectFill, options: requestOptions, resultHandler: { (uiimage, info) in
if let image = uiimage
{
allImages.append(Photos(image: image, selected: false))
}
})
}
return allImages
}
To solve your problem you want to actually batch the image loading, load 10 images at a time, and display them, when the user scrolls a bit more, load the next batch, etc..
if you load all images, then of course there will be an unavoidable performance hit.
what you want to use instead is the UIImagePickerController class from UIKit
Here is the documentation from apple on UIImagePickerController.
If you don't want to use UIImagePickerController, then you have to load a smaller set of images at a time and allow the users to "load more" by scrolling further on the selection view.
EDIT: Multi image selection
After reading your comment about wanting to select multiple images, it's clear that UIImagePickerController is made to select single images, however, you can just not dismiss it upon selection, and have a toast message saying "Image selected", when the user select an image, and not dismiss the view, allowing the user to select another image, etc.. if the user select the same image again, you may just show the toast again saying "Image removed" since you can verify if the image has already been selected from before.
if you don't want to implement that workaround, your only choice is to batch load images in smaller chunks of around 10 images or so as I recommended in the original answer.
however, If you really don't want to implement that yourself, i recommend using a 3rd party library, something like ELCImagePickerController for example, there are hundreds of other libraries that do just that.
Hope this helps, good luck
Related
I am building an app where you can pick images from your phone or make new ones with the camera. The question is about picking internal images. First I was using ImagepickerController where a new Controller pops up and I can select the image I want. Later I decided to change that design approach, but to embed all the pictures from my phone inside the main screen (the one that shows when you enter the app) where you can select each image by just tapping on it (so no new controller pops up). And there is the problem. Reading pictures and loading them inside my collectionview takes just too much time (reading pictures is the main issue). I have over 1000 pictures on my phone, and it takes like 7 seconds for the app to read 100 pictures (than it crashes, not sure why, maybe some memory issue, didnt look further into it bcz my main problem was the reading performance).
For reading I am using PHAssetCollection, this is the code:
let allPhotosResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: nil)
let imageManager = PHCachingImageManager()
allPhotosResult.enumerateObjects({(object: AnyObject!,
count: Int,
stop: UnsafeMutablePointer<ObjCBool>) in
if object is PHAsset{
let imgAsset = object as! PHAsset
countI += 1
if countI > 100 {
return
}
let imageSize = CGSize(width: imgAsset.pixelWidth,
height: imgAsset.pixelHeight)
let options = PHImageRequestOptions()
options.deliveryMode = .fastFormat
options.isSynchronous = true
imageManager.requestImage(for: imgAsset,
targetSize: imageSize,
contentMode: .aspectFill,
options: options,
resultHandler: {
(image, info) -> Void in
self._images.append(image!)
}
})
}
})
I quit at 100 images bcz then they are being displayed, but again thats not the issue I think I will be able to solve that.
So, is there a faster way to read all the images and load them as thumbnails into my collectionview ? I didnt have any of the popular photos apps, but I downloaded some of them to see how their performance is, and Instagram is pretty impressive. All the images are there without any delay, which means there must be a way to get them all pretty fast. Maybe they fetch them in the background even when the app is not running ?
Regards
P.S. - ah yes, I know I can execute the reading async, but that doesnt solve my problem bcz the delay will still be there
I have silly problem with loading image from the file. I have two views putted to UITabBarController.
At the first view user can load his image from the Photo Library or Camera. Second view present this photo. This file is on the server. If user doesn't choose his image server sent custom image. If there is uploaded photo it will push user's picture.
When user tap button there is a menu with options. For example we will decide to take picture from the Photo Library. After user took image:
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
self.saveUserImage(userID, imageData: UIImagePNGRepresentation(image)!)
apiManager.userUploadProfile(userID, imageData: UIImagePNGRepresentation(image)!)
userImageView.image = image
}
func saveUserImage(userUUID: String, imageData: NSData) {
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
let savePath = path! + "/\(userUUID)-user.png"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: imageData, attributes: nil)
}
After that point user can see chosen picture and everything is okey. When user change tab, second view will refresh all data on it and again will download all images and data from the server. Unfortunately images that contains old user image doesn't refresh and there is still old photo.
When we come back to the first tab image is going back to old image but after few seconds.
The strangest thing is if I am checking server there is new uploaded image and in the app container it exist too. When I restart the app everything works perfectly.
It looks like this image is saved in the memory and iOS takes old version from RAM or from some other swap. How refresh UIImageView and show current saved image?
EDIT:
There is a method which load the image
func userProfilePicture(userId: String) -> UIImage {
let cacheDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last
let savePath = cacheDirectory! + "/\(userId)-user.png"
if NSFileManager.defaultManager().fileExistsAtPath(savePath) {
if let image = UIImage(named: savePath) {
return image
}
}
return UIImage(named: "test_avatar")!
}
I can't comment but i was facing a similar issue but i have a question, does the picture get uploaded before the user changes tabs, or after? Even if you call the function to update the database, it takes some time to actually "send the new photo/path to the photo to the database". Here are your options, if the picture is not in the server at the time of the switching tabs, implement a loading ui until it has successfully uploaded and the new, updated value, into the database. An easy way to do that would be to use the SVProgressHUD pod and set the default mask type to .clear which disables user interaction. Option 2, is to pass the actual UIImage via a static variable, or in the prepare for segue method, or through a struct, or any other way and set the picture that needs to be refreshed to the uiimage without getting it from the server only when you switch tabs.
Okey, I found the answer. The problem was in initialize UIImage. We have two methods to load image from file.
First one load the image and cache it in memory. Later it use only a reference to this image data in memory:
let image = UIImage(named: savePath)
Second method load image strictly from file every time when user use that function:
let image = UIImage(contentsOfFile: savePath)
Right now it works perfectly :D
I'm trying to develop an iOS app where users can pick their images from Camera Roll, Facebook, Instagram, etc. and edit them later (crop, filters, rotate).
Now, I'm having problems loading multiple images using Photos Framework to get PHAssets and converting them to full quality images and show them in next view.
When loading full quality images in SelectedImagesView I get memory warning and the app just blows because I'm storing images in memory.
How can I store a list of selected images (original and edited) and show them to the user according to the image "SelectedImagesView"?
1) PickImagesView
2) SelectedImagesView
So far, this is how I get the images using the Photo Framework:
Load the PHAssets from selected album in CollectionView
When user select an image, store the PHAsset identifier in NSUserDefault
When user finish selecting images, load all selected identifiers and send them to next View
Load images from Photos Library in SelectedImagesView with following code:
func processImages() {
for identifier in self.selectedAssets {
let options = PHImageRequestOptions()
options.deliveryMode = .FastFormat
// request images no bigger than 1/3 the screen width
let maxDimension = UIScreen.mainScreen().bounds.width / 3 * UIScreen.mainScreen().scale
let size = CGSize(width: maxDimension, height: maxDimension)
let assets = PHAsset.fetchAssetsWithLocalIdentifiers([identifier], options: nil)
guard let asset = assets.firstObject as? PHAsset
else { fatalError("no asset") }
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: PHImageManagerMaximumSize, contentMode: .AspectFill, options: options)
{ result, info in
// probably some of this code is unnecessary, too,
// but I'm not sure what you're doing here so leaving it alone
let photo: Photo = Photo()
photo.originalPhoto = result
self.selectedImages.append(photo)
}
}
self.tableView!.reloadData()
self.tableView!.reloadInputViews()
}
But the memory cost is too high.
Thanks in advance.
I am using "SVProgressHUD" for loader. When I want to load an image from url I am using async process to load the image in background. After completing the download I am updating the UI. And for that time span I am using a placeholder on the imageview and a loader with "Please wait..". But I want to use a loader like "Instagram". That means, when an image is loading from online it loads a blurry image of the original image and upon downloading the image , the blurry image show the original image.
Can any one please suggest me how can I do this?
The stuff you are talking about is Progressive JPEG.
Progressive JPEG (PJPEG) is an image format that stores multiple,
individual “scans” of a photo, each with an increasing level of
detail. When put together, the scans create a full-quality image. The
first scan gives a very low-quality representation of the image, and
each following scan further increases the level of detail and quality.
When images are downloaded using PJPEG, we can render the image as
soon as we have the first scan. As later scans come through, we update
the image and re-render it at higher and higher quality.
So, you have to make images loadable in this modern progressive format.You can follow a approach where every image before saving on server just convert it into appropriate progressive format.There are several tools avaiable for different type of server. E.g jpegtran
To check whether image is progressive or not you can use this tool or tool2.You can search for online tool a lot of tool is available.
Now for iOS
You can follow this tutorial
Some library
Concorde
DFImageManager
Assuming you are in control of the hosting of the images you can host lower resolution images and make 2 fetches, one for the lower resolution, and one for the higher. If you make the lower resolution image significantly smaller in file size the fetch for it will finish before the fetch for the full image, and you populate the UIImageView with this image until such a time as you can replace it.
Purely example code would look like this:
let imageView = UIImageView()
let lowResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/low-res-image")!),
image = UIImage(data: imageData) else { return }
if imageView.image == nil {
imageView.image = image
}
}
let highResOperation = NSBlockOperation {
guard let imageData = NSData(contentsOfURL: NSURL(string: "https://myhost.org/high-res-image")!),
image = UIImage(data: imageData) else { return }
imageView.image = image
}
let backgroundQueue = NSOperationQueue()
backgroundQueue.addOperations([lowResOperation, highResOperation], waitUntilFinished: true)
I'm trying to retrieve over 1,000 images from the user's camera roll with PHAsset but it ends up crashing or taking a long time if it's just thumbnails. Here is my function where I retrieve the images...
func retrieveImages(thumbnail: Bool) {
/* Retrieve the items in order of modification date, ascending */
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "modificationDate",
ascending: false)]
/* Then get an object of type PHFetchResult that will contain
all our image assets */
let assetResults = PHAsset.fetchAssetsWithMediaType(.Image,
options: options)
let imageManager = PHCachingImageManager()
assetResults.enumerateObjectsUsingBlock{(object: AnyObject!,
count: Int,
stop: UnsafeMutablePointer<ObjCBool>) in
if object is PHAsset{
let asset = object as! PHAsset
print("Inside If object is PHAsset, This is number 1")
var imageSize: CGSize!
if thumbnail == true {
imageSize = CGSize(width: 100, height: 100)
} else {
imageSize = CGSize(width: self.cameraView.bounds.width, height: self.cameraView.bounds.height)
}
/* For faster performance, and maybe degraded image */
let options = PHImageRequestOptions()
options.deliveryMode = .FastFormat
options.synchronous = true
imageManager.requestImageForAsset(asset,
targetSize: imageSize,
contentMode: .AspectFill,
options: options,
resultHandler: { (image, _: [NSObject : AnyObject]?) -> Void in
if thumbnail == true {
self.libraryImageThumbnails.append(image!)
self.collectionTable.reloadData()
} else {
self.libraryImages.append(image!)
self.collectionTable.reloadData()
}
})
/* The image is now available to us */
print("enum for image, This is number 2")
print("Inside If object is PHAsset, This is number 3")
}
print("Outside If object is PHAsset, This is number 4")
}
}
Please tell me if any more information is needed. Thank you!
Generally you don't want to be loading tons of full-size or screen-sized images and keeping them in memory yourself. For that size, you're only going to be presenting one (or two or three, if you want to preload for screen-transition animations) at a time, so you don't need to fetch more than that at once.
Similarly, loading hundreds or thousands of thumbnails and caching them yourself will cause you memory issues. (Telling your collection view to reloadData over and over again causes a lot of churn, too.) Instead, let the Photos framework manage them — it has features for making sure that thumbnails are generated only when needed and cached between uses, and even for helping you with tasks like grabbing only the thumbnails you need as the user scrolls in a collection view.
Apple's Using Photos Framework sample code project shows several best practices for fetching images. Here's a summary of some of the major points:
AssetGridViewController, which manages a collection view of thumbnails, caches a PHFetchResult<PHAsset> that's given to it upon initialization. Info about the fetch result (like its count) is all that's needed for the basic management of the collection view.
AssetGridViewController requests thumbnail images from PHImageManager individually, only when the collection view asks for a cell to display. This means that we only create a UIImage when we know a cell is going to be seen, and we don't have to tell the collection view to reload everything over and over again.
On its own, step 2 would lead to slow scrolling, or rather, to slow catch-up of images loading into cells after you scroll. So, we also do:
Instead of using PHImageManager.default(), we keep an instance of PHCachingImageManager, and tell it what set of images we want to "preheat" the cache for as the user scrolls.
AssetGridViewController has some extra logic for keeping track of the visible rect of the scroll view so that the image manager knows to prepare thumbnails for the currently visible items (plus a little bit of scroll-ahead), and can free resources for items no longer visible if it needs to. Note that prefetching doesn't create UIImages, just starts the necessary loading from disk or downloading from iCloud, so your memory usage stays small.
On selecting an item in the collection view (to segue to a full-screen presentation of that photo), AssetViewController uses the default PHImageManager to request a screen-sized (not full-sized) image.
Opportunistic delivery means that we'll get a lower-quality version (basically the same thumbnail that Photos has already cached for us from its previous use in the collection view) right away, and a higher quality version soon after (asynchronously, after which we automatically update the view).
If you need to support zooming (that sample code app doesn't, but it's likely a real app would), you could either request a full-size image right away after going to the full-screen view (at the cost of taking longer to transition from blown-up-thumbnail to fullscreen image), or start out by requesting a screen-sized image and then also requesting a full-sized image for later delivery.
In CellForRowAtIndexPath use the PHImageManager's RequestImageForAsset method to lazy load the image property on your UIImageView.