Not able to share the video on twitter through UIActivityViewController.
Text and local video storage works fine. So authentication is not an issue.
If video is stored in the app itself, then the share works fine
Path URL
file:///private/var/containers/Bundle/Application/B7855569-3254-4CC2-9573-254D09528E38/podhunt.app/PlugIns/podhunt-shareExtension.appex/demo_video.mp4
If the path is below, then the twitter share does not work
Path Url
file:///var/mobile/Containers/Data/PluginKitPlugin/5831780C-50AF-41FA-8435-941CAC47EBE6/Documents/10c41d4a-c161-4c78-bc61-ca789804a982.mp4
This does not work
URLSession.shared.downloadTask(with: audioUrl) { location, response, error in
guard let location = location, error == nil else { return }
do {
try FileManager.default.moveItem(at: location, to: destinationUrl)
DispatchQueue.main.async {
let activityVC = UIActivityViewController(activityItems: [destinationUrl], applicationActivities: nil)
self.present(activityVC, animated: true, completion: nil)
}
} catch {
}
}.resume()
This works
URLSession.shared.downloadTask(with: audioUrl) { location, response, error in
guard let location = location, error == nil else { return }
do {
try FileManager.default.moveItem(at: location, to: destinationUrl)
DispatchQueue.main.async {
guard let path = Bundle.main.path(forResource: "demo_video", ofType:"mp4") else {
return
}
let activityVC = UIActivityViewController(activityItems: [URL(fileURLWithPath: path)],applicationActivities: nil)
self.present(activityVC, animated: true, completion: nil)
}
} catch {
}
}.resume()
I am working on iOS app extension.
Resolved. Apparently I was sharing mp3 file as an mp4 to twitter which it does not recognize. You cannot share mp3 file to twitter as it does not support audio format.
Related
I am trying to implement rich link sharing on posts within my app. Sharing a link in iMessage should show the video or image if there's one associated with the post. This swift implementation is not working, not sure if its my code or and issue with the website meta data
What do I need in my website to make this work? Is the current swift implementation
static func addURLPreview(urlString: String) {
let metaDataProvider = LPMetadataProvider()
let url = URL(string: urlString)
metaDataProvider.startFetchingMetadata(for: url!) { (metaData, error) in
guard let _ = error, let metaData = metaData else {
return
}
let vc = UIActivityViewController(activityItems: [metaData], applicationActivities: nil)
UIApplication.shared.getKeyWindow()?.rootViewController?.present(vc, animated: true)
}
}
I'd like to share an Audio file that I have loaded into a AVPlayerAsset. I was wondering if this is possible, or what the correct way of doing this would look like. Here is what my code looks like:
func shareTrack(track: Track) {
guard let file = track.playerItem else { return } // This is the AVPlayerAsset
let activityController = UIActivityViewController(activityItems: [file], applicationActivities: nil)
activityController.completionWithItemsHandler = { (nil, completed, _, error ) in
if completed {
print("Success")
} else {
print("Canceled")
}
}
DispatchQueue.main.async{
self.present(activityController, animated: true)
}
}
You are using the word "file" but the asset is not a file. It is an asset. Other programs aren't going to know anything about that. If you want to share something, share a file URL.
I am trying to create a folder for my app in the Files app for access when i want to save a file from my app
In iOS 12 and earlier i could achieve this by enabling
Application supports iTunes file sharing and Supports opening documents in place as shown below
Since updating to iOS 13, the same folder that used to show up in iOS 12 no-longer shows.
What has changed and how can i get this resolved in iOS 13?
I use the extension below to save files:
extension WebViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// create destination URL with the original pdf name
guard let url = downloadTask.originalRequest?.url else { return }
let documentsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
// delete original copy
try? FileManager.default.removeItem(at: destinationURL)
// copy from temp to Document
do {
try FileManager.default.copyItem(at: location, to: destinationURL)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
let activityViewController = UIActivityViewController(activityItems: [self.fileName, destinationURL], applicationActivities: nil)
if(UIDevice.current.userInterfaceIdiom == .pad){
if let popOver = activityViewController.popoverPresentationController {
popOver.sourceView = self.view
popOver.sourceRect = self.view.bounds
popOver.barButtonItem = self.navigationItem.rightBarButtonItem
self.present(activityViewController, animated: true, completion: nil)
}
}
else{
self.present(activityViewController, animated: true, completion: nil)
}
}
} catch let error {
print("Copy Error: \(error.localizedDescription)")
}
}
}
For those looking for the answer to the specific problem above,
Updating to iOS 13.1 seems to fix this issue, so i'm not sure if this was a bug of some sort
Is there a way to choose file from iCloud Drive similar way to UIImagePickerController()?
You can present controller the following way:
import MobileCoreServices
let documentPickerController = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF), String(kUTTypeImage), String(kUTTypeMovie), String(kUTTypeVideo), String(kUTTypePlainText), String(kUTTypeMP3)], inMode: .Import)
documentPickerController.delegate = self
presentViewController(documentPickerController, animated: true, completion: nil)
In your delegate implement the method:
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL)
Note that you don't need to set up iCloud Entitlement to use UIDocumentPickerViewController. Apple provides sample code that demonstrates how to use this controller here
Swift 5, iOS 13
Jhonattan's and Ashu's answers are definitely on the right track for the core functionality, there are a number of issues with multiple-document-selection, error outcomes and deprecated document picker API.
The code below shows a modern start-to-finish version of a common use case: pick an external iCloud document to import into app and do something with it.
Note that you have to have your app's Capabilities set up to use iCloud documents and have a ubiquity container set up in your app's .plist... See for example:
Swift write/save/move a document file to iCloud drive
class ViewController: UIViewController {
#IBAction func askForDocument(_ sender: Any) {
if FileManager.default.url(forUbiquityContainerIdentifier: nil) != nil {
let iOSPickerUI = UIDocumentPickerViewController(documentTypes: ["public.text"], in: .import)
iOSPickerUI.delegate = self
iOSPickerUI.modalPresentationStyle = .formSheet
if let popoverPresentationController = iOSPickerUI.popoverPresentationController {
popoverPresentationController.sourceView = sender as? UIView
}
self.present(iOSPickerUI, animated: true, completion: nil)
}
}
func processImportedFileAt(fileURL: URL) {
// ...
}
}
extension ViewController: UIDocumentPickerDelegate, UINavigationControllerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
if controller.allowsMultipleSelection {
print("WARNING: controller allows multiple file selection, but coordinate-read code here assumes only one file chosen")
// If this is intentional, you need to modify the code below to do coordinator.coordinate
// on MULTIPLE items, not just the first one
if urls.count > 0 { print("Ignoring all but the first chosen file") }
}
let firstFileURL = urls[0]
let isSecuredURL = (firstFileURL.startAccessingSecurityScopedResource() == true)
print("UIDocumentPickerViewController gave url = \(firstFileURL)")
// Status monitoring for the coordinate block's outcome
var blockSuccess = false
var outputFileURL: URL? = nil
// Execute (synchronously, inline) a block of code that will copy the chosen file
// using iOS-coordinated read to cooperate on access to a file we do not own:
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: firstFileURL, options: [], error: &error) { (externalFileURL) -> Void in
// WARNING: use 'externalFileURL in this block, NOT 'firstFileURL' even though they are usually the same.
// They can be different depending on coordinator .options [] specified!
// Create file URL to temp copy of file we will create:
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
tempURL.appendPathComponent(externalFileURL.lastPathComponent)
print("Will attempt to copy file to tempURL = \(tempURL)")
// Attempt copy
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
print("Deleting existing file at: \(tempURL.path) ")
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
print("Attempting move file to: \(tempURL.path) ")
try FileManager.default.moveItem(atPath: externalFileURL.path, toPath: tempURL.path)
blockSuccess = true
outputFileURL = tempURL
}
catch {
print("File operation error: " + error.localizedDescription)
blockSuccess = false
}
}
navigationController?.dismiss(animated: true, completion: nil)
if error != nil {
print("NSFileCoordinator() generated error while preparing, and block was never executed")
return
}
if !blockSuccess {
print("Block executed but an error was encountered while performing file operations")
return
}
print("Output URL : \(String(describing: outputFileURL))")
if (isSecuredURL) {
firstFileURL.stopAccessingSecurityScopedResource()
}
if let out = outputFileURL {
processImportedFileAt(fileURL: out)
}
}
}
The document picker calls the delegate’s documentPicker:didPickDocumentAtURL: method when the user selects a destination outside your app’s sandbox. The system saves a copy of your document to the specified destination. The document picker provides the copy’s URL to indicate success; however, your app does not have access to the file referred to by this URL. Link
This code work for me:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: url, options: [], error: &error) { (url) -> Void in
_ = urls.compactMap { (url: URL) -> URL? in
// Create file URL to temporary folder
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
// Apend filename (name+extension) to URL
tempURL.appendPathComponent(url.lastPathComponent)
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
try FileManager.default.moveItem(atPath: url.path, toPath: tempURL.path)
YourFunction(tempURL)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
}
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
navigationController?.dismiss(animated: true, completion: nil)
}
This changed once again in iOS 14!!
Working example for JSON:
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
func selectFiles() {
let types = UTType.types(tag: "json",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(
forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
Swift 4.X
You need to enable iCloud entitlements in XCode Capabilities. Also you have to turn on iCloud in you app bundle in developer account of Apple. Once you do this, you are able to present document picker controller by following way:
Use UIDocumentPickerDelegate methods
extension YourViewController : UIDocumentMenuDelegate, UIDocumentPickerDelegate,UINavigationControllerDelegate {
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print("url = \(url)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
}
Add below code for Button Action
#IBAction func didPressAttachment(_ sender: UIButton) {
let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
if let popoverPresentationController = importMenu.popoverPresentationController {
popoverPresentationController.sourceView = sender
// popoverPresentationController.sourceRect = sender.bounds
}
self.present(importMenu, animated: true, completion: nil)
}
iCloudUrl.startAccessingSecurityScopedResource()
// is returning true for me at this point,
However the following code gave the error:
try FileManager.default.createDirectory(atPath: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
"You can’t save the file “xyz” because the volume is read only."
This actually works :
try FileManager.default.createDirectory(at: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
Which makes sense because the URL probably is carrying around it’s security access, but this little oversight stumped me for half a day…
For my swiftUI users: It's quite easy.
struct HomeView: View {
#State private var showActionSheet = false
var body: some View {
Button("Press") {
showActionSheet = true
}
.fileImporter(isPresented: $showActionSheet, allowedContentTypes: [.data]) { (res) in
print("!!!\(res)")
}
}
}
I use
try FileManager.default.copyItem(at: url, to: destinationUrl)
instead of moveItem. Otherwise, the files are removed from iCloud Drive, which is not what I want.
I am relatively new to iOS development and Swift but I have an app I'm working on which is supposed to record the activity on the screen and save the resulting video to the camera roll. I am using ReplayKit.
What is working now:
This is the code I have beginning the recording and ending the recording
the startRecording() function is run by a button that says "start" and the stopRecording() function is called by a button that says "stop".
var preview : RPPreviewViewController?
func startRecording() {
let recorder = RPScreenRecorder.sharedRecorder()
recorder.startRecordingWithMicrophoneEnabled(true) {
[unowned self] (error) in
print(recorder)
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
}
}
}
func stopRecording() {
let recorder = RPScreenRecorder.sharedRecorder()
recorder.stopRecordingWithHandler {
[unowned self] (preview, error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
}
if let unwrappedPreview = preview {
print("end")
unwrappedPreview.previewControllerDelegate = self
unwrappedPreview.modalPresentationStyle=UIModalPresentationStyle.FullScreen
self.presentViewController(unwrappedPreview, animated: true, completion: nil)
}
}
The screen records fine. I have a button which says "Finish" which will call the stopRecording() function. When that button is clicked, a preview will show up which will play the recorded video and allow the user to manually edit and save the video.
What I'm trying to do:
I need to make the button simply save the video as is to the camera roll. I want to bypass the preview screen which allows the user to edit and manually save. Is this possible? If so, how would you approach the problem?
The preview is of type RPPreviewViewController? and try as I might, I just can't seem to access the video for saving. Since ReplayKit is an extension of UIKit, I tried using the
UISaveVideoAtPathToSavedPhotosAlbum(_ videoPath: String, _ completionTarget: AnyObject?, _ completionSelector: Selector, _ contextInfo: UnsafeMutablePointer<Void>)
method but none of those attributes exist!
If you need anymore info, please let me know. If I'm an idiot, please let me know! This is my first post here so be nice! and Thanks.
As mentioned by Geoff H, Replay Kit 2 now allows you to record the screen and save it either within your app or to the gallery without having to use the preview.
The documentation is sparse but after some trial and experiment the below code works in iOS 12.
Note this only captures video and not audio, although that should be straightforward to add, and you may want to add more error checking if using it. The functions below can be triggered by UI buttons, for example.
#objc func startRecording() {
//Use ReplayKit to record the screen
//Create the file path to write to
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
self.videoOutputURL = URL(fileURLWithPath: documentsPath.appendingPathComponent("MyVideo.mp4"))
//Check the file does not already exist by deleting it if it does
do {
try FileManager.default.removeItem(at: videoOutputURL)
} catch {}
do {
try videoWriter = AVAssetWriter(outputURL: videoOutputURL, fileType: AVFileType.mp4)
} catch let writerError as NSError {
os_log("Error opening video file", writerError);
videoWriter = nil;
return;
}
//Create the video settings
let videoSettings: [String : Any] = [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : 1920, //Replace as you need
AVVideoHeightKey : 1080 //Replace as you need
]
//Create the asset writer input object whihc is actually used to write out the video
//with the video settings we have created
videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings);
videoWriter.add(videoWriterInput);
//Tell the screen recorder to start capturing and to call the handler when it has a
//sample
RPScreenRecorder.shared().startCapture(handler: { (cmSampleBuffer, rpSampleType, error) in
guard error == nil else {
//Handle error
os_log("Error starting capture");
return;
}
switch rpSampleType {
case RPSampleBufferType.video:
os_log("writing sample....");
if self.videoWriter.status == AVAssetWriter.Status.unknown {
if (( self.videoWriter?.startWriting ) != nil) {
os_log("Starting writing");
self.videoWriter.startWriting()
self.videoWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
}
}
if self.videoWriter.status == AVAssetWriter.Status.writing {
if (self.videoWriterInput.isReadyForMoreMediaData == true) {
os_log("Writting a sample");
if self.videoWriterInput.append(cmSampleBuffer) == false {
print(" we have a problem writing video")
}
}
}
default:
os_log("not a video sample, so ignore");
}
} )
}
#objc func stoprecording() {
//Stop Recording the screen
RPScreenRecorder.shared().stopCapture( handler: { (error) in
os_log("stopping recording");
})
self.videoWriterInput.markAsFinished();
self.videoWriter.finishWriting {
os_log("finished writing video");
//Now save the video
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.videoOutputURL)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
if error != nil {
os_log("Video did not save for some reason", error.debugDescription);
debugPrint(error?.localizedDescription ?? "error is nil");
}
}
}
I too wanted to do what you have asked, but as of now RPScreenRecorder doesn't provide any of those functionalities.
Yes, you can. Check this ReplayKit2 Swift 4:
https://medium.com/#giridharvc7/replaykit-screen-recording-8ee9a61dd762
Once you have the file, it shouldn't be too much trouble to save it to the camera roll with something along the lines of:
static func saveVideo(url: URL, returnCompletion: #escaping (String?) -> () ) {
DispatchQueue.global(qos: .userInitiated).async {
guard let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
if !FileManager.default.fileExists(atPath: documentsDirectoryURL.appendingPathComponent(url.lastPathComponent).path) {
URLSession.shared.downloadTask(with: url) { (location, response, error) -> Void in
guard let location = location else { return }
let destinationURL = documentsDirectoryURL.appendingPathComponent(response?.suggestedFilename ?? url.lastPathComponent)
do {
try FileManager.default.moveItem(at: location, to: destinationURL)
PHPhotoLibrary.requestAuthorization({ (authorizationStatus: PHAuthorizationStatus) -> Void in
if authorizationStatus == .authorized {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: destinationURL)}) { completed, error in
DispatchQueue.main.async {
if completed { returnCompletion(url.lastPathComponent)
} else {
returnCompletion(nil)
}
}
}
}
})
returnCompletion(url.lastPathComponent)
} catch {
returnCompletion(nil)
}
}.resume()
} else {
returnCompletion(nil)
}
}
}
I am running into an error, when it hits:
self.videoWriterInput.markAsFinished();
It is giving me :
-[AVAssetWriterInput markAsFinished] Cannot call method when status is 0