Reputation: 9863
I'm trying both Swift (5.3) and Xcode (12) for the first time. The task is to parse a *.json file, that already exists nearby.
Honestly, adding the file in Xcode project is more tricky than actually parsing it: wherever I put it, it is always not found. This answer didn't help, because there's no Target Membership section in File Inspector (I guess, it was renamed or moved somewhere else in Xcode 12; maybe I'm wrong; anyway, couldn't find it as well).
I've also noticed that there's a difference in behavior of Target Membership section, when creating a new Project versus new Swift Package. If it is a Project, then Target Membership shows up. Since I've created a package, this section is not available for me.
Could someone help me understand what's going on here?
The structure of the project (called "TryParseJSON", it is organised as a Swift Package) is as follows:
TryParseJSON/
Package.swift
Sources/
TryParseJSON/
main.swift # defines `findPath(to:)` and calls `findPath(to: "persons.json")`
Person.swift
persons.json # this file is not found
The contents of Package.swift
file are as follows:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "TryParseJSON",
products: [
.executable(name: "TryParseJSON", targets: ["TryParseJSON"]),
],
targets: [
.target(
name: "TryParseJSON",
dependencies: [],
resources: [
.copy("persons.json"),
]
),
]
)
The path in .copy("persons.json")
is relative to main.swift; if I put a path relative to Package.swift (.copy("Sources/TryParseJSON/persons.json")
), Xcode doesn't find the JSON (the "found 1 file(s) which are unhandled"
warning is shown), and the package still doesn't work; with .copy("persons.json")
there's no warning.
The persons.json file was added with File > New > File > "Empty" (category "Other") > rename to "persons.json".
The findPath
function:
func findPath(to file: String) -> URL {
guard
let path = Bundle.main.url(forResource: file, withExtension: nil)
else {
fatalError("File \(file) was not found") // this is called
}
return path
}
This function is both defined and called inside main.swift:
let path = findPath(to: "persons.json")
I don't know how this works, I don't know what exactly I'm supposed to do to "add a new member in the target", I don't know what targets and products are, and I'm frustrated that adding files in Xcode requires that much of an effort.
Upvotes: 1
Views: 1809
Reputation: 86651
With Swift 5.3, packages have the ability to add and process non code resources. However, the Package.swift
has to explicitly define them. Do this by adding the resources to the target:
targets: [
.target(
name: "TryParseJSON",
resources: [
.copy("persons.json")
]
)
]
That will copy it into the Bundle, hopefully in the right place. If not, there may be extra config required.
Note that the path is relative to the directory containing the sources for the target. Apple recommends creating a subdirectory inside your source directory called Resources
. If you do that you need
.copy("Resources/persons.json")
Also, make sure Package.swift
defines Swift tools 5.3 at the top like this:
// swift-tools-version:5.3
It also matters how you try to extract the URL of the file when you need to access it. According to the Apple docs, you must always use Bundle.module.url(forResource:,withExtension:)
to get the URL of the resource. So, in the above case use
guard let resourceUrl = Bundle.module.url(forResource: "persons",withExtension: "json")
else { /* fail */ }
Upvotes: 1