IOS swiftUI can't read json from local files [closed] - ios

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm new with IOS development, I'm using swiftUI and following this tutorial https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation but I have been stuck here for a while, In the tutorial, they pass one item from a json file in the project
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
LandmarkRow(landmark: landmarkData[0])
}
}
I'm trying to do the same with my own data, I have a file called sorteosData.json but when I try to pass the first item to the preview it says "use of unresolved identifier"
struct PastSorteoRowView_Previews: PreviewProvider {
static var previews: some View {
PastSorteoRowView(sorteo: sorteosData[0])
}
Xcode doesn't recognize sorteosData[0], how can I solve this?
I have a followed all the steps from the tutorial, but for some reason, I can't pass my data to the preview

With JSON you need use JSONDecoder(). Assume that we have this file:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange",
"points": 100
}
]
For convenience you can create a struct (convenient even for nested elements):
struct product: Codable, Hashable {
var name: String
var points: Int
var description: String?
}
Create a function to parse your JSON in bundle called list.json that return an array of struct product:
func jsonTwo() -> [product]{
let url = Bundle.main.url(forResource: "list", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
let products = try? decoder.decode([product].self, from: data)
return products!
}
Finally set your interface:
var body: some View {
List{
ForEach(jsonTwo(), id: \.self) { item in
VStack(alignment: .leading, spacing: 0){
Text("name:\(item.name) - points:\(item.points)")
Text("\(item.description ?? "")")
}
}
}
}
Complete code:
struct product: Codable, Hashable {
var name: String
var points: Int
var description: String?
}
struct ContentView: View {
func jsonTwo() -> [product]{
let url = Bundle.main.url(forResource: "list", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
let products = try? decoder.decode([product].self, from: data)
return products!
}
var body: some View {
List{
ForEach(jsonTwo(), id: \.self) { item in
VStack(alignment: .leading, spacing: 0){
Text("name:\(item.name) - points:\(item.points)")
Text("\(item.description ?? "")")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Core data for favourite buttons

I want to make it so you can favourite a "landmark" in one view (LandmarkDetail), and access a list of all the "landmarks" in another view with the ones I've favourited highlighted. First I used "#AppStorrage" but I was told too to use Core Data for it instead. So far I have the favourite button working in the LandmarkDetail view with "#AppStorage" but apparently I need to change that so it uses Core Data.
I've look around to get an understanding of how to do it with Core Data but I could really use a helping hand if anyone can help. I've already seen and read some tutorials about core data and how to set it up, but I can't find anything for my specific problem where I pull in data from a JSON and I need Core Data to handle the favourite feature.
Here is my code for the favourite button
struct FavoriteButton: View {
#AppStorage ("isFavorite") var isFavorite: Bool = false
var body: some View {
Button {
isFavorite.toggle()
} label: {
Label("Toggle Favorite", systemImage: isFavorite ? "star.fill" : "star")
.labelStyle(.iconOnly)
.foregroundColor(isFavorite ? .yellow : .gray)
}
}
}
Code from the landmark detail view
struct LandmarkDetail: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
HStack {
Text(landmark.name)
.font(.title)
FavoriteButton()
}
}
}
}
}
Code for the rows in the list view
This is the one not working yet, so far it just pulls the data from a JSON.
MODEL
import Foundation
import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable, Identifiable {
var id: Int
var name: String
var park: String
var state: String
var description: String
var isFavorite: Bool
var isFeatured: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
private var imageName: String
var image: Image{
Image(imageName)
}
var featureImage: Image? {
isFeatured ? Image(imageName + "_feature") : nil
}
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
import Foundation
import Combine
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
#Published var profile = Profile.default
var features: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.category.rawValue }
)
}
}
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)")
}
}
First of all you need to create a .xcdatamodeld file named Landmarks. You can create it by pressing right button on the principal folder of your project and searching Data Model. After you need to create a new Entity named Landmark. You can add attributes showed in your model like id, name, park, etc... with their types. After you need to create a new Swift file in which you can create you Core Data Controller like this:
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "Landmarks")
init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
Successively, you need to add to your LandmarksApp.swift file the following code:
struct LandmarksApp: App {
#StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}
Continue adding this to your LandmarkDetail view:
struct LandmarkDetail: View {
#Environment(\.managedObjectContext) var moc
var body: some View {
ScrollView {
VStack(alignment: .leading) {
HStack {
Text(landmark.name)
.font(.title)
FavoriteButton()
}
}
}
}
}
To create a new item and add to Core Data you can write:
let landmark = Landmark(context: moc)
landmark.id = id
landmark.name = name
landmark.park = park
etc...
try? moc.save()
For your JSON data you can create a function that convert all JSON data in Core Data following these steps.

How to set up SwiftUI app to pass inputted value as parameter into URL string

I am attempting to build a basic SwiftUI weather app. The app allows the user to search weather by city name, using the OpenWeatherMap API. I configured the inputted city name from the text field to be injected into name: "" in WeatherModel, inside the fetchWeather() function in the viewModel. I then configured the OpenWeatherMap URL string to take in searchedCity.name as a parameter (see viewModel below). This setup seems to work fine, as I am able to search for weather by city name. However, I want to seek feedback as to whether or not the practice of passing searchCity.name directly into the URL (in the viewModel) is correct. In regards to:
let searchedCity = WeatherModel(...
... I am not sure what to do with the CurrentWeather and WeatherInfo inside that instance of WeatherModel. Since I'm only using "searchedCity" to pass the name of the city into the URL, how should "CurrentWeather.init(temp: 123.00)" and "weather: [WeatherInfo.init(description: "")]" be set? Is it correct to implement values for temp and description, such as 123 and ""?
Here is my full code below:
ContentView
import SwiftUI
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
// #State private var textField = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $viewModel.enterCityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather()
viewModel.enterCityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
Text(viewModel.title)
.font(.system(size: 32))
Text(viewModel.temp)
.font(.system(size: 44))
Text(viewModel.descriptionText)
.font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Model
import Foundation
// Data, Model should mirror the JSON layout
//Codable is the property needed to convert JSON into a struct
struct WeatherModel: Codable {
let name: String
let main: CurrentWeather
let weather: [WeatherInfo]
}
struct CurrentWeather: Codable {
let temp: Float
}
struct WeatherInfo: Codable {
let description: String
}
ViewModel
import Foundation
class WeatherViewModel: ObservableObject {
//everytime these properties are updated, any view holding onto an instance of this viewModel will go ahead and updated the respective UI
#Published var title: String = "-"
#Published var temp: String = "-"
#Published var descriptionText: String = "-"
#Published var enterCityName: String = ""
init() {
fetchWeather()
}
func fetchWeather() {
let searchedCity = WeatherModel(name: enterCityName, main: CurrentWeather.init(temp: 123.00), weather: [WeatherInfo.init(description: "")])
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(searchedCity.name)&units=imperial&appid=<myAPIKey>") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// get data
guard let data = data, error == nil else {
return
}
//convert data to model
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.title = model.name
self.temp = "\(model.main.temp)"
self.descriptionText = model.weather.first?.description ?? "No Description"
}
}
catch {
print(error)
}
}
task.resume()
}
}
There are many ways to do what you ask, the following code is just one approach. Since you only need the city name to get the result, just use only that in the url string. Also using your WeatherModel in the WeatherViewModel avoids duplicating the data into various intermediate variables.
PS: do not post your secret appid key in your url.
import Foundation
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = "" // <-- use this to get the city name
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $cityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather(for: cityName) // <-- let the model fetch the results
cityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
// --- display the results ---
Text(viewModel.cityWeather.name).font(.system(size: 32))
Text("\(viewModel.cityWeather.main.temp)").font(.system(size: 44))
Text(viewModel.cityWeather.firstWeatherInfo()).font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.navigationViewStyle(.stack)
}
}
class WeatherViewModel: ObservableObject {
// use your WeatherModel that you get from the fetch results
#Published var cityWeather: WeatherModel = WeatherModel()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=YOURKEY") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityWeather = model
}
}
catch {
print(error) // <-- need to deal with errors here
}
}
task.resume()
}
}
struct WeatherModel: Codable {
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Float = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
I want to seek feedback as to whether or not the practice of passing searchCity.name directly into the URL (in the viewModel) is correct.e
You should alway avoid to pass fake values to an object/class like Int(123).
Instead you should use nullable structures or classes.
I don't see the need of create a whole WeatherModel instance just to read one property from it, one property that you already have in a viewmodel's enterCityName property. Just use the viewmodel's enterCityName property instead.

SwiftUI NavigationLink not sending data to another view [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
This is my contentView.swift file. I am trying to send data from one view to another view using navigationLink but I can't. I am learning the swiftUI and iOS development. So I am confused what I did wrong.
import SwiftUI
import URLImage
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var trackId: Int
var trackName: String
var collectionName: String
var artworkUrl100: URL
}
struct ContentView: View {
#State var results = [Result]()
var body: some View {
NavigationView {
List(results, id: \.trackId) { item in
NavigationLink(
destination: SingleView(item: item),
label: {
HStack() {
URLImage(item.artworkUrl100) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, height: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.cornerRadius(5.0)
}
VStack(alignment: .leading) {
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
}
}
})
}
.onAppear(perform: loadData)
.navigationTitle("Native App")
}
}
func loadData() {
guard let url = URL(string: "https://itunes.apple.com/search?term=arijit&entity=song") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse.results
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I want to send value to SingleView.swift page . but no luck.
My SingleView.swift code is:
import SwiftUI
struct SingleView: View {
let item: Result
var body: some View {
Text("")
}
}
struct SingleView_Previews: PreviewProvider {
static var previews: some View {
SingleView(item: Result)
}
}
Please let me know where I did wrong. Thank you.
Working fine with some changes to SingleView
import SwiftUI
struct SingleView: View {
let item: Result
var body: some View {
Text(item.trackName)
}
}
struct SingleView_Previews: PreviewProvider {
static var previews: some View {
SingleView(item: Result(trackId: 1, trackName: "Track Name", collectionName: "Coll name", artworkUrl100: URL(string: "https://stackoverflow.com/questions/68556148/swiftui-navigationlink-not-sending-data-to-another-view")!))
}
}

API not displaying data in Swift for iOS

Currently not getting data from API to display.
Current code:
import SwiftUI
struct Response: Decodable {
var content: [Result]
}
struct Result : Decodable {
var code: String
var fire: String
var name: String
var police: String
var medical: String
}
struct ContentView: View {
#State private var content = [Result]()
var body: some View {
List(content, id: \.code) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.medical)
}
}
.onAppear(perform: loadData)
}
func loadData() {
let url = URL(string: "https://emergency-phone-numbers.herokuapp.com/country/gb")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { print(error); return }
do {
let decodedResponse = try JSONDecoder().decode(Response.self, from: data!)
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.content = decodedResponse.content
}
} catch {
print(error)
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Current error:
2021-01-21 19:21:07.094582+0000 iTunes API[39924:1098972] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
keyNotFound(CodingKeys(stringValue: "content", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "content", intValue: nil) ("content").", underlyingError: nil))
Please advise. Thanks in advance.
Please note that the content from https://emergency-phone-numbers.herokuapp.com/country/gb is:
{"code":"GB","fire":"999","police":"999","name":"United Kingdom","medical":"999"}
The problem is that there's no content on the response that you're getting, so the JSONDecoder is failing to parse the response. It seems the response you get contains content that fits on Result, so if you change your code to parse that, it works fine:
import SwiftUI
struct Response: Decodable {
var content: [Result]
}
struct Result : Decodable {
var code: String
var fire: String
var name: String
var police: String
var medical: String
}
struct ContentView: View {
#State private var content = [Result]()
var body: some View {
List(content, id: \.code) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.medical)
}
}
.onAppear(perform: loadData)
}
func loadData() {
let url = URL(string: "https://emergency-phone-numbers.herokuapp.com/country/gb")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { print(error); return }
do {
let decodedResponse = try JSONDecoder().decode(Result.self, from: data!)
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.content = [decodedResponse]
}
} catch {
print(error)
}
}.resume()
}
}
I guess, in the end, you probably wanted a collection of Results to fit on the content, so you might need to change something on that API of yours to return that collection, then your code would work fine.

How to generate fixed number of views from json API in SwiftUI?

So I am learning how to use SwiftUI with a json api. I am currently generating views through a list and a ForEach Loop. I am wondering how I can make it so that it only generates the first, say 10 elements in the posts array, instead of generating the entire list from the api. Basically I want to use RandomElement() to display 10 random posts from the entire array. I am just beginning here and learning so any help woul dbe appreicated.
Below is the code for my main view that is displaying the list
import SwiftUI
struct postList: View {
//state variable of the posts
#State var posts: [Post] = []
var array = [Post]()
var body: some View {
List {
ForEach(posts) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.font(.callout)
}
.padding()
}
}
.onAppear() {
Api().getPosts { (posts) in
self.posts = posts
}
}
}
}
struct postList_Previews: PreviewProvider {
static var previews: some View {
postList()
}
}
Down here is the Data file I use to retrieve the json data
import SwiftUI
struct Post: Codable, Identifiable {
let id = UUID()
var title: String
var body: String
}
class Api {
func getPosts(completion: #escaping ([Post]) -> ()) {
guard let url = URL(string: "http://jsonplaceholder.typicode.com/posts") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
completion(posts)
}
}
.resume()
}
}

Resources