I am starting to use PDFKit, with a pdf file located in the root, it works with the following code:
if let path = Bundle.main.path(forResource: "mypdf1", ofType: "pdf") {
let url = URL(fileURLWithPath: path)
if let pdfDocument = PDFDocument(url: url) {
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.displayDirection = .vertical
pdfView.document = pdfDocument
print(path)
}
}
But if I change the pdf file inside a directory for example "mydirectory", it does not work, my code is the following:
if let path = Bundle.main.path(forResource: "mypdf1", ofType: "pdf", inDirectory: "mydirectory") {
let url = URL(fileURLWithPath: path)
if let pdfDocument = PDFDocument(url: url) {
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.displayDirection = .vertical
pdfView.document = pdfDocument
}
}
Any suggestions to fix the path problem.
UPDATE
According to the suggestion, try the following code, but I can not visualize the PDF either.
if let documentURL = Bundle.main.url(forResource: "mypdf1", withExtension: "pdf", subdirectory: "mydirectory") {
if let document = PDFDocument(url: documentURL) {
pdfView.autoScales = true
pdfView.backgroundColor = UIColor.lightGray
pdfView.document = document
}
}
Your code to read the pdf from a subdirectory is correct and as I read they get a nil in the path, that's for sure because your container folder has not been created correctly. You can see yellow and blue folder, your container folder should be blue.
To make blue folder you should do following steps:
Prepare folder structure with files in it.
Drag that folder into xcode i.e. project navigation pan.
Select " Create folder references for any added folders " option.
Finally click to add.
You will get that folder with blue color and your code can read the pdf.
There are two types of folders in Xcode: groups and folder references.
You can use groups to organize files in your project without affecting
their structure on the actual file system. This is great for code,
because you’re only going to be working with your code in Xcode. On
the other hand, groups aren’t very good for resource files.
On any reasonably complicated project, you’ll usually be dealing with
dozens – if not hundreds – of asset files, and those assets will need
to be modified and manipulated from outside of Xcode, either by you or
a designer. Putting all of your resource files in one flat folder is a
recipe for disaster. This is where folder references come in. They
allow you to organize your files into folders on your file system and
keep that same folder structure in Xcode.
Blue is used to represent a "Folder Reference".
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
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.
Attempting to create a cocoapod that utilizes an .scnassets folder. The folder is imported into Resources without any problems with the children objects, but I can't find a way to load the .dae files into an SCNScene. From my understanding there may be a problem with how XCode converts .dae into .scn assets.
.podspec includes:
s.resource_bundles = {
'ARUtilsScenes' => ['ARUtils/Assets/ARScenes.scnassets']
}
which properly loads the ARScenes.scnassets into Resources folder. I'm attempting to load a scene inside of my Pod (inside the pod and not a project that uses the pod, and have tried a variety of methods):
let arrowScene = SCNScene(named: "arrow.dae")
let arrowScene = SCNScene(named: "Resources/ARScenes.scnassets/arrow.dae")
let arrowScene = SCNScene(named: "arrow.dae", inDirectory: "Resources/ARScenes.scnassets", options: nil)
I've tried a variety of file/folder names, and am able to load images but I'm not able to get a .dae loaded as an SCNScene. Do I need to add .xib files to my resource_bundles, or is there some other way to ensure .scnassets folder properly compiles these .dae to .scn as well as make them available for loading into an SCNScene?
Suppose ZARKit is your framework distributed in CocoaPods.
.podspec includes:
s.resource_bundles = {
'ARUtilsScenes' => ['ARUtils/Assets/ARScenes.scnassets']
}
then you should do this
let bundle = Bundle(for: #Your Class#.self).
let resourcePath = bundle.path(forResource: <#T##String?#>, ofType: <#T##String?#>)
let resourceBundle = Bundle(path: resourcePath)
let url = resourceBundle.url(forResource: "ARScenes.scnassets/<#YourResourceName-arrow#>", withExtension: "<#scn#>")
let arrowScene = SCNScene(url: url)
edit: by Z. Bagley
Accepted answer, but this was the actual code I used:
// find bundle based on existing class in pod
let frameworkBundle = Bundle(for: ARUtilsClass.self) //ARUtilsClass is an empty class, Pod is only extensions for existing classes
// append the scenes bundle, scnassets, and scn/dae
let arrowURL = frameworkBundle.resourceURL?.appendingPathComponent("ARUtilsScenes.bundle/ARScenes.scnassets/newArrow.scn")
// create a node in parent
var arrow = SCNNode()
do {
// get arrow scene from bundle
let bundleArrowScene = try SCNScene(url: (arrowURL)!, options: nil)
// add arrow node from scene
arrow = bundleArrowScene.rootNode.childNode(withName: "arrow", recursively: true)!
} catch {
print("failed to find arrow resource")
}
When accessing the .dae asset from the .scnassets folder, there should be no need to include the "Resources/" in the path
For example: let arrowScene = SCNScene(named: "ARScenes.scnassets/arrow.dae")
Assuming that your .scnassets folder is called that.
There is a known bug introduced with updated build system by Apple which causes asynchronous compilation of asset catalogs making them unaccessible in the final build. For reference see this thread on Cocoapods GitHub. There are several workarounds for this listed, but none of them guarantee the results, so for now we seem to have to live with it
I ran into this bug that prevents my app from displaying PDF using UIDocumentInteractionController or QLPreviewController: https://forums.developer.apple.com/thread/91835
According to the suggestions, the solution is to copy files to documents or tmp folders and load files from there.
However, this does not work for me. Loading the files from .documentDirectory or NSTemporaryDirectory() produces the same error, but now not only on device, but also in simulator.
Edit:
The following code solved the problem for me:
func copyFiles(fileName: String) -> URL {
let filemgr = FileManager.default
filemgr.delegate = self
let tempDocsFolder = URL.init(fileURLWithPath: NSTemporaryDirectory()).path
// my fileName is in format "file.pdf"
let fileSplit = fileName.components(separatedBy: ".")
let filePath = Bundle.main.path(forResource: fileSplit[0], ofType: fileSplit[1])
let destPath = "\(tempDocsFolder)/\(fileName)"
do {
try? filemgr.copyItem(atPath: filePath!, toPath: destPath)
}
return URL.init(fileURLWithPath: destPath)
}
Then returned URL is then feeded to the UIDocumentInteractionController. The reason it didn't work for me before was because I tried to copy my files to /tmp/documents/, but the files must be copied to the root of the tmp folder: /tmp/ (I have no idea why).
Check for case-sensitivity of resources(File names).
Add any screen shots of the code.
I'm downloading the archive from ardaka.com/koa.zip. After downloading I need to extract it but I cannot. Which module that need to use? I tried to use Simple Unzipper and SSZipArchive, but it didn't helped. My second question is how to display the html content that I extracted? You can see the code for webView that I used but I cannot access the html file which is located(I think I can extract it in documents) in Documents folder. Maybe I can use other archive formats if there is useful extractor.
Web View Code:
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let localfilePath = NSBundle.mainBundle().URLForResource(documentsPath.index, withExtension: "html", subdirectory: "koa") //It's not working
let myRequest = NSURLRequest(URL: localfilePath!);
webView.loadRequest(myRequest);