In my iOS Swift App, I have created files in my App's documents directory through this code:
let localFileName = String("\(fileName).rtf")
let text = String("text text text")
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(localFileName)
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {
}
}
Now I want to Add a link in my app to this directory where file is created so that user can see files. Currently I am doing this by this code:
let importMenu = UIDocumentPickerViewController(documentTypes: [String(kUTTypeRTF)], in: .open)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
present(importMenu, animated: true, completion: nil)
but this code is for picking documents, not for opening directory, So How I can open my App's Documents directory, not for picking documents, just only for showing documents?
It is not possible to explore/open a directory in iOS app. Apple doesn't provide any api for the same. You need to create it by your own.
What you can do
You need to fetch all the files from the specific directory and list them all in either tableview or collection view.
And when user click on the any file, you can show that in web view or based on the file type you can do any specific operations.
So ultimately you need to explore more about FileManager.
This class contains what you want.
Related
I am trying to list all PDFs inside a folder that is stored in my Xcode project using SwiftUI, but after trying many different methods I found on here I cannot get it working.
Currently I am using file manager to list the items but when I try and print or list the items found it returns nil.
I have added the PDFs by dragging them into the Xcode project. I have also made sure that they are in my Copy Bundle Resource so I can use FileManager. See below:
Here is my Xcode structure, I am trying to list all items inside PDF. As you can see below the PDFs are stored outside the main folder structure in "/Swift/Products/Detail/Tab5/PDF", so when trying to list the files using Bundle.main.bundlePath, it looks at products.app.
Here is the code where I am trying to use FileManager to find all PDFs inside the folder:
struct ProductTab5View: View {
#State var files = getFiles()
var body: some View {
VStack{
ForEach(0..<files.count, id: \.self) { item in
Text(files[item])
}
}
}
}
func getFiles() -> Array<String>{
// Get document directory url
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
var files: [String] = []
do {
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil)
print(directoryContents)
// filter directory contents:
let pdfFiles = directoryContents.filter{ $0.pathExtension == "pdf" }
let pdfFileNames = pdfFiles.map{ $0.deletingPathExtension().lastPathComponent }
files.append(contentsOf: pdfFileNames)
} catch {
print(error)
}
return files
}
In such a way resources are copied flat into root application bundle, not user 's Documents as it was tried in provided code snapshot (and no PDF sub-directory is created for you).
So the solution would be just to iterate internal pdf files, like
func getFiles() -> Array<String> {
return Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: nil)?
.compactMap { $0.lastPathComponent } ?? []
}
Tested with Xcode 12.1 / iOS 14.1
There are a few potential problems with your current code.
The first is this line:
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
Unless you have gone through a process separately from what you've shown of adding all of these files to the app's document directory, then there's no reason to believe that these files will be there. In other words: your Xcode project is not the same as your app's document directory. Think of the app's document directory as a place where you may store user-created or perhaps downloaded content as managed by the app -- not Xcode itself.
Next thing to check is whether all of these files are truly added to your target. Check your target's "Build Phases" -> "Copy Bundle Resources" section to see if they appear there.
If they do, you can use FileManager, but you have to access the correct directory, which is inside the main bundle -- not the app's user document directory.
The following answer goes into details about this (including making sure you create folder references): Getting a list of files in the Resources folder - iOS
The gist will be doing something like this:
if let files = try? FileManager.default.contentsOfDirectory(atPath: Bundle.main.bundlePath) { //may need to dig into subdirectories
for file in files {
//manipulate the file
}
}
and then using FileManager to list the documents in that path.
Note that you can also get all the files (including subdirectories) recursively by doing something like this:
let url = URL(fileURLWithPath: Bundle.main.bundlePath)
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in enumerator {
print(fileURL)
}
}
By using the above code during your debugging process, this should give you an insight into what your directory structure is really like inside your bundle. Once you've figured that out, adjust the previous code sample to be something like:
if let files = try? FileManager.default.contentsOfDirectory(atPath: Bundle.main.bundlePath + mySubPath) {
for file in files {
//manipulate the file
}
}
which will give you just the files in the one subdirectory you want once you fill in mySubPath. Note that if you want recursive search, you can use the code sample above.
You may want to exclude non-file items eventually -- see this answer for more details about recursive directory lists in Swift: listing all files in a folder recursively with swift
I have saved and opened a PDF file on my view controller, and then I save the array of PDFs to the system data. I then pull my array from the data and use the string of the location in the new view controller. However when I attempt to load the file at said directory it fails to appear in the WebView, even though I was able to load it from the same directory on the first ViewController.
How can I get it to open after the app has been closed?
Here is how I save and open the PDF the first time:
func generatePreview() {
let A4paperSize = CGSize(width: 595, height: 842)
let pdf = SimplePDF(pageSize: A4paperSize, pageMargin: 20.0)
createFirstPage(x: pdf)
pdf.beginNewPage()
addAreas(x: pdf)
//add disclamer and like dress the pdf up
let pdfData = pdf.generatePDFdata()
let resourceDocPath = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last! as URL
let pdfNameFromUrl = "Survey-\(finalOverview.name).pdf"
let actualPath = resourceDocPath.appendingPathComponent(pdfNameFromUrl)
do {
try pdfData.write(to: actualPath, options: .atomic)
print("pdf successfully saved!")
} catch {
print("Pdf could not be saved")
}
let request = URLRequest(url: actualPath)
pdfPreview.load(request)
let pdfObj = pdfArray(fileTitle: finalOverview.name, fileName: actualPath)
//pdfView.GlobalVariable.myPDFs.append(pdfObj)
savePDF(x: pdfObj)
}
I made the mentioned changes but still no luck
My issue was when I was creating the string the second time, the optional was not wrapped. So when I unwrapped it the code worked as intended.
I have an app say called MyApp it is primarily wkwebview. It is already integrated to Files app so I can import/export files from my wkwebview to be able to download files to local machine or upload files to the server.
Finally I am trying to build a UIbutton in my app that will allow my users to Jump into Files app in the folder where I am storing all their content. Instead of building a full FileProviderUI into my app, I just want the button to take the user into the Files App navigating to the folder.
When I give the path for UIDocumentInteractionController to be a directory and do a shared open to present it, nothing happens. No error, nothing at at all. I just want the user to be able to Jump into the folder called MyApp inside the Files app.
I thought it will be very simple. Adding a FileProvider extension or FilePRoviderUI extension seems superflous to just jump the user into this folder and let him interact with Files app to do whatever he likes - open/delete/modify document.
I have to assume that the users are not savvy enough to know even if I tell them that files are saved in Files App for them to be able to interact with directly when they are offline!
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// Just jump to the folder forcing Files app to open this
let viewer = UIDocumentInteractionController(documentsUrl)
viewer.delegate = self
viewer.presentPreview(animated: true)
Nothing gets presented to the user and nothing happens. The button tap just quietly fails.
I think I figured this out. There is a specific URL format that will automatically open the Files app it is shareddocuments:// - Here is a simple function that seems to achieve this quiet simply.
func openSharedFilesApp() {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
let furl:URL = URL(string: sharedurl)!
UIApplication.shared.open(furl, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
}
Thanks, Vijay. Here it is in Objective-C.
- (void) openSharedFilesApp
{
NSArray<NSString *>* directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); // yes = expand tilde
NSURL* url = [NSURL fileURLWithPath:directories.firstObject];
NSString* sharedDocumentPath = [url.absoluteString stringByReplacingOccurrencesOfString:#"file://" withString:#"shareddocuments://"];
NSURL* sharedDocumentURL = [NSURL URLWithString:sharedDocumentPath];
[UIApplication.sharedApplication openURL:sharedDocumentURL options:#{UIApplicationOpenURLOptionUniversalLinksOnly: #(NO)} completionHandler:^(BOOL success) {
}];
}
Adding to #Vijay's answer in iOS 15, Swift 5:
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
let furl:URL = URL(string: sharedurl)!
if UIApplication.shared.canOpenURL(furl) {
UIApplication.shared.open(furl, options: [:])
}
This question already has answers here:
Save An Image To Application Documents Folder From UIView On IOS
(6 answers)
Closed 5 years ago.
I want to in my app, be able to allow users to save large images(jpg) as well as data for each image(txt) and load the images/data. I'm having trouble figuring out where to save these images and text files. Userdefault wouldnt work because of the size of the image files and I don't want to save in the documents directory because then the user can access and potentially corrupt the data.
Where is a good place to save large data files for my app so I can load them later?
You can save and retrieve your files in application directory folder. Also you can use iCloud to save your files.
Use below code if you want to save and retrieve files from Directory folder .
Xcode 8.3.2 Swift 3.x. Using NSKeyedArchiver and NSKeyedUnarchiver
Reading file from documents
let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!
let jsonFilePath = documentsDirectoryPath.appendingPathComponent("Filename.json")
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: (jsonFilePath?.absoluteString)!, isDirectory: &isDirectory) {
let finalDataDict = NSKeyedUnarchiver.unarchiveObject(withFile: (jsonFilePath?.absoluteString)!) as! [String: Any]
}
else{
print("File does not exists")
}
Write file to documents
NSKeyedArchiver.archiveRootObject(finalDataDict, toFile:(jsonFilePath?.absoluteString)!)
If for some reason you don't want to use the documents directory, or you want those data in a separate folder, or you don't want to permanently save them, you can also create your own temporary directory
saving data as file in a new directory (swift 3):
func saveToMyDirectory(data: Data, filename: String) {
var tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
tempDirectoryURL = tempDirectoryURL.appendingPathComponent(filename)
do {
try data?.write(to: tempDirectoryURL)
} catch {}
}
Alternative approach: use Apple's UIDocumentInteractionController or UIActivityViewController and let the user choose how to save his/her documents:
https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller
https://developer.apple.com/documentation/uikit/uiactivityviewcontroller
I'm working on an iOS Content Blocker, but I would like to have the user choose what lists they want to block (ads, tracks, adult sites etc.). I found that the app extension and the containing app's bundles are separate and have no access to each other's files, so a shared container is needed. I created an app group, but it seems like what I write there does not actually get written. What I am attempting to do is read a .json file from the bundle, and then write it to a sharedJson.json file that the content blocker extension can access.
func writeJsonToSharedJson(arrayOfStrings:[String]) -> Bool {
let composedString = arrayOfStrings.joined(separator: "\n")
let sharedJsonPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.alexspear.BlockerTestGroup")?.appendingPathComponent("sharedJson.json")
//let sharedJsonPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("sharedJson.json")
do {
try composedString.write(to: sharedJsonPath!, atomically: true, encoding: .utf8)
}
catch {
print("Could not write to sharedJson.json\n")
return false
}
return verifyJsonWrite()
}
The result is that through the verifyJsonWrite() function, there is nothing there. Am I incorrect in assuming you can create a file in the app group container? I have also tried using FileManager's createFile function with the same result.