How to pass a Document File to UIActivityViewController i.e. Share Sheet - ios

I have an iOS App which generates a CSV File and saves it to the working Documents directory of the device. Now when a user presses a button, the UIActivityViewController Share Sheet is displayed which allows you to open send data to other apps.
My question is how do I pass this CSV file to the Share Sheet. I know how to make this work with Text and Images, but not exactly sure how to get this to work with a CSV file. The end result is that I want this file to show up as an attachment in any email client which is selected from the Share Sheet.

This is the code I use to open a document (word, pdf, etc.). Should work for you...
#IBAction func openDocument(sender: AnyObject)
{
let interactionController = UIDocumentInteractionController(URL: documentURL)
// (build your document's URL from its path in the Documents directory)
interactionController.delegate = self
// First, attempt to show the "Open with... (app)" menu. Will fail and
// do nothing if no app is present that can open the specified document
// type.
let result = interactionController.presentOpenInMenuFromRect(
self.view.frame,
inView: self.view,
animated: true
)
if result == false {
// Fall back to "options" view:
interactionController.presentOptionsMenuFromRect(
self.view.frame,
inView: self.view,
animated: true)
}
// DONE
}

Related

Move file from app to phone documents folder

So in my app I have created a Test.JSON file that I want the user to be able to move to the documents directory, outside of the app. I understand I have to do this by using UIDocumentPickerViewController, but haven't found any way to proceed. I have created the Test.JSON file, and can use it from variable data.
I have this following code to open the UIDocumentPickerViewController:
let documentPicker =
UIDocumentPickerViewController(forExporting: [.documentsDirectory])
documentPicker.delegate = self
// Set the initial directory.
documentPicker.directoryURL = .documentsDirectory
// Present the document picker.
present(documentPicker, animated: true, completion: nil)
How can I attach the data file to the UIDocumentPickerViewController, so I can place it in the documents directory?
If you already have the URL for the document replace 'newFile' with the document URL. 'vc' is the current ViewController
Note asCopy = false will move the the document, asCopy = true will copy the document. There appears to be bug in iOS 16+ which disables the Move button when asCopy = false. Bug fixed in subsequent release FB11627056
//Present Document Picker
let controller = UIDocumentPickerViewController(forExporting: [newFile], asCopy: false)
vc.present(controller, animated: true) {
//this will be called as soon as the picker is launched NOT after save
}
Have you followed the instructions that Apple provides here? I'm summarizing the important bits here:
After the user taps Done, the system calls your delegate’s documentPicker(_:didPickDocumentsAt:) method, passing an array of security-scoped URLs for the user’s selected directories .... When the user selects a directory in the document picker, the system gives your app permission to access that directory and all of its contents.
So first, you need to implement the delegate methods so that you know what the user selects. Specifically, documentPicker(_:didPickDocumentsAt:) is the important one, although you'll want to listen for "cancel" as well. Then you need to access that scoped resource and write to it.
Here is an example that I took from the documentation linked above. This example only reads from the directory, but you can also write to it in the same way.
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
// Start accessing a security-scoped resource.
guard url.startAccessingSecurityScopedResource() else {
// Handle the failure here.
return
}
// Make sure you release the security-scoped resource when you finish.
defer { url.stopAccessingSecurityScopedResource() }
// Use file coordination for reading and writing any of the URL’s content.
var error: NSError? = nil
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in
let keys : [URLResourceKey] = [.nameKey, .isDirectoryKey]
// Get an enumerator for the directory's content.
guard let fileList =
FileManager.default.enumerator(at: url, includingPropertiesForKeys: keys) else {
Swift.debugPrint("*** Unable to access the contents of \(url.path) ***\n")
return
}
for case let file as URL in fileList {
// Start accessing the content's security-scoped URL.
guard url.startAccessingSecurityScopedResource() else {
// Handle the failure here.
continue
}
// Do something with the file here.
Swift.debugPrint("chosen file: \(file.lastPathComponent)")
// Make sure you release the security-scoped resource when you finish.
url.stopAccessingSecurityScopedResource()
}
}
}

How to preview multiple PDF files in iOS similar to WhatsApp upload documents functionality?

I am integrating UIDocumentPickerViewController to show the local storage (File App) for browsing and selecting the PDF. Right now i am selecting a single PDF and previewing it by passing the URL to WKWebview which is working fine. But when i enable allowsMultipleSelection i am able to select the multiple files and getting multiple URLs
NSArray *types = #[(NSString*)kUTTypePDF];
//Create a object of document picker view and set the mode to Import
UIDocumentPickerViewController *docPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:types inMode:UIDocumentPickerModeImport];
//Set the delegate
docPicker.delegate = self;
docPicker.allowsMultipleSelection = true; // Allows multiple selection.
//present the document picker
[self presentViewController:docPicker animated:YES completion:nil];
The delegate for getting multiple URLs is :
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls API_AVAILABLE(ios(11.0));
while previewing using WKWebView i am able to preview only one file as shown below:
But i want to preview both the selected files as WhatsApp does as shown below. Here i can swipe horizontally to preview the selected files
How to preview multiple files similar to WhatsApp? Please help me in this regard.
Use a QLPreviewController; you'll need to import QuickLook. It's a view controller. You show it as a presented view controller or push it onto a navigation controller's stack.
In this example, I have somewhere in my Documents directory one or more PDF or text documents. I acquire a list of their URLs and present a preview for them (self.exts has been initialized to a set consisting of ["pdf", "txt"]):
self.docs = [URL]()
do {
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let dir = fm.enumerator(at: docsurl, includingPropertiesForKeys: nil)!
for case let f as URL in dir {
if self.exts.contains(f.pathExtension) {
if QLPreviewController.canPreview(f as QLPreviewItem) {
self.docs.append(f)
}
}
}
guard self.docs.count > 0 else { return }
let preview = QLPreviewController()
preview.dataSource = self
preview.currentPreviewItemIndex = 0
self.present(preview, animated: true)
} catch {
print(error)
}
You'll notice that I haven't told the QLPreviewController what documents to preview. That is the job of QLPreviewController's data source. In my code, I (self) am also the data source. I simply fetch the requested information from the list of URLs, which I previously saved into self.docs:
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return self.docs.count
}
func previewController(_ controller: QLPreviewController,
previewItemAt index: Int) -> QLPreviewItem {
return self.docs[index] as QLPreviewItem
}
The second data source method requires us to return an object that adopts the QLPreviewItem protocol. URL does adopt this protocol.

How did Apple implement the BookMark feature on its iOS Books app?

I am making a PDF Reader similar to the Books App built in on iOS. My bookmarks work and update, except when I exit the PDF View and go back to document browsing (file picker), then open my PDF again. Then my app crashes:
private func updateBookmarkStatus() {
if let documentURL = pdfDocument?.documentURL?.absoluteString,
let bookmarks = UserDefaults.standard.array(forKey: documentURL) as? [Int],
let currentPage = pdfView.currentPage,
let index = pdfDocument?.index(for: currentPage) {
/*crashes on this line*/: bookmarkButton.image = bookmarks.contains(index) ? #imageLiteral(resourceName: "Bookmark-P") : #imageLiteral(resourceName: "Bookmark-N")
} else {
print("Bookmark Error")
}
}
with
Unexpectedly found nil while implicitly unwrapping an Optional value:
The line that I put the comment on just updates the bookmark to the "filled version", showing the user that the page is bookmarked.
The bookmark updates and works within the PDF. It is once I go out of the PDF, to the document browsing, then go back into the PDF that it crashes. It might be worth noting that "Bookmark Error" gets printed when I initially open the PDF for the first time.
If I do not touch the bookmark button, then I can go to document browsing and reopen the PDF as many times as I want. But as soon as I touch the bookmark button, I cannot re-open my PDF file again.
Any suggestions?
Thanks in advance.

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..

How to access music files stored on the iOS device?

My application is having trouble locating the files that are in the music folder on my iOS device.
If the files are held below as application data (in my playpen), no problem. But when I go looking for the "Music" folder, using the following lines:
dest_dir = try! FileManager.default.url(for: .musicDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
musicDir = dest_dir
if UIApplication.shared.canOpenURL(musicDir) {
print("found music directory")
}else {
print("did not find music directory")
}
When the code executes the canOpenURL, I get a permissions complaint.
I also tried accessing the user directory (thinking I could then navigate into Music).
Any clues on how an application can access the files and playlists held under the music system folder on the iOS device?
There is no such thing as "the Music folder" in iOS. To access the user's music library, use the Media Player framework.
Example (assuming you have obtained the necessary authorization from the user):
let query = MPMediaQuery()
let result = query.items
Here you go! (Swift 4.2)
Inside a button method or #IBAction button
let mediaItems = MPMediaQuery.songs().items
let mediaCollection = MPMediaItemCollection(items: mediaItems ?? [])
let player = MPMusicPlayerController.systemMusicPlayer
player.setQueue(with: mediaCollection)
player.play()
let picker = MPMediaPickerController(mediaTypes: .anyAudio)
picker.delegate = self
picker.allowsPickingMultipleItems = false
picker.prompt = "Choose a song"
present(picker, animated: true, completion: nil)
iOS doesn’t give you direct access to the Music folder, for security and privacy reasons; you need to use the APIs in the MediaPlayer framework. Assuming you’re trying to play content from the user’s library, take a look at MPMusicPlayerController; you’ll need to provide it some MPMediaItem instances retrieved with an MPMediaQuery, then call methods from the MPMediaPlayback protocol on the controller to make it play the content.

Resources