20
votes

In my PhoneGap/jQuery Mobile app, I'm currently using PhoneGaps' Camera API to allow the user to take a photo, which is also stored in the Camera Roll. I'm setting DestinationType to FILE_URI and saving this path in a local db. However, FILE_URI is a path to a temporary location that is destroyed when the app is closed. I was thinking of saving the name of the image and then retrieving the image from the Camera Roll. Is there a path I can use to later retrieve the images in the Camera Roll?

I was saving the images as Base64 in the db, but I'm a little weary of this method because of this note in the PhoneGap API Doc:

Note: The image quality of pictures taken using the camera on newer devices is quite good. Encoding such images using Base64 has caused memory issues on some of these devices (iPhone 4, BlackBerry Torch 9800). Therefore, using FILE_URI as the 'Camera.destinationType' is highly recommended.

EDIT:

Got it working with @Simon MacDonald's answer. Here is my stripped-down code:

function capturePhoto() {
    navigator.camera.getPicture(onPhotoURISuccess, onFail, { quality: 25, destinationType: Camera.DestinationType.FILE_URI });
}

function onPhotoURISuccess(imageURI) {
    createFileEntry(imageURI);
}

function createFileEntry(imageURI) {
    window.resolveLocalFileSystemURI(imageURI, copyPhoto, fail);    
}

function copyPhoto(fileEntry) {
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys) { 
        fileSys.root.getDirectory("photos", {create: true, exclusive: false}, function(dir) { 
                fileEntry.copyTo(dir, "file.jpg", onCopySuccess, fail); 
            }, fail); 
    }, fail); 
}

function onCopySuccess(entry) {
    console.log(entry.fullPath)
}

function fail(error) {
    console.log(error.code);
}
4
I'm trying to handle this exact same situation, and your posted code seems to work and I get all the way to your onCopySuccess function, but I still see no images in my Camera Roll or any other album. The entry that I get in the onCopySuccess function describes a file that gets cleared when temp is cleared. – Yoh Suzuki just now editYoh Suzuki
The above code won't save the photo to an album, but to the Documents folder of your app. The path to the photo will look something like this: "/var/mobile/Applications/017323CA-35C1-4D50-9CBA-DCC7C697FAF1/Documents/photos/file.jpg" . The arbitrary string is the folder iOS creates for your app. To save a photo to the camera roll, in the getPicture call, add this to the options object: "savePhotoToAlbum" : "true". Note that you can't get the path of the image in the Camera Roll (AFAIK), which is why you have to save a copy to the Documents folder of your app.Casey
savePhotoToAlbum does not seem to work for me, and is nowhere in the documentation.Yoh Suzuki
Ah, I see. It's actually "saveToPhotoAlbum". And that works!Yoh Suzuki
Sorry for the typo. Thanks for the fix. Yeah, not sure why its not in the documentation. I forget where I found it, but they reasoned that it wasn't in the documentation because it iOS specific; which doesn't seem like a good reason to me.Casey

4 Answers

10
votes

After you retrieve the file using the FILE_URI method you should use the File API to copy the image from the temp folder to the Documents folder and store the new path in your DB.

So you would take the result of your getPicture() pass it to window.resolveLocalFileSystemURI() to get a FileEntry. Then you can call FileEntry.copyTo() method to back up your file.

0
votes

Sorry that I had to create a separate post because I have not enough reputation to comment.

Thank you Simon for the advice and Casey for the solution.

I was working on a Cordova app that requires me to take a picture on iOS, save it in a directory and the link on local storage to be accessed later. After user submits the picture, the app needs to delete it from the directory.

Here are add on codes on how you can do that and also how you can view the project Documents folder.

You can get the full directory of the picture you want to delete by using $('#pictureid').attr('src').

function deletePic(file){
    console.log("deleting: "+file+"...");
    var file = "photos/"+file.toString().split('/').slice(-1)[0]; //gets photo name

    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys){
    fileSys.root.getFile(file, 
                {create:false}, 
                function(entry){
                    entry.remove();
                }, resOnErrorDelete);
                },
    resOnErrorDelete);          
}

function resOnErrorDelete(error) {
    console.log("resOnErrorDelete: "+error.code);
}

Alternatively if you have a form with multiple photo attachments, you may want to give the Documents folder you created to store the photos a unique name. This way, you can delete the whole folder after you submit the batch of photos instead of deleting them one by one.

function deleteFolder(folder){  
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys){
    fileSys.root.getDirectory(folder, 
                {create:true, exclusive: false}, 
                function(entry){
                    entry.removeRecursively();
                }, resOnErrorDelete);
                },
    resOnErrorDelete);          
}

function resOnErrorDelete(error) {
    console.log("resOnErrorDelete: "+error.code);
}

As for accessing the project Documents folder, you need to edit the plist to enable iTunes File Sharing

Go to [project_folder]/platforms/ios/[project_name]/[project_name]-Info.plist. Right-click and open with XCode.

You will see a table of keys. Right-click anywhere below the table and Add Row. A new row will be created. Double-click on the key and type UIFileSharingEnabled. It will automatically change to Application supports iTunes file sharing, put that key value to YES.

Now if you go to iTunes under your iOS's device's applications, scroll all the way down to see the File Sharing area where you can see your folder. However, it is just there for you to 'Save to' but no editing.

Hope all these additional information helps. It took me 2 days of research to find out.

0
votes

Sharing my version which uses promises and resolveLocalFileSystemURL except resolveLocalFileSystemURI because second one is deprecated. It also uses original file name in new created one.

function copyPhotoToAppDir(imageURI) {
  if(!imageURI){
    console.warn("You need to pass imageURI");
    return;
  }

  var fileNameSplit = imageURI.split("/"),
    fileName = fileNameSplit[fileNameSplit.length - 1],
    deferred = $q.defer();

  var copyPhoto = function(fileEntry) {
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys) {
      fileSys.root.getDirectory("photos", {create: true, exclusive: false}, function(dir) {
        fileEntry.copyTo(dir, fileName, onCopySuccess, onFileFail);
      }, onFileFail);
    }, onFileFail);
  };

  var onCopySuccess = function onCopySuccess(entry) {
    console.log("NEW PATH", entry.fullPath);
    deferred.resolve(entry.fullPath);
  };

  var onFileFail = function (error) {
    console.log("COPY FAIL", error);
    deferred.reject();
  };

  window.resolveLocalFileSystemURL(imageURI, copyPhoto, onFileFail);

  return deferred.promise;
}

Usage:

var uri = "path1";
copyPhotoToAppDir(uri).then(function(newURI){
  console.log(newURI);
});

Multiple URIs usage:

var uriArr = ["path1", "path2"]; 
  copyPhotoPromises = [];

uriArr.forEach(function(val){
  copyPhotoPromises.push(copyPhotoToAppDir(val));
});
$q.all(copyPhotoPromises).then(function(values){
  console.log("Array with new paths", values);
});

New file URIs will be saved as "/photos/fileName.jpg". To get the absolute path, you can use:

cordova.file.documentsDirectory + newFileUri.substring(1);

I'm using $q from angular here to handle promises but it can be easily changed to jQuery one.

deferred = $q.defer();

would need to be changed to:

deferred = jQuery.Deferred();

Cheers