NSData WriteToFile Fails while saving a photo from gallery through Share Extension - ios

I am Writing an app which has share extension to save selected photo to my app' local storage from iphone photo gallery.
NSData WriteToFile returns YES but I couldn't find the stored file into the directory in of which I gave path while writing.
So, in short NSData WriteToFile fails to save a photo at given path.
Below is my code.
- (IBAction)acceptButtonTapped:(id)sender
{
__block UIImage *photo;
for (NSExtensionItem *item in self.extensionContext.inputItems)
{
for (NSItemProvider *itemProvider in item.attachments)
{
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image)
{
dispatch_async(dispatch_get_main_queue(), ^{
photo = image;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy_MM_dd_hh_mm_ss"];
NSString *fileName;
fileName = [NSString stringWithFormat:#"%#.jpeg",[formatter stringFromDate:[NSDate date]]];
dataPath = [dataPath stringByAppendingPathComponent:fileName];
NSData * imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
BOOL isdone = [imageData writeToFile:dataPath atomically:NO];
NSLog(#"%u", isdone);
});
}
}];
break;
}
}
}
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
Any Help would be much appreciable.
Thank you.

If you're trying to access the Document directory from the share extension, NO you can't do that. Share extension or other widgets are separate application from their containing app and therefore have their own sandbox. So you will need to use App Groups to share files.
Application groups are primarily targeted for extensions, more specifically, for widgets.
NSFileManager has a method on it containerURLForSecurityApplicationGroupIdentifier: where you can pass in the identifier you created when turning on App Groups for your apps
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:#"group.com.company.app"];
You can save the files to this location, because you can access the shared application groups from both extension and host app.

You're modifying dataPath on each pass through the loop, appending another filename to it. That will create an ever-growing series of badly formed paths that contain all the filenames.
Don't do that. Create a new local variable filePath, and construct a filename into filePath using
filePath = [docsPath stringByAppendingPathComponent: filename];
Log your path and LOOK AT IT. When your program doesn't behave as expected, don't trust any of your assumptions, because one or more of them may be wrong.

Related

iOS Save file to non-private directory

Right now I am working on a project which uploads files from the system and I was wondering if it is possible to save NSData to a location that is not private. Basically when I save data right now the cooresponding url looks something like this:
file:///private/var/mobile/Containers/Data/Application/.../image.jpg
But when I get files back from the UIImagePicker the URL looks like this:
file:///var/mobile/Containers/Data/Application/.../image.jpg
How can I (if it is possible) save data to a directory not under private?
Just for reference this is the code I have to move files so far:
- (NSURL *)moveFileToTemporaryMemory:(NSURL *)url {
NSError *readingError, *writingError;
NSString *fileName = url.lastPathComponent;
NSURL *tempURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:fileName];
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&readingError];
[data writeToURL:tempURL options:NSDataWritingAtomic error:&writingError];
return tempURL;
}
There's no difference -- /var is a symbolic link to /private/var, so both paths are equivalent.

Uploading images to Firebase

I'm trying to upload images to Firebase like this:
Firebase *ref = [[Firebase alloc] initWithUrl:#"https://<app-name>.firebaseio.com/posts"];
Firebase *newPost = [ref childByAutoId];
NSDictionary *newPostData = #{
#"image" : [self encodeToBase64String:image]
};
[newPost updateChildValues:newPostData];
I'm using this code to encode the image:
- (NSString *)encodeToBase64String:(UIImage *)image {
return [UIImagePNGRepresentation(image) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}
But this does not work as the string exceeds the maximum size:
Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(updateChildValues:) String exceeds max size of 10485760 utf8 bytes:
What can I do to resolve this problem? I haven't found anything online in regards to iOS development and images when using Firebase.
If the image is too big, you should store a smaller image. Let me quote myself: How do you save a file to a Firebase Hosting folder location for images through android?
The Firebase Database allows you to store JSON data. While binary data is not a type that is supported in JSON, it is possible to encode the binary data in say base64 and thus store the image in a (large) string. [But] while this is possible, it is not recommended for anything but small images or as a curiosity to see that it can be done.
Your best option is typically to store the images on a 3rd party image storage service.
As Frank van Puffelen suggested, my solution was to use Amazon S3 for imagine storage, and use Firebase to store a reference to the image location.
I created a method called uploadImage: and it looks like this:
-(void)uploadImage:(UIImage *)image
{
// Create reference to Firebase
Firebase *ref = [[Firebase alloc] initWithUrl:#"https://<MY-APP>.firebaseio.com"];
Firebase *photosRef = [ref childByAppendingPath:#“photos];
Firebase *newPhotoRef = [photosRef childByAutoId];
// Image information
NSString imageId = [[NSUUID UUID] UUIDString];
// Create dictionary containing information
NSDictionary photoInformation = #{
#“photo_id” : imageId
// Here you can add more information about the photo
};
NSString *imagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png", imageId]];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:imagePath atomically:YES];
NSURL *imageUrl = [[NSURL alloc] initFileURLWithPath:imagePath];
AWSS3TransferManagerUploadRequest *uploadRequest = [AWSS3TransferManagerUploadRequest new];
uploadRequest.bucket = #“<AMAZON S3 STORAGE NAME>“; // create your own by setting up an account on Amazon aws.
uploadRequest.key = imageId;
uploadRequest.contentType = #"image/png";
uploadRequest.body = imageUrl;
AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
[[transferManager upload:uploadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor] withBlock:^id(AWSTask *task) {
if (!task.error) {
// Update Firebase with reference
[newPhotoRef updateChildValues:currentPHD withCompletionBlock:^(NSError *error, Firebase *ref) {
if (!error) {
[newPhotoRef updateChildValues:photoInformation withCompletionBlock:^(NSError *error, Firebase *ref) {
if (!error) {
// Uploaded image to Amazon S3 and reference to Firebase
}
}];
}
}];
} else {
// Error uploading
}
return nil;
}];
}
Edit
The method should be a block method, something like this:
-(void)uploadImage:(UIImage *)image withBlock:(void (^)(Firebase *ref, NSError *error, AWSTask *task))handler
{
// upload
}

Save generated GIF to camera roll?

Thanks for reading. I've created a GIF using methods from this question:
Create and and export an animated gif via iOS?
I'm trying to use the only method that appears to be able to save non JPG/PNG images to the camera roll, ALAssetLibrary's writeImageDataToSavedPhotosAlbum:metadata:completionBlock:
I save the Gif to the temp Directory like this:
NSString *exportPath = [NSTemporaryDirectory() stringByAppendingString:#"/animated.gif"];
NSURL *fileURL = [NSURL fileURLWithPath:exportPath isDirectory:NO];
Then access the NSData like:
NSData * gifData = [NSData dataWithContentsOfFile:fileURL.absoluteString];
The GIF is created as I'm able to display it in a UIImageView, but when I try to save it, the method returns as a success (no error) but doesn't actually save (returns Nil for the NSURL * assetURL and does not appear in the camera roll).
How can I get my GIFs to save successfully to the camera roll?
**
Solution 01 : Only saving the existing GIF file to Camera Roll
**
As I understand your problem. You are able to generate a GIF file but cannot save and also view it to the Camera Roll.
So I am attaching a sample test using existing GIF File.
Step 01. I copied a gif IMG_0009.GIF file in my Application Document directory.
Step 02 Than I use the below code to load this files NSData:
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:#"IMG_0009.gif"];
NSData *gifData = [NSData dataWithContentsOfFile:[fileURL path]];
Step 03: Now I save the file in the Media Directory:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageDataToSavedPhotosAlbum:gifData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
NSLog(#"Success at %#", [assetURL path] );
}];
The Asset URL is proper. Now you can check you media directory. you can locate the saved gif image.
Have Fun :)
**
Solution 02: Demo of Creating and saving GIF to Camera roll
**
I cloned some solution to show creating and saving of GIF files to Camera Roll.
You can download and check my fork at github:
The demo creates a GIF file by taking 2 or more images and save in the Camera Roll Directory
https://github.com/bllakjakk/Giraffe
The main Code to focus is like below:
[export encodeToFile:tempFile callback:^(NSString * aFile) {
NSLog(#"Path: %#", aFile);
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSData *data = [NSData dataWithContentsOfURL:[[NSURL alloc]initFileURLWithPath:aFile]];
[library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
NSLog(#"Success at %#", [assetURL path] );
}];
}];
It uses the library as I mentioned in my solution before http://jitsik.com/wordpress/?p=208
How to verify:
Step 01: Run the demo project.
Step 02: As directed by the application add 2 images and click Export.
Step 03: Now check the camera roll you will find the created gif.
Previous:
GIF is a proprietary format, so you would need a 3rd party lib to save it.
check following link: http://jitsik.com/wordpress/?p=208
I found the issue was that I was unable to actually grab the GIF from the file. I switched from using CGImageDestinationCreateWithURL to CGImageDestinationCreateWithData and used a CFMutableDataRef to hold the Gif data. I don't know why, but that made saving to camera roll with writeImageDataToSavedPhotosAlbum work.
Has this been updated to work with iOS 9, and the deprecation of ALAssets? I do not see similar calls in PHPhotoLibrary.
Here is an updated answer using PHPhotoLibrary, since ALAssetsLibrary is deprecated.
I used this answer from another user - 陈星旺
PHPhotoLibrary save a gif data
NSString *exportPath = [NSTemporaryDirectory() stringByAppendingString:#"/animated.gif"];
NSURL *fileURL = [NSURL fileURLWithPath:exportPath isDirectory:NO];
NSData * gifData = [NSData dataWithContentsOfFile:fileURL.absoluteString];
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
[[PHAssetCreationRequest creationRequestForAsset]
addResourceWithType:PHAssetResourceTypePhoto
data:gifData
options:options];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"image saved!");
} else {
NSLog(#"error saving image - %#", error ? error.localizedDescription : #"");
}
}];
If you needed to download the GIF data from a URL, you could use this:
NSData *gifData = [NSData dataWithContentsOfURL:theGIFsURL];

Move Photos to other folder ios

How to move photos from one folder to another folder in iphone?
I have fetched all photos from camera roll. Now, i want some of them to move to some specific folder in iphone. Is it possible to do?
Yes you can, sort of. As in you can access the user media library and you can copy them to you apps sandbox environment. Since iOS has no public filesystem you will not be able to copy/move them directly to some directory.
The images in the user media librarycan accessed with the AssetsLibrary.
You can add new groups to the user media library and add picture to this group. But you can not move, delete or replace existing images in the user media library.
You can Move All files to Under Documents folder or Caches Folder.
You Can Create Folder Programatically in iPhone Photos.
Please check the below Link for Reference:
iphone: copy or move file from document directory folder
here is some (untested) code for copying the thumbnail & full size version of a photo called 'name' from the shared camera roll to the documents directory in your app's sandbox:
NSString *newNameThumb = #"newThumbName.png";
NSString *newName = #"newFullsizeName.png";
NSString *name = #"nameOfSharedPhotoToMove.png";
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos.
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just photos.
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
// Chooses the photo at the last index
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
ALAssetRepresentation *representation = [alAsset defaultRepresentation];
if([representation.filename isEqualToString:name]){
UIImage *latestPhoto = [UIImage imageWithCGImage:[alAsset thumbnail]];
NSData *imageData = UIImagePNGRepresentation(latestPhoto);
UIImage *latestFullPhoto = [UIImage imageWithCGImage:[representation fullScreenImage]];
NSData *imageFullData = UIImagePNGRepresentation(latestFullPhoto);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* newThumbPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:newNameThumb];
[imageData writeToFile:newThumbPath atomically:YES];
NSString* newFullPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:newName];
[imageFullData writeToFile:newFullPath atomically:YES];
*stop = YES; *innerStop = YES;
});
}
}
}];
} failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
NSLog(#"No photos found or permission denied");
}];
Try this:
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
NSURL *oldDir = [NSURL fileURLWithPath:#"/tmp/mynewdir"];
NSURL *newDir = [NSURL fileURLWithPath:#"/tmp/mynewdir2"];
[filemgr moveItemAtURL: oldDir toURL: newDir error: nil];
Hope this helps.. :)

Moving a core data model that allows external storage into a UIManagedDocument

What is the right way to move a core data model that allows external storage into a UIManagedDocument? I have a core data store that I am trying to move into a UIManagedDocument. I have users with lots of data. Some of it is 2 - 3 minute audio clips. I am subclassing UIManaged document and overriding the configurePersistentStoreCoordinatorForURL. Then copying the files over into the UIManagedDocument bundle. It all seems to work great accept for the Audio files that are stored externally. In my Core Data Model, my audio files are set up to allow external storage. These files are no longer connected after the move and when I try to play them int the app after the move, I get an audio session error. Thanks for any help you can offer on the topic. Here is my code that I am using to override the UIMD…
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)storeURL
ofType:(NSString *)fileType
modelConfiguration:(NSString *)configuration
storeOptions:(NSDictionary *)storeOptions
error:(NSError *__autoreleasing *)error{
[self printFileDir];
// If legacy store exists, create a UIManaged Document and store it there
NSURL *docsDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *legacyStoreURL = [docsDir URLByAppendingPathComponent:#"RRLevelBook.sqlite"];
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:legacyStoreURL.path])
{
NSLog(#"Old db exists");
//swap files
NSURL *storeURLshm = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *storeURLwal = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *supportFiles = [[storeURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".persistenStore_SUPPORT"];
NSURL *legacyStoreURLshm = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *legacyStoreURLwal = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *legacySupportFiles = [[legacyStoreURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".RRLevelBook_SUPPORT"];
NSError* thisError = nil;
//swap the sqlite file
[fileManager replaceItemAtURL:storeURL
withItemAtURL:legacyStoreURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -shm file
[fileManager replaceItemAtURL:storeURLshm
withItemAtURL:legacyStoreURLshm
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -wal file
[fileManager replaceItemAtURL:storeURLwal
withItemAtURL:legacyStoreURLwal
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//Move in the Support files
[fileManager moveItemAtURL:legacySupportFiles toURL:supportFiles error:nil];
//delete old files that have been swapped
[fileManager removeItemAtURL:legacyStoreURL error:nil];
[fileManager removeItemAtURL:legacyStoreURLwal error:nil];
[fileManager removeItemAtURL:legacyStoreURLshm error:nil];
[fileManager removeItemAtURL:legacySupportFiles error:nil];
NSLog(#"%#",[thisError localizedDescription]);
}
[self printFileDir];
return [super configurePersistentStoreCoordinatorForURL:storeURL ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
}
Well, here is what I ended up doing - for better or worse:
Open the new UIManagedDocument.
Open up the legacy Core Data Model.
Copy each audio file (NSData) from Legacy CoreData Context to the UIManagedDocument Context.
Reconnect all relationships based on the Legacy CoreData Context.
NSManagedObjectContext *legacyMOC = [[NSManagedObjectContext alloc]init];
[legacyMOC setPersistentStoreCoordinator:psc];
//fetch all audio recordings from legacyStore
NSArray *legacyRecordingArray = [self fetchAudioRecordingsfrom:legacyMOC];
//fetch all audio recordings form UIMDStore
NSArray *uimdRecordingArray = [self fetchAudioRecordingsfrom:self.managedObjectContext];
//for each audio recording, copy the audio object from legacy and save it to UIMDStore
for (int i = 0; i < legacyRecordingArray.count; i++) {
//save audio to core data
RunningRecord *legacyRR = (RunningRecord *)legacyRecordingArray[i];
RunningRecord *uimdRR = (RunningRecord *)uimdRecordingArray[i];
uimdRR.audioData = [NSData dataWithData:legacyRR.audio.file];
uimdRR.audio.file = nil;
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}

Resources