FileManager can't locate PDF directory in Xcode project - ios

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

Related

iOS: How to add Assets folder inside a Framework and how to access them in code?

I create an iOS app and added a framework to it. The generated framework doesn't have an assets folder like the generate Single View App. So I made an Assets folder inside the framework folder and drag and drop it to xcode, choose the target as my framework.
I tried using the asset but the asset doesn't show up. Can show one show me how to correctly do this? is it possible to create an assets folder inside a framework?
I am very new to iOS so any guidance would be much appreciated. Thanks in advance.
Add Asset catalog to framework target as usual via New File... > Resources, but to get resource from such asset it needs to specify bundle explicitly, as in below example...
Assuming that ImageProvider is in framework target, the code could be
public class ImageProvider {
// convenient for specific image
public static func picture() -> UIImage {
return UIImage(named: "picture", in: Bundle(for: self), with: nil) ?? UIImage()
}
// for any image located in bundle where this class has built
public static func image(named: String) -> UIImage? {
return UIImage(named: named, in: Bundle(for: self), with: nil)
}
}
of course you can name such class anyhow.
Here's how I do it.
First of all, here's how you can create a Bundle to hold your assets like images.
First, create a new target:
Navigate to main Xcode menu, File => New => Target. Choose the "macOS tab" then
from "Framework & Library" select "Bundle".
Give it your desired name and hit Finish. You should see the bundle in your project folder.
Second, Configuration changes in build settings of Bundle:
Go to Build Settings on your bundle target and change the Base SDK to be iOS.
Third, Add images:
Add your images to the Bundle directly, no need to add an assets folder. Just drag and drop.
Fourth, build the bundle:
Choose your bundle as a destination and choose the generic iOS device and hit Command + B
Fifth, the .bundle will appear in your products folder under your project folder. Right-click on it and view it in Finder and then drag and drop it inside of your main project folder.
Finally, here's how I'd access the assets inside of your bundle.
// Empty UIImage array to store the images in it.
var images = [UIImage]()
let fileManager = FileManager.default
let bundleURL = Bundle.main.bundleURL
let assetURL = bundleURL.appendingPathComponent("MyBundle.bundle") // Bundle URL
do {
let contents = try fileManager.contentsOfDirectory(at: assetURL,
includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.isDirectoryKey],
options: .skipsHiddenFiles)
for item in contents { // item is the URL of everything in MyBundle imgs or otherwise.
let image = UIImage(contentsOfFile: item.path) // Initializing an image
images.append(image!) // Adding the image to the icons array
}
}
catch let error as NSError {
print(error)
}
You will have the .plist file inside of your bundle, therefore, I suggest you handle this by a simple condition to check if the file name is Info.plist don't create an image out of it.
Here's how I handled it in a very trivial way.
let fileManager = FileManager.default
let bundleURL = Bundle.main.bundleURL
let assetURL = bundleURL.appendingPathComponent("Glyphs.bundle")
do {
let contents = try fileManager.contentsOfDirectory(at: assetURL, includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.isDirectoryKey], options: .skipsHiddenFiles)
for item in contents {
let imageName = item.lastPathComponent
if imageName != "Info.plist" {
let image = UIImage(contentsOfFile: item.path)
icons.append(image!)
}
}
}
catch {
//print(error)
showAlert(withTitle: "Error", message: "Can't get the icons.")
}
I had the same problem.
scenario:
our framework will be consumed iOS/OSX
a lot of PNGs inside Framework
we want pngs in iOSApp && MacOs
let's assume out framework project and target is "MyCustomFramework.framework" (usual yellow icon..) already in our app
-
func filePathInFrameworkBundle(name: String)->String{
let myBundlePath = Bundle.main.bundlePath
#if os(iOS)
let inMyFW = myBundlePath +
"/Frameworks" +
"/MyCustomFramework.framework"
#elseif os(OSX)
let inMyFW = myBundlePath +
"/Contents/Frameworks" +
"/MyCustomFramework.framework" + "/Versions/A/Resources"
#else
#endif
let pathInBundle = inMyFW+"/"+name
return pathInBundle
}
Now You can use THIS path in usual image loading calls.
For SwiftUI use:
Add an Asset catalog to your framework project (eg, "Media.xcassets").
Add a resource, like a Color Set. Let's say you name your color "Thunder".
Create a file in your framework called "Color+Extensions.swift" (or whatever you like).
Inside this file do:
import SwiftUI
private class LocalColor {
// only to provide a Bundle reference
}
public extension Color {
static var thunder: Color {
Color("Thunder", bundle: Bundle(for: LocalColor.self))
}
}
I imagine this works for other asset types as well.
In the project which is using your framework, once you have added the import for your framework, you should be able to just use the color normally:
Rectangle().foregroundColor(.thunder)

Is it possible to list/access files stored after ios app update?

I store videos using AVAsset library. It's all working fine and I can watch videos offline.
But when the app gets updated, video stop playing.
The file was, let's say, downloaded to this location:
file:///private/var/mobile/Containers/Data/Application/5D5DE2DB-4518-453D-AD27-5E32E5665FAF/Library/.../2921436_AE26A440BC211429.movpkg
But if I do FileManage.fileExists after app update, it says file doesn't exist.
I tried listing using this code to list all files:
extension FileManager {
func urls(for directory: FileManager.SearchPathDirectory, skipsHiddenFiles: Bool = true ) -> [URL]? {
let documentsURL = urls(for: directory, in: .userDomainMask)[0]
let fileURLs = try? contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: skipsHiddenFiles ? .skipsHiddenFiles : [] )
return fileURLs
}
}
It lists all files but the uuid of the file path is different than that of older stored file and it doesn't list the older file.
The question is is it possible to access the old downloaded file?

How to I hide specific file in File App of Device

First of all I know this is a subjective question, not code based, How ever I need to find solution. Please provide any references
I am working on a task in which I saving the files in Device Document directory. Upto here all is working fine. However when I see these files from :
Files App -> App Folder and the files
I can see all those files.
Now I want to hide few of them, How can I achieve these....?
As per your requirement. You need to change some lines of code in saving file.
Firstly, The FileApp shows all the data that save in documentsDirectory of app whether you put code for hide or not. To hide few files you need to make separate path for them.
First, the file you want to show in documentDirectory:
let documentsDirectory = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last! as URL
The file you don’t want to show with user, you need to create path here:
let libraryDirectory = (FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)).last! as URL
Once you set the library path for saving all your important files. All will be hidden and cannot be accessed by user.
To know more about files, please refer to this link:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
Xcode 9 • Swift 4 or Xcode 8 • Swift 3
extension URL {
var isHidden: Bool {
get {
return (try? resourceValues(forKeys: [.isHiddenKey]))?.isHidden == true
}
set {
var resourceValues = URLResourceValues()
resourceValues.isHidden = newValue
do {
try setResourceValues(resourceValues)
} catch {
print("isHidden error:", error)
}
}
}
}
for more reference see chain here Cocoa Swift, get/set hidden flag on files and directories
or
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/ManagingFIlesandDirectories/ManagingFIlesandDirectories.html

Reading/Writing data that is accessible from iOS app extension

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.

How do I count a number of images in a folder reference in swift

I'm working on an custom emoji keyboard in Swift and I'm having trouble finding and counting the images in a folder reference called "emojis".
EDIT: To clarify my issue is that let contents always end up as nil.
The structure from the location of the .xcodeproj file looks like this:
EmojiBoard/emojis/emoji-0.png and so on.
I've been trying to use the NSFileManager with no luck.
let fileManager = NSFileManager.defaultManager()
let contents = fileManager.contentsOfDirectoryAtPath("emojis", error: &error)
println(contents)
This prints nil. I've also tried "EmojiBoard/emojis" and "/EmojiBoard/emojis".
I need this to determine how many images there are and loop them all out without having to resort to an insane switch statement or something like that.
Thank you!
P.S. Please note that I'm coding in Swift, not Objective C. I'm not proficient enough to convert C programming to swift I'm afraid. D.S.
if you created folder reference when adding the folder to your project use it like this (emojis folder icon is a blue folder):
let resourceURL = Bundle.main.resourceURL!.appendingPathComponent("emojis")
var resourcesContent: [URL] {
(try? FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil)) ?? []
}
let emojiCount = resourcesContent.count
print(emojiCount)
if you created groups when adding the folder to your project use it like this (emojis folder icon is a yellow folder):
let resourceURL = Bundle.main.resourceURL!
let resourcesContent = (try? FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil)) ?? []
let emojiCount = resourcesContent.filter { $0.lastPathComponent.hasPrefix("emoji-") }.count
print(emojiCount)
From the top of my head, without access to an IDE to test this code, I reckon something like this:
let fileManager = NSFileManager.defaultManager()
let contents = fileManager.contentsOfDirectoryAtPath(path, error: &error)
for var index = 0; index < contents.count; ++index {
println("File is \(contents[index])")
}
If you replace 'path' above with your documents directory, this code should loop through the whole folder and print out all files.
If you just want the count of items just do this:
println("count is \(contents.count)")
The problem (or at least a major part of the problem) is your path. You can't pass in a path that's just a filename. You need an absolute path to one of the sandboxed directories available to your app like the documents directory.
Your code might look like this:
let documentsDir = NSSearchPathForDirectoriesInDomains(
NSSearchPathDirectory.DocumentDirectory,
NSSearchPathDomainMask.UserDomainMask,
true)[0] as! NSString
let emojisPath = documentsDir.stringByAppendingPathCompnent("emojis")
let contents = fileManager.contentsOfDirectoryAtPath(emojisPath,
error: &error)
println(contents)
(That would work if your emojis folder is in your app's documents folder. If instead your emojis are in your app bundle (built into the app) you would need to use different code entirely (using NSBundle functions to get a path to the directory inside the bundle).
EDIT:
If you want to find files in your app's bundle use the NSBundle method resourcePath, and then append the folder name to the bundle's resourcePath using stringByAppendingPathCompnent, like the code above.

Resources