iOS PHPhotoLibrary cannot save video to Camera Roll - ios

I'm trying to save a video file from the application's documents folder to the device's Camera Roll. The error occurs when the albumChangeRequest tries to copy the asset to the Camera Roll. It fails to copy the file to the album, and gives back an error message (Domain=NSCocoaErrorDomain Code=-1 "(null)", The operation couldn't be completed). I previously used a deprecated method (writeVideoAtPathToSavedPhotosAlbum) to copy the video, and it also used the same file path, so the problem should not be the file path url.
...
PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary];
NSURL *filePathURL = [NSURL fileURLWithPath:videoPath isDirectory:NO];
__block NSString* assetURL = nil;
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
PHAssetCollection* album = fetchResult.firstObject;
if (album) {
[photoLibrary performChanges:^{
PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:filePathURL];
PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:album];
PHObjectPlaceholder *assetChangePlaceHolder = assetChangeRequest.placeholderForCreatedAsset;
assetURL = [assetChangePlaceHolder localIdentifier];
[albumChangeRequest addAssets:#[assetChangePlaceHolder]];
} completionHandler:^(BOOL success, NSError * error) {
if ( success ){
NSString* urlSubStr = [assetURL substringToIndex:36];
// Handle the result asset id in urlSubStr.
} else {
// Copy failed.
}
}];
}
...
Swift 3.0
let photoLibrary = PHPhotoLibrary.shared()
let filePathURL = URL(fileURLWithPath: videoPath, isDirectory: false)
var assetURL: String! = nil
let fetchResult = PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.smartAlbum, subtype: PHAssetCollectionSubtype.smartAlbumUserLibrary, options: nil)
if let album = fetchResult.firstObject {
photoLibrary.performChanges({
if let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL),
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
let assetChangePlaceholder = assetChangeRequest.placeholderForCreatedAsset {
assetURL = assetChangePlaceholder.localIdentifier
albumChangeRequest.addAssets(assetChangePlaceholder as! NSFastEnumeration)
}
}, completionHandler: { (succeed, error) in
if succeed {
let index = assetURL.index(assetURL.startIndex, offsetBy: 36)
let urlSubStr = assetURL.substring(to: index)
}
else {
// Copy failed
}
})
}
}

Related

How to create folder inside photos library in iOS swift [duplicate]

I am developing an iPhone photo application, so i need to create a separate album with a name "My Album" in camera roll and i need to save my UIImageView image with custom name for example "My Image.png" inside the newly created directory.
How can i do this?
Since the AssetsLibrary is deprecated, please use the Photos framework instead (iOS 8 and later).
// Deprecated!
import AssetsLibrary
// Swift 3.0
let assetsLibrary = ALAssetsLibrary()
assetsLibrary.addAssetsGroupAlbum(withName: "NewAlbum", resultBlock: { assetsGroup in
print(assetsGroup == nil ? "Already created" : "Success")
}, failureBlock: { error in
print(error)
})
You can use the shared PHPhotoLibrary object to create new photos but you can't give them specific names because you will be working with assets that need to be managed by the Photos.app. Each asset has specific properties. You can fetch objects, request changes, asset/thumbnail loading and caching, etc.
To create a custom album, please use the PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle:).
Brief example:
// Swift 3.0
func createPhotoLibraryAlbum(name: String) {
var albumPlaceholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
// Request creating an album with parameter name
let createAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
// Get a placeholder for the new album
albumPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { success, error in
if success {
guard let placeholder = albumPlaceholder else {
fatalError("Album placeholder is nil")
}
let fetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
guard let album: PHAssetCollection = fetchResult.firstObject else {
// FetchResult has no PHAssetCollection
return
}
// Saved successfully!
print(album.assetCollectionType)
}
else if let e = error {
// Save album failed with error
}
else {
// Save album failed with no error
}
})
}
Don't forget to import Photos library.
To create a new photo asset on that album, please use the PHAssetChangeRequest.creationRequestForAsset(from:).
// Swift 3.0
func createPhotoOnAlbum(photo: UIImage, album: PHAssetCollection) {
PHPhotoLibrary.shared().performChanges({
// Request creating an asset from the image
let createAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: photo)
// Request editing the album
guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album) else {
// Album change request has failed
return
}
// Get a placeholder for the new asset and add it to the album editing request
guard let photoPlaceholder = createAssetRequest.placeholderForCreatedAsset else {
// Photo Placeholder is nil
return
}
albumChangeRequest.addAssets([photoPlaceholder] as NSArray)
}, completionHandler: { success, error in
if success {
// Saved successfully!
}
else if let e = error {
// Save photo failed with error
}
else {
// Save photo failed with no error
}
})
}
UPDATE:
We need to request access to be able to use the Photos library:
PHPhotoLibrary.requestAuthorization { status in
switch status {
...
}
As of iOS 10 and above we also need to add entry for access in the target .plist file for "Privacy - Photo Library Usage Description":
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to photos is needed to provide app features</string>
You can create a custom album and add an image pretty easy with these lines of code in iOS:
// Create the new album.
__block PHObjectPlaceholder *myAlbum;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCollectionChangeRequest *changeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
myAlbum = changeRequest.placeholderForCreatedAssetCollection;
} completionHandler:^(BOOL success, NSError *error) {
if (success) {
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:#[myAlbum.localIdentifier] options:nil];
PHAssetCollection *assetCollection = fetchResult.firstObject;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
// add asset
PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
[assetCollectionChangeRequest addAssets:#[[assetChangeRequest placeholderForCreatedAsset]]];
} completionHandler:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"Error: %#", error);
}
}];
} else {
NSLog(#"Error: %#", error);
}
}];
Create a new album:
/// Create album with given title
/// - Parameters:
/// - title: the title
/// - completionHandler: the completion handler
func createAlbum(withTitle title: String, completionHandler: #escaping (PHAssetCollection?) -> ()) {
DispatchQueue.global(qos: .background).async {
var placeholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
let createAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
placeholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { (created, error) in
var album: PHAssetCollection?
if created {
let collectionFetchResult = placeholder.map { PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [$0.localIdentifier], options: nil) }
album = collectionFetchResult?.firstObject
}
completionHandler(album)
})
}
}
Get an album with with a specified name:
/// Get album with given title
/// - Parameters:
/// - title: the title
/// - completionHandler: the completion handler
func getAlbum(title: String, completionHandler: #escaping (PHAssetCollection?) -> ()) {
DispatchQueue.global(qos: .background).async { [weak self] in
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", title)
let collections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let album = collections.firstObject {
completionHandler(album)
} else {
self?.createAlbum(withTitle: title, completionHandler: { (album) in
completionHandler(album)
})
}
}
}
And save a photo to a Photos album:
func save(photo: UIImage, toAlbum titled: String, completionHandler: #escaping (Bool, Error?) -> ()) {
getAlbum(title: titled) { (album) in
DispatchQueue.global(qos: .background).async {
PHPhotoLibrary.shared().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAsset(from: photo)
let assets = assetRequest.placeholderForCreatedAsset
.map { [$0] as NSArray } ?? NSArray()
let albumChangeRequest = album.flatMap { PHAssetCollectionChangeRequest(for: $0) }
albumChangeRequest?.addAssets(assets)
}, completionHandler: { (success, error) in
completionHandler(success, error)
})
}
}
}
It was working from since iOS 5.0.
Please import AssetsLibrary/AssetsLibrary.h
ALAssetsLibrary* libraryFolder = [[ALAssetsLibrary alloc] init];
[libraryFolder addAssetsGroupAlbumWithName:#"My Album" resultBlock:^(ALAssetsGroup *group)
{
NSLog(#"Adding Folder:'My Album', success: %s", group.editable ? "Success" : "Already created: Not Success");
} failureBlock:^(NSError *error)
{
NSLog(#"Error: Adding on Folder");
}];
You can try My below Method for Create Album for iOS 7 and iOS 8
#define PHOTO_ALBUM_NAME #"AlbumName Videos"
-(void)createAlbum{
// PHPhotoLibrary_class will only be non-nil on iOS 8.x.x
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
if (PHPhotoLibrary_class) {
// iOS 8..x. . code that has to be called dynamically at runtime and will not link on iOS 7.x.x ...
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:PHOTO_ALBUM_NAME];
} completionHandler:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"Error creating album: %#", error);
}else{
NSLog(#"Created");
}
}];
}else{
[self.library addAssetsGroupAlbumWithName:PHOTO_ALBUM_NAME resultBlock:^(ALAssetsGroup *group) {
NSLog(#"adding album:'Compressed Videos', success: %s", group.editable ? "YES" : "NO");
if (group.editable == NO) {
}
} failureBlock:^(NSError *error) {
NSLog(#"error adding album");
}];
}}
/// Save images or videos(保存图片或视频)(kUTTypeImage, kUTTypeMovie)
/// Add to album if specified album name, and create album if needed
/// #params mediaArray UIImage, fileURL for a image or video
+ (void)_saveMediaArray:(NSArray *)mediaArray
options:(LAImageSaverOptions *)options
completion:(void (^)(NSError * _Nullable err))completion
{
NSInteger __block count = 0;
[PHPhotoLibrary.sharedPhotoLibrary performChanges:^{
// Create album if needed
PHAssetCollectionChangeRequest *assetCollectionChangeRequest = nil;
NSMutableArray<PHObjectPlaceholder *> *assetChangeRequestPlaceholders = nil;
if (options.targetAlbumName.length > 0) {
assetChangeRequestPlaceholders = [NSMutableArray arrayWithCapacity:mediaArray.count];
PHFetchOptions *fetchOptions = PHFetchOptions.new;
//fetchOptions.includeAssetSourceTypes = PHAssetSourceTypeUserLibrary;
fetchOptions.predicate = [NSPredicate predicateWithFormat:#"localizedTitle = %#", options.targetAlbumName]; // 不能用 block 形式的 predicate
PHAssetCollection * assetCollection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:fetchOptions].firstObject;
if (nil == assetCollection) {
assetCollectionChangeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:options.targetAlbumName];
} else {
assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
}
}
// Save images
for (id item in mediaArray) {
PHAssetChangeRequest *assetChangeRequest = nil;
// image object
if ([item isKindOfClass:UIImage.class]) {
UIImage *image = (UIImage *)item;
assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
[assetChangeRequestPlaceholders addObject:assetChangeRequest.placeholderForCreatedAsset];
++count;
continue;
}
// file url for image or movie
NSURL *fileURL = (NSURL *)item;
if ([item isKindOfClass:NSURL.class] && fileURL.isFileURL) {
NSString *extension = fileURL.pathExtension;
if (extension.length == 0) {
NSLog(#"illegal fileURL(no path extension): %#", fileURL);
continue; // illegal file url
}
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
BOOL isImage = false;
BOOL isVideo = false;
if (nil != uti && CFStringGetLength(uti) > 0) {
isImage = UTTypeConformsTo(uti, kUTTypeImage);
isVideo = UTTypeConformsTo(uti, kUTTypeMovie); // kUTTypeVideo, kUTTypeAudiovisualContent
}
if (isImage) {
assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
[assetChangeRequestPlaceholders addObject:assetChangeRequest.placeholderForCreatedAsset];
++count;
} if (isVideo) {
assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:fileURL];
[assetChangeRequestPlaceholders addObject:assetChangeRequest.placeholderForCreatedAsset];
++count;
} else {
NSLog(#"illegal fileURL(neither image nor movie): %#", fileURL);
continue; // illegal file url
}
}
}
// add to album if needed
[assetCollectionChangeRequest addAssets:assetChangeRequestPlaceholders];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// not in main thread
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}];
}
By the way, you can do more about LAImageSaverOptions
#interface LAImageSaverOptions : NSObject
/// to show alert controller on the hostVC
#property(nonatomic, weak, null_resettable) UIViewController *hostVC;
/// total progress
#property (nonatomic, strong, null_resettable) NSProgress *progress;
// album name for saving images
#property (nonatomic, copy, nullable) NSString *targetAlbumName;
#end
you can save image with custom name to sandbox first,and then save it to album,it work to me.

Save video to the gallery and get the path for the video stored to Gallery

I want to save a video chosen from UIImagePickerController to the Photos album.
After that i need to fetch the URL to the saved path. I saw some pre-existing questions which answer with use of ALAssetsLibrary which is now deprecated.
Like : Save video on the gallery and store the path to the video
Can someone please guide me to use Photo's framework and achieve the above desired result. Kindly correct me if I am wrong anywhere.
Thanks!!
This worked for me perfectly.
Swift 3.1 ->
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
// After uploading we fetch the PHAsset for most recent video and then get its current location url
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).lastObject
PHImageManager().requestAVAsset(forVideo: fetchResult!, options: nil, resultHandler: { (avurlAsset, audioMix, dict) in
let newObj = avurlAsset as! AVURLAsset
print(newObj.url)
// This is the URL we need now to access the video from gallery directly.
})
}
}
First, you need to set the following permission in your app's plist file:
Privacy - Photo Library Usage Description
Provide a string that is presented to the user explaining why you are requesting the permission.
Next,
import photos
Then use this code to store video
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileURL)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
// fetchResult is your latest video PHAsset
// To fetch latest image replace .video with .image
}
}
To get url from PHAsset, refer to this question's answer
If you want to fetch the saved element (not using a sort descriptor with date order), you can do it this way:
var changeRequest: PHAssetChangeRequest?
var blockPlaceholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
changeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
blockPlaceholder = changeRequest?.placeholderForCreatedAsset
}) { saved, error in
if saved {
guard let placeholder = blockPlaceholder else {
return
}
let fetchOptions = PHFetchOptions()
let fetchResult:PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: fetchOptions)
if let asset = fetchResult.firstObject {
//here you have the PHAsset
}
}
}
Try like this!
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){
if mediaType.isEqualToString(kUTTypeMovie as NSString as String) || mediaType.isEqualToString(kUTTypeVideo as NSString as String){
let videoPath = info[UIImagePickerControllerMediaURL] as! NSURL // Path of video Url
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(videoPath)
let assetPlaceholder = createAssetRequest?.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
albumChangeRequest!.addAssets([assetPlaceholder!])
}, completionHandler: { (success, error) in
NSLog("Adding video to Library ->%#", (success ? "Success" : "Error"))
picker.dismissViewControllerAnimated(true, completion: nil)
})
}
}
Here's the answer for Objective-C
Code Sample:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL URLWithString:urlOfFile]];
} completionHandler:^(BOOL success, NSError *error) {
if (success)
{
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc]init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:false]];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:fetchOptions];
PHAsset *lastAsset = [fetchResult lastObject];
[[PHImageManager defaultManager] requestAVAssetForVideo:lastAsset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
//here's the url to the file
NSURL *url = (NSURL *)[(AVURLAsset *)asset URL];
}else{
NSLog(#"%#",error.description);
}
}];
I do this by creating extension of the URL Class
extension URL {
func saveVideo( success:#escaping (Bool,URL?)->()){
URLSession.shared.downloadTask(with: URLRequest(url: self)) { (url, response, error) in
let mgr = FileManager.default
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let destination = URL(fileURLWithPath: String(format: "%#/%#", documentsPath, "video.mp4"))
try? mgr.moveItem(atPath: /url?.path, toPath: /destination.path)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: destination)
}) { completed, error in
if completed {
print("Video is saved!")
success(true,destination)
}
if error != nil{
success(false,nil)
}
}
}.resume()
}
}
when you want to save and play you can use this in ViewController as
guard let urlToDownload = URL(string: videoURLStringToDownload) else { return }
urlToDownload.saveVideo(success: { (isSaved,url) in
guard let videoURL = url, isSaved else { return }
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player?.play()
}
})
import AVKit for playing videos through AVPlayerViewController
import Photos and PhotosUI for saving Video with PHAsset.
Hope it helps!

Save the exif metadata using the new PHPhotoLibrary

I am currently using AVCaptureStillImageOutput to get a full resolution picture. I am also able to get the exif metadata using the following code:
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
NSLog(#"test attachments %#", mutableDict);
// set the dictionary back to the buffer
CMSetAttachments(imageSampleBuffer, mutableDict, kCMAttachmentMode_ShouldPropagate);
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[self.delegate frameReadyToSave:image withExifAttachments: mutableDict];
}];
The metadata being located in the mutableDict variable. Now, I want to save this picture in two different places, with the metadata. I want to save it on the disk in the application folders and in the Photo Library.
Now, I tried to save the image, in another method, using the following (the image variable you see is a custom object):
NSData* imageData = UIImageJPEGRepresentation(image.image, 1.0f);
[imageData writeToFile:image.filePath atomically:YES];
UIImageWriteToSavedPhotosAlbum(image.image, nil, nil, nil);
Now, the image is properly saved but does not contain any Exif metadata.
From what I have read, I need to use the PHPhotoLibrary to do so but the documentation isn't too loquacious on that. Here's what I found:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image.image];
} completionHandler:nil];
But how do I save the metadata with it?
I would suggest you use ImageIO to accomplish that:
-(void)frameReadyToSave:(UIImage*)image withExifAttachments:(NSMutableDictionary*)mutableDict
{
NSData* imageData = UIImageJPEGRepresentation(image, 1.0f);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
__block NSURL* tmpURL = [NSURL fileURLWithPath:#"example.jpg"]; //modify to your needs
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef) tmpURL, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) mutableDict);
CGImageDestinationFinalize(destination);
CFRelease(source);
CFRelease(destination);
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL];
} completionHandler:^(BOOL success, NSError *error) {
//cleanup the tmp file after import, if needed
}];
}
Use this to merge metadata into image data and save it to Photo Library:
func saveImageData(data: Data, metadata: NSDictionary? = nil, album: PHAssetCollection, completion:((PHAsset?)->())? = nil) {
var placeholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
var changeRequest: PHAssetChangeRequest
if let metadata = metadata {
let newImageData = UIImage.mergeImageData(imageData: data, with: metadata)
changeRequest = PHAssetCreationRequest.forAsset()
(changeRequest as! PHAssetCreationRequest).addResource(with: .photo, data: newImageData as Data, options: nil)
}
else {
changeRequest = PHAssetChangeRequest.creationRequestForAsset(from: UIImage(data: data)!)
}
guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
let photoPlaceholder = changeRequest.placeholderForCreatedAsset else { return }
placeholder = photoPlaceholder
let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
albumChangeRequest.addAssets(fastEnumeration)
}, completionHandler: { success, error in
guard let placeholder = placeholder else {
completion?(nil)
return
}
if success {
let assets:PHFetchResult<PHAsset> = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
let asset:PHAsset? = assets.firstObject
completion?(asset)
}
else {
completion?(nil)
}
})
}
func mergeImageData(imageData: Data, with metadata: NSDictionary) -> Data {
let source: CGImageSource = CGImageSourceCreateWithData(imageData as NSData, nil)!
let UTI: CFString = CGImageSourceGetType(source)!
let newImageData = NSMutableData()
let cgImage = UIImage(data: imageData)!.cgImage
let imageDestination: CGImageDestination = CGImageDestinationCreateWithData((newImageData as CFMutableData), UTI, 1, nil)!
CGImageDestinationAddImage(imageDestination, cgImage!, metadata as CFDictionary)
CGImageDestinationFinalize(imageDestination)
return newImageData as Data
}

Could we upload the documents directory video to facebook using Facebook SDK

I have a question in uploading the video to Facebook using Facebook SDK.I am trying to upload the video which I have selected from SavedPhotos is working fine. But When I am trying to upload the video from my documents directory it is saying this below error. What I know is we can upload the video which are having the asset url. Is there any other way to upload the documents directory video to the facebook???
Error is
2015-05-26 16:30:02.369 graphtwentysixth[3025:1413799] FB: ERROR=Error Domain=com.facebook.sdk.share Code=2 "The operation couldn’t be completed. (com.facebook.sdk.share error 2.)" UserInfo=0x156b0f90 {com.facebook.sdk:FBSDKErrorArgumentValueKey=file:///private/var/mobile/Containers/Bundle/Application/48DA75B3-63BA-400A-AC92-BE6B4A2B954B/graphtwentysixth.app/demo-video-high-quality.mov, com.facebook.sdk:FBSDKErrorArgumentNameKey=videoURL, com.facebook.sdk:FBSDKErrorDeveloperMessageKey=Invalid value for videoURL: file:///private/var/mobile/Containers/Bundle/Application/48DA75B3-63BA-400A-AC92-BE6B4A2B954B/graphtwentysixth.app/demo-video-high-quality.mov}
Code
NSURL *videoURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"demo-video-high-quality" ofType:#"mov"]];
FBSDKShareVideo *video = [[FBSDKShareVideo alloc] init];
video.videoURL = videoURL;
FBSDKShareVideoContent *content1 = [[FBSDKShareVideoContent alloc] init];
content1.video = video;
[FBSDKShareAPI shareWithContent:content1 delegate:self];
Thank you for your valuable time
The video cannot be uploaded from document directory. You can achieve this by making video an asset and than give the url of asset to Facebook and on completion handler call delete that video asset from gallery. This is a trick but not a good solution as when you make video an asset of galley it will visible in savedPhotos.
Like Shoaib said, you'll need to make the video into an asset first. Be sure to include #import <AssetsLibrary/AssetsLibrary.h> in your class.
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
ALAssetsLibraryWriteVideoCompletionBlock videoWriteCompletionBlock = ^(NSURL *newURL, NSError *error) {
if (error)
{
NSLog( #"Error writing image with metadata to Photo Library: %#", error );
}
else
{
NSLog(#"Wrote image with metadata to Photo Library %#", newURL.absoluteString);
FBSDKShareVideo* video = [FBSDKShareVideo videoWithVideoURL:newURL];
FBSDKShareVideoContent* content = [[FBSDKShareVideoContent alloc] init];
content.video = video;
[FBSDKShareAPI shareWithContent:content delegate:self];
}
};
NSURL *videoURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"demo-video-high-quality" ofType:#"mov"]];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL])
{
[library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:videoWriteCompletionBlock];
}
(EDIT inspired in part by this post)
You should first save the video to your Library, then get the correct PHAsset and generate the correct URL:
guard let schemaUrl = URL(string: "fb://") else {
return //be safe
}
if UIApplication.shared.canOpenURL(schemaUrl) {
PHPhotoLibrary.requestAuthorization({ [weak self]
(newStatus) in
guard let strongSelf = self else {
return
}
if newStatus == PHAuthorizationStatus.authorized {
strongSelf.saveVideoToCameraRoll(url: assetURL, completion: { (result, phAsset) in
phAsset?.getURL(completionHandler: { (url) in
if let url = url {
dispatchAsyncOnMainQueue {
let video = FBSDKShareVideo()
video.videoURL = url
let content = FBSDKShareVideoContent()
content.video = video
dialog.shareContent = content
dialog.show()
}
}
})
})
} else {
//unauthorized
}
})
} else {
//facebookAppNotInstalled
}
...
func saveVideoToCameraRoll(url: URL, completion:#escaping (Bool, PHAsset?) -> ()) {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
completion(true, fetchResult)
} else {
completion(false, nil)
}
}
}
...
extension PHAsset {
func getURL(completionHandler : #escaping ((_ responseURL : URL?) -> Void)){
if self.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .original
let nameParts = self.localIdentifier.components(separatedBy: "/")
if nameParts.count > 0 {
let assetFormatString = "assets-library://asset/asset.MP4?id=%#&ext=MP4"
let name = nameParts[0]
let urlString = String(format: assetFormatString, name)
if let url = URL(string: urlString) {
completionHandler(url)
} else {
completionHandler(nil)
}
}
}
}
}
You just try below code may it's help to you.
- (void)upload{
if (FBSession.activeSession.isOpen) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"demo-video-high-quality" ofType:#"mov"];
NSURL *pathURL = [[NSURL alloc]initFileURLWithPath:filePath isDirectory:NO];
NSData *videoData = [NSData dataWithContentsOfFile:filePath];
NSDictionary *videoObject = #{
#"title": #"FB SDK 3.1",
#"description": #"hello there !",
[pathURL absoluteString]: videoData
};
FBRequest *uploadRequest = [FBRequest requestWithGraphPath:#"me/videos"
parameters:videoObject
HTTPMethod:#"POST"];
[uploadRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error)
NSLog(#"Done: %#", result);
else
NSLog(#"Error: %#", error.localizedDescription);
}];
}
}

Save images with phimagemanager to custom album?

I am making an app that takes pictures with AVFoundation and I want to save them to a custom album that I can then query and show in my app. (I'd prefer to not have them in the general photo roll, unless the user wants that) I can't really find anything showing how to do this in Swift... or at all. Is there a different way I am supposed to do this?
I found this example on SO but it doesn't make sense to me and I can't get it to work.
func savePhoto() {
var albumFound : Bool = false
var assetCollection: PHAssetCollection!
var photosAsset: PHFetchResult!
var assetThumbnailSize:CGSize!
// Create the album if does not exist (in viewDidLoad)
if let first_Obj:AnyObject = collection.firstObject{
//found the album
self.albumFound = true
self.assetCollection = collection.firstObject 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
NSLog("Creation of folder -> %#", (success ? "Success":"Error!"))
self.albumFound = (success ? true:false)
if(success){
let collection = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([albumPlaceholder.localIdentifier], options: nil)
self.assetCollection = collection?.firstObject as PHAssetCollection
}
})
}
let bundle = NSBundle.mainBundle()
let myFilePath = bundle.pathForResource("highlight1", ofType: "mov")
let videoURL:NSURL = NSURL.fileURLWithPath(myFilePath!)!
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0), {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
//let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(videoURL)
let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
albumChangeRequest.addAssets([assetPlaceholder])
}, completionHandler: {(success, error)in
dispatch_async(dispatch_get_main_queue(), {
NSLog("Adding Image to Library -> %#", (success ? "Sucess":"Error!"))
//picker.dismissViewControllerAnimated(true, completion: nil)
})
})
})
}
Any help/explanations would be great!
This is how I do:
At the top:
import Photos
var image: UIImage!
var assetCollection: PHAssetCollection!
var albumFound : Bool = false
var photosAsset: PHFetchResult!
var assetThumbnailSize:CGSize!
var collection: PHAssetCollection!
var assetCollectionPlaceholder: PHObjectPlaceholder!
Creating the album:
func createAlbum() {
//Get PHFetch Options
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", "camcam")
let collection : PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
//Check return value - If found, then get the first album out
if let _: AnyObject = collection.firstObject {
self.albumFound = true
assetCollection = collection.firstObject as! PHAssetCollection
} else {
//If not found - Then create a new album
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle("camcam")
self.assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { success, error in
self.albumFound = success
if (success) {
let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([self.assetCollectionPlaceholder.localIdentifier], options: nil)
print(collectionFetchResult)
self.assetCollection = collectionFetchResult.firstObject as! PHAssetCollection
}
})
}
}
When saving the photo:
func saveImage(){
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(self.image)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
albumChangeRequest!.addAssets([assetPlaceholder!])
}, completionHandler: { success, error in
print("added image to album")
print(error)
self.showImages()
})
}
Showing the images from that album:
func showImages() {
//This will fetch all the assets in the collection
let assets : PHFetchResult = PHAsset.fetchAssetsInAssetCollection(assetCollection, options: nil)
print(assets)
let imageManager = PHCachingImageManager()
//Enumerating objects to get a chached image - This is to save loading time
assets.enumerateObjectsUsingBlock{(object: AnyObject!,
count: Int,
stop: UnsafeMutablePointer<ObjCBool>) in
if object is PHAsset {
let asset = object as! PHAsset
print(asset)
let imageSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
let options = PHImageRequestOptions()
options.deliveryMode = .FastFormat
imageManager.requestImageForAsset(asset, targetSize: imageSize, contentMode: .AspectFill, options: options, resultHandler: {(image: UIImage?,
info: [NSObject : AnyObject]?) in
print(info)
print(image)
})
}
}
Answer in Objective-C and cleaned up a bit.
__block PHFetchResult *photosAsset;
__block PHAssetCollection *collection;
__block PHObjectPlaceholder *placeholder;
// Find the album
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.predicate = [NSPredicate predicateWithFormat:#"title = %#", #"YOUR_ALBUM_TITLE"];
collection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum
subtype:PHAssetCollectionSubtypeAny
options:fetchOptions].firstObject;
// Create the album
if (!collection)
{
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCollectionChangeRequest *createAlbum = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:#"YOUR_ALBUM_TITLE"];
placeholder = [createAlbum placeholderForCreatedAssetCollection];
} completionHandler:^(BOOL success, NSError *error) {
if (success)
{
PHFetchResult *collectionFetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:#[placeholder.localIdentifier]
options:nil];
collection = collectionFetchResult.firstObject;
}
}];
}
// Save to the album
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:[UIImage imageWithData:imageData]];
placeholder = [assetRequest placeholderForCreatedAsset];
photosAsset = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:collection
assets:photosAsset];
[albumChangeRequest addAssets:#[placeholder]];
} completionHandler:^(BOOL success, NSError *error) {
if (success)
{
NSString *UUID = [placeholder.localIdentifier substringToIndex:36];
self.photo.assetURL = [NSString stringWithFormat:#"assets-library://asset/asset.PNG?id=%#&ext=JPG", UUID];
[self savePhoto];
}
else
{
NSLog(#"%#", error);
}
}];
The bit at the end with the UUID was something I found on another StackOverflow thread for creating a replacement for AssetURL property from an ALAsset.
Note: See chris' comment below for more complete answer.
I like to reuse the code I write so I decided to create an extension for PHPhotoLibrary where it is possible to use it like:
PHPhotoLibrary.saveImage(photo, albumName: "Trip") { asset in
guard let asset = asset else {
assert(false, "Asset is nil")
return
}
PHPhotoLibrary.loadThumbnailFromAsset(asset) { thumbnail in
print(thumbnail)
}
}
Here is the code: https://gist.github.com/ricardopereira/636ccd0a3c8a327c43d42e7cbca4d041
As updated for Swift 2.1+ and for Video in case you are trying to do that and ended up here. Compare to the other answers for slight differences (such as using for Images rather than Video)
var photosAsset: PHFetchResult!
var collection: PHAssetCollection!
var assetCollectionPlaceholder: PHObjectPlaceholder!
//Make sure we have custom album for this app if haven't already
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", "MY_APP_ALBUM_NAME")
self.collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions).firstObject as! PHAssetCollection
//if we don't have a special album for this app yet then make one
if self.collection == nil {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle("MY_APP_ALBUM_NAME")
self.assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { success, error in
if success {
let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([self.assetCollectionPlaceholder.localIdentifier], options: nil)
print(collectionFetchResult)
self.collection = collectionFetchResult.firstObject as! PHAssetCollection
}
})
}
//save the video to Photos
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(self.VIDEO_URL_FOR_VIDEO_YOU_MADE!)
let assetPlaceholder = assetRequest!.placeholderForCreatedAsset
self.photosAsset = PHAsset.fetchAssetsInAssetCollection(self.collection, options: nil)
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.collection, assets: self.photosAsset)
albumChangeRequest!.addAssets([assetPlaceholder!])
}, completionHandler: { success, error in
if success {
print("added video to album")
}else if error != nil{
print("handle error since couldn't save video")
}
}
})
I improved on #ricardopereira and #ColossalChris code. Added video to the extension, and added another extension on top of PHAsset to get rid of the compilation errors. Works in Swift 2.1.
Sample usage:
#import "Yourtargetname-Swift.h"//important!
NSURL *videoURL = [[NSURL alloc] initFileURLWithPath:PATH_TO_VIDEO];
[PHPhotoLibrary saveVideo:videoURL albumName:#"my album" completion:^(PHAsset * asset) {
NSLog(#"success");
NSLog(#"asset%lu",(unsigned long)asset.pixelWidth);
}];
Import both swift files:
https://github.com/kv2/PHPhotoLibrary-PhotoAsset.swift
It is usable in objective-c as long as you import the swift header for your target (see the ViewController.m file).

Resources