How to save document to Files App in swift
If you have access to your PDFdata
then you could present the user with a UIActivityViewController
where they can then save it to Files
, or share it with any of the other available options:
let activityViewController = UIActivityViewController(activityItems: ["Name To Present to User", pdfData], applicationActivities: nil)
present(activityViewController, animated: true, completion: nil)
Swift - Automatically save pdf file to Files app On My iPhone
Example of download any pdf file and automatically save inside files folder of iPhone.
let urlString = "https://www.tutorialspoint.com/swift/swift_tutorial.pdf"
let url = URL(string: urlString)
let fileName = String((url!.lastPathComponent)) as NSString
//Mark: Create destination URL
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
//Mark: Create URL to the source file you want to download
let fileURL = URL(string: urlString)
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
//Mark: Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
do {
//Mark: Show UIActivityViewController to save the downloaded file
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
}
}
catch (let err) {
print("error: \(err)")
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "")")
}
}
task.resume()
iOS: Open saved image inside the native Files app
After doing some research, I don't think this is unwanted behavior but more like behavior that Snapseed
has subscribed to, as this behavior does not happen automatically.
However, there is some bug (I think) that does not let you unsubscribe from this behavior easily.
The place we need to look at is using LSSupportsOpeningDocumentsInPlace
and CFBundleDocumentTypes
, more on that here and here:
Here I created a small example to save an image from the app main bundle to the documents directory when the user taps a button using your code:
Absolutely no difference to your code
@objc
private func saveImage()
{
if let imageData
= UIImage(named: "dog")?.jpegData(compressionQuality: 1.0)
{
save(imageData: imageData,
toFolder: "image",
withFileName: "dog.jpeg")
}
}
func save(imageData: Data,
toFolder folderName: String,
withFileName fileName: String)
{
DispatchQueue.global().async
{
let manager = FileManager.default
let documentFolder = manager.urls(for: .documentDirectory,
in: .userDomainMask).last
let folder = documentFolder?.appendingPathComponent(folderName)
let file = folder?.appendingPathComponent(fileName)
do {
try manager.createDirectory(at: folder!,
withIntermediateDirectories: true,
attributes: [:])
if let file = file
{
try imageData.write(to: file)
print("file \(fileName) saved")
}
}
catch
{
print(error.localizedDescription)
}
}
}
If you add the following to your info.plist
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
This supports exposing your documents directory in the files app and after this everything works as normal:
However, if you want your app to be part of a group that opens certain file formats, you modify your info.plist to add the CFBundleDocumentTypes
to suggest that your app is able to open specific files, like images in our case.
To do that, we add the following to info.plist
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.image</string>
</array>
<key>CFBundleTypeName</key>
<string>image</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
This would allow your app to be listed when some wants to share or open an image and by adding this, then we get the behavior shown in your gif which opens the app instead of the preview in the files app
Unfortunately, it seems once this behavior is assigned to your app, even if you remove the above above key from info.plist, this behavior persists.
The only way I could restore the original behavior was by using a fresh bundle identifier and only using these two keys in the info.plist
in order to expose your documents directory to the Files app
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
I believe this is some sort of bug as there should be a simple way to unsubscribe from this behavior.
Hope this helps even though it does not fully solve your problem
How can I display my App documents in the Files app for iPhone
If you would like to expose your App document files inside Apple's Files App you need to include the "Supports Document Browser"
key in your info plist file and set it to YES:
How can I write a file in a folder located at Apple's Files App in SwiftUI?
As I have already posted in comments you can NOT programmatically save a file out of your APP Bundle. You can use a UIDocumentInteractionController
and ask the user to choose the location where the file is supposed to be written.
So if you are working with SwiftUI this gets a bit more complicated than the regular Storyboard approach as you can see in this post because you need to implement UIViewControllerRepresentable
for UIDocumentInteractionController
:
struct DocumentInteractionController: UIViewControllerRepresentable {
fileprivate var isExportingDocument: Binding<Bool>
fileprivate let viewController = UIViewController()
fileprivate let documentInteractionController: UIDocumentInteractionController
init(_ isExportingDocument: Binding<Bool>, url: URL) {
self.isExportingDocument = isExportingDocument
documentInteractionController = .init(url: url)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentInteractionController>) -> UIViewController { viewController }
func updateUIViewController(_ controller: UIViewController, context: UIViewControllerRepresentableContext<DocumentInteractionController>) {
if isExportingDocument.wrappedValue && documentInteractionController.delegate == nil {
documentInteractionController.uti = documentInteractionController.url?.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = documentInteractionController.url?.localizedName
documentInteractionController.presentOptionsMenu(from: controller.view.frame, in: controller.view, animated: true)
documentInteractionController.delegate = context.coordinator
documentInteractionController.presentPreview(animated: true)
}
}
func makeCoordinator() -> Coordintor { .init(self) }
}
And its Coordinator:
class Coordintor: NSObject, UIDocumentInteractionControllerDelegate {
let documentInteractionController: DocumentInteractionController
init(_ controller: DocumentInteractionController) {
documentInteractionController = controller
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { documentInteractionController.viewController }
func documentInteractionControllerDidDismissOptionsMenu(_ controller: UIDocumentInteractionController) {
controller.delegate = nil
documentInteractionController.isExportingDocument.wrappedValue = false
}
}
Now you can create your DocumentInteraction View and its previews:
struct DocumentInteraction: View {
@State private var isExportingDocument = false
var body: some View {
VStack {
Button("Export Document") { self.isExportingDocument = true }
.background(DocumentInteractionController($isExportingDocument,
url: Bundle.main.url(forResource: "sample", withExtension: "pdf")!))
}
}
}
struct DocumentInteraction_Previews: PreviewProvider {
static var previews: some View { DocumentInteraction() }
}
You will need those helpers as well:
extension URL {
var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
var localizedName: String? { (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName }
}
Sample project
Showing iOS app files within in the Files app
As well as UIFileSharingEnabled
add this to your Info.plist
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
Here is my test code. Works well for me on macOS 12, using Xcode 13.2,
targets: ios-15 and macCatalyst 12. Tested on real ios-15 devices.
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
let image = UIImage(systemName: "globe")!
var body: some View {
Image(uiImage: image).resizable().frame(width: 111, height: 111)
Button(action: {
saveImage(file: "globe")
}) {
Text("save test file")
}
}
func saveImage(file: String) {
do {
let fileURL = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(file)
.appendingPathExtension("png")
try image.pngData()?.write(to: fileURL)
} catch {
print("could not create file: \(file)")
}
}
}
EDIT1: Starting from a "new" project in Xcode.
I created a new ios project from scratch, using Xcode 13.2 on macos 12.2-beta.
Copied and pasted the code in my answer. Then in the Targets->Info
I added the following entries:
Application supports iTunes file sharing
YES
and
Supports opening documents in place
YES
Compiled and ran the project on a iPhone ios-15. In the Files
app,
went to On My iPhone
and the TestApp
folder was there, and inside was the "globe"
image file.
Added the mac catalyst in General -> Deployment Info
,
removed the App sandbox
from the Signing & Capabilities
.
Compiled and ran the project on a iMac macos 12.2, and in the Documents
directory was the globe.png
file.
How to write a file to a folder located at Apple's Files App in Swift
You can use UIDocumentInteractionController
and let the user select where he wants to save your file when you share your url. The user just needs to select save to files and choose which directory to save the file you are exporting.
You can use UIDocumentInteractionController
to share any file type located inside your App bundle, at your Documents directory or another folder accessible from your App.
class ViewController: UIViewController {
let documentInteractionController = UIDocumentInteractionController()
func share(url: URL) {
documentInteractionController.url = url
documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = url.localizedName ?? url.lastPathComponent
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
}
@IBAction func shareAction(_ sender: UIButton) {
guard let url = URL(string: "https://www.ibm.com/support/knowledgecenter/SVU13_7.2.1/com.ibm.ismsaas.doc/reference/AssetsImportCompleteSample.csv?view=kc") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
let tmpURL = FileManager.default.temporaryDirectory
.appendingPathComponent(response?.suggestedFilename ?? "fileName.csv")
do {
try data.write(to: tmpURL)
DispatchQueue.main.async {
self.share(url: tmpURL)
}
} catch {
print(error)
}
}.resume()
}
}
extension URL {
var typeIdentifier: String? {
return (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
}
var localizedName: String? {
return (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName
}
}
edit/update:
If you would like to expose the files located on your App Bundle's document directory you can check this post How can I display my App documents in the Files app for iPhone
Related Topics
How to Rotate Sprites Around a Joint
How to Call the More Specific Method of Overloading
Swift: How to Get Form Values Using Eureka Form Builder
My Uiviewcontroller Is Not Filling the Entire Screen
How to Access Firebase Variable Outside Firebase Function
Swift: When Should I Use "Var" Instead of "Let"
Appending Tuples to an Array of Tuples
Using Uiapplicationdelegateadaptor to Get Callbacks from Userdidacceptcloudkitsharewith Not Working
Alamofire Type 'Parameterencoding' Has No Member 'Url' Swift 3
How to Prevent Timer Slowing Down in Background
How Are Hash Collisions Handled
Difference Between Nsrange and Nsmakerange
Swift [1,2] Conforms to Anyobject But [Enum.A, Enum.B] Does Not
Difference Between Text("") and Text(Verbatim: "") Initializers in Swiftui