i created a weather app, which download current weather data by city name (user type it) and put this data inside a list - it work's. But i also created WeatherDetailView() which, should show a detailed data like, hourly forecast for current location. Unfortunately it's impossible to download this data by using city name, so i decided to use Latitude and Longitude, from first View - it should work like this: user type a city name, the city appear in list with temperature and icon (works) and if the user decide to go to next view by using NavigationLink, detailed data is downloaded by using lon&lat which is assigned to this city, and show detail data in next view. I have no idea how to do it, there is an error "Cannot find '$weatherElement' in scope" i tried many things and just can figure it out.
FirstView()
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherVM : WeatherViewModel
init(){
self.weatherVM = WeatherViewModel()
}
var body: some View {
NavigationView{
List{
TextField("Add city...", text: self.$weatherVM.city){
weatherVM.search()
}
.padding(6)
ForEach(weatherVM.weatherList) { weatherElement in
WeatherDetailView(lat: $weatherElement.lat , lon:
$weatherElement.lon) // error Cannot find '$weatherElement' in scope
NavigationLink {
} label: ...
DetailViewModel()
import SwiftUI
struct WeatherDetailView: View {
@ObservedObject var detailVM : DetailViewModel
@Binding var lat : Double
@Binding var lon : Double
init(lat : Binding<Double>, lon : Binding<Double>){
self.detailVM = DetailViewModel()
self._lat = lat
self._lon = lon
}
let weather = WeatherDetails.addWeather()
var body: some View {
VStack(spacing: 30){
//... some ui code
ScrollView(.horizontal){ // here i want to put detail data
LazyHStack(spacing: 10){
ForEach(self.detailVM.detailsList, id: \.hours) { detail in
VStack{
Text(detail.hours)
Image(systemName: detail.icon)
Text(detail.temperature)
}
}
}
}
}.padding(.top, 10)
.onAppear {
detailVM.starUpdate(lat: 2.0 , lon: 5.0)// <- here i use @Binding values
}
}
}
WeatherModelView:
import Foundation
class WeatherViewModel : ObservableObject{
private var weatherService : WeatherService!
@Published var weather = WeatherModel()
struct weatherDetails : Identifiable {
let id = UUID()
let cityName : String
let temperature : String
let icon : String
let lat : Double
let lon : Double
}
@Published var weatherList = [weatherDetails(cityName: "London", temperature: "12", icon: "cloud.fill", lat: 2.0, lon: 3.0)]
func addCity(){
weatherList.removeAll()
if tempereture != "" && conditionName != "" {
weatherList.append(weatherDetails(cityName: city, temperature: tempereture, icon: conditionName, lat: weather.coord!.lat!, lon: weather.coord!.lon!))
} else {
print("no such city")
}
}
init(){
self.weatherService = WeatherService()
}
var tempereture : String {
if let temp = self.weather.main?.temp{
return String(format: "%.2f", temp)
} else {
return ""
}
}
var conditionName: String {
if let id = self.weather.weather?[0].id{
switch id {
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
} else {
return ""
}
}
private func fetchWeather(by city: String){
self.weatherService.getWeather(city: city) {weather in
if let weather = weather {
DispatchQueue.main.async {
self.weather = weather
}
self.addCity()
}
}
}
var city : String = ""
func search(){
if let city = city.self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed){
fetchWeather(by: city)
}
}
}
WeatherDetailView:
import Foundation
import SwiftUI
class DetailViewModel : ObservableObject{
private var detailedWeatherService : DetailedWeatherService!
@Published var details = WeatherDetailModel()
@Published var detailsList = [Details(hours: "12:00", temperature: "13", icon: "cloud.fill")]
struct Details : Identifiable {
let id = UUID()
let hours : String
let temperature : String
let icon : String
}
init(){
self.detailedWeatherService = DetailedWeatherService()
}
private func getHour(dt : Int) -> String{
let date = NSDate(timeIntervalSince1970: TimeInterval(dt))
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date as Date)
return "\(hour):00"
}
private func getTemperature(temp : Double) -> String{
return String(format: "%.1f", temp)
}
private func getIcon(icon : Int) -> String{
switch icon{
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
}
private func fullFillDetails(){
detailsList.removeAll()
if details.hourly != nil{
for i in 0..<24{
let detail = details.hourly![i]
detailsList.append(Details(hours: getHour(dt: detail.dt!), temperature: getTemperature(temp: detail.temp!), icon: getIcon(icon: detail.weather![0].id!)))
}
} else{
print("no data")
}
}
private func updateData(lat : Double, lon: Double){
self.detailedWeatherService.getDetails(lat : lat, lon: lon) { details in
if let details = details {
DispatchQueue.main.async {
self.details = details
self.fullFillDetails()
}
}
}
}
func starUpdate(lat: Double, lon : Double){ // rozpoczecie z View
updateData(lat: lat, lon: lon)
}
}
thank you.
CodePudding user response:
The problem is that you're trying to create bindings to variables that aren't held in your master view, of course you couldn't access $weatherElement because it doesn't have a weatherElement to begin with.
Your detail view should accept an @ObservedObject:
WeatherDetailView(weatherElement)
And in your WeatherDetailView:
struct WeatherDetailView: View {
@ObservedObject var detailVM : DetailViewModel
@ObservedObject var weatherDetail: WeatherDetails
init(_ details: WeatherDetails){
self.detailVM = DetailViewModel()
self._weatherDetails = details
}
That should fix it !
CodePudding user response:
I figured out!
ContentView()
...
NavigationLink {
WeatherDetailView(element: weatherElement) // <- here
} label: { ...
WeatherDetailView()
import SwiftUI
struct WeatherDetailView: View {
@ObservedObject var detailVM : DetailViewModel
@ObservedObject var weatherVM : WeatherViewModel
var element : WeatherViewModel.weatherDetails
init(element: WeatherViewModel.weatherDetails){
self.detailVM = DetailViewModel()
self.weatherVM = WeatherViewModel()
self.element = element
}
thanks for help, i post it here, it may help somebody.