{"id":4204,"date":"2026-03-25T17:05:02","date_gmt":"2026-03-25T17:05:02","guid":{"rendered":"https:\/\/nmi.cool\/native-app\/?page_id=4204"},"modified":"2026-04-23T21:20:47","modified_gmt":"2026-04-23T21:20:47","slug":"homework-9a-building-a-weather-app","status":"publish","type":"page","link":"https:\/\/nmi.cool\/appdev\/homework-9a-building-a-weather-app\/","title":{"rendered":"Homework 9a: Building a Weather App"},"content":{"rendered":"\n<p>In Homework 9, we learned how to call an API and display data in SwiftUI. Now we&#8217;re going to do it again \u2014 but this time we&#8217;ll write the whole thing ourselves, from scratch. The app is simple: it fetches the current temperature in Athens, GA from the <a href=\"https:\/\/open-meteo.com\/\">Open-Meteo API<\/a> and displays it on screen.<\/p>\n\n\n\n<p>Here&#8217;s the finished file we ended up with. Let&#8217;s walk through it piece by piece \u2014 not just <em>what<\/em> each part does, but <em>why<\/em> it&#8217;s in the order it&#8217;s in.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Big Picture: MVVM<\/h2>\n\n\n\n<p>Before we dive in, it helps to know that this file follows a pattern called <strong>MVVM<\/strong> \u2014 Model, View, ViewModel. Almost every modern SwiftUI app uses it. Here&#8217;s what those three things mean:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Model<\/strong> \u2014 your data. Just raw structs that describe the shape of the information you&#8217;re working with.<\/li>\n\n\n\n<li><strong>ViewModel<\/strong> \u2014 the logic layer. It fetches data, transforms it, and exposes it to the View. It doesn&#8217;t know anything about how the UI looks.<\/li>\n\n\n\n<li><strong>View<\/strong> \u2014 the UI. It just displays what the ViewModel gives it. It doesn&#8217;t fetch or transform anything.<\/li>\n<\/ul>\n\n\n\n<p>This separation keeps each piece focused on one job. And it determines the order our file is written in: <strong>Model \u2192 ViewModel \u2192 View<\/strong>. Each layer depends on the one below it, so the lower layers have to be defined first. Swift reads your file top to bottom, and you can&#8217;t reference something before it&#8217;s been defined.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Imports<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\nimport Combine<\/code><\/pre>\n\n\n\n<p>Every Swift file starts by importing the frameworks it needs. <code>SwiftUI<\/code> gives us everything for building the UI \u2014 <code>View<\/code>, <code>Text<\/code>, <code>VStack<\/code>, <code>NavigationView<\/code>, and so on. <code>Combine<\/code> is Apple&#8217;s reactive programming framework; it gives us <code>ObservableObject<\/code> and <code>@Published<\/code>, which we&#8217;ll use in the ViewModel to automatically notify the UI when data changes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: The Data Models<\/h2>\n\n\n\n<p>Next come the structs that describe our data. Before writing a single line of networking code, we need to know what shape the data coming back from the API is in.<\/p>\n\n\n\n<p>Here&#8217;s what the Open-Meteo response actually looks like as JSON:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"current\": {\n    \"temperature_2m\": 68.4\n  }\n}<\/code><\/pre>\n\n\n\n<p>Notice that it&#8217;s <em>nested<\/em> \u2014 there&#8217;s an outer object with a key called <code>current<\/code>, and <em>inside that<\/em> is the actual temperature. That means we need two structs: one for the inner object, and one for the outer wrapper.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>struct CurrentWeather: Codable {\n    let temperature_2m: Double\n}\nstruct CurrentWeatherResponse: Codable {\n    let current: CurrentWeather\n}<\/code><\/pre>\n\n\n\n<p><code>CurrentWeather<\/code> maps to that inner object \u2014 it has one property, <code>temperature_2m<\/code>, which is a <code>Double<\/code> (a decimal number). The property name matches the JSON key exactly. That matters: Swift&#8217;s <code>JSONDecoder<\/code> matches property names to JSON keys by name, so they have to line up.<\/p>\n\n\n\n<p><code>CurrentWeatherResponse<\/code> maps to the outer wrapper. It has one property, <code>current<\/code>, of type <code>CurrentWeather<\/code> \u2014 the struct we just defined. Notice that <code>CurrentWeather<\/code> is used inside <code>CurrentWeatherResponse<\/code>. That&#8217;s exactly why <code>CurrentWeather<\/code> had to be defined first.<\/p>\n\n\n\n<p>Both structs are marked <code>Codable<\/code>. That&#8217;s a Swift protocol that gives the struct the ability to be decoded from (and encoded to) formats like JSON \u2014 for free, with no extra code, as long as your property names match the JSON keys.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: The ViewModel<\/h2>\n\n\n\n<p>Now that we know what shape our data is, we can write the code that goes and gets it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class WeatherViewModel: ObservableObject {\n    @Published var current: CurrentWeather?\n    func fetchWeather() {\n        guard let url = URL(string: \"https:\/\/api.open-meteo.com\/v1\/forecast?latitude=33.95&amp;longitude=-83.37&amp;current=temperature_2m&amp;temperature_unit=fahrenheit\") else { return }\n        URLSession.shared.dataTask(with: url) { data, response, error in\n            if let error = error { print(\"Error: \\(error)\"); return }\n            guard let data = data else { return }\n            do {\n                let decodedResponse = try JSONDecoder().decode(CurrentWeatherResponse.self, from: data)\n                DispatchQueue.main.async { self.current = decodedResponse.current }\n            } catch { print(\"Error decoding: \\(error)\") }\n        }.resume()\n    }\n}<\/code><\/pre>\n\n\n\n<p>A few things to unpack here:<\/p>\n\n\n\n<p><strong>Why <code>class<\/code> instead of <code>struct<\/code>?<\/strong> The ViewModel needs to be a <code>class<\/code> because it conforms to <code>ObservableObject<\/code>, which requires reference type semantics. In Swift, structs are value types (copied when passed around) and classes are reference types (shared). SwiftUI needs to hold a single shared reference to the ViewModel so it can observe changes to it.<\/p>\n\n\n\n<p><strong><code>@Published var current: CurrentWeather?<\/code><\/strong> \u2014 The <code>@Published<\/code> property wrapper is the magic that connects the ViewModel to the View. Any time <code>current<\/code> changes, SwiftUI automatically knows to re-render any View that&#8217;s watching it. The <code>?<\/code> makes it optional \u2014 it starts as <code>nil<\/code> (no data yet), and gets set once the fetch completes.<\/p>\n\n\n\n<p><strong><code>guard let url = ...<\/code><\/strong> \u2014 <code>URL(string:)<\/code> is failable \u2014 it returns an optional because not every string is a valid URL. The <code>guard let<\/code> safely unwraps it: if the URL is valid, we continue; if not, we <code>return<\/code> early. It&#8217;s a clean way to handle failure cases without deep nesting.<\/p>\n\n\n\n<p><strong><code>URLSession.shared.dataTask<\/code><\/strong> \u2014 This is the standard iOS way to make a network request. <code>URLSession.shared<\/code> is a built-in shared networking session. <code>dataTask(with:)<\/code> creates a task that fetches data from the URL. The trailing closure receives three things when it finishes: the raw data, the HTTP response metadata, and any error. Critically, <strong>this runs on a background thread<\/strong> \u2014 it does not block the UI while waiting for the network.<\/p>\n\n\n\n<p><strong><code>JSONDecoder().decode(CurrentWeatherResponse.self, from: data)<\/code><\/strong> \u2014 This is where our <code>Codable<\/code> structs earn their keep. We tell the decoder what type to expect (<code>CurrentWeatherResponse.self<\/code>) and hand it the raw data. It automatically maps JSON keys to struct properties and gives us back a fully populated <code>CurrentWeatherResponse<\/code>. The <code>try<\/code> means it can throw an error if the JSON doesn&#8217;t match \u2014 that&#8217;s caught by the surrounding <code>do\/catch<\/code>.<\/p>\n\n\n\n<p><strong><code>DispatchQueue.main.async { self.current = ... }<\/code><\/strong> \u2014 Remember how <code>dataTask<\/code> runs on a background thread? Here&#8217;s the catch: <strong>UI updates in SwiftUI must happen on the main thread<\/strong>. If you try to update a <code>@Published<\/code> property from a background thread, you&#8217;ll get a runtime warning (or a crash). <code>DispatchQueue.main.async<\/code> jumps back to the main thread before making the update. This one line is what keeps our app safe.<\/p>\n\n\n\n<p>Finally, <strong><code>.resume()<\/code><\/strong> \u2014 data tasks don&#8217;t start automatically. You have to call <code>.resume()<\/code> to kick them off. It&#8217;s easy to forget, and if you do, nothing will ever happen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: The View<\/h2>\n\n\n\n<p>With the data model and the fetching logic in place, we can finally build the UI.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>struct ContentView: View {\n    @StateObject var viewModel = WeatherViewModel()\n    var body: some View {\n        NavigationView {\n            VStack {\n                if let temp = viewModel.current?.temperature_2m {\n                    Text(\"\\(temp, specifier: \"%.1f\") \u00b0F\")\n                        .font(.system(size: 48, weight: .semibold, design: .rounded))\n                        .monospacedDigit()\n                } else {\n                    ProgressView(\"Loading current temperature\u2026\")\n                }\n            }\n            .onAppear { viewModel.fetchWeather() }\n            .navigationTitle(\"In Athens, GA, it is currently the following temperature:\")\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong><code>@StateObject var viewModel = WeatherViewModel()<\/code><\/strong> \u2014 <code>@StateObject<\/code> tells SwiftUI to create this object once and keep it alive for the lifetime of the view. It also subscribes to the ViewModel&#8217;s <code>@Published<\/code> properties so the view re-renders whenever they change. (You might also see <code>@ObservedObject<\/code> elsewhere \u2014 the difference is that <code>@StateObject<\/code> <em>owns<\/em> the object and manages its lifecycle, while <code>@ObservedObject<\/code> receives one that was created and owned somewhere else.)<\/p>\n\n\n\n<p><strong><code>if let temp = viewModel.current?.temperature_2m<\/code><\/strong> \u2014 This is optional binding. <code>viewModel.current<\/code> is <code>nil<\/code> at first (we haven&#8217;t fetched anything yet), so we can&#8217;t just write <code>viewModel.current.temperature_2m<\/code> \u2014 that would crash. The <code>if let<\/code> checks whether the value exists: if it does, it unwraps it into <code>temp<\/code> and runs the first branch; if it doesn&#8217;t, it falls through to the <code>else<\/code>.<\/p>\n\n\n\n<p><strong><code>Text(\"\\(temp, specifier: \"%.1f\") \u00b0F\")<\/code><\/strong> \u2014 Swift&#8217;s string interpolation lets you embed a format specifier directly. <code>%.1f<\/code> means &#8220;one decimal place,&#8221; so 68.4321 displays as &#8220;68.4&#8221;. <code>.monospacedDigit()<\/code> makes the digits fixed-width so the number doesn&#8217;t shift layout as it updates.<\/p>\n\n\n\n<p><strong><code>ProgressView(\"Loading current temperature\u2026\")<\/code><\/strong> \u2014 This is what shows while we&#8217;re waiting for data. SwiftUI&#8217;s built-in spinner with a label. It gives users immediate feedback that something is happening, rather than a blank screen.<\/p>\n\n\n\n<p><strong><code>.onAppear { viewModel.fetchWeather() }<\/code><\/strong> \u2014 This modifier fires as soon as the view appears on screen. It&#8217;s how we kick off the fetch: the moment <code>ContentView<\/code> loads, it tells the ViewModel to go get the weather. We do it here rather than in an initializer because <code>.onAppear<\/code> is tied to the view lifecycle \u2014 if you navigate away and come back, it&#8217;ll fetch fresh data automatically.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: The Preview<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#Preview { ContentView() }<\/code><\/pre>\n\n\n\n<p>This one line is all Xcode needs to render a live preview of your app in the canvas. It just instantiates <code>ContentView<\/code> \u2014 nothing fancy. You&#8217;ll notice the preview shows the <code>ProgressView<\/code> spinner, since it won&#8217;t actually make a network call in the preview canvas.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"473\" height=\"1024\" src=\"https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-473x1024.png\" alt=\"iPhone Simulator showing the Weather App displaying the current temperature in Athens, GA as 64.4 \u00b0F\" class=\"wp-image-4210\" srcset=\"https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-473x1024.png 473w, https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-139x300.png 139w, https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-768x1662.png 768w, https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-710x1536.png 710w, https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57-946x2048.png 946w, https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/Simulator-Screenshot-iPhone-16e-2026-03-25-at-18.21.57.png 1170w\" sizes=\"auto, (max-width: 473px) 100vw, 473px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Why Is It In This Order?<\/h2>\n\n\n\n<p>Here&#8217;s the key insight: <strong>each layer of the file depends on the one defined before it<\/strong>. The View uses the ViewModel. The ViewModel uses the data model structs. The structs use Swift&#8217;s built-in types.<\/p>\n\n\n\n<p>Swift compiles files top to bottom. If you tried to put <code>ContentView<\/code> at the top, Swift would hit <code>@StateObject var viewModel = WeatherViewModel()<\/code> and complain that it doesn&#8217;t know what <code>WeatherViewModel<\/code> is yet. Same thing if you put <code>WeatherViewModel<\/code> before the structs \u2014 it references <code>CurrentWeather<\/code> and <code>CurrentWeatherResponse<\/code>, which wouldn&#8217;t exist yet.<\/p>\n\n\n\n<p>So the order follows the dependency chain: <strong>define the things that are depended on before you define the things that depend on them<\/strong>. That&#8217;s true in most programming languages, but in SwiftUI it maps neatly onto MVVM \u2014 which is part of why the pattern feels so natural here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Your Turn<\/h2>\n\n\n\n<p>Try making a few changes to make sure you understand how the pieces connect:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Download the starter file to follow along: <a href=\"https:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/WeatherApp-Starter.zip\">WeatherApp-Starter.zip<\/a><\/li>\n\n\n\n<li>If you&#8217;d like, you can also download the completed file (with bonus additional formatting and relative humidity call!): <a href=\"http:\/\/nmi.cool\/appdev\/wp-content\/uploads\/sites\/17\/2026\/03\/WeatherApp-Completed.zip\">WeatherApp-Completed.zip<\/a><\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Change the <code>navigationTitle<\/code> to something shorter and snappier.<\/li>\n\n\n\n<li>The Open-Meteo API also supports <code>wind_speed_10m<\/code> as a current variable. Try adding it to the URL and displaying it alongside the temperature. You&#8217;ll need to add a property to <code>CurrentWeather<\/code>, update the URL string, and add a second <code>Text<\/code> view.<\/li>\n\n\n\n<li>What happens if you remove the <code>.resume()<\/code> call? What does the app do? Why?<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>In Homework 9, we learned how to call an API and display data in SwiftUI. Now we&#8217;re going to do it again \u2014 but this time we&#8217;ll write the whole thing ourselves, from scratch. The app is simple: it fetches the current temperature in Athens, GA from the Open-Meteo API and displays it on screen. &hellip; <a href=\"https:\/\/nmi.cool\/appdev\/homework-9a-building-a-weather-app\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Homework 9a: Building a Weather App<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4204","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/pages\/4204","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/comments?post=4204"}],"version-history":[{"count":8,"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/pages\/4204\/revisions"}],"predecessor-version":[{"id":4264,"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/pages\/4204\/revisions\/4264"}],"wp:attachment":[{"href":"https:\/\/nmi.cool\/appdev\/wp-json\/wp\/v2\/media?parent=4204"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}