I just converted my app to the SwiftUI framework. The front end works great, but I am having a huge issue - I can't figure out how to fetch my data from a user UITextField entry and update my view. This project has a lot of code, but I've condensed it into a few files and will share only a portion since the other portions of the app are structured in a similar manner. I would really appreciate help anyone can offer. I intend to replace many of the strings in the METARView
Text labels with variables based on the decoded API call data.
Front End: ContentView()
//
// ContentView.swift
// Aviate
//
// Created by Grayson Bertaina on 9/27/20.
//
import Combine
import SwiftUI
import Foundation
class Query: ObservableObject {
var didChange = PassthroughSubject<Void, Never>()
}
struct ContentView: View {
init() {
UITableView.appearance().backgroundColor = .clear
}
@State var stationQuery: String = ""
var body: some View {
NavigationView{
VStack {
Text("The App for GA Pilots")
Spacer()
VStack(alignment: .leading){
Text("News")
.background(Color.blue)
.font(.title)
.padding()
Text("Aviate now supports chart lookup functionality!")
.padding()
}
.background(Color.blue)
.foregroundColor(.white)
.padding()
VStack(alignment: .leading) {
Text("Favorite Airports")
.font(.title)
.padding()
List {
Section(header: Text("Favorite Airports")) {
NavigationLink(destination: METARView(search: stationQuery)) {
Text("Airport 1")
}
}
Section(header: Text("Nearby Stations")) {
NavigationLink(destination: METARView(search: stationQuery)) {
Text("Airport 1")
}
}
}
}
Spacer()
HStack{
TextField("Enter ICAO", text: $stationQuery)
.padding()
.border(Color.black)
NavigationLink(destination: METARView(search: stationQuery)) {
Text("Search")
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
}
.padding()
}
.navigationBarTitle("Aviate Home")
}
}
}
struct METARView: View {
@State var weatherManager = WeatherManager()
init(search: String) {
self.weatherManager.fetchWeather(stationICAO: search)
}
var body: some View {
ScrollView{
METARReport(model: weatherManager.weather)
MapView()
.frame(height: 200)
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
METARTitleView()
.frame(height: 250)
METARReport()
Spacer()
}
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
}
}
struct TAFView: View {
var search: String
var body: some View {
ScrollView{
MapView()
.frame(height: 200)
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
METARTitleView()
.frame(height: 250)
TAFReport()
}
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
}
}
struct AirportView: View {
var search: String
var body: some View {
ScrollView{
MapView()
.frame(height: 200)
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
AirportReport()
}
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 11")
}
}
final class DataModel: ObservableObject {
static let shared = DataModel()
@Published var observedString = "" {
didSet {
//RUNS SUCCESSFULLY
print("Observed string changed to \(observedString)")
}
}
}
Here is the part of METARView() that is needed to understand where the data is going - it will present the pulled data.
METARReport()
//
// METARReport.swift
// Aviate
//
// Created by Grayson Bertaina on 9/29/20.
//
import SwiftUI
import Foundation
var weatherManager = WeatherManager()
struct METARReport: View {
var body: some View {
ScrollView {
VStack{
HStack{
Text("METAR Report")
.font(.title)
Spacer()
Text("Time: " + "")
.font(.headline)
}
.padding()
HStack{
Text("Flight Rules")
Spacer()
Text("VFR")
.padding()
.background(Color.green)
}
.padding()
.font(.headline)
HStack(spacing: 20){
VStack(alignment: .leading){
Text("Visibility")
Spacer()
Text("10" + " SM")
}
VStack(alignment: .leading){
Text("Temperature")
Spacer()
Text("25" + " °C")
}
VStack(alignment: .leading){
Text("Dew Point")
Spacer()
Text("25" + " °C")
}
}
.padding()
.font(.headline)
VStack(alignment: .center){
Text("Clouds")
.font(.headline)
.padding()
HStack() {
Text("Few")
Spacer()
Text("1200")
}
.padding()
HStack() {
Text("Few")
Spacer()
Text("1200")
}
.padding()
HStack() {
Text("Few")
Spacer()
Text("1200")
}
.padding()
}
.font(.headline)
HStack{
Text("Altimeter")
Spacer()
Text("29.92")
}
.font(.headline)
.padding()
VStack{
Text("Wind")
HStack{
Text("5" + "kt")
Text("G")
Text("12" + "kt")
Text("From")
Text("310" + "°")
}
.padding()
}
.font(.headline)
HStack {
Text("Special Wx")
Spacer()
Text("+RA")
}
.padding()
.font(.headline)
VStack {
Text("Remarks")
Spacer()
Text("No Remarks")
}
.padding()
.font(.headline)
}
}
}
}
struct METARReport_Previews: PreviewProvider {
static var previews: some View {
METARReport()
}
}
Back End: WeatherStorage (Contains WeatherData, WeatherModel, and WeatherManager with API Call)
struct WeatherData: Codable {
let flight_rules: String?
let remarks: String?
let wind_speed: WindSpeed?
let wind_gust: WindGust?
let wind_direction: WindDirection?
let visibility: Visibility?
let time: Time?
let station: String?
let temperature: Temperature?
let raw: String?
let clouds: [Clouds?]
let wx_codes: [Wxcodes?]
}
struct Clouds: Codable {
let type: String
let altitude: Int
}
struct Time: Codable {
let repr: String
}
struct WindSpeed: Codable {
let value: Int
}
struct WindGust: Codable {
let value: Int
}
struct WindDirection: Codable {
let repr: String
}
struct Visibility: Codable {
let repr: String
}
struct Remarks: Codable {
let remarks: String
}
struct Altimeter: Codable {
let repr: String?
let spoken: String?
}
struct Temperature: Codable {
let repr: String
}
struct Dewpoint: Codable {
let repr: String
}
struct Wxcodes: Codable {
let value: String
}
// WeatherModel
//
// WeatherModel.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/22/20.
//
import Foundation
struct WeatherModel {
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: String
let visibility: String
let flightRules: String
let time: String
let remarks: String
let temperature: String
let dewpoint: String
let rawMETAR: String
let lowestCloudsType: String
let lowestCloudsAlt: Int
let middleCloudsType: String
let middleCloudsAlt: Int
let highestCloudsType: String
let highestCloudsAlt: Int
let firstWxCode: String
var windGustString: String {
return String(format: "%u" + "kt", windGust)
}
var windSpeedString: String {
return String(format: "%u" + "kt", windSpeed)
}
var visUnits: String {
return visibility + " SM"
}
var degUnits: String {
return windDirection + "°"
}
var tempUnits: String {
return temperature + "°C"
}
var dewUnits: String {
return dewpoint + "°C"
}
var altToString1: String {
return String(format: "%u" + "00 ft", lowestCloudsAlt)
}
var altToString2: String {
return String(format: "%u" + "00 ft", middleCloudsAlt)
}
var altToString3: String {
return String(format: "%u" + "00 ft", highestCloudsAlt)
}
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
private func parseJSON(_ weatherData: Data) -> WeatherModel? {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let clouds = decodedData.clouds
let wxcodes = decodedData.wx_codes
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?.repr ?? "N/A"
let visibilityValue = decodedData.visibility?.repr ?? "N/A"
let flightRulesValue = decodedData.flight_rules ?? "N/A"
let timeReportedMETAR = decodedData.time?.repr ?? "N/A"
let remarksReportedMETAR = decodedData.remarks ?? "N/A"
let tempMETAR = decodedData.temperature?.repr ?? "No Data"
let rawMETARData = decodedData.raw ?? "N/A"
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 firstWxCode1 = (wxcodes.count > 0 ? wxcodes[0]?.value : "N/A") ?? "N/A"
let weather = WeatherModel(reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue, time: timeReportedMETAR, remarks: remarksReportedMETAR, temperature: tempMETAR, dewpoint: rawMETARData, rawMETAR: rawMETARData, lowestCloudsType: lowCloudsType, lowestCloudsAlt: lowCloudsAlt, middleCloudsType: midCloudsType, middleCloudsAlt: midCloudsAlt, highestCloudsType: highCloudsType, highestCloudsAlt: highCloudsAlt, firstWxCode: firstWxCode1)
return weather
} catch {
print(error)
return nil
}
}
You will notice that there are some useless functions, like trying fetchWeather at the top of my ContentView. I am trying various things to get data to be pulled but don't know where to put the required:
I am new to Swift so I apologize if any part of my question is vague. I am definitely in a bit over my head! Thanks for the help and have a great day.
Add on:
//
// WeatherData.swift
// SwitUIAppExample
//
// Created by mac on 30/09/2020.
// Copyright © 2020 Kamran. All rights reserved.
//
//
// 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 time: Time?
let station: String?
let altimeter: Altimeter?
let temperature: Temperature?
let dewpoint: Dewpoint?
let wx_codes: [Wxcodes?]
let raw: String?
}
struct Clouds: Codable {
let type: String
let altitude: Int
}
struct Time: Codable {
let repr: String
}
struct WindSpeed: Codable {
let value: Int
}
struct WindGust: Codable {
let value: Int
}
struct WindDirection: Codable {
let repr: String
}
struct Visibility: Codable {
let repr: String
}
struct Remarks: Codable {
let remarks: String
}
struct Altimeter: Codable {
let value: Double
}
struct Temperature: Codable {
let repr: String
}
struct Dewpoint: Codable {
let repr: String
}
struct Wxcodes: Codable {
let value: String
}
// WeatherModel
//
// WeatherModel.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/22/20.
//
import Foundation
struct WeatherModel {
let lowestCloudsType: String
let lowestCloudsAlt: Int
let middleCloudsType: String
let middleCloudsAlt: Int
let highestCloudsType: String
let highestCloudsAlt: Int
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: String
let visibility: String
let flightRules: String
let time: String
let remarks: String
let altimeter: Double
let temperature: String
let dewpoint: String
let firstWxCode: String
let rawMETAR: String
var altToString1: String {
return String(format: "%u" + "00 ft", lowestCloudsAlt)
}
var altToString2: String {
return String(format: "%u" + "00 ft", middleCloudsAlt)
}
var altToString3: String {
return String(format: "%u" + "00 ft", highestCloudsAlt)
}
var windGustString: String {
return String(format: "%u" + "kt", windGust)
}
var windSpeedString: String {
return String(format: "%u" + "kt", windSpeed)
}
var altimeterString: String {
return String(format: "%.2f" + " inHg", altimeter as CVarArg)
}
var visUnits: String {
return visibility + " SM"
}
var degUnits: String {
return windDirection + "°"
}
var tempUnits: String {
return temperature + "°C"
}
var dewUnits: String {
return dewpoint + "°C"
}
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
// WeatherManager
//
// WeatherManager.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/21/20.
//
import Foundation
class WeatherManager: ObservableObject {
@Published var weater: WeatherModel?
let weatherURL = "https://avwx.rest/api/metar/"
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 {
print(error)
return
}
if let safeData = data {
self.weater = self.parseJSON(safeData)
}
}
task.resume()
print(urlString)
}
}
private func parseJSON(_ weatherData: Data) -> WeatherModel? {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let clouds = decodedData.clouds
let wxcodes = decodedData.wx_codes
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?.repr ?? "N/A"
let visibilityValue = decodedData.visibility?.repr ?? "N/A"
let flightRulesValue = decodedData.flight_rules ?? "N/A"
let timeReportedMETAR = decodedData.time?.repr ?? "N/A"
let remarksReportedMETAR = decodedData.remarks ?? "N/A"
let altimeterMETAR = decodedData.altimeter?.value ?? 0
let tempMETAR = decodedData.temperature?.repr ?? "No Data"
let dewMETAR = decodedData.dewpoint?.repr ?? "No Data"
let firstWxCode1 = (wxcodes.count > 0 ? wxcodes[0]?.value : "N/A") ?? "N/A"
let rawMETARData = decodedData.raw ?? "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, time: timeReportedMETAR, remarks: remarksReportedMETAR, altimeter: altimeterMETAR, temperature: tempMETAR, dewpoint: dewMETAR, firstWxCode: firstWxCode1, rawMETAR: rawMETARData)
return weather
} catch {
print(error)
return nil
}
}
}
You need to introduce few changes as below,
Change WeatherManager
to class
, conform to ObservableObject
and introduce weather model as below,
class WeatherManager: ObservableObject {
@Published var weather: WeatherModel?
}
Rest of the WeatherManager
will be same except that you need to set the above property after parsing inside performRequest
method as below,
if let safeData = data {
self.weather = self.parseJSON(safeData)
}
Now change METARReport
by introducing var model: WeatherModel?
and set all the labels same as time shown below,
struct METARReport: View {
var model: WeatherModel?
var body: some View {
ScrollView {
VStack{
HStack{
Text("METAR Report")
.font(.title)
Spacer()
Text("Time: " + (self.model?.time ?? ""))
.font(.headline)
}
.....
}
Finally just update METARView
as below,
struct METARView: View {
@State var weatherManager = WeatherManager()
init(search: String) {
self.weatherManager.fetchWeather(stationICAO: search)
}
var body: some View {
ScrollView{
// ...
METARReport(model: weatherManager.weather)
Spacer()
}
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
}
}
Note: There were some parsing issues as well in your code so i used the below models to make it work.
struct WeatherData: Codable {
let flight_rules: String?
let remarks: String?
let wind_speed: WindSpeed?
let wind_gust: WindGust?
let wind_direction: WindDirection?
let visibility: Visibility?
let time: Time?
let station: String?
let temperature: Temperature?
let raw: String?
}
struct Clouds: Codable {
let type: String
let altitude: Int
}
struct Time: Codable {
let repr: String
}
struct WindSpeed: Codable {
let value: Int
}
struct WindGust: Codable {
let value: Int
}
struct WindDirection: Codable {
let repr: String
}
struct Visibility: Codable {
let repr: String
}
struct Remarks: Codable {
let remarks: String
}
struct Altimeter: Codable {
let repr: String?
let spoken: String?
}
struct Temperature: Codable {
let repr: String
}
struct Dewpoint: Codable {
let repr: String
}
struct Wxcodes: Codable {
let value: String
}
// WeatherModel
//
// WeatherModel.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/22/20.
//
import Foundation
struct WeatherModel {
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: String
let visibility: String
let flightRules: String
let time: String
let remarks: String
let temperature: String
let dewpoint: String
let rawMETAR: String
var windGustString: String {
return String(format: "%u" + "kt", windGust)
}
var windSpeedString: String {
return String(format: "%u" + "kt", windSpeed)
}
var visUnits: String {
return visibility + " SM"
}
var degUnits: String {
return windDirection + "°"
}
var tempUnits: String {
return temperature + "°C"
}
var dewUnits: String {
return dewpoint + "°C"
}
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
private func parseJSON(_ weatherData: Data) -> WeatherModel? {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
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?.repr ?? "N/A"
let visibilityValue = decodedData.visibility?.repr ?? "N/A"
let flightRulesValue = decodedData.flight_rules ?? "N/A"
let timeReportedMETAR = decodedData.time?.repr ?? "N/A"
let remarksReportedMETAR = decodedData.remarks ?? "N/A"
let tempMETAR = decodedData.temperature?.repr ?? "No Data"
let rawMETARData = decodedData.raw ?? "N/A"
let weather = WeatherModel(reportingStation: reportingStationVar, windGust: windGustValue, windSpeed: windSpeedValue, windDirection: windDirectionValue, visibility: visibilityValue, flightRules: flightRulesValue, time: timeReportedMETAR, remarks: remarksReportedMETAR, temperature: tempMETAR, dewpoint: rawMETARData, rawMETAR: rawMETARData)
return weather
} catch {
print(error)
return nil
}
}