SwiftUI: Writing Values or Key:Value Pairs to JSON Decoded Dictionary - ios

I am struggling with modifying a value in a Dictionary that is made up of data from JSON download data from a php query to SQL. I need to either create a new pair key:value pair (best approach) or reuse a field from the original SQL data that I am not using and rewrite the value in the pair. In the code below I am trying the second approach (rewrite the value in a key:value pair I am not using). The issue is in the getData function (near the bottom) and is noted by the error comment. There is a LocationManager to get the users current location and some extensions that help the search box capability that are not shown, but let me know if needed.
import SwiftUI
import MapKit
struct Response: Codable, Hashable {
var District: String
var BusinessName: String
var LocationAddress: String
var LocationCity: String
func hash(into hasher: inout Hasher) {
hasher.combine(BusinessName)
}
}
struct ContentView: View {
#ObservedObject var lm = LocationManager()
#State var response = [Response]()
#State var search: String = ""
#State private var showCancelButton: Bool = false
#State private var searchPerfromed: Bool = false
#State var location2: CLLocationCoordinate2D?
#State var distance: Double = 0
#State var dist: [String: Double] = Dictionary()
var body: some View {
VStack {
HStack {
HStack {
Image(systemName: "magnifyingglass")
TextField("Search", text: $search, onEditingChanged: { isEditing in
self.showCancelButton = true
}, onCommit: {
self.searchPerfromed = true
getData() // Function to get JSON data executed after search execution
}).foregroundColor(.primary)
Button(action: {
self.search = ""
self.searchPerfromed = false
}) {
Image(systemName: "xmark.circle.fill").opacity(search == "" ? 0 : 1)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
if showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true)
self.search = ""
self.showCancelButton = false
self.searchPerfromed = false
}
}
}
.padding(EdgeInsets(top: 10, leading: 0, bottom: 1, trailing: 0))
.padding(.horizontal)
Spacer()
if searchPerfromed == true {
List {
ForEach(response.sorted {$0.LocationAddress > $1.LocationAddress}, id: \.self) { item in
VStack(alignment: .leading) {
HStack(alignment: .center) {
Text("\(item.BusinessName)").font(.subheadline)
Spacer()
if dist[item.LocationAddress] == 0.0 {
Text("Unknown").font(.caption).foregroundColor(.gray)
} else {
Text("\(dist[item.LocationAddress] ?? 0, specifier: "%.0f") mi")
.font(.caption).foregroundColor(.gray)
}
}
Text("\(item.LocationAddress), \(item.LocationCity)").font(.subheadline)
}
}
}
}
}
}
func getLocation(from address: String, completion: #escaping (_ location: CLLocationCoordinate2D?)-> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, error) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate else {
completion(nil)
return
}
completion(location)
}
}
func getDistance() {
let loc1 = CLLocation(latitude: lm.location1!.latitude, longitude: lm.location1!.longitude)
let loc2 = CLLocation(latitude: location2?.latitude ?? lm.location1!.latitude, longitude: location2?.longitude ?? lm.location1!.longitude)
let dist = loc1.distance(from: loc2)
distance = dist * 0.00062 // convert from meters to miles
}
func getData() {
let baseURL = "https://???"
let combinedURL = baseURL + search
let encodedURL = combinedURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let url = URL(string: encodedURL)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let responseData = data {
let decodedData = try JSONDecoder().decode([Response].self, from: responseData)
DispatchQueue.main.async {
self.response = decodedData
print(decodedData)
for item in self.response {
let address = item.LocationAddress
getLocation(from: address) { coordinates in
self.location2 = coordinates
getDistance()
dist[address] = distance
let d = String(format: "%.1f", distance)
print(item.District)
item[District] = d //Error: Cannot find 'District' in scope
}
}
}
} else {
print("No data retrieved")
}
} catch {
print("Error: \(error)")
}
}.resume()
}
}
Just above the issue I assigned the distance value to a new Dictionary and match the values based on address, but this has limitations with other changes I need to make to get to the end state with the app. I have tried lots of different ways to assign the distance to an existing key:value pair in Response or create a new key value pair in Response. Some examples below:
item.District = d //Error: Cannot assign to property: 'item' is a 'let' constant
item[Distance] = d //Error: Cannot find 'Distance' in scope
item["Distance"] = d //Error: Value of type 'Response' has no subscripts
How do I create a new key:value pair in Response, or assign the value of d to the District key? Thanks so much in advance for any help you can provide.

First thing, your instance variables on your Response struct are a little confusing because they don't follow the convention of having variables in all-lowercase, and types capitalized.
Second, for your first error in your list of 3, item is a let constant because it is inside a for loop. You can get around this by declaring the loop this way:
for var item in self.response {
// I can modify item in this loop because it is declared a var
}
The other two errors are pretty self-explanatory, I think.
Third, it sounds like you want to alter your Response object programmatically after receiving it, which is also a bit of an anti-pattern. If you want to modify an object you have downloaded from a server, that's understandable, but it is confusing for someone reading your code to alter an object called "Response." (once you modify it, it no longer represents the server response for which it is named) At a minimum, you could change District to be a computed property of Response.
All that said, if you instantiate your loop using the var keyword, you should be able to do:
item.District = d

Related

Can the SearchBar search the CSV file's DATA?

i'm here to ask help.
I just create a tiny dictionary for self use.
And now i want to add a SearchBar to search the keyword from the CSV file,
and it's not work and i cant find any reference from internet
Here is Preview
My CVS Model Code:
import Foundation
struct Model: Identifiable {
let id: Int
let Vocab: String
let Type1 : String
let Type2 : String
let Meaning: String
let Meaning2: String
init(raw:[String]){
self.id = Int(raw[0])!
self.Vocab = raw [1]
self.Type1 = raw[2]
self.Type2 = raw[3]
self.Meaning = raw[4]
self.Meaning2 = raw[5]
}
}
Handle CSV Code:
import Foundation
func cleanRows(file:String) -> String{
var cleanFile = file
cleanFile = cleanFile.replacingOccurrences(of: "\r", with: "\n")
cleanFile = cleanFile.replacingOccurrences(of: "\n\n", with: "\n")
return cleanFile
}
func loadCSVData() ->[Model]{
var csvToStruct = [Model]()
guard let filePath = Bundle.main.path(forResource: "Book2", ofType:"csv") else {
print("Error: file not found")
return []
}
var data = ""
do{
data = try String(contentsOfFile: filePath)
} catch{
print(error)
return []
}
data = cleanRows(file: data)
var rows = data.components(separatedBy: "\n")
rows.removeFirst()
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count == rows.first?.components(separatedBy: ",").count{
let linesStruct = Model.init(raw:csvColumns)
csvToStruct.append(linesStruct)
}
}
return csvToStruct
}
Content:
import SwiftUI
struct ContentView: View {
#State var Modelx:[Model]
#State private var searchText = ""
var body: some View{
NavigationView{
List{
ForEach (Modelx){ model in
NavigationLink(destination:{ModelDetailView(thisModel:model)}){
HStack{
Text("#"+String(format: "%03d", model.id))
.font(.subheadline)
Text(model.Vocab)
.font(.headline)
Spacer()
reusableTypeView(thisVocabType: model.Type1)
if model.Type2 != ""{
reusableTypeView(thisVocabType: model.Type2)
}
}
}
}
}.navigationTitle("Music Picionary")
.searchable(text: $searchText)
}
}
}
struct reusableTypeView: View {
var thisVocabType:String
var body:some View{
Text(thisVocabType)
.font(.system(size:15))
.padding(5)
.background(Color(thisVocabType))
.cornerRadius(9)
.foregroundColor(.white)
}
}
I think the main point is in the Handle CSV file right?
how can i add a SearchBar to search the keyword from the CSV file
thanks everyone help~
The problem is you never use the search test. You must say the list what data you want and how to apply the filter set by searchTest.
A example on how to use searchText :
struct ContentView: View {
#State var models:[Model]
#State private var searchText = ""
// use a model list filtered by the searchText
var filteredModels: [Model] {
if searchText == "" {
return models
}
return models.filter { model in
model.meaning.contains(searchText)
}
}
init() {
models = Model.loadCSVData()
}
var body: some View{
NavigationView{
List{
ForEach (filteredModels){ model in
NavigationLink(destination:{ModelDetailView(thisModel:model)}){
HStack{
Text("#"+String(format: "%03d", model.id))
.font(.subheadline)
Text(model.vocab)
.font(.headline)
Spacer()
reusableTypeView(thisVocabType: model.type1)
if model.type2 != ""{
reusableTypeView(thisVocabType: model.type2)
}
}
}
}
}.navigationTitle("Music Picionary")
.searchable(text: $searchText)
.textInputAutocapitalization(.none)
.autocorrectionDisabled()
// here just to suppress autocapitalisation
// and autocorrection (not needed)
}
}
}
Model with its associated functions :
struct Model: Identifiable {
let id: Int
let vocab: String
let type1 : String
let type2 : String
let meaning: String
let meaning2: String
init(raw:[String]){
self.id = Int(raw[0])!
self.vocab = raw [1]
self.type1 = raw[2]
self.type2 = raw[3]
self.meaning = raw[4]
self.meaning2 = raw[5]
}
static func cleanRows(file:String) -> String{
var cleanFile = file
cleanFile = cleanFile.replacingOccurrences(of: "\r", with: "\n")
cleanFile = cleanFile.replacingOccurrences(of: "\n\n", with: "\n")
return cleanFile
}
static func loadCSVData() ->[Model]{
var csvToStruct = [Model]()
guard let filePath = Bundle.main.path(forResource: "Book2", ofType:"csv") else {
print("Error: file not found")
return []
}
var data = ""
do{
data = try String(contentsOfFile: filePath)
} catch{
print(error)
return []
}
data = cleanRows(file: data)
var rows = data.components(separatedBy: "\n")
// compute column number from first row
let titlesRow = rows.removeFirst()
let columnCount = titlesRow.components(separatedBy: ",").count
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count == columnCount {
let linesStruct = Model.init(raw:csvColumns)
csvToStruct.append(linesStruct)
}
}
return csvToStruct
}
}
Note : I made some little changes in property naming using lowercase for first character. I also initialise the models array in ContentView.init.
I also put the functions inside the model definition as these are specific to your model.

Export Array using fileExporter

I am loading in a json file and creating an array. When a button is clicked, additional data is inserted into the array. What I want to do is export the modified array to a file. So essentially the array that has new data inserted into it.
What I'm not sure about is whether it is possible when exporting data from an array? or maybe I am going about this the wrong way?
EDIT: I don't necessarily want to export a json file, that was just the file type I first tried. I would be happy to export text files or csv's
ContentView
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
#State private var name = ""
#FocusState private var nameIsFocused: Bool
#State var labels: [LabelData] = []
#State var index = 0
#State var saveFile = false
var body: some View {
HStack {
Button(action: {
index += 1
if index <= labels.count {
labels[index - 1]._label = "Yellow" }
}) {
Text("Y")
}
Button(action: {
saveFile.toggle()
//print(labels[index - 1])
}) {
Text("Export")
.frame(width: 100, height: 100)
.foregroundColor(Color(red: 0.362, green: 0.564, blue: 1))
.background(Color(red: 0.849, green: 0.849, blue: 0.849))
.clipShape(RoundedRectangle(cornerRadius: 25.0, style: .continuous))
}
.offset(x: 0, y: 0)
.fileExporter(isPresented: $saveFile, document: Doc(url: Bundle.main.path(forResource: "labeldata", ofType: "json")!), contentType: .json) { (res) in
do {
let fileUrl = try res.get()
print(fileUrl)
}
catch {
print("cannot save doc")
print(error.localizedDescription)
}
}
}
VStack{
VStack {
if index < labels.count{
if let test = labels[index] {
Text(test._name)
}}}
.offset(x: 0, y: -250)
.frame(
minWidth: 0,
maxWidth: 325
)
VStack {
if index < labels.count{
if let test = labels[index] {
Text(test._name)
}}}
.offset(x: 0, y: -150)
.frame(
minWidth: 0,
maxWidth: 325
)
VStack {
if index < labels.count{
if let test = labels[index] {
Text(test._label)
}}}
.offset(x: 0, y: -50)
.frame(
minWidth: 0,
maxWidth: 325
)
}
.onAppear {
labels = load("labeldata.json")
}
}
}
struct Doc : FileDocument {
var url : String
static var readableContentTypes: [UTType]{[.json]}
init(url : String) {
self.url = url
}
init(configuration: ReadConfiguration) throws {
url = ""
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let file = try! FileWrapper(url: URL(fileURLWithPath: url), options: .immediate)
return file
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
LabelData
import Foundation
struct LabelData: Codable {
var _id: Int
var _name: String
var _type: String
var _description: String
var _label: String
}
labeldata.json
[
{
"_id" : 1,
"_name" : "Label1",
"_type" : "type1",
"_description" : "description1",
"_label" : ""
},
{
"_id" : 2,
"_name" : "Label2",
"_type" : "type2",
"_description" : "description2",
"_label" : ""
}
]
DataLoader
import Foundation
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)")
}
}
The fileExporter writes in-memory data to location selected by a user, so we need to create document with our content and generate FileWrapper from content to exported data (CSV in this example).
So main parts, at first, exporter:
.fileExporter(isPresented: $saveFile,
document: Doc(content: labels), // << document from content !!
contentType: .plainText) {
and at second, document:
struct Doc: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
private var content: [LabelData]
init(content: [LabelData]) {
self.content = content
}
// ...
// simple wrapper, w/o WriteConfiguration multi types or
// existing file selected handling (it is up to you)
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let text = content.reduce("") {
$0 + "\($1._id),\($1._name),\($1._type),\($1._description),\($1._label)\n"
}
return FileWrapper(regularFileWithContents:
text.data(using: .utf8) ?? Data()) // << here !!
}
Tested with Xcode 13.4 / iOS 15.5
Test module is here
It sounds like you want to create a new JSON file from the modified data within the array. It is a bit unusual to want to create a new JSON file. Maybe you want to persist the data? In that case you wouldn't save it as JSON you would persist it with a proper DB (DataBase) like CoreData, FireBase, Realm, or ect...
But if you really want to do this. Then you need to create a new JSON file from the data in the array. You have a load<T: Decodable> function but you are going to want a save<T: Codable> function. Most people would, once again, use this opportunity to save the data to a DB.
This guys does a pretty good job explaining this: Save json to CoreData as String and use the String to create array of objects
So here is a good example of saving JSON data to a file:
let jsonString = "{\"location\": \"the moon\"}"
if let documentDirectory = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("myJsonString.json")
do {
try jsonString.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
} catch {
// Handle error
}
}

How do i resolve the error Type 'CommunityEventsApp' does not conform to protocol 'App'

I've been stuck trying to debug this for a while so i thought i would ask for some advice as i've got to the point where i've really confused myself. I'm trying to get data from a local api that i've created and use mapkit to display the events i get from my api on the map.
In my apps entry point i have this error "Type CommunityEventsApp does not conform to protocol 'App', it appears on the same line as struct CommunityEventsApp: App. I've trying adding an initialiser above my body in this file as this is what the error suggested as the fix however this didn't resolve the error. I have no other errors in my app. This is the code:
struct CommunityEventsApp: App {
#StateObject var viewModel: ContentViewModel
var event: Event
var body: some Scene {
WindowGroup {
TabView {
//rest of the tab view code
}
.environmentObject(viewModel)
}
}
}
I'm trying to get data from a local api i've made here in my ContentViewModel:
var eventsList: [Event]
var primary: Event
init() {
self.eventsList = []
self.primary = eventsList[0]
}
func getEvents() async throws {
guard let url = URL(string: "http://localhost:5172/events") else {fatalError("Missing URL")}
let urlRequest = URLRequest(url:url)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data")}
eventsList = try JSONDecoder().decode([Event].self, from:data)
print("Async decodedEvent", eventsList)
}
}
This is my Event struct
struct Event: Decodable, Identifiable {
let id: String
let title: String
let date: String
let time: String
let location: String
let latitude: Double
let longitude: Double
let price: Double
let description: String
let link: String?
let imageUrl: String
init(id: String,
title: String,
date: String,
time: String,
location: String,
latitude: Double,
longitude: Double,
price: Double,
description: String,
link: String,
imageUrl: String) {
self.id = id
self.title = title
self.date = date
self.time = time
self.location = location
self.latitude = latitude
self.longitude = longitude
self.price = price
self.description = description
self.link = link
self.imageUrl = imageUrl
}
}
I call getEvents inside the onAppear in the ContentView:
let event: Event
let viewModel: ContentViewModel
var body: some View {
// UI formatting here
.onAppear {
Task {
do {
try await viewModel.getEvents()
} catch {
print("Error", error)
}
}
}
}
.navigationTitle("Event Information")
}
}
}
MapView where i use my api data to display events on the map:
#EnvironmentObject var events: ContentViewModel
#State var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 53.483959, longitude: -2.244644),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region,
annotationItems: events.eventsList) {
event in
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude)) {
NavigationLink(destination: ContentView(event: event, viewModel: ContentViewModel())) {
Image(systemName: "pin.fill")
.onHover { hover in
print(event.title)
}
}
}
}
.navigationTitle("Events Near You")
}
}
I don't have anyone else that i can ask for help so any help or information would be greatly appreciated! I'm still a beginner with Swift development and don't really feel comfortable with it yet. I'm using xcode version 13 and swift version 5
Your properties need default values.
The App struct is the entry point in your application. So every property needs to be properly initialized.
Especially your #StateObject var.

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 Firestore data doesn't showing on the first load

i connected my app wirh Firestore. Everything works fine exept for my data does not show first time when i launch the app. When i switch between tabs, the data shows. It's accting like [Place] array is appending slowly after my app shows. How to force to show data on the first load? here is code:
class FirebaseManager: ObservableObject {
#Published var places = [Place]()
init() {
fetchData()
}
func fetchData(){
Firestore.firestore().clearPersistence()
let db = Firestore.firestore()
db.collection("places").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
self.places.removeAll()
for i in (snap?.documentChanges)!{
let name = i.document.data()["name"] as! String
let type = i.document.data()["type"] as! String
let desc = i.document.data()["desc"] as! String
let image = i.document.data()["image"] as! String
let loc = i.document.data()["loc"] as! String
let contact = i.document.data()["contact"] as! String
let fol = i.document.data()["followers"] as! String
DispatchQueue.main.async {
self.places.append(Place(id: fol, name: name, type: type, description: desc, image: image, location: loc, contact: contact))
print(name)
}
}
}
}
}
struct topView : View {
#ObservedObject var firebaseMan = FirebaseManager()
var body : some View{
VStack(alignment: .leading, spacing: 15){
Text("Clubs & Places")
.font(.system(size: 25, weight: .regular))
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 15){
ForEach(0 ..< firebaseMan.places.count) {i in
Image(self.firebaseMan.places[i].image)
I tried with .onAppear{... on view, but that doesn't help too.
Thanks.
Resolved! This code is ok, but in SwiftUI ScrollView has a problem that it's not refreshing observasble object! After ScrollView always check if (your #Published var) =! nil! I hope Apple will resolve this problem soon, in next update.

Resources