Get all URLs for resources in sub-directory in Swift - ios

I am attempting to create an array of URLs for all of the resources in a sub-directory in my iOS app. I can not seem to get to the correct path, I want to be able to retrieve the URLs even if I do not know the names (i.e. I don't want to hard code the file names into the code).
Below is a screen shot of the hierarchy, I am attempting to get all the files in the 'test' folder:
Any help is greatly appreciated, I have attempted to use file manager and bundle main path but to no joy so far.
This is the only code I have currently:
let fileManager = FileManager.default
let path = Bundle.main.urls(forResourcesWithExtension: "pdf", subdirectory: "Files/test")
print(path)
I have also tried this code but this prints all resources, I can't seem to specify a sub-directory:
let fm = FileManager.default
let path = Bundle.main.resourcePath!
do {
let items = try fm.contentsOfDirectory(atPath: path)
for item in items {
print("Found \(item)")
}
} catch {
// failed to read directory – bad permissions, perhaps?
}
Based on an answer from #vadian , The folders were changed from virtual groups to real folders. Using the following code I was able to get a list of resources:
let fileManager = FileManager.default
let path = Bundle.main.resourcePath
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: "\(path!)/Files/test")!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("pdf") || element.hasSuffix("jpg") { // checks the extension
print(element)
}
}

Consider that the yellow folders are virtual groups, not real folders (although Xcode creates real folders in the project directory). All files in the yellow folders are moved into the Resources directory in the bundle when the app is built.
Real folders in the bundle are in the project navigator.

You can follow the following steps to get them:
Create a new folder inside your project folder with the extension is .bundle (for example: Images.bundle).
Copy resource files into that new folder.
Drag that new folder into the project that opening in Xcode.
Retrieve the URLs by using the following code snippet:
let urls = Bundle.main.urls(forResourcesWithExtension: nil, subdirectory: "Images.bundle")
You can also view the guide video here: https://youtu.be/SpMaZp0ReEo

I came across a similar issue today. I needed to retrieve the URL of a resource file in a bundle ignoring its path.
I wrote the following:
public extension Bundle {
/// Returns the file URL for the resource identified by the specified name, searching all bundle resources.
/// - Parameter resource: The name of the resource file, including the extension.
/// - Returns: The file URL for the resource file or nil if the file could not be located.
func recurseUrl(forResource resource: String) -> URL? {
let contents = FileManager.default.allContentsOfDirectory(atPath: self.bundlePath)
for content in contents {
let fileNameWithPath = NSString(string: content)
if let fileName = fileNameWithPath.pathComponents.last {
if resource == fileName {
return URL(fileURLWithPath: content)
}
}
}
return nil
}
Based on this:
public extension FileManager {
/// Performs a deep search of the specified directory and returns the paths of any contained items.
/// - Parameter path: The path to the directory whose contents you want to enumerate.
/// - Returns: An array of String objects, each of which identifies a file or symbolic link contained in path. Returns an empty array if the directory does not exists or has no contents.
func allContentsOfDirectory(atPath path: String) -> [String] {
var paths = [String]()
do {
let url = URL(fileURLWithPath: path)
let contents = try FileManager.default.contentsOfDirectory(atPath: path)
for content in contents {
let contentUrl = url.appendingPathComponent(content)
if contentUrl.hasDirectoryPath {
paths.append(contentsOf: allContentsOfDirectory(atPath: contentUrl.path))
}
else {
paths.append(contentUrl.path)
}
}
}
catch {}
return paths
}
}
Which achieves the goal of retrieving the URL for the first match of a given resource filename in a bundle's resources, all directories wide.
I tend to think that Swift's func url(forResource name: String?, withExtension ext: String?) -> URL? should behave this way in the first place.

Related

Save/create folder that it to be treated as a file with FileManager

I have a iOS/CatalystMacOS-app that can create, save, open custom text-files (with my own file extension). This works fine. However, now I need more than text. I want to save optional files in this file as well. Apparently macOS (and iOS?) can treat folders as files. But I cannot get it to work as wanted. The folder is still treated as a folder, even if it has a file extension.
This is the code I use to create the folder:
func showNewFilePathDialog(from viewController: UIViewController, saveCompleted: URLCallback?) {
guard !isPresenting else {
return
}
let objectToSave = ...
// Find an available filename
var number = 0
var exportURL: URL!
var data: Data!
var fullFileName = ""
while true {
let numberText = number == 0 ? "" : number.asString()
fullFileName = "baseFileName" + "\(numberText).myFileExtension"
exportURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(fullFileName)
let dict = objectToSave.toDict()
let json = dict.json!
data = json.data(using: .utf8)!
if FileManager.default.fileExists(atPath: exportURL.path) {
number += 1
continue
} else {
break
}
}
do {
try FileManager.default.createDirectory(atPath: exportURL.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create document directory")
viewController.presentErrorDialog(from: error)
return
}
// 2. Create containing json file
do {
try data.write(to: exportURL.appendingPathComponent("content.json"))
} catch {
viewController.presentErrorDialog(from: error)
return
}
isPresenting = true
self.onSaveDialogComplete = saveCompleted
let pickerViewController = UIDocumentPickerViewController(url: exportURL, in: .exportToService)
pickerViewController.delegate = self
viewController.present(pickerViewController, animated: true)
}
And then it appears like this in macOS finder:
It will show up similar in iOS, not allowing me to open the folder as a single file either.
Edit: Using UIDocument/public.composite-content/FileWrapper seems to work as well, but the problem still consists: When viewed in macOS finder it is still treated as a folder. Also when trying to open the app from the open-dialog via UIDocumentPickerViewController trying to open the file-bundle only opens the folder and wont let me open it into the app :(
This is my info.list Export Type UTIs:
Edit2: Also tried with removing all but com.apple.package but does not work either. Still cannot open my custom type as it behaves like a folder.
Got it working. Seemed as old builds of my app was interfering with the system file types. So I searched for my app name and removed old builds from my computer. Then the system recognized my file suffix and opened it right away!
But I lost the icon this time, but that's another issue :)

Multiple folder with Json file of same name swift 3

I'm facing difficulty to fetch the Json File.In my project there is one folder,in that folder multiple sub folders are there.And in every sub folder there is two json file.Json file name is common in every sub folder.Then how I fetch json file with subfolder name? Which means i want to differentiate json file through sub folder name.I'm new in Swift.Thank you.
(NS)Bundle provides a very convenient way to address subfolders, the optional parameter subdirectory of url(forResource: withExtension:)
if let jsonFileURL = Bundle.main.url(forResource: "jsonFile",
withExtension: "json",
subdirectory: "Subfolder1/SubFolder2") {
let json = try! String(contentsOf: jsonFileURL, encoding: .utf8)
print(json)
} else { fatalError("Check the file paths") }
You can get the file from different folders by providing the actual folder name in the document path.
Make sure that your files are added to resources.
if let docsPath = Bundle.main.resourcePath! + "/Resources/subfolder" {
let fileManager = FileManager.default
do {
let docsArray = try fileManager.contentsOfDirectory(atPath: docsPath)
//get the required json file from the documents array
} catch {
print(error)
}
}

How to get partial file path after downloading with Alamofire?

I want to save the path of where I downloaded a file to my model object. Based on this StackOverflow answer, I should "only store the filename, and then combine it with the location of the documents directory on the fly". Well in my case it's the Application Support directory. I am not sure how to do this.
The full path of where the file will be downloaded is: /Library/ApplicationSupport/com.Company.DemoApp/MainFolder/myFile.jpg
/Library/Application Support/ is the part I can't save anywhere and I have to get it during runtime from FileManager.
com.Company.DemoApp/MainFolder/myFile.jpg is part of that file path that I can store in database/config files.
However I do not know how to get this path: com.Company.DemoApp/MainFolder/myFile.jpg from Alamofire after downloading it. For example, here is the code to download the file, how do I get this path?
func destination(named name: String, pathExtension: String) -> DownloadRequest.DownloadFileDestination {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let appSupportDirURL = FileManager.createOrFindApplicationSupportDirectory()
let fileURL = appSupportDirURL?.appendingPathComponent("com.Company.DemoApp/MainFolder/\(name).\(pathExtension)")
return (fileURL!, [.removePreviousFile, .createIntermediateDirectories])
}
return destination
}
let finalDestination = destination(named: image.title, pathExtension: image.preview.pathExtension)
Alamofire.download(urlString, to: finalDestination).response { response in
if let imagePath = response.destinationURL?.path {
/// I want to this path here: com.Company.DemoApp/MainFolder/myFile.jpg
/// But not sure how to get it. How do I get this path?
}
}
The problem is that Alamofire gives me the full path only: /Library/ApplicationSupport/com.Company.DemoApp/MainFolder/myFile.jpg. But I only want this part: com.Company.DemoApp/MainFolder/myFile.jpg.
Any ideas on how to get this path?
Also, Apple seems to refer to Bookmark's if you want to get a file at runtime here: Apple Docs for Bookmarks
Note this is a follow up to a previous question.
UPDATE 1
This is one way I think this could work. (Based of an answer above).
enum DataDirectory: String {
case feed = "com.Compnay.DemoApp/MainFolder/"
}
Alamofire.download(urlString, to: finalDestination).response { response in
let destURL = response.destinationURL!
/// One way to save this path is to do so like this:
myImageObject.partialLocalPath = "\(DataDirectory.feed.rawValue)\(destURL.lastPathComponent)"
}
}
So I am saving the part in an enum and appending the name to it once the download finishes - then I add this to my model object for saving.
Any thoughts?

find a file inside application documents directory

i have copied a video file inside my installed application documents directory using iexplorer and i am trying to play it on avplayer by accessing the video.but i cant found the video.here is my code to find the files under application documents directoy
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
var documentsDirectory: String = paths[0] as! String
do {
let filePathsArray: [AnyObject] = try NSFileManager.defaultManager().subpathsOfDirectoryAtPath(documentsDirectory)
print("files array %#", filePathsArray)
}catch {
}
it shows empty
from Apple Docs:
subpathsOfDirectoryAtPath:error:
Performs a deep enumeration of the specified directory and returns
the paths of all of the contained subdirectories.
try iterating thru .contentsOfDirectoryAtPath:error instead

Swift iOS filemanager copy changes sqlite file

I have a routine that when my dataModelObject is initiate, its to copy myDataBase.sqlite file from the App Bundle folder, to the app document directory so that the app can use it. It only copies it if its not there, meaning in live environment it will not replace users existing db. The operation executes with no problem, however the database at the destination location is completely different than the database i designed. Has anyone else run into this, and might know what is causing this?
2 tables in said database one record only in one table with a weird number and a blob.
Yes, i have confirmed that the file in the bundle points to the correct .sqlite file in the project folder.
Here is my routine that does the copy, does something stand out as bad? Should i use move instead?
class func copyFile(fileName: NSString) {
let dbPath: String = getPath(fileName)
println("copyFile fileName=\(fileName) to path=\(dbPath)")
var fileManager = NSFileManager.defaultManager()
var fromPath: String? = NSBundle.mainBundle().resourcePath?.stringByAppendingPathComponent(fileName)
if !fileManager.fileExistsAtPath(dbPath) {
println("dB not found in document directory filemanager will copy this file from this path=\(fromPath) :::TO::: path=\(dbPath)")
fileManager.copyItemAtPath(fromPath!, toPath: dbPath, error: nil)
} else {
println("DID-NOT copy dB file, file allready exists at path:\(dbPath)")
}
}
class func getPath(fileName: String) -> String {
return NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent(fileName)
}

Resources