I am trying to load a JSON file that is bundled with a Swift package I am working on.
The JSON file is called config.json and it is located in my project under /Sources/<Target>/data/config.json
UPDATE: I have also added the resource to the target in the Package Description as follows:
...
.target(
name: "MyPackage",
resources: [
.process("data/config.json")
]
),
...
I am trying to load it using the following code:
guard let sourcesURL = Bundle.main.url(forResource: "config", withExtension: "json") else {
fatalError("Could not find config.json")
}
... but I keep getting nil. Has anyone had an issue like this? Any idea what I'm doing wrong?
You should use module
Bundle.module.url(forResource: "config", withExtension: "json")
And you need to make sure the file is included when building by adding below to your target in Package.swift
resources: [
.copy("data/config.json") //This could alo be .process
])
The above assumes that the resource is loaded from within the same package, if you want to load a resource from another package you can use the following general solution.
First define a constant in the package that holds the bundle identifier for the package, for instance in an extension to Bundle
extension Bundle {
public static let myPackageNameBundleIdentifier = Bundle.module.bundleIdentifier!
}
And then create an instance of that Bundle when loading the resource in another package or the app
if let bundle = Bundle(identifier: Bundle.myPackageNameBundleIdentifier) {
let url = bundle.url(forResource: "config", withExtension: "json")
// ...
}
Related
I've been trying to access a file in my command line tool Xcode project but program isn't able to find the file path. I'm assuming it's because the file isn't being copied to my project.
I've gone to build phases and added my file (graph2.net) but the program can't find the file.
func readGraph() {
if let path = Bundle.main.path(forResource: "graph2", ofType: "net") {
print(path)
} else {
print("Failed")
}
}
always returns failed. However in an iOS app, the option to copy bundle resources exists and I am able to find the file. How can I solve this issue?
A command-line-tool only consists of an executable. No bundle is created and what you are trying to do is not possible.
You’ll have to place the file in some other place and load it from there.
You can create a bundle and place it in same directory as your executable file.
import Foundation
private class BundleLocator {}
/// Category for easily retrieving the accompanying bundle.
extension Bundle {
/// Local bundle, assuming it resides in the main bundle.
static var resources: Bundle {
let executableBundle = Bundle(for: BundleLocator.self)
guard let bundlePath = executableBundle.path(forResource: "Resources", ofType: "bundle") else {
fatalError(
"Could not find Resources bundle. Make sure you copy the bundle to the same folder as the executable file."
)
}
guard let bundle = Bundle(path: bundlePath) else {
fatalError("Failed to create bundle from path: \(bundlePath)")
}
return bundle
}
}
Looking for some help to understand what is going on here.
The Problem
We are using a translation service that requires creating JSON resource files of copy, and within these resource files, we need to add some specific keys that the service understands so it knows what should and should not be translated.
To do this as simple as possible I want to import JSON files into my code without them being tree shaken and minified. I just need the plain JSON file included in my bundle as a JSON object.
The Solution - or so I thought
The developers at the translation service have instructed me to create a webpack rule with a type of assets/source to prevent tree shaking and modification.
This almost works but the strange thing is that the JSON gets added to the bundle as a string like so
module.exports = "{\n \"sl_translate\": \"sl_all\",\n \"title\": \"Page Title\",\n \"subtitle\": \"Page Subtitle\"\n}\n";
This of course means that when I try and reference the JSON values in my JSX it fails.
Test Repo
https://github.com/lukehillonline/nextjs-json-demo
NextJs 12
Webpack 5
SSR
Steps To Reproduce
Download the test repo and install packages
Run yarn build and wait for it to complete
Open /.next/server/pages/index.js to see the SSR page
On line 62 you'll find the JSON object as a string
Open .next/static/chunks/pages/index-{HASH}.js to see the Client Side page
If you format the code you'll find the JSON object as a string on line 39
Help!
If anyone can help me understand what is going wrong or how I can improve the webpack rule to return a JSON object rather than a string that would be a massive help.
Cheers!
The Code
next.config.js
module.exports = {
trailingSlash: true,
productionBrowserSourceMaps: true,
webpack: function (config) {
config.module.rules.push({
test: /\.content.json$/,
type: "asset/source",
});
return config;
},
};
Title.content.json
{
"sl_translate": "sl_all",
"title": "Page Title",
"subtitle": "Page Subtitle"
}
Title.jsx
import content from "./Title.content.json";
export function Title() {
return <h1>{content.title}</h1>;
}
pages/index.js
import { Title } from "../components/Title/Title";
function Home({ dummytext }) {
return (
<div>
<Title />
<p>{dummytext}</p>
</div>
);
}
export const getServerSideProps = async () => {
const dummytext = "So we can activate SSR";
return {
props: {
dummytext,
},
};
};
export default Home;
I'm new to programming and I'm trying to write a function in Swift to download a JSON and parse it.
However, the JSON is very complicated and I have a daily limit on number of requests of data from the server.
Is there a way to download the data and save it to a file as 'data' so I can run all tests on this data in the file and not have to get it from the server everytime? Once I'm ready, I can start getting data from the server again.
Basically I could initialize a data variable with the contents of the file so I can use it on my tests.
Thank you
Determine where you want to store it and what it should be named. For example, for files that can be re-retrieved from the network, we’d use the cachesDirectory:
let folder = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("downloads")
Or, you can use the applicationSupportDirectory (if re-retrieving it is impractical) or .documentsDirectory (if you want to potentially expose the file to the end user). But the idea is the same.
Create that folder if you haven’t already:
try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
Create URL for the file within that folder:
let fileURL = folder.appendingPathComponent("sample.json")
Save that Data to that fileURL:
do {
try data.write(to: fileURL)
} catch {
print(error)
}
I have got a file downloaded from AWS S3 to my NSTemporaryDirectory using this code:
let downloadFilePath = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("FILENAME")
let downloadingFileURL = NSURL(fileURLWithPath:downloadFilePath)
let downloadRequest: AWSS3TransferManagerDownloadRequest = AWSS3TransferManagerDownloadRequest();
downloadRequest.bucket = "Bucketname";
downloadRequest.key = "FileName";
downloadRequest.downloadingFileURL = downloadingFileURL;
The file = .json file
the apps content is all saved on the JSON file, therefore i need to redirect the file from the NSTemporaryDirectory in order for the content to appear on the application.
Does anyone know the parse function in order to load the data from my JSON file into the application?
Thank you
You cannot modify the main bundle, so this isn't going to work.
There are a few directories under your control, for example the home directory, the application support directory, cache directories, or possibly the document directory.
As gnasher729 notes, you can't modify the main bundle itself, but there's no reason to here. You don't need to move the file in order to display it. You can read from your temporary directory. It's inside your application sandbox, and there's nothing special about it (it doesn't even get cleaned up for you either, so that's your responsibility if you need that).
The temp directory isn't backed up, so if you want that, you should move this to your documents directory. See NSFileManager moveItemAtPath:toPath:error: if you want to do that (or just download to your documents directory in the first place).
I figured it out.
So after downloading the File:
let downloadFilePath = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("FILENAME")
let downloadingFileURL = NSURL(fileURLWithPath:downloadFilePath)
let downloadRequest: AWSS3TransferManagerDownloadRequest = AWSS3TransferManagerDownloadRequest();
downloadRequest.bucket = "Bucketname";
downloadRequest.key = "FileName";
downloadRequest.downloadingFileURL = downloadingFileURL;
i have to submit the download request - as seen below:
// submit download request
let transferManager: AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager();
print("Downloading started, please wait...");
transferManager.download(downloadRequest).continueWithExecutor(AWSExecutor.defaultExecutor(), block: { (task) -> AnyObject? in
print("TASK:::::: \(task)");
if (task.error != nil){
print("Error Downloading");
}else{
self.readFile()
print("Download complete");
}
return nil;
}, cancellationToken: nil)
}
create a function that will parse your JSON file through the temp directory, using AlamoFire and SwiftyJSON:
func readFile() {
// JSON parsing step (from temporary directory)
let path = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("FILENAME")
do {
let readFile:NSString? = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
let json = JSON.parse(readFile as String!)
for (_, subJson) in json["FILECONTENT"] {
let version = subJson["FILECONTENT"].string
let newsletter = Edition(Version: version!)
self.editions.append(ARRAYNAME!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
//print(readFile)
} catch {
}
}
this will showcase the text on your application when ran.
I am trying to get the images to do the something now.
I have an app (the same one from my previous post about unwrapping nil. I really hate nil now.) that searches the iTunes store and returns data in JSON. I have it working, it gets the song name, artist name, everything! I created an #IBAction button for playing the song's preview. The JSON has a property that is the url to the song preview. When I click the button, it does the following:
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(url, ofType: "m4a")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
The url is this: http://a1993.phobos.apple.com/us/r1000/101/Music/b7/b3/e0/mzm.ooahqslp.aac.p.m4a. I know my setup for playing an audio file works; I have another app I am building that uses the exact same setup. Why does it tell me that I unwrap nil here: http://a1993.phobos.apple.com/us/r1000/101/Music/b7/b3/e0/mzm.ooahqslp.aac.p.m4a? The url is valid and the file plays.
Examine this line of code.
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(url, ofType: "m4a")!)
fileUrlWithPath is asking for a local path, that is one on your device.
NSBundle.mainBundle().pathForResource(url.....
This method returns the local path for the resource you send to it. You are sending it a web url, which is not in the mainBundle unless you've explicitly put it there. So the path that it returns is nil, because there is no local path that satisfies the arguments you are passing to it.
If you have a local resource you should use a method called URLForResource
This line makes no sense. You should always prefer working with urls and extract the path from it if needed.
Replace this line:
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("fileName", ofType: "m4a")!) // this would crash if not found (nil)
with this block of code
if let alertSoundUrl = NSBundle.mainBundle().URLForResource("fileName", withExtension: "m4a") {
println(true)
} else {
println(false)
}
If it is a web link you need to use NSURL(string:). fileUrlWithPath it is only for local resources.
if let checkedUrl = NSURL(string: "http://a1993.phobos.apple.com/us/r1000/101/Music/b7/b3/e0/mzm.ooahqslp.aac.p.m4") {
println(true)
} else {
println(false)
}