0

I am attempting to build a multifaceted openweathermap app. My app is designed to prompt the user to input a city name on a WelcomeView, in order to get weather data for that city. After clicking search, the user is redirected to a sheet with destination: DetailView, which displays weather details about that requested city. My goal is to disable dismissal of the sheet in WelcomeView and instead add a navigationlink to the sheet that redirects to the ContentView. The ContentView in turn is set up to display a list of the user's recent searches (also in the form of navigation links).

My issues are the following:

  1. The navigationLink in the WelcomeView sheet does not work. It appears to be disabled. How can I configure the navigationLink to segue to destination: ContentView() ?

  2. After clicking the navigationLink and redirecting to ContentView, I want to ensure that the city name entered in the WelcomeView textfield is rendered as a list item in the ContentView. For that to work, would it be necessary to set up an action in NavigationLink to call viewModel.fetchWeather(for: cityName)?

Here is my code:

WelcomeView

struct WelcomeView: View {
    
    @StateObject var viewModel = WeatherViewModel()
    @State private var cityName = ""
    @State private var showingDetail: Bool = false
    @State private var linkActive: Bool = true
    @State private var acceptedTerms = false
    
    var body: some View {
        Section {
            HStack {
                TextField("Search Weather by City", text: $cityName)
                    .padding()
                    .overlay(RoundedRectangle(cornerRadius: 10.0).strokeBorder(Color.gray, style: StrokeStyle(lineWidth: 1.0)))
                    .padding()
                Spacer()
                Button(action: {
                    viewModel.fetchWeather(for: cityName)
                    cityName = ""
                    self.showingDetail.toggle()
                }) {
                    HStack {
                        Image(systemName: "plus")
                            .font(.title)
                    }
                    .padding(15)
                    .foregroundColor(.white)
                    .background(Color.green)
                    .cornerRadius(40)

                }
                .sheet(isPresented: $showingDetail) {
                    VStack {
                        
                        NavigationLink(destination: ContentView()){
                            Text("Return to Search")
                        }
                        ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
                            if (city == viewModel.cityNameList.count-1) {
                                DetailView(detail: viewModel.cityNameList[city])
                            }
                        }.interactiveDismissDisabled(!acceptedTerms)
                    }
                }
            }.padding()
        }
    }
}

struct WelcomeView_Previews: PreviewProvider {
    static var previews: some View {
        WelcomeView()
    }
}

ContentView

let coloredToolbarAppearance = UIToolbarAppearance()

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 cityName = ""
    @State var showingDetail = false

    init() {
        // toolbar attributes
        coloredToolbarAppearance.configureWithOpaqueBackground()
        coloredToolbarAppearance.backgroundColor = .systemGray5
        UIToolbar.appearance().standardAppearance = coloredToolbarAppearance
        UIToolbar.appearance().scrollEdgeAppearance = coloredToolbarAppearance
    }
        
    var body: some View {
        NavigationView {
            VStack() {

                List () {
                    ForEach(viewModel.cityNameList) { city in
                        NavigationLink(destination: DetailView(detail: city)) {
                            HStack {
                                Text(city.name).font(.system(size: 32))
                                Spacer()
                                Text("\(city.main.temp, specifier: "%.0f")&deg;").font(.system(size: 32))
                            }
                        }
                    }.onDelete { index in
                        self.viewModel.cityNameList.remove(atOffsets: index)
                    }
                }.onAppear() {
                    viewModel.fetchWeather(for: cityName)
                }
            }.navigationTitle("Weather")
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    HStack {
                        TextField("Enter City Name", text: $cityName)
                            .frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
                        Spacer()
                        Button(action: {
                            viewModel.fetchWeather(for: cityName)
                            cityName = ""
                            self.showingDetail.toggle()
                        }) {
                            HStack {
                                Image(systemName: "plus")
                                    .font(.title)
                            }
                            .padding(15)
                            .foregroundColor(.white)
                            .background(Color.green)
                            .cornerRadius(40)
                        }.sheet(isPresented: $showingDetail) {
                            ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
                                if (city == viewModel.cityNameList.count-1) {
                                    DetailView(detail: viewModel.cityNameList[city])
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

DetailView

struct DetailView: View {
    
    var detail: WeatherModel
        
    var body: some View {
                                
        VStack(spacing: 20) {
            Text(detail.name)
                .font(.system(size: 32))
            Text("\(detail.main.temp, specifier: "%.0f")&deg;")
                .font(.system(size: 44))
            Text(detail.firstWeatherInfo())
                .font(.system(size: 24))
        }
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        DetailView(detail: WeatherModel.init())
    }
}

ViewModel


class WeatherViewModel: ObservableObject {
    @Published var cityNameList = [WeatherModel]()

    func fetchWeather(for cityName: String) {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") 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.cityNameList.append(model)
                }
            }
            catch {
                print(error) // <-- you HAVE TO deal with errors here
            }
        }
        task.resume()
    }
}

Model

struct WeatherModel: Identifiable, Codable {
    let id = UUID()
    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: Double = 0.0
}

struct WeatherInfo: Codable {
    var description: String = ""
}

DemoApp

@main
struct SwftUIMVVMWeatherDemoApp: App {
    var body: some Scene {
        WindowGroup {
            // ContentView()
            WelcomeView()
        }
    }
}

JS_is_awesome18
  • 1,587
  • 7
  • 23
  • 67
  • 1
    There's too much here to dig through, but here are a couple of tips: 1) Your `NavigationLink` is disabled because `NavigationLink` only works within a `NavigationView`. In your case, it looks like you probably want a variable that you can change with a `Button` (not a `NavigationLink`) that will determine whether `ContentView` or `WelcomeView` is displayed. You could, for example, store this in an `ObservableObject` that gets passed to child views via `.environmentObject`. – jnpdx Nov 13 '21 at 22:37
  • Is there a solution to this problem? – IDTIMW Feb 17 '22 at 02:54
  • I found the solution, you can see this https://stackoverflow.com/questions/57024263/how-to-navigate-to-a-new-view-from-navigationbar-button-click-in-swiftui – IDTIMW Feb 17 '22 at 03:00

0 Answers0