Parse data from local JSON file outside application bundle - ios

I am trying to parse data from a JSON file which was created from a .sh script that is executed via CLI prior to building/running the project.
The following snippet does not work since my file was created from the .sh script and never added to my application bundle.
func fetchDatadogClientId() -> String? {
guard let url = Bundle.main.url(forResource: "client_token_key", withExtension: "json"),
let data = try? Data(contentsOf: url),
let datadogResponse = try? JSONDecoder().decode(DatadogResponse.self, from: data)
else {
return nil
}
return datadogResponse.clientToken
}
Would there be a way to read from this JSON file outside the application bundle or perhaps add the file to the project bundle using a script that would be run during the Build Phases?
Any suggestions are appreciated. Thanks in advance.

Related

How to run iOS unit tests with cocoapods + static library

We are having many development pods that are currently linked as dynamic frameworks.
I'm trying to migrate the dynamic framework to static libraries
For static-libs, I'm setting resource bundle like below (pod_spec)
s.resource_bundles = { 'BundleName' => [ 'BundleName/**/*.{xcassets,lproj,storyboard,xib,xcassets,imageset,png,mp3,mp4,wma}' ] }
and accessing in code like below
Bundle.main.url(forResource: "BundleName", withExtension: "bundle")
when I run the app, I'm getting the resource bundle path, from that I can able to get images/localized text
The same is not working when I run Unit tests. resource bundle path is nil and all my test related to localized text are failing
how can I fix this?
thanks in advance
The issue is that your tests are a separate bundle from your application, so when running Bundle.main.url(...) it will try to give a resource from your test bundle, not your app bundle.
I guess the easiest way of doing things is to add the resources to your test bundle. Depending on what you want to achieve. Some more background information might be helpful.
Answering my own question after struggled for a whole day
When the unit test runs your code, your unit test bundle is NOT the main bundle.
Even though you are running tests, not your application, your application bundle is still the main bundle. so, when you run unit tests with static libraries mode, you won't find resource_bundles if you search the main bundle.
you have to check in the list of Bundle and find the one with ".xctest" extension and append the module name
enter code here
class func bundle(for name: String) -> Bundle {
var url = Bundle.main.bundleURL
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
url = bundle.bundleURL.deletingLastPathComponent()
}
url = url.appendingPathComponent( name + "/" + name + ".bundle")
guard let bundle = Bundle(url: url) else {
return Bundle.main
}
return bundle
}

Xcode 10: Load the same .xml file in project target and unit test target

I try to load the same .xml file in project target and unit test target.
It seems to be a similar problem like this question: Xcode. Image resources added to a test target are not copied into the tests bundle
In project target, this code snipped worked fine. I just have to add the "xmlString.xml" file to "Copy Files" (see image 1).
func parseFile() {
let stringPath = Bundle.main.path(forResource: "xmlString", ofType: "xml")
let url = NSURL(fileURLWithPath: stringPath!)
...
}
If I run the code snipped in unit test target, I get an error because the .xml file can not be found. I tried to add the .xml file to the "Copy Bundle Resources" without any success (see image 2).
The only way to get it working is to use the absolute path
func parseFile() {
let stringPath: String? = "/Users/.../Documents/Git/.../.../.../.../xmlString.xml"
let url = NSURL(fileURLWithPath: stringPath!)
...
}
Is there a way to use the Bundle.main.path(forResource: "xmlString", ofType: "xml") function instead of the absolute path?
Thanks in advance!
You can get the bundle for your project like this:
let projectBundle = Bundle(for: AnyClassInMyProject.self)
where you replace AnyClassInMyProject with the name of an actual class in your project.
You can then get the path to your .xml file like this:
let stringPath = projectBundle.path(forResource: "xmlString", ofType: "xml")

WKWebView load files from both Bundle and Document directory

I am running a webapp using WKWebView which load content and assets (html, js, img, css...) from a Bundle directory called Web_Assets.
I proceed like this to load index.html, and set Web_assets as baseURL:
if let filePath = Bundle.main.path(forResource:"index", ofType:"html", inDirectory: "Web_Assets") {
let html = try String(contentsOfFile: filePath, encoding: .utf8)
webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL?.appendingPathComponent("Web_Assets"))
}
This works perfectly: index.html is loaded and Web_Assets serve as base path for other ressources without any problem.
I could also use the loadFileURL method which is equivalent (to my understanding).
BUT, now i'd like to use other ressources (video in my case) inside my WKWebView from the Document directory which is static and manageable via iTunes File Sharing, while of course keeping the present architecture as it is (Web assets inside Bundle directory).
For example my page index.html would load his style.css and other assets from Web_Assets and display an HTML5 video located in Document
Is it possible to do this ?
Ideas (which i don't know how to achieve):
Symlink of Document in Web_assets ?
Different baseURL for different file types in WKWebView ?
(route all requests to Web_Assets except for mp4 files requests which are routed to Document)
Thanks for your help !
EDIT 1:
Idea 1 is not possible since Bundle directories are read-only.
Document can't be symlinked in Web_assets.
I can't manage to symlink Web_assets into Document either (weird error).
So i decided for now to copy Web_assets inside Document on each boot, so my Document folder can act as baseURL with my videos and web assets altogether.
It's a bit dirty since all my web assets are now visible in my Document folder (but not modifiable since the folder is erased and copied from Bundle uppon App restart)
I finally end up as EDIT 1 and #ngbaanh suggested:
on viewDidLoad:
let filemgr = FileManager.default
docURL = filemgr.urls(for: .documentDirectory, in: .userDomainMask)[0]
destPath = docURL.path+"/www/"
sourcePath = Bundle.main.resourceURL!.appendingPathComponent("Web_Assets").path
//COPY Web_Assets content from Bundle to Documents/www
do {
try filemgr.removeItem(atPath: destPath)
} catch {
print("Error: \(error.localizedDescription)")
}
do {
try filemgr.copyItem(atPath: sourcePath, toPath: destPath)
} catch {
print("Error: \(error.localizedDescription)")
}
// Configure Webview
webView.navigationDelegate = self
webView.configuration.userContentController.add(self, name: "teleco")
do {
let baseURL = try filemgr.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = baseURL.appendingPathComponent("www/index.html")
self.loadFileURL(fileURL, allowingReadAccessTo: baseURL)
} catch {
print(error)
}
This way my baseURL is Documents/www/
So in my index.html i can include ressources like style.css
AND i can also access files in Documents/ by using relative path like ../video.mp4
So index.html and style.css are distributed via the Bundle, copied to Documents at runtime, which allows me to access Documents files too.
A bit twisted, but works great.
Downside: it exposes Web_assets files in user accessible Documents folder.
But they can't be altered since they are replaced on App start.

Scanning the contents of a directory which is stored in an Xcode project in swift

This seems to be a problem which is very difficult to explain. I am trying to read data from a folder full of files and the way I am trying to do this is by scanning the whole directory for all of the files. The file in question is stored in my Xcode project in the same directory as the view controllers and storyboards. Like so:
I would like to be able to scan the contents of the "data" folder. Consider the following code:
var tmpDir = NSTemporaryDirectory()
var error: NSError?
let filesInDirectory: [String]! = fileManager.contentsOfDirectoryAtPath(tmpDir, error: &error) as? [String]
This scans the contents of the app's local folder and returns it as an array of the file names. I would like to be able to do this with the "data" folder in my Xcode project. The following code will find the contents of a single file which is stored in the same directory as the "data" folder.
let files = NSBundle.mainBundle().pathForResource("sampleFile", ofType: "csv")
var contents = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: nil)!
Is there a way in which I can combine the capabilities of the two in order to scan the contents of the "data" folder in the Xcode project and then return the file names as an array?
Add the data folder to Copy Bundle Resources in Build Phases of your target.
Then you'll be able to read the path as it's within the same sandbox scope. (Assuming you are applying sandbox.) If not, just use absolute path/URL and you can access it directly without the copying process.
You might want to have a look here: Cocoa contents of directory

Read file in Xcode project with Swift

I made a method to read a file from the temporary folder. I use the following lines to get the document's path:
path = NSTemporaryDirectory().stringByAppendingPathComponent(folder)
path = path.stringByAppendingPathComponent(fileName + "." + fileType)
And it works great. My question is: how do I get the path for a file located in the main folders of the Xcode project? They are two text files that I drag and drop from a folder to the Xcode project. I selected the option "copy files if needed" when dropping the files. Thanks!
What you are looking for is app's main bundle. You get a path of a file in it like this:
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType:fileType) {
// use path
}
Keep in mind this folder is read-only so you will need to copy the files to temp or documents folders if you want to make changes.
Update the Swift 3.1 - Thanks #Filip Radelic
if let path = Bundle.main.path(forResource: fileName, ofType: fileType) {
// use path
print(path)
}
Swift 4
if let path = Bundle.main.path(forResource: "imagename.png", ofType: nil) {
print("[check] FILE AVAILABLE \(path)")
}
You would use NSBundle to get your main bundle, then pathForResource(_ name: String?, ofType extension: String?) -> String?.

Resources