I have a PHAssetCollection (an album), which I have in my app. On viewDidLoad(), my code checks to see if there is a picture already in the album, and if so, it adds the pre-existing picture to the PHAssetCollecton. However, I NEVER want to add pre-existing pictures in the PHAssetCollection. I always want my assetCollection to be fresh, without any picture(s) or data.
How do I set self.assetCollection to be a brand new AssetCollection with no picture(s) or data? Deleting that line of code causes my app to crash...
code in viewDidLoad()
//Check if the folder exists, if not, create it
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", albumName)
let collection:PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let first_Obj:AnyObject = collection.firstObject{
//Album already exists, and has pictures.
self.albumFound = true
//Here is my issue. I want to set self.assetCollection to a brand new PHAssetCollection with NO data... brand new
self.assetCollection = first_Obj as! PHAssetCollection
There is also this code in viewDidAppear, I am unsure if this is of any use, but contains self.assetCollection so I added it:
self.photosAsset = PHAsset.fetchAssetsInAssetCollection(self.assetCollection, options: nil)
Related
I am trying to fetch images from album and this code fetch all images from specific album, but on scrolling app will close and give error "Message from debugger: Terminated due to memory issue". Please check the code and find the error.(I want to fetch all albums and images like "Lalalab" app without memory warning).
func fatchImagesfromAlbum() {
DispatchQueue.global(qos: .background).async {
self.photoAssets = self.fetchResult as! PHFetchResult<AnyObject>
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
self.photoAssets = PHAsset.fetchAssets(in: self.assetCollection, options: fetchOptions) as! PHFetchResult<AnyObject>
for i in 0..<self.photoAssets.count{
let asset = self.photoAssets.object(at: i)
let imageSize = CGSize(width: asset.pixelWidth,
height: asset.pixelHeight)
let options = PHImageRequestOptions()
options.deliveryMode = .fastFormat
options.isSynchronous = true
self.imageManager.requestImage(for: asset as! PHAsset, targetSize: imageSize, contentMode: .aspectFill, options: options, resultHandler: { (image, info) -> Void in
self.images.append(image!)
let url:NSURL = info!["PHImageFileURLKey"] as! NSURL
let urlString: String = url.path!
let theFileName = (urlString as NSString).lastPathComponent
print("file name\(info!)")
self.imageName.append("\(theFileName)")
self.imagePath.append("\(urlString)")
})
print(self.imagePath)
print(self.imageName)
DispatchQueue.main.async
{
[unowned self] in
self.collectionView.reloadData()
}
}
}
PHPhotoLibrary.shared().register(self)
if fetchResult == nil {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
fetchResult = PHAsset.fetchAssets(with: allPhotosOptions)
}
}
Faced such an issue; It has nothing to do with making weak or unowned references. When objects have been created by either your Objective-C code or using Cocoa classes, what you should do is to deal with autoreleasepool, try to call your method inside autoreleasepool:
autoreleasepool {
fatchImagesfromAlbum()
}
Should it be fetchImagesfromAlbum instead of fatchImagesfromAlbum?
Citing from Advanced Memory Management Programming Guide:
Autorelease pool blocks provide a mechanism whereby you can relinquish
ownership of an object, but avoid the possibility of it being
deallocated immediately (such as when you return an object from a
method). Typically, you don’t need to create your own autorelease pool
blocks, but there are some situations in which either you must or it
is beneficial to do so.
However, it should not be required to execute the whole method in the autoreleasepool, probably the reason of the memory issue is caused by executing fetchAssets or requestImage iteratively (inside the for loop).
Refers to: Is it necessary to use autoreleasepool in a Swift program?
You need to create a diapatch group then whenever you run loop enter group and leave. After loop has finished iteration you can call diapatch.notify and relaod your UI
Right now I use that to exclude Screenshots:
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "NOT (mediaSubtype & \(PHAssetMediaSubtype.photoScreenshot.rawValue) != 0)")
var fetchResult: PHFetchResult<PHAsset> = fetchResult =
PHAsset.fetchAssets(with: PHAssetMediaType.image, options: options)
But I want to include only photos that were taken from iPhone Camera (not photos that were saved from other applications like Skype and so on). Is it possible?
Photos and videos are not saving in my custom photo album (TestAlbum). Instead, it is saving in the Camera Roll. Can you kindly help me to identify the issue and help me to correct the code.
PHPhotoLibrary.shared().performChanges({
let albumName = "TestAlbum"
let assetCollection: PHAssetCollection!
let fetchOptions = PHFetchOptions()
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumName)
fetchOptions.predicate = NSPredicate(format: "title = %#", albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject {
collection.firstObject
}
let options = PHAssetResourceCreationOptions()
options.shouldMoveFile = true
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .video, fileURL: outputFileURL, options: options)
}, completionHandler: { success, error in
if !success {
print("Could not save movie to photo library: \(String(describing: error))")
}
cleanUp()
})
As in the comments above I would separate the creation of the photo album and the saving of your photo/video into two performChanges blocks. But I don't see anyway in your code where you are actually adding the asset (photo/video) to your asset collection (photo album). To do that do as follows
let assetPlaceholder = creationRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for:collection)
if let changeRequest = albumChangeRequest, let placeholder = assetPlaceholder {
changeRequest.addAssets(NSArray(array:[placeholder]))
}
In iOS PhotoKit, I can fetch all non-empty albums like this:
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "estimatedAssetCount > 0")
let albumFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: albumFetchOptions)
albumFetchResult.enumerateObjects({ (collection, _, _) in
// Do something with the album...
})
Then I can get only photos from the album like this:
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetResourceType.photo.rawValue)
let fetchResults = PHAsset.fetchAssets(in: collection, options: fetchOptions)
But the first part can give me albums with only videos, which means that after I apply the predicate to the second part, the album will be empty. Is there a way to filter out those albums in the first part, before I start using them?
It seems that collections cannot be filtered like this without also fetching the items in the collections. See the docs for available fetch options; none allow filtering by number of a specific type of media.
The way I would achieve this is by fetching all albums created by the user, and then fetching the assets from the albums with a predicate that returns only images.
So to put it in code:
var userCollections: PHFetchResult<PHAssetCollection>!
// Fetching all PHAssetCollections with at least some media in it
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "estimatedAssetCount > 0")
// Performing the fetch
userCollections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: options)
Next, fetch the assets from a collection that are images by specifying a predicate:
// Getting the specific collection (I assumed to use a tableView)
let collection = userCollections[indexPath.row]
let optionsToFilterImage = PHFetchOptions()
optionsToFilterImage.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.Image.rawValue)
// Fetching the asset with the predicate to filter just images
let justImages = PHAsset.fetchAssets(in: collection, options: optionsToFilterImage)
Lastly, count the number of images:
if justImages.count > 0 {
// Display it
} else {
// The album has no images
}
I created an PHAssetCollection to house my photos for my app, and it works fine. However, I am trying to have it so the user can delete the PHAssetCollection when they press a button. How do I go about deleting the entire AssetCollection ("App Folder") that I created in the following code?
Code to create PHAssetCollection:
let albumName = "App Folder"
//Check if the folder exists, if not, create it
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", albumName)
let collection:PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let first_Obj:AnyObject = collection.firstObject{
//found the album
self.albumFound = true
self.assetCollection = first_Obj as! PHAssetCollection
}else{
//Album placeholder for the asset collection, used to reference collection in completion handler
var albumPlaceholder:PHObjectPlaceholder!
//create the folder
NSLog("\nFolder \"%#\" does not exist\nCreating now...", albumName)
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let request = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(albumName)
albumPlaceholder = request.placeholderForCreatedAssetCollection
},
completionHandler: {(success:Bool, error:NSError!)in
if(success){
println("Successfully created folder")
self.albumFound = true
if let collection = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([albumPlaceholder.localIdentifier], options: nil){
self.assetCollection = collection.firstObject as! PHAssetCollection
}
}else{
println("Error creating folder")
self.albumFound = false
}
})
}
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
PHAssetCollectionChangeRequest.deleteAssetCollections([self.deleteTarget])
}, completionHandler: nil)
There's a class method in PHAssetCollectionChangeRequest called deleteAssetCollections: which does just that: requests that specific asset collections be deleted. Looking at the documentation it seems you can just call this with an array of PHAssetCollections like so:
PHAssetCollectionChangeRequest.deleteAssetCollections(self.assetCollection)
just made it easy to use. hope it helps :
Here's function to delete custom album programmatically with error handeling
func deleteAlbum(albumName: String){
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "title = %#", albumName)
let album = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: options)
// check if album is available
if album.firstObject != nil {
// request to delete album
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.deleteAssetCollections(album)
}, completionHandler: { (success, error) in
if success {
print(" \(albumName) removed succesfully")
} else if error != nil {
print("request failed. please try again")
}
})
}else{
print("requested album \(albumName) not found in photos")
}
}
How to Use -
deleteAlbum(albumName: "YourAlbumName")