How can I iterate inside each directory in resource path - swift

I have a simple scenario, there is a folder called content that contains image file, I want all the image file starting with nssl to be saved to an array , so I do below code, but I cannot seem to think or know a way to find out how I can move in each directory and search for such a file and append to my array , here is my code below , I can get the names of all the directories, but what to do next ?
let path = Bundle.main.resourcePath!
let fm = FileManager.default
do {
let items = try fm.contentsOfDirectory(atPath: path)
for item in items {
}
} catch {
}

FileManager is not needed.
Bundle provides urls(forResourcesWithExtension: subdirectory:) which returns multiple urls for a specific extension
if let urls = Bundle.main.urls(forResourcesWithExtension: "png", subdirectory: "content") {
for url in urls where url.lastPathComponent.hasPrefix("nssl") {
}
}
Change the png extension to the desired type.

Related

MacOS how is KIND implemented

I am trying to write a piece of code that instead of checking file extensions for the many different types of image files, but instead looking at the file attributes. What I can’t figure out from searching the docs is if KIND:Image is really a file attribute or simply a construct Apple created in the FinderApp to make things easier for the user.
I wrote a snippet that pulls the attributes for files with an extension of jpeg and for each file the fileType is returned as NSFileTypeRegular.
let attr = try filemanager.attributesOfItem(atPath: pathConfig+"/"+file) as NSDictionary
if file.hasSuffix(ext) {
   print ("Adding \(file) [ \(attr.fileSize()) \(attr.fileType())]")
   print ( attr.fileModificationDate() )
}
Does anybody know if MacOS retains an attribute for the category a file falls in to. e.g. IMAGE, DOCUMENT etc.
To achieve a functionality similar to the Kind search tag in Finder you can use UTType (Link to reference).
You can get the UTType of a file by initialising it with the file extension:
let fileType = UTType(filenameExtension: fileURL.pathExtension)
The cool thing about UTTypes is that they have a hierarchy, for example, UTType.jpeg is a subtype of UTType.image, along with others like .png.
If you want to check if a file is any kind of image, you can do it like this
let isImage = fileType.conforms(to: .image)
You can check the list for the kind of types you want to support as "Kinds", and filter using those UTTypes
This was my final solution based on the information provided by #EmilioPelaez I am not completely comfortable with Swift especially the unwrapping operations so if the code looks weird that might be why.
func imagesInDir(path: String?) -> [String] {
if let path {
let filemanager: FileManager = FileManager()
let files = filemanager.enumerator(atPath: path)
var array = [String]()
var urlFile: NSURL
while let file = files?.nextObject() as? String {
urlFile = NSURL(fileURLWithPath: file, isDirectory: false)
if (urlFile.isFileURL) {
if let pathExt = urlFile.pathExtension {
if let fileType = UTType(filenameExtension: pathExt) {
let isImage = fileType.conforms(to: .image)
if (isImage){
array.append(file)
print ("\(array.count) \(fileType)")
}
}
}
}
}
}
return array
}

Link app object to file on disk with metadata

Following this topic : iOS PDFkit cannot add custom attribute to pdf document
My app is using PDFKit to save files
I'm trying to set custom key metadata to PDFDocument I save on the device.
The object in my app ('Test') has two important properties :
id: a UUID to be able to retrieve the file on disk (the linked file on disk URL is this_UUID.jpg).
name: a human-readable string set by the user.
This cause some problems :
the file name is a UUID not human readable, so it's bad user experience.
If the user renames the file, the app won't be able to get the file.
So the id is to have a human-readable label for the file. So when the user opens the File app he can find it easily. And add metadata with the id so my app can retrieve it even if renamed. Looks like a nice solution right?
// First I create my own attribute
fileprivate extension PDFDocumentAttribute {
static let testId = PDFDocumentAttribute(rawValue: "com.sc.testID")
}
// Then I set the news attributes before saving the file, in the 'test' class
func setDocument(pdf: PDFDocument) {
let fileURL = self.getPDFDocumentURL()
print("the metadata is \(pdf.documentAttributes)") // print an empty dictionary
pdf.documentAttributes?[PDFDocumentAttribute.testId] = self.id
pdf.documentAttributes?[PDFDocumentAttribute.titleAttribute] = self.name // I suppose the ddisplay name of the document ? It's not, so what is that ?
print("the metadata is now \(pdf.documentAttributes)") // both are printed, it looks ok
//pdf.write(to: fileURL) // I tested this one too, same issues
let data = pdf.dataRepresentation()
do {
try data?.write(to: fileURL, options: .completeFileProtection)
} catch {
print(error.localizedDescription)
}
}
From here it looks ok, when I want to retrieve the pdf document I will check in the folder the id of each doc and return the doc when id match. But the problem is when I get the documentAttributes the attribute 'testId' isn't in. Note the native title, is set correctly.
So I could get the id from there but that looks pretty inappropriate
//still in 'Test' class
func getPDFDocument() -> PDFDocument? {
// get all docs in the folder ad check metadata for each
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let fileURLs = try fileManager.contentsOfDirectory(at: SchoolTest.getSubjectFolderURL(subject: self.subject!), includingPropertiesForKeys: nil)
for url in fileURLs {
print("the doc attributes are : \(PDFDocument(url: url)?.documentAttributes)") // contain title and others preset by Apple but not my custom 'testId'
if let doc = PDFDocument(url: url), doc.documentAttributes?[PDFDocumentAttribute.titleAttribute/*testId*/] as? String == self.documentName {
return doc // this work temporary
}
}
} catch {
print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
return nil
}
Display name:
Currently, the display name/label displayed in the File app is the file name (from URL).
This can cause problems too because if two 'Test' have the same name, their linked file gonna have the same URL. So when the most recent one will be saved on disk it will overwrite the other.
That's a problem I don't have when using the 'Test' id property for the file URL.
If I could set a display name for the file and keep the URL with the UUID that should resolve the problem.
Directories have the same localizing issue, I have named them with English but Apple native directories are localized. A localized name could be nice for user experience.
After hours of research, I can't find a way to localize or apply a display name to my files/directories.
// I tried that without positive result
var url = fileURL as NSURL
try url.setResourceValue("localized label", forKey: .localizedLabelKey)
print("localized name is \(try url.resourceValues(forKeys: [.localizedLabelKey]))")
let newURL = url as URL
try data?.write(to: newURL, options: .completeFileProtection)
Am I doing something badly? How should we do when adding custom metada to a file?

Get path of a file in a data set located in Assets.xcassets

I have a data set of audio files in my Assets.xcassets:
I'm trying to get the path of one of those audio files like this:
let path: String = Bundle.main.path(forResource: "acoustic_grand_piano/A4", ofType: "f32")!
But I get a EXC_BAD_INSTRUCTION. I tried to look on the internet but I don't find anything on Data Sets.
How can I get the content of one of these files?
Thanks!
Try this:
Manually put your files into a folder, named anything you want.
Append ".bundle" to the folder to create a bundle. You'll get a warning, accept it. Congrats, you've just created your first bundle! :-)
Manually drag that folder into your app.
Get at your files by using the following code....
public func returnFile(_ named:String) -> String {
let path: String = Bundle.main.path(forResource: "myAudioFiles", ofType: "bundle")! + "/" + name + ".f32"
do {
return try String(contentsOfFile: path)
}
catch let error as NSError {
return error.description
}
}
Now, my files are text files of CIKernel code. Since your's are audio files you may need to change the String return to something else.
EDIT:
In my case I'm using a framework, as I wish to share these files/images with extensions and other apps. If you are working in such a set up, here's the unaltered code:
public func returnFile(_ resource:String, _ fileName:String, _ fileType:String) -> String {
let identifier = "com.companyname.appname" // replace with framework bundle identifier
let fileBundle = Bundle.init(identifier: identifier)
let filePath = (fileBundle?.path(forResource: resource, ofType: "bundle"))! + "/" + fileName + "." + fileType
do {
return try String(contentsOfFile: filePath)
}
catch let error as NSError {
return error.description
}
}

Where do I get the OSX volume for replacing "/" [duplicate]

When using
let directoryEnumerator = FileManager().enumerator(at: ...
in Swift 3, I get all files from the folder, e.g.
"file:///Volumes/MacOS/fasttemp/Fotos/"
The results are not including the leading path (here "/Volumes/MacOS"). So I get
"file:///fasttemp/Fotos/2005/"
How can I get the fullpath (directly from the enumerator) or convert them. I want to use URL functions, not string function manipulating by assumptions.
If "MacOS" is the name of your current startup disk then "/Volumes/MacOS" is a symbolic link to "/", so both "/fasttemp/Fotos/2005/" and "/Volumes/MacOS/fasttemp/Fotos/" are absolute paths to the same file.
In order to get a unique file name representation you can query
a URL for its canonical path. Example:
let url = URL(fileURLWithPath: "/Volumes/MacOS/Applications/Utilities/")
if let cp = (try? url.resourceValues(forKeys: [.canonicalPathKey]))?.canonicalPath {
print(cp)
}
// Output: "/Applications/Utilities"
This requires macOS 10.12/iOS 10 or later. On older systems you can
use the realpath() system call:
if let rp = url.withUnsafeFileSystemRepresentation ({ realpath($0, nil) }) {
let fullUrl = URL(fileURLWithFileSystemRepresentation: rp, isDirectory: true, relativeTo: nil)
free(rp)
print(fullUrl.path)
}
// Output: "/Applications/Utilities"
Note that you want to use URL wherever possible, from the NSURL documentation:
URL objects are the preferred way to refer to local files. Most
objects that read data from or write data to a file have methods that
accept an NSURL object instead of a pathname as the file reference.
Here’s an example of how to get all the objects from a directory:
import Foundation
let manager = FileManager.default
// Get URL for the current user’s Documents directory
// Use URL instead of path, it’s more flexible and preferred
if let documents = manager.urls(for: .documentDirectory, in: .userDomainMask).first,
// Get an Enumerator for the paths of all the objects in the directory
// but do not descend into directories or packages
let directoryEnumerator = manager.enumerator(at: documents, includingPropertiesForKeys: [URLResourceKey.pathKey], options: [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) {
// iterate through the objects (files, directories, etc.) in the directory
for path in directoryEnumerator {
print(path)
}
}

Show folder's contents in finder using Swift

I want to be able to select a folder and show its contents in the Finder. I have managed to select the folder itself and select a file within the folder. But I don't know how to show the contents of an empty folder.
e.g.
Folder A/Folder B
I want to display the contents of folder Folder B (which could be empty).
I have written the following code:
func showFolder(fileName : String)
{
var dataPath = homeDirectory.stringByAppendingPathComponent(fileName)
var urlPath = NSURL(fileURLWithPath: dataPath)
var selectedURLs = [urlPath!]
NSWorkspace.sharedWorkspace().activateFileViewerSelectingURLs(selectedURLs)
}
This only opens Folder A with Folder B highlighted. This is very close, but not quite right.
I need to be able to open Folder B with nothing highlighted. I'm obviously using the wrong command.
Use the selectFile method and pass nil as first argument and the path to the folder to be shown as second argument.
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: "/Users/")
2021 | SWIFT 5.1:
func showInFinder(url: URL?) {
guard let url = url else { return }
if url.isDirectory {
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: url.path)
} else {
NSWorkspace.shared.activateFileViewerSelecting([url])
}
}
extension URL {
var isDirectory: Bool {
return (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
}
}
showInFinder:
Folder's url = will show content of the folder.
File's url = will open in Finder file's parent and select file there.
Url is nil = Will do nothing
File/path does not exist = Will do nothing
Swift 2.1 code to Launch OS X Finder
Use the selectFile or activateFileViewerSelectingURLs to select files.
Select 1 item in finder with path YOUR_PATH_STRING
NSWorkspace.sharedWorkspace().selectFile(YOUR_PATH_STRING, inFileViewerRootedAtPath: "")
The second param use empty string, if you specify an empty string "" for this parameter, the file is selected in the main viewer.
If you want to select 1 or more files use activateFileViewerSelectingURLs(_ fileURLs: [NSURL])
To select one file
NSWorkspace.sharedWorkspace().activateFileViewerSelectingURLs([NSURL].init(arrayLiteral: NSURL.init(fileURLWithPath: YOUR_PATH_STRING)))
To select multiple files
let urls : [NSURL] = [NSURL.init(fileURLWithPath: "/Users/USER_NAME/Pictures"),
NSURL.init(fileURLWithPath: "/Users/USER_NAME/Music")]
If you provide item that are not in the same folder more windows selecting the specified files are open.
let urls : [NSURL] = [NSURL.init(fileURLWithPath: "/Users/USER_NAME/Pictures"),
NSURL.init(fileURLWithPath: "/Users/USER_NAME/Music"),
NSURL.init(fileURLWithPath: "/Users")]