Extraction of video URL for sharing via UIActivityViewController - ios

I'm working on share videos using UIActivityViewController and have some questions about the URL extraction from PHAsset objects.
I use "requestAVAsset" in "PHImageManager" and cast the AVAsset object to AVURLAsset to access its url property. I've tried the following types of activities:
Copy to Drive - this opens Google Drive app (Success)
Google Drive - a dialog shows up for confirmation (Fail) (no video attached in the dialog)
Gmail - (Fail) (mail can be sent but no video attached)
Add to Notes - this add video to built-in Notes app, a dialog should show up for confirmation (Fail) (app freezes after UIActivityViewController disappears and no dialog shows up)
Facebook/LINE - (Fail) (the progress bar never moves)
My questions:
Does the URL extracted by this method has the access to the real resource file of video?
If yes, am I missing something? Are there bugs in my code (see below)?
Code to share contents (inside an UIViewController):
PHImageManager.default().requestAVAsset(forVideo: videoAsset, options: nil, resultHandler: {
(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable: Any]?) in
if let urlAsset = asset as? AVURLAsset {
print("Share url=\(urlAsset.url.absoluteURL)")
let shareVC: UIActivityViewController = UIActivityViewController(activityItems: [urlAsset.url.absoluteURL], applicationActivities: nil)
shareVC.completionWithItemsHandler = {
(type: UIActivityType?, completed: Bool, returnedItems: [Any]?, err: Error?) in
print("Share result: completed=\(completed), \(type)")
if err != nil {
print("\(err.debugDescription)")
}
}
DispatchQueue.main.async {
self.present(shareVC, animated: true, completion: nil)
}
}
})
Environment: iPhone 7 plus, iOS 10.1.1
Btw, I also tried another 2 methods for sharing.
Using "writeData" in "PHAssetResourceManager" to output video to a temporary directory and then build URL by the file path.
Using "requestExportSession" in "PHImageManager" to output video to a temporary directory and then build URL by the file path.
These method work fine. In my opinion, this is because the video file can be accessed directly by the extracted url. But they are not suitable for me since I would like to share not only single file but also multiple files within one action. (They take time to process data)

Related

Share PHAsset stored in camera roll using UIActivityViewController

I'm storing images and videos in a Camera Roll album using PhotoKit, and want to allow the user to share them using UIActivityViewController. If I pass UIActivityViewController a UIImage instance, it works as expected, probably because the image data is passed in memory. However, videos need to be passed by URL because there's no video analogue to UIImage. When I pass a URL to a video, I get an error "Could not create sandbox extension". If I pass a URL to an image, I get a similar error.
Based on this, it seems as though I might be able to get around this error by exporting the assets to the Documents directory, and passing UIActivityViewController the URL to the asset in Documents. However, I've read elsewhere that the Camera Roll can serve a similar purpose, and it goes to reason that the Camera Roll would be one of the few places that can hold data for sharing between apps.
Is there a way to pass UIActivityViewController URLs to Camera Roll assets without copying them to Documents? Is there a better way to be sharing images and video that are already in Camera Roll?
Implementation Details:
I'm generating URLs for assets using this:
func videoFor(asset: PHAsset, resultHander: #escaping (AVAsset?, AVAudioMix?, [AnyHashable : Any]?) -> Void) {
imageManager.requestAVAsset(forVideo: asset, options: nil, resultHandler: resultHander)
}
func urlFor(asset: PHAsset, resultHandler: #escaping (URL?) -> Void) {
if ( asset.mediaType == .video ) {
videoFor(asset: asset) { (asset, audioMix, info) in
let asset = asset as! AVURLAsset
resultHandler(asset.url)
}
}
else if ( asset.mediaType == .image ) {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
asset.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) -> Void in
resultHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
}
else {
resultHandler(nil)
}
}
Here is the full error I get in console when trying to share an image by URL:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG. Error: No such file or directory}
... and for a video:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V. Error: Operation not permitted}
I was stuck on the same problem today. Here is my solution. Hope this helps or guides you to the right path.
PHImageManager.default().requestExportSession(forVideo: video, options: nil, exportPreset: AVAssetExportPresetPassthrough) { (exportSession, nil) in
if let exportSession = exportSession {
exportSession.outputURL = destinationURLForFile
exportSession.outputFileType = AVFileType.m4v
exportSession.exportAsynchronously() {
// Load the share sheet using destinationURLForFile
}
}
}
What this does is export the video to the provided location destinationURLForFile (i used the Documents directory. Make sure you delete the file if its already there otherwise the export MAY not work cause it may not override the file).
You can set the type based on available types. I needed m4v.
Then, export async and just call the share sheet or whatever sharing mechanism you have.

Using a PHLivePhoto with a UIActivityViewController

Trying to share as activity a PHLivePhoto within a UIActivityViewController without any result more than the console output:
Resource not found (3 times logged)
I use the PHLivePhoto request with local URL's from document directory: Both files are already there. I set a PHLivePhotoView with the completion Live Photo witch works fine and I can reproduce/replay without any issues.
PHLivePhoto.request(withResourceFileURLs: [ URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.MOV"), URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.JPG")],
placeholderImage: nil,
targetSize: self!.view.bounds.size,
contentMode: PHImageContentMode.aspectFit,
resultHandler: { (livePhoto, info) -> Void in
self?.livePhotoView.livePhoto = livePhoto
self?.exportLivePhoto()
})
The problem is that I'm not quite sure what should be the content of the activity, I've try without any results:
let ac = UIActivityViewController(activityItems: [self.livePhotoView.livePhoto], applicationActivities: nil)
Been trying to fill as activity items with:
URL Document path (where video and image exist)
Both URLS from image and video (reverse order)
LivePhoto itself (as code below)
Trying to delay the LivePhoto creation as other people may suggest.

XCODE / IOS - Share audio file to whatsapp with a direct opening

I am trying to share an audio file from my ios app to whatsapp, but with a direct opening of whatsapp, and not an opening of the sharing menu with all the tiles.
Here is what i have now:
// Getting the original file
let fileName = #MY FILE NAME#
let filePath = Bundle.main.path(forResource: fileName, ofType: "mp3")!
let urlData = URL.init(fileURLWithPath: filePath)
let nsData = NSData(contentsOf: urlData)
if (nsData != nil){
// Creating the temporary file to share in the accessible ressources
let newFileName = "file.mp3"
let newFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(newFileName)"
nsData?.write(toFile: newFilePath, atomically: true)
let newUrlData = URL.init(fileURLWithPath: newFilePath)
// Sharing the file to whatsapp
// Possibility 1 (does not work yet)
// let documentController = UIDocumentInteractionController(url: newUrlData)
// documentController.uti = "net.whatsapp.audio"
// documentController.presentOpenInMenu(from: CGRect.zero, in: self.view, animated: true)
// Possibility 2 (works only with the sharing menu)
let activityVC = UIActivityViewController(activityItems: [NSURL(fileURLWithPath: newFilePath)], applicationActivities: nil)
self.present(activityVC, animated: true, completion: nil)
}
As I do this, sharing an audio file to whatsapp works, but it first open the sharing menu, with the messenger tile, message tile, notes tile, ... (and it doesn't works for the messenger app). In the end I would like to be able to share on messenger AND whatsapp.
As explicated here in the whatsapp documentation, I want to open directly the whatsapp application when I try to share the file:
Alternatively, if you want to show only WhatsApp in the application list (instead of WhatsApp plus any other public/*-conforming apps) you can specify a file of one of aforementioned types saved with the extension that is exclusive to WhatsApp:
images - «.wai» which is of type net.whatsapp.image
videos - «.wam» which is of type net.whatsapp.movie
audio files - «.waa» which is of type net.whatsapp.audio
When triggered, WhatsApp will immediately present the user with the contact/group picker screen. The media will be automatically sent to a selected contact/group.
So I tried to change the line :
let newFileName = "file.mp3"
To one of these :
let newFileName = "file.mp3.waa"
let newFileName = "file.waa"
let newFileName = "file.waa.mp3"
But it still shows the same sharing menu (and can't read the audiofile if it ends with the .waa extension).
-> 1) Is it possible to do what I want to do ?
-> 2) If not, is there a way to share to messenger & whatsapp with the same code keeping one sharing menu
-> 3) If not, is there a way to reduce the sharing menu to only one tile depending on different calling event, so there is no confusing choosing of tiles
Thanks,
Antoine
Cf: XCODE / IOS - How to use exclusive extension to immediately present whatsapp (.wai, .waa, .wam)
FYI: As I went through a lot of tests with this, I couldn't find any solution yet.
Whatsapp recognize the file extension, but cannot even read it. Once shared, when you click on it, it's written ".whatsapp audio file", nothing more (And it's not even shared directly).
I sent a email to whatsapp developper team, they said they have others problem to fix currently, so it's not even on their to do list.
Wait & see..

Sharing Video PHAsset via UIActivityController

I am trying to share video PHAsset via UIActivityController using requestAVAsset. This works with Messaging, but not with AirDrop, indicating as 'Failed'.
PHCachingImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler:
{ (givenAsset, audioMix, info) in
let videoAsset = givenAsset as! AVURLAsset
let videoURL = videoAsset.url
DispatchQueue.main.async {
let activityViewController = UIActivityViewController(
activityItems: [videoURL],
applicationActivities: nil)
activityViewController.excludedActivityTypes = [UIActivityType.saveToCameraRoll]
if let popoverPresentationController = activityViewController.popoverPresentationController {
popoverPresentationController.barButtonItem = (sender)
}
self.present(activityViewController, animated: true, completion: nil)
}
})
This seems to properly put up UIActivityController and only work with certain activities:
Messaging - ✔️Works, properly exports video.
AirDrop - ✖️Shows "Failed"
Dropbox - ✖️Puts up the proper Dropbox View, yet says "Unknown error occurred"
I've run into similarly odd behavior when working with PHAssets. My guess is this is a (purposely) undocumented security/sandboxing restriction.
I was able to work around this problem by copying the underlying file to a user directory, and then performing the operation on the copied file.
I did this in a loop. Occasionally, the copy fails with a vague file permissions error. When it does, I retry it after a few seconds (using DispatchQueue.main.asyncAfter). Eventually, it works!

Unknown activity items supplied for ActivityViewController

I am trying to share videos from my app using a UIActivityViewController. Below is the code I use:
var url = NSURL(string: path!)!
var activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.presentViewController(activityViewController, animated: true, completion: { () -> Void in })
The path points to a valid .mov file so no problems there. When I present the activity view controller I get the error: Unknown activity items supplied with the path to the .mov file and the sharing options only show AirDrop.
The app is running on iOS 8.
Any ideas?
EDIT I found out that when I save the video to camera roll. the user can share it with Photos app to any app. So there's nothing wrong with the video format I guess.
You should use init?(fileURLWithPath path: String, isDirectory isDir: Bool) if the movie is in resource bundle.
If you use imagePicker controller to select the video, you can get the url from info dictionary using the key UIImagePickerControllerMediaURL, the corresponding delegate method is didFinishPickingMediaWithInfo.
Try this :
var activityViewController = UIActivityViewController(activityItems: [path], applicationActivities: nil)
self.presentViewController(activityViewController, animated: true, completion: { () -> Void in })
I had a similar issue for a zip file and realised the url path I was passing was relative and not an absolute path. In other words, UIActivityViewController urls require the prefix file:///
I was using a relative path because the third party library Objective-Zip needs a relative path to create zip files.
Works:
file:///private/var/mobile/Containers/Data/Application/63284C22-6E22-4865-965C-3B67F58D0659/tmp/myfile.zip
Doesn't work:
/private/var/mobile/Containers/Data/Application/63284C22-6E22-4865-965C-3B67F58D0659/tmp/myfile.zip

Resources