8thWall AR Screenshot - IOS Download to Camera Roll

Hi!
Summary: I’ve got a method for downloading a screenshot that currently works, but the path to downloading is a little complicated and our client is concerned that the user won’t be able to figure it out. I’d like to invoke the share feature on the image instead of the current path.

Current Code:

Screenshot.prototype.bindedTakeScreenshot = function(e) {
    var self = this;
    
    e.stopPropagation(); //so it doesn't also trigger portal placement
    
    self.e_ScreenshotButton.element.opacity = 0; //cosmetic stuff
    self.e_GameButton.element.opacity = 0;
    self.e_PlaceButton.element.opacity = 0;
    
    XR8.canvasScreenshot().takeScreenshot().then(
        data => { 
            var image = 'data:image/jpeg;base64,' + data;
            var link = document.getElementById('link');
            this.numPhotos++;
            link.setAttribute('download', 'My_Wildlife_Photo_' + this.numPhotos + '.png');
            link.setAttribute('href', image.replace("image/jpeg", "image/octet-stream"));
            link.click();   
            bindedResetOpacity.bind(self); //cosmetic stuff
        },
        error => {
            console.log(error);
        });
};

Sources: I used the 8thWall screenshot example (8th Wall Documentation) merged with Leonidas’s example of downloading a screenshot (Can not download canvas as image - #2 by Leonidas)

I’ve also looked through all the links on this thread - Save image to Camera roll on iPhone

and referenced this project (it has the same issue) - https://playcanvas.com/editor/scene/476102

Current Behavior: Image from iOS.MP4 - Google Drive This is how it currently works on IOS in safari. As you can see, it does work, but it takes many steps to get the photo from the app onto your camera roll (or photo album) where our end users will likely anticipate it. It first goes into the files section, which our client is worried our end users won’t understand or expect. On android it works fine, you just click the download link and it downloads to the phone.

Goal: I would like to invoke a share feature either directly after you take the picture or within one click after you take the picture. It would also be acceptable to directly download to camera roll. Is this possible to do on IOS?

Thanks for your help!

Hi @Ezra_Szanton ,

There is a web share API, not supported by all browsers, but seems to be supported in iOS / Safari. It seems its purpose is to invoke the native share method.

I haven’t tried it myself, but maybe it works for you:

2 Likes

Thanks Leonidas!

I’ve got the basics working and the sharing is working on the device how I want if I use the example link in the API that you referenced. My issue now is that I’m having a hard time figuring out how to make it share the image. I know this is a bit of a beginner question but could you point me to some more info about it or give your best guess at how I might format it? It seems like I’m supposed to pass in a frozen array if I want to download an image - what would I put in the array? Or is there a way to use the url variable to download an image somehow? In either case, how would I convert my current ‘image’ variable so it works with this new technique?

This is what I’ve got so far that isn’t working (it works if I swap image for a regular url)

Screenshot.prototype.bindedTakeScreenshot = function(e) {
    var self = this;
    
    e.stopPropagation(); //so it doesn't also trigger portal placement
    
    self.e_ScreenshotButton.element.opacity = 0;
    self.e_GameButton.element.opacity = 0;
    self.e_PlaceButton.element.opacity = 0;
    
    XR8.canvasScreenshot().takeScreenshot().then(
        data => { 
            var image = 'data:image/jpeg;base64,' + data;
            var link = document.getElementById('link');
            this.numPhotos++;
            
            const shareData = {
              title: 'My_Wildlife_Photo_' + this.numPhotos + '.png',
              text: 'My_Wildlife_Photo!',
              url: image,
            };
            
            const sharePromise = navigator.share(shareData);
            // link.setAttribute('download', 'My_Wildlife_Photo_' + this.numPhotos + '.png');
            // link.setAttribute('href', image.replace("image/jpeg", "image/octet-stream"));
            // link.click();   
            bindedResetOpacity.bind(self);
            //setTimeout(self.bindedResetOpacity.bind(self), 10000);
        },
        error => {
            console.log(error);
        });
};

I think it expects a blob, so something like this will work:

    const file = new File([blob], "picture.jpg", {type: 'image/jpeg'});
    const filesArray = [file];

      navigator.share({
        text: 'some_text',
        files: filesArray,
        title: 'some_title',
        url: 'some_url'
      });

The only thing you need is to create a blob from your base64 image. There are several ways to do that, you can search online, here is a minimal way using the Fetch method:

var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(base64)
.then(res => res.blob())
.then(blob => {
   // now use that blob in the file statement above
});

All code is untested, hope it’s of help.

2 Likes

It works! (At least for my android phone, I’m going to get a coworker to test on their IOS device when they’re available)

Thank you so much, you’re a lifesaver Leonidas :slight_smile:

Here’s what the code ended up being, if you’re curious

Screenshot.prototype.bindedTakeScreenshot = function(e) {
    var self = this;
    
    e.stopPropagation(); //so it doesn't also trigger portal placement
    
    self.e_ScreenshotButton.element.opacity = 0;
    self.e_GameButton.element.opacity = 0;
    self.e_PlaceButton.element.opacity = 0;
    
    XR8.canvasScreenshot().takeScreenshot().then(
        data => { 
            
            var url = "data:image/jpeg;base64," + data;

            fetch(url)
            .then(res => res.blob())
            .then(blob => {
                
                const file = new File([blob], "picture.jpg", {type: 'image/jpeg'});
                const filesArray = [file];
                this.numPhotos++;

                  navigator.share({
                    text: 'My Wildlife Photo ' + this.numPhotos,
                    files: filesArray,
                    title: 'My Wildlife Photo ' + this.numPhotos
                  });
            });

        },
        error => {
            console.log(error);
        });
    
    bindedResetOpacity.bind(self);
};
1 Like

Happy to hear it works! Many thanks for sharing your final code @Ezra_Szanton, this is super useful to have.

1 Like

Happy to give back haha, it’s mostly yours anyway :smiley: if it works on IOS too I’ll come back and post a more stripped down version without the junk specific to my project

1 Like

Update - testing on IOS it does open native sharing but it gets downloaded as a text file. Do you have any idea why that might happen? It’s the same code as posted above.

Here’s what it looks like - Safari Picture Clip.mp4 - Google Drive

So, I am not able to test currently, but searching around on stackoverflow I’ve found someone saying it worked for him on iOS by only sharing the files array, no accompanying text.

Try it just in case!

image

PS Your app looks awesome!

2 Likes

That was a good thought but unfortunately it still downloads as a text file, the contents of the file just end up being the title. If I remove the title as well, then nothing downloads :frowning:

I appreciate the advice though!

P.S. Thanks :slight_smile:

1 Like

Sorry it didn’t work out for you, Apple is quite strict on the Web APIs it allows. I hope you find some solution in the end.

1 Like

Looks like you are not the only one with this problem: html - Is it possible to download an image or video from Safari to the Camera Roll in iOS 13? - Stack Overflow

2 Likes