Home > database >  I can't pass data by using @Binding from one View to other
I can't pass data by using @Binding from one View to other

Time:03-02

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.

  • Related