Adding JSON as asset and reading it - ios

I'm trying to load some JSON data from a local file.
In this Apple doc it says:
Manage the data files for your app using the asset catalog. A file can
contain any sort of data except device executable code generated by
Xcode. You can use them for JSON files, scripts, or custom data types
So I added a new data set and dropped the JSON file inside. Now I can see it under Assets.xcassets folder (Colours.dataset folder with colours.json and Contents.json inside it)
I found this SO answer that shows how to read a JSON file and I'm using this code to read the file:
if let filePath = NSBundle.mainBundle().pathForResource("Assets/Colours", ofType: "json"), data = NSData(contentsOfFile: filePath) {
print (filePath)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)
print(json)
}
catch {
}
} else {
print("Invalid file path")
}
But this code is printing "Invalid file path" and not reading the file. I also tried "Colours" and "Colours.json" but to no avail.
Could anyone please tell me how to properly add a local JSON file and read it?
Thanks.

You can't access data asset files in the same way you access a random file using NSBundle.pathForResource. Since they can only be defined within Assets.xcassets, you need to initialize a NSDataAsset instance in order to access the contents of it:
let asset = NSDataAsset(name: "Colors", bundle: NSBundle.mainBundle())
let json = try? NSJSONSerialization.JSONObjectWithData(asset!.data, options: NSJSONReadingOptions.AllowFragments)
print(json)
Please note that NSDataAsset class was introduced as of iOS 9.0 & macOS 10.11.
Swift3 version:
let asset = NSDataAsset(name: "Colors", bundle: Bundle.main)
let json = try? JSONSerialization.jsonObject(with: asset!.data, options: JSONSerialization.ReadingOptions.allowFragments)
print(json)
Also, NSDataAsset is surprisingly located in UIKit/AppKit so don't forget to import the relevant framework in your code:
#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif

objC
#ifdef use_json_in_bundle
NSString * path = [mb pathForResource:json_path ofType:#"json" inDirectory:#"JSON"];
NSString * string = [NSString stringWithContentsOfUTF8File:path];
NSData * data = [string dataUsingEncoding:NSUTF8StringEncoding];
#else
NSDataAsset * asset = [[NSDataAsset alloc] initWithName:path];
NSLog(#"asset.typeIdentifer = %#",asset.typeIdentifier);
NSData * data = [asset data];
#endif
NSError * booboo = nil;
id blob = [NSJSONSerialization JSONObjectWithData:data options:0 error:&booboo];
for either branch, 'path' is just the json file name.

Related

SWIFT 3 access JSON that is within a folder

I have a lot of JSON files that all of them are in a new folder called assets. How can I access some of the JSON files that are in a folder within the assets folder. Here is a screenshot of the file I want to work with. http://prntscr.com/eiv7p4
UPDATE:
here is the code with which I access the file "mc-summer-0.json"
if let path = Bundle.main.path(forResource: "mc-summer-0", ofType: "json") {
do {
let jsonData = try NSData(contentsOfFile: path, options: NSData.ReadingOptions.mappedIfSafe)
do {
let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if let times_to : [String] = jsonResult["times_to"] as? [String] {
for (value) in times_to {
print("\(value)")
}
}
} catch {}
} catch {}
}
but if I want to access a file that is in the "assets" folder I change the line into if let path = Bundle.main.path(forResource: "assets/mc-summer-1", ofType: "json") {
but unfortunately it doesnt work.
Those files are in groups in your project, not in separate folders. Very likely they will be in the root level of your app bundle, but we can't be sure based on what you've shown. I suggest building your app for the simulator and then opening the resulting bundle in the Finder and examining it.
alright, in case somebody get stuck just like me the real solution is to remove the assets "folder" and re add it and select to add them as reference instead as a group

Invalid filename/path when getting JSON

In my project Navigator I have this structure
-MyApp
--ViewController.swift
--AppDelegate.Swift
--Main.StoryBoard
--info.plist
--JSONFiles
---test.json
-MyAppUITests
As you can see, this is the very basic structure that happens when you create a new single view application.
I created a new group called JSONFiles and added in a JSON files called test.
When I try to get the file using:
if let path = Bundle.main.path(forResource: "JSONFiles/test", ofType: "json") {
do {
let data = try NSData(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.mappedIfSafe)
let jsonData : NSData = NSData(contentsOfFile: path)!
allEntries = (try! JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers)) as! NSArray
print(allEntries)
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("Invalid filename/path.")
}
I get the error:
Invalid filename/path.
If I move the JSON file our of the group folder and change the forResource to just "test" it works fine and prints in the console.
Can anyway tell me how to make it read from the folder? I could have all my JSON files in the root but I am wanting to tidy it up slightly.
Thanks
You must include your json file into "Copy Bundle Ressources". Go to your project target -> Build phases -> Copy Bundle ressources and there, add your json file.
Then, you should be able to retrieve the path using the function: Bundle.main.path(forResource: "test", ofType: "json") .

Cannot find file in app extension's shared container

I want to write log file at my extension, and read it at my app.
For this purpose, I'm using shared groups (so both the app and the extension would be able to read from the same file)
I wrote the following code:
Extension:
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs")
let logFileManager = DDLogFileManagerDefault(logsDirectory: extensionLogDirectory)
PacketTunnelProvider.fileLogger = DDFileLogger(logFileManager: logFileManager)
PacketTunnelProvider.fileLogger!.rollingFrequency = 60*60*12
PacketTunnelProvider.fileLogger!.logFileManager.maximumNumberOfLogFiles = 1
DDLog.addLogger(PacketTunnelProvider.fileLogger)
App (just to read the log file):
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
if let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs") {
do {
let directoryContents = try fileManager.contentsOfDirectoryAtPath(extensionLogDirectory)//always fails
for file in directoryContents {
let path = extensionLogDirectory.stringByAppendingString(file)
do {
let fileContents = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
NSLog("file: \(fileContents)")
}
catch {/* error handling here */
}
}
}
catch {/* error handling here */
NSLog("nope!")
}
But, something now right - it's seems like contentsOfDirectoryAtPath always fails with "no such file" error
What's wrong in this code?
The problem is unrelated to app extensions or CocoaLumberjack.
stringByAppendingString just concatenates strings, so that the path
separator "/" is missing in the generated directory name.
There was a dedicated method stringByAppendingPathComponent, which however
has been deprecated in Objective-C and is no longer available in Swift.
You should operate on the URL by using URLByAppendingPathComponent
instead:
let extensionLogDirectory = containerUrl?.URLByAppendingPathComponent("AppExtensionLogs").path

Can't get data from file stored in asset catalog

I am storing several .svg files in an asset catalog.
To retrieve an asset I use the following code:
NSDataAsset *asset = [[NSDataAsset alloc] initWithName:#"p"];
When running this code I get the following log message:
CoreUI: attempting to lookup a named data 'p' with a type that is not a data type in the AssertCatalog
The name of the file is 'p.svg' which is stored in a folder with 'dataset' as extension. I tried using other extensions for the file but nothing works. I always get the same error and the asset is nil.
This may be related to a problem I'm having, in that the UTI I'm identifying my assets as being could run up the UTI dependancies and find public.image, thus making CoreUI think the asset is an image type and not a data type.
SVGs inherit from public.image, so you will need to set the File Type to something that doesn't inherit from public.image. Having it be just public.data will suffice.
Just reference the svg or whatever file in your project like .h/.m:
NSString *path = [[NSBundle mainBundle] pathForResource:#"p" ofType:#"svg"];
[NSData dataWithContentsOfFile:path];
If path is nil, try:
In the Xcode target "Build Phases" add the file under "Copy Bundle Resources"".
I also have this issue; I wanted to provide images etc from local assets store. Dragging the files into the catalog I would fetch them like so - here a String:
extension NSString {
class func string(fromAsset: String) -> String {
guard let asset = NSDataAsset.init(name: fromAsset) else {
return String(format: "Unable to locate asset:\n%#", fromAsset)
}
let data = NSData.init(data: (asset.data))
let text = String.init(data: data as Data, encoding: String.Encoding.utf8)
if fromAsset.hasSuffix(".md"), let html = try? Down(markdownString: text!).toHTML()
{
let htmlDoc = String(format: "<html><body>%#</body></html>", html)
let data = Data(htmlDoc.utf8)
if let attrs = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
return attrs.string
}
}
return text!
}
}
the premise is they have to exist. I think a per type / class extension would make retrieval simple. Eventually a generic object extension could dispatch to the proper extension, as here, you'd have to know what it was, so the decision which to use is currently a manual one.

Swift .writeToFile does not update JSON file in mainBundle

I am trying a simple dictionary retrieve, update key value and write back to file. For some reason the writeToFile does not update the file in the main bundle.
the code reads:
let filename = "testFile"
if let path = NSBundle.mainBundle().pathForResource(filename, ofType: "json") {
var error: NSError?
let InputData: NSData? = NSData(contentsOfFile: path, options: NSDataReadingOptions(), error: &error)
var jsonDictionary: NSMutableDictionary = NSJSONSerialization.JSONObjectWithData(InputData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSMutableDictionary
jsonDictionary.setValue(1, forKey: "levelRow")
let options = NSJSONWritingOptions.PrettyPrinted
var outputData : NSData? = NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: options, error: &error)
outputData?.writeToFile(path, atomically: true)
}
the file looks like this:
{
"levelColumn" : 0,
"levelRow" : 0,
}
the read and update work fine... but the file doe not update levelRow to 1?
thanks in advance.
You cannot write to the main bundle. All files in the bundle are read-only. Copy your file into the application documents directory before modifying it.
If you need a different file in the bundle to include in your application, you can update it in the documents directory during development and then manually copy it to the bundle before shipping your app.

Resources