Search code examples
iosswiftapisegueviewcontroller

Question about passing data from an API into a view controller after user query - Swift


I am working tirelessly on an app and I'm pretty new to Swift. I am trying to take data from an API call after user entry on the landing screen and then present the results of the API call on the modal popover that comes up to display the data that the user wanted. This is an aviation weather app; The user enters something like "KDAB" and the API returns the current aviation weather at that airport. I have a bunch of labels set up for different values and can print them to console after a search but can't seem to get them printed in the VC. I have tried some destination.segue fixes but don't see why I would use them - the data is coming directly from the decoded JSON. Help would be appreciated!

I am trying to display on the ReportViewController in each of the value labels. I.E. Wind speed in the windSpeedValueLabel by utilizing the .text property.

Right now, nothing changes from the default labels on the actual ReportViewController.

Thanks again.

My code:

//
//  ReportViewController.swift
//  AvWx Pro
//
//  Created by Grayson Bertaina on 9/22/20.
//

import UIKit

class ReportViewController: UIViewController, WeatherManagerDelegate {

    var weatherManager = WeatherManager()
    
    @IBOutlet weak var flightRulesTitleLabel: UILabel!
    
    @IBOutlet weak var flightRulesValueLabel: UILabel!
    
    @IBOutlet weak var visibilityValueLabel: UILabel!
    
    @IBOutlet weak var altimeterValueLabel: UILabel!
    
    @IBOutlet weak var cloudsTitleLabel: UILabel!
    
    @IBOutlet weak var cloudsType1Label: UILabel!
    
    @IBOutlet weak var cloudsAltitude1Label: UILabel!
    
    @IBOutlet weak var cloudsType2Label: UILabel!
    
    @IBOutlet weak var cloudsAltitude2Label: UILabel!
    
    @IBOutlet weak var cloudsType3Label: UILabel!
    
    @IBOutlet weak var cloudsAltitude3Label: UILabel!
    
    @IBOutlet weak var windGTextLabel: UILabel!
    
    @IBOutlet weak var windSpeedValueLabel: UILabel!
    
    @IBOutlet weak var windGustValueLabel: UILabel!
    
    @IBOutlet weak var windFromTextLabel: UILabel!
    
    @IBOutlet weak var windDirectionValueLabel: UILabel!
    
    @IBOutlet weak var remarksValueLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
    }
    

    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
        DispatchQueue.main.async {
            self.flightRulesValueLabel.text = weather.flightRules
            self.cloudsType1Label.text = weather.lowestCloudsType
        }
        
    }
    
    func didFailWithError(error: Error) {
        print(error)
    }
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    

}
//
//  ViewController.swift
//  AvWx Pro
//
//  Created by Grayson Bertaina on 9/21/20.
//

import UIKit

class WxViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {

    var weatherManager = WeatherManager()
    
    @IBOutlet weak var stationSearch: UITextField!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        weatherManager.delegate = self
        stationSearch.delegate = self
    }

    @IBAction func searchPressed(_ sender: Any) {
        print(stationSearch.text!)
        stationSearch.endEditing(true)
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        print(stationSearch.text!)
        stationSearch.endEditing(true)
        return true
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        if let station = stationSearch.text {
            weatherManager.fetchWeather(stationICAO: station)
        }

        stationSearch.text = ""
    }
    
    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        if stationSearch.text != "" {
            return true
        } else {
            stationSearch.placeholder = "Type an ICAO"
            return false
        }
    }
    
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
        print(weather.flightConditions)
        }
        
    
    
    func didFailWithError(error: Error) {
        print(error)
    }
    
    
  
    
    
}
//
//  WeatherManager.swift
//  AvWx Pro
//
//  Created by Grayson Bertaina on 9/21/20.
//

import Foundation

protocol WeatherManagerDelegate {
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
    func didFailWithError(error: Error)
}

struct WeatherManager {
    let weatherURL = "https://avwx.rest/api/metar/"
    
    var delegate : WeatherManagerDelegate?

    func fetchWeather (stationICAO: String) {
        let urlString = "\(weatherURL)\(stationICAO)?token=OVi45FiTDo1LmyodShfOfoizNe5m9wyuO6Mkc95AN-c"
        performRequest(with: urlString)
    }
    
    func performRequest (with urlString: String) {
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
                
            
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil {
                    self.delegate?.didFailWithError(error: error!)
                    return
                }
                
                if let safeData = data {
                    if let weather = self.parseJSON(safeData) {
                        self.delegate?.didUpdateWeather(self, weather: weather)
                    }
                }
            }
            
            task.resume()
            print(urlString)
            
            
            }
        }
    
    func parseJSON(_ weatherData: Data) -> WeatherModel? {
        
        
        do {
            let decoder = JSONDecoder()
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            
            
            
            let clouds = decodedData.clouds
            let lowCloudsType = (clouds.count > 0 ? clouds[0]?.type : nil) ?? "N/A"
            let midCloudsType = (clouds.count > 1 ? clouds[1]?.type : nil) ?? "N/A"
            let highCloudsType = (clouds.count > 2 ? clouds[2]?.type : nil) ?? "N/A"
            let lowCloudsAlt = (clouds.count > 0 ? clouds[0]?.altitude : nil) ?? 0
            let midCloudsAlt = (clouds.count > 1 ? clouds[1]?.altitude : nil) ?? 0
            let highCloudsAlt = (clouds.count > 2 ? clouds[2]?.altitude : nil) ?? 0
            let reportingStationVar = decodedData.station ?? "N/A"
            let windGustValue = decodedData.wind_gust?.value ?? 0
            let windSpeedValue = decodedData.wind_speed?.value ?? 0
            let windDirectionValue = decodedData.wind_direction?.value ?? 999
            let visibilityValue = decodedData.visibility?.value ?? 0
            let flightRulesValue = decodedData.flight_rules ?? "N/A"
            
            let weather = WeatherModel(lowestCloudsType: lowCloudsType , lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType , middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType , highestCloudsAlt: highCloudsAlt, reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue)
            
            return weather
            
        } catch {
            delegate?.didFailWithError(error: error)
            return nil
        }
    }
    
    

}
//
//  WeatherData.swift
//  AvWx Pro
//
//  Created by Grayson Bertaina on 9/21/20.
//

import Foundation

struct WeatherData: Codable {
   
    
    let clouds: [Clouds?]
    let flight_rules: String?
    let remarks: String?
    let wind_speed: WindSpeed?
    let wind_gust: WindGust?
    let wind_direction: WindDirection?
    let visibility: Visibility?

    let station: String?
}



struct Clouds: Codable {
    let type: String
    let altitude: Int
}

struct WindSpeed: Codable {
    let value: Int
}

struct WindGust: Codable {
    let value: Int
}

struct WindDirection: Codable {
    let value: Int
}

struct Visibility: Codable {
    let value: Int
}


Thanks again.


Solution

  • First, on WeatherManager, declare your delegate as weak to avoid reference cycle

    weak var delegate : WeatherManagerDelegate?
    

    Second, after you decode the data, you have to call the delegate and pass the data, in this case,the WeatherModel you just created using one of your delegate methods

     do {
                let decoder = JSONDecoder()
                let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
                
                
                
                let clouds = decodedData.clouds
                let lowCloudsType = (clouds.count > 0 ? clouds[0]?.type : nil) ?? "N/A"
                let midCloudsType = (clouds.count > 1 ? clouds[1]?.type : nil) ?? "N/A"
                let highCloudsType = (clouds.count > 2 ? clouds[2]?.type : nil) ?? "N/A"
                let lowCloudsAlt = (clouds.count > 0 ? clouds[0]?.altitude : nil) ?? 0
                let midCloudsAlt = (clouds.count > 1 ? clouds[1]?.altitude : nil) ?? 0
                let highCloudsAlt = (clouds.count > 2 ? clouds[2]?.altitude : nil) ?? 0
                let reportingStationVar = decodedData.station ?? "N/A"
                let windGustValue = decodedData.wind_gust?.value ?? 0
                let windSpeedValue = decodedData.wind_speed?.value ?? 0
                let windDirectionValue = decodedData.wind_direction?.value ?? 999
                let visibilityValue = decodedData.visibility?.value ?? 0
                let flightRulesValue = decodedData.flight_rules ?? "N/A"
                
                let weather = WeatherModel(lowestCloudsType: lowCloudsType , lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType , middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType , highestCloudsAlt: highCloudsAlt, reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue)
                
                delegate?.didUpdateWeather(self, weather: weather)
    
                return weather
                
            } catch {
                delegate?.didFailWithError(error: error)
                return nil
            }
    

    On ReportViewController, assign self as your WeatherManagerDelegate on viewDidLoad(), then you have to implement the delegate function didUpdateWeather by updating your labels with corresponding values -- which you already started

    class ReportViewController: UIViewController, WeatherManagerDelegate {
    
        var weatherManager = WeatherManager()
        
        @IBOutlet weak var flightRulesTitleLabel: UILabel!
        
        @IBOutlet weak var flightRulesValueLabel: UILabel!
        
        @IBOutlet weak var visibilityValueLabel: UILabel!
        
        @IBOutlet weak var altimeterValueLabel: UILabel!
        
        @IBOutlet weak var cloudsTitleLabel: UILabel!
        
        @IBOutlet weak var cloudsType1Label: UILabel!
        
        @IBOutlet weak var cloudsAltitude1Label: UILabel!
        
        @IBOutlet weak var cloudsType2Label: UILabel!
        
        @IBOutlet weak var cloudsAltitude2Label: UILabel!
        
        @IBOutlet weak var cloudsType3Label: UILabel!
        
        @IBOutlet weak var cloudsAltitude3Label: UILabel!
        
        @IBOutlet weak var windGTextLabel: UILabel!
        
        @IBOutlet weak var windSpeedValueLabel: UILabel!
        
        @IBOutlet weak var windGustValueLabel: UILabel!
        
        @IBOutlet weak var windFromTextLabel: UILabel!
        
        @IBOutlet weak var windDirectionValueLabel: UILabel!
        
        @IBOutlet weak var remarksValueLabel: UILabel!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            weatherManager.delegate = self
        }
        
    
        func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
            DispatchQueue.main.async {
                self.flightRulesValueLabel.text = weather.flightRules
                self.cloudsType1Label.text = weather.lowestCloudsType
            }
            
     }