My project contains the JSON file 'LocationsData.json'
An error is displayed 'Cannot find 'LocationsData' in scope'
(If needed - the following code is contained within my 'Data.swift' file, which is what the var 'locationsDataTypes' in the 'SearchRedirect.swift' is referencing)
import UIKit
import SwiftUI
import CoreLocation
let locationsDataTypes: [LocationsDataTypes] = load("LocationsData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
final class ImageStore {
typealias _ImageDictionary = [String: CGImage]
fileprivate var images: _ImageDictionary = [:]
fileprivate static var scale = 2
static var shared = ImageStore()
func image(name: String) -> Image {
let index = _guaranteeImage(name: name)
return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(name))
}
static func loadImage(name: String) -> CGImage {
guard
let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else {
fatalError("Couldn't load image \(name).jpg from main bundle.")
}
return image
}
fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
if let index = images.index(forKey: name) { return index }
images[name] = ImageStore.loadImage(name: name)
return images.index(forKey: name)!
}
}
As pawello2222 hinted, locationsData isn't declared anywhere.
I suspect that you have your List view builder function written incorrectly.
Instead of this...
List(LocationsData, id: \.id) { locationsDataTypes in
Try the following...
List(locationsDataTypes, id: \.id) { locationsData in
...
Image(locationsData.imageName)
.resizable
...
}
Where locationsDataTypes is the collection, in your case the array of LocationsDataTypes and locationsData is the single instance of each iteration through that collection.
Also, when writing a question in stack overflow, best practice is to include the relevant code within a code block in your question, not accessed via a hyperlink. There are many reasons for this, here are a couple...
it is easier for the SO community to copy and paste your code into their solution as a part of their response, saving us all a lot of time;
links break, leaving your question without context for anyone who might have the same problem in the future.
Related
Any pointers would be much appreciated. No build or run errors with the code below, but 239 "Fail" prints in the Console. Also, once I get it to work properly, this code should be in a separate function or extension and not part of the View, and for reusability. I am expecting to see a country's flag next to its name in a Picker listing. The 'country.name' displays and 'selectedCountry' can be selected for subsequent Core Data storage, but sadly no flag displays. The relevant code and a sample "Country" from the JSON file below:
In the Properties section:
let countryImage: [Country] = Bundle.main.decode("Countries.json")
#State private var selectedContact: [ContactEntity] = []
let flag = "flag"
In the View:
VStack {
Picker(selection: $selectedCountry,
label: Text("Host Country:")) {
ForEach(countryImage) { country in
HStack {
Text(country.name).tag(country.name)
// TODO - Find Flag
if case country.flag = Data(base64Encoded: flag), let uiImage = UIImage(data: country.flag) {
Image(uiImage: uiImage).tag(country.flag)
} else {
let _ = print("Fail")
}
//Image(flag)
}
}
}
.padding(.horizontal, 0)
.padding()
.pickerStyle(MenuPickerStyle())
}//: INNER PICKER VSTACK
The Bundle references an extension:
extension Bundle {
func decode<T: Codable>(_ file: String) -> T { // "_" obviates the need to call the name of the parameter
// 1. Locate the JSON file
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
// 2. Create a property for the data
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
// 3. Create a decoder
let decoder = JSONDecoder()
// 4. Create a property for the decoded data
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
// 5. Return the ready-to-use data
return loaded
}
}
Sample JSON item:
{
"id": 202,
"name": "Sudan",
"isoAlpha2": "SD",
"isoAlpha3": "SDN",
"isoNumeric": 736,
"currency": {
"code": "SDD",
"name": "Dinar",
"symbol": false
},
"flag": "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNjc2Q0MxMjE3ODgxMUUyQTcxNDlDNEFCRkNENzc2NiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozNjc2Q0MxMzE3ODgxMUUyQTcxNDlDNEFCRkNENzc2NiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkY1RTdDNTBCMTc4NzExRTJBNzE0OUM0QUJGQ0Q3NzY2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkY1RTdDNTBDMTc4NzExRTJBNzE0OUM0QUJGQ0Q3NzY2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+dsEThQAAAfVJREFUeNq8lT9oE1Ecxz/v3ZmkMae2xdbuiqRiqaugKDo5WFGQOiiimy5aR6dOOrhYHRx0qQoVaQOCbgpO/oHatTrVhCYNtIlJbWJJ7p6/C7aiVhfv5Qf3jrt33Of9vt/f7z118sTe+TvPE15XQzHbGaABZbAeihu7zc6sw71MB0cWXGY9n28uOJbhDgM910q9QXx8oEFnVXMsu6mVcSVuF64lZ1jUrYcrp2sMD9XwBbinpPFlzihb4LVxRQhlzdP9qxy6sMKbHT6DZYdYk9YCovd4JF2Vu7f+JpS3O4BVuPUiydXpBPMUKLLc+tweOAzhkpIVxJsMv1bm/rZTfrK7y2QrBWUi8t39qwGh9HWHiX1l9bF/znl8/rpJk1LReryxFiK7I0MPMy8nVf+lXfrhh2fqx8x/X/rf6xJdfR/60tAMOHdziMNjZyguL1rM+I+OF1caUKpXpd2MJY9/BxY/QSzJ3ZFxLh84a7G41toq3MJ0haMHj5tHFx+Y3giLa2NwIMDNMpWSje1V0WzJav/2zKjJfVlQgQksgUPo9oT4KYBMHvO+rKaYcKPeuX7+MCwYR2qtrwPmvsJUDgp1a4eE+4u0W2PwVlplMmv9PHbXpZU+5clnmF6iHeGKtB65mkgrWebrtCtc3i3lyeQ8jKGd8V2AAQDj9qv7QrTRKwAAAABJRU5ErkJggg=="
},
It's easier than expected. Decodable can decode a base64-encoded String directly as Data
In Country declare flag
let flag: Data?
and a computed property
var flagImage : UIImage? {
guard let flagData = flag else { return nil }
return UIImage(data: flagData)
}
And display the image
if let flagImage = country.flagImage {
Image(uiImage: flagImage).tag(country.flag)
} else {
let _ = print("Fail")
}
I need my JSON data to be translated to a list as an output for an app I am creating.
I have found this tutorial, which seems to be using some old elements like "BindableObject":
https://www.youtube.com/watch?v=ri1A032zfLo
As far as I've checked several times, I went word for word in the NetworkingManager file being described from 1:52 in the video (just changing the names and the URL). My code:
import Foundation
import SwiftUI
import Combine
/*BindableObject was renamed to ObservableObject */
class NetworkingManager: ObservableObject{
var didChange: PassthroughSubject <NetworkingManager, Never>
var gitHubList = Root(items: []){
didSet{
didChange.send(self)
}
}
init() {
guard let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in guard let data = data else { return }
let gitHubList = try! JSONDecoder().decode(GitHubAPIlist.self, from: data)
DispatchQueue.main.async {
self.gitHubList = gitHubList
}
}.resume()
}
}
I get 2 errors:
'self' captured by a closure before all members were initialized
on the line with URLSession and
Return from initializer without initializing all stored properties
on the line with the } after the .resume() command
Are there some obsolete syntaxes in the code or am I missing something?
try something like this approach, to get you data from github. Works very well for me:
class NetworkingManager: ObservableObject{
#Published var gitHubList: [Item] = []
init() {
loadData()
}
func loadData() {
guard let url = URL(string: "https://api.github.com/search/repositories?q=CoreData&per_page=20") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(Root.self, from: data)
DispatchQueue.main.async {
self.gitHubList = response.items
}
} catch {
print("error: \(error)")
}
}.resume()
}
}
struct Root: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Item: Identifiable, Codable {
let keysURL, statusesURL, issuesURL: String
let id: Int
let url: String
let pullsURL: String
// ... more properties
enum CodingKeys: String, CodingKey {
case keysURL = "keys_url"
case statusesURL = "statuses_url"
case issuesURL = "issues_url"
case id
case url
case pullsURL = "pulls_url"
}
}
struct ContentView: View {
#StateObject var netManager = NetworkingManager()
var body: some View {
List {
ForEach(netManager.gitHubList) { item in
Text(item.url)
}
}
}
}
You declared the subject but didn't initialize it
var didChange = PassthroughSubject<NetworkingManager, Never>()
However actually you don't need the subject, mark gitHubList as #Published (and delete the subject).
And if you are only interested in the items declare
#Published var gitHubList = [Repository]()
and assign
DispatchQueue.main.async {
self.gitHubList = gitHubList.items
}
The #Published property wrapper will update the view.
In the view declare NetworkingManager as #StateObject
#StateObject private var networkManager = NetworkingManager()
and in the rendering area refer to
List(networkManager.gitHubList...
Notes:
Forget the tutorial , the video is more than two years old which are ages in terms of Swift(UI)'s evolution speed.
Ignoring errors and force unwrapping the result of the JSON decoder is very bad practice. Handle the potential URLSession error and catch and handle the potential decoding error.
I'm working on a map app and I have a geojson file with a few hundred thousand items in it. I can load and show the data, can see the pins on the map, the custom annotations and the clustering all works. I'm not happy with the way I load the file though.
I have two versions:
private var thingsOnMap: [MyDataClass] = []
private func loadInitialData() {
guard let fileName = Bundle.main.url(forResource: "myGeojsonFile", withExtension: "geojson"),
let myData = try? Data(contentsOf: fileName)
else {
return
} do {
let features = try MKGeoJSONDecoder()
.decode(myData)
.compactMap { $0 as? MKGeoJSONFeature }
let allData = features.compactMap(MyDataClass.init)
thingsOnMap.append(contentsOf: allData)
} catch {
print("Unexpected error: \(error).")
}
}
then I just call this method. This works, I can see the pins within a few seconds, but it feels like a really wrong way to do it this way.
The second version looks like this:
private func loadInitialData(completion: #escaping (_ data: [MyDataClass]?, _ error: Error?) -> ()) {
var receivedError: Error?
DispatchQueue.global(qos: .background).async {
guard let fileName = Bundle.main.url(forResource: "myGeojsonFile", withExtension: "geojson"),
let myData = try? Data(contentsOf: fileName)
else {
return
} do {
let features = try MKGeoJSONDecoder()
.decode(myData)
.compactMap { $0 as? MKGeoJSONFeature }
let allData = features.compactMap(MyDataClass.init)
self.thingsOnMap.append(contentsOf: allData)
} catch {
receivedError = error
}
DispatchQueue.main.async {
completion(self.thingsOnMap, receivedError)
}
}
}
Then I call it this way:
loadInitialData { (data, error) in
if data != nil {
self.mapView.addAnnotations(self.thingsOnMap)
}
}
This works nicely and it feels safer, but it takes roughly 35-40 seconds to see the data on the map. Why is that huge difference between the loading times? I see map apps all the time with lots of items (like car charging stations across Europe - hundreds of thousands items) loading in basically instantly. How can I achieve that? Or at least, is there a way to make it at least a bit faster?
i want to load my data with a json file. So far so good. But now I am struggling. What if the user made some changes to the Oil Object and want to save them? My idea was, that i save the changed oils object to CoreData. But what is this possible? Because every time the user launches the app, the untouched json file gets loaded and the user will not see his changed objects. How can i handle that? Or is my thinking wrong?
struct Oil: Codable, Hashable, Identifiable {
var id: Int
let image: String
let color: String
let title: String
let subtitle: String
let description: String
var localizedTitle: LocalizedStringKey {
return LocalizedStringKey(title)
}
var localizedDescription: LocalizedStringKey {
return LocalizedStringKey(description)
}
var isFavorite: Bool
static let exampleOil = Oil(id: 10001, image: "",color: "lavenderColor" ,title: "lavender", subtitle: "Lavandula angustifolia", description: "", isFavorite: false)
}
final class Oils: ObservableObject {
var oils: [Oil] = load("oilDatabase.json")
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
Do you really need Core Data? If you are just storing a single json file you could save yourself a lot of complexity and just write the updated version of the file to the documents directory.
All you would have to do is to check that the file exists in the documents directory, if it does load it from there otherwise load it from your bundle.
This gets the URL for the documents directory
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
Then we just need to update your load to check that the file exists in the documents directory before loading it.
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
// Check that the fileExists in the documents directory
let filemanager = FileManager.default
let localPath = getDocumentsDirectory().appendingPathComponent(filename)
if filemanager.fileExists(atPath: localPath.path) {
do {
data = try Data(contentsOf: localPath)
} catch {
fatalError("Couldn't load \(filename) from documents directory:\n\(error)")
}
} else {
// If the file doesn't exist in the documents directory load it from the bundle
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
You will also need to save any changes that you make to the json. You can do that with the following function.
func save<T: Encodable>(_ filename: String, item: T) {
let encoder = JSONEncoder()
do {
let url = getDocumentsDirectory().appendingPathComponent(filename)
let encoded = try encoder.encode(item)
let jsonString = String(data: encoded, encoding: .utf8)
try jsonString?.write(to: url, atomically: true, encoding: .utf8)
} catch {
// handle your error
}
}
I'm a beginner at Swift so let me know if this doesn't quite make sense, but i have a JSON file that i can access in swift and parse into an array, from there i can get a string from the array and store it in a var. I want to be able to access this variable globally but i'm not sure how to do it.
With the help of another user "rmaddy". I have this code:
struct Games: Decodable {
let videoLink: String
}
class BroadService {
static let sharedInstance = BroadService()
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
let jsonUrlString = "LINK IS HERE."
guard let url = URL(string: jsonUrlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
}
}
I can then access it in another class from here:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
}
I want to be able to access the contents of "videoLink" globally, without having to use "BroadService.sharedInstance.fetchBroadcasts { (games) in" how would i go about doing this
You shouldn't use global variables, I don't think that's recommended in any language.
Now here you have what looks like a Singleton class (BroadService), that's good because it's a nice solution for what you're looking for.
Next all you need to do is add a property to that class. Let's say videoLink is a string, you can add a string property to BroadService, for example storedVideoLink as an optional String, and the next time you need to obtain that value after you have already fetched it, you can access it like so: BroadService.sharedInstance.storedVideoLink.
One more thing, to have BroadService work properly as a singleton, you should make its init private.
To sum up, here's what I'm suggesting:
class BroadService {
static let sharedInstance = BroadService()
var storedVideoLink: String?
private init() {} // to ensure only this class can init itself
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
// your code here
}
}
// somewhere else in your code:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
BroadService.sharedInstance.storedVideoLink = videoLink
}
}
// now you can access it from anywhere as
// BroadService.sharedInstance.storedVideoLink
This way it all stays cohesive in the same class. You can even add a getter method for storedVideoLink so you don't have to access it directly, and in this method you could state that if the string is nil then you fetch the data, store the link to the string, and then return the string.
You could create a file with a struct called something like Global and create a static var and set that inside your completion block once you have fetched the games.
Here is an example.
struct Global {
static var games:[Any]? = nil
static func setGames(games:[Any]) {
Global.games = games
}
}
Then you fetch the data once upon load of the app or somewhere before you use the Global and set that property:
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
Global.setGames(games: games)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
Please note that this will make the Global.games accessible from everywhere but it will also not be a constant so you should be careful not to override it.
This way Global.games will be accessible from anywhere.