Create a weather app from scratch with this SwiftUI Crash Course

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hi, Stephanie from Design+Code. In this tutorial, we'll learn how to create a simple weather app, all in SwiftUI. We'll start from scratch from the Hello World screen, all the way to creating a beautiful weather app with dynamic data, depending on the user's location. So let's dive right in. We'll first start by getting the user's current location coordinates using the Core Location framework from Apple. Then we'll learn how to use these coordinates to get the current weather information with open weather, a free weather API. Finally, we'll present all the data we fetched in this beautiful UI. As we'll be building a mobile application with SwiftUI, you need to make sure to have Xcode 13 installed on your Mac, as well as an actual iPhone or iPad running on iOS 15 or later, to test on. As you might have guessed it, we'll use the latest APIs and technologies introduced in iOS 15, so make sure to have Xcode and your phone updated to the latest versions. Also, as we will require the user's location, we cannot test with the simulator and we'll need to test with an actual device. Now, let's not waste any more time and start straight away. First, let's create a new Xcode project. In the modal that appears when you launch Xcode, click on create a new Xcode project. Next under the iOS tab, select app, and then click on next. Give your project a name. In my case, I will call it Weather Demo. And then you can select your team and your organization identifier. This is the reverse of your URL. So in our case, since our website is designcode.io, we will write io.designcode. Make sure that the interface is SwiftUI and the language is Swift. Usually these are the default values, so you don't need to change anything, then click on next. Choose where you want to save your project and click on create. Now that our project is created, we need to make sure to target devices on iOS 15 or higher. In other words, only devices running on iOS 15 will be able to download and use this application. So click on your project name. Then under target, click again on your project name. Under general and deployment info, we need to select iOS 15 as our iOS target. If you want, you can also add an icon to your application by going into the assets folder. And then under app icon, you need to add an icon for each empty slot that you can see on the screen. This is the logo you'll see when you install the application on your phone. So here I have a folder with all the icons for all the sizes. So I'll simply need to drag and drop them into each empty slot for their corresponding size. Now at a setup is done, we need to create a location manager. This will be the file in which we'll handle everything related to the location. So under the weather demo folder, we'll right click and create a new group. This group will be called managers, because we'll have two different managers files in our project. Then, let's right click this folder and create a new file. This file will be a Swift file. And we'll call it LocationManager. Then click on create. At the top, we'll need to import foundation, as well as CoreLocation. CoreLocation is the framework from Apple we'll use to get the user's current location. Next we'll need to create our LocationManager class, so we'll do class LocationManager. And it will need to conform to a few protocols. The first one is the NSObject protocol, then the second one is the ObservableObject, and the last one is CLLocationManagerDelegate. This will allow us to manage everything related to location without running into any error. Then at the top of the class, we'll need to add a manager. So let manager and we'll need to create a new instance of CLLocationManager. To create a new instance in SwiftUI, we'll need to add parentheses at the end. And right below that we'll add two published variables: a location and a loading state. So we'll need to do @Published var location, which will be of CLLocationCoordinate2D and it will be optional, because we might not get a location, for example, if the user refuses to grant us access to their location. The second published variable is called isLoading and we'll set it by default to false. Finally we'll need to add a few functions to this class. The first one is an override init function. When our class will be initialized, we need to call the super dot init. And then, we need to set the manager delegate to self. After the init function, we'll create a function called request location. And inside of it, we'll set is loading to true. Then we'll call our location manager and call dot request location. Right below that, we need to add two functions to make the CLLocationManagerDelegate work. The first one is the didUpdateLocations function. You can simply start typing didUpdateLocations and you will find this function in the suggestions from Xcode. Inside of this, we will assign the location variable that we created on line 14. So location will be equals two locations and this is an aray of CLLocation, so we'll get the first one and this might be null, so we'll add an optional, dot coordinate. And right below that, we'll do isLoading false because we got the location, so the state is not loading anymore. Finally, we need to add a function to handle errors. So the function is called didFailWithError. And we'll print a message that says error, getting location. And we'll print the actual error. We'll also set that is loading to false, if we run into an error. For the purposes of this tutorial, we'll request the user to share their location every time they launch the app. Of course, this is not the best user experience, but we want to keep this tutorial simple. You can take it a step further and always get the current location on the background by reading Apple's documentation on the subject. You can find this link in the text content of this section. Now that we created our LocationManager, it's time to get the coordinates. Since we'll have a few views in our project, let's organize a bit our files. Under the weather demo, we will create a new group called Views. Let's drag and drop the content view inside of the views folder. Then, open the content view and on the first line, after defining your struct, we will initialize our LocationManager. So we'll do @StateObject var locationManager is equals to the LocationManager with parentheses at the end, because that's how you initialize something in SwiftUI. We also added the StateObject property wrapper, so that the view can be notified every time that the published variables in our LocationManager are updated. Now, we'll create our welcome view. So before creating this view, let's simply set our device to something else, other than Mac. Let's set it to an iPhone 13. Now let's create the welcome view. So under the views folder, we will create a new file. Instead of selecting a Swift file, we'll select a SwiftUI view, so that we have the basic layout of a view already coded for us. This view will be called WelcomeView. Then, click on create. Let's resume our preview here. We'll add an environment object at the top of this struct. So we'll do at EnvironmentObject var locationManager will be of LocationManager type. Let's call the welcome view in the body of content view. So let's go back to content view. And instead of displaying a text saying hello world, we will add a VStack. And inside we'll call the WelcomeView. Remember to add the environment, object modifier and to pass the locationManager if you don't want to run into a crash later on. On the VStack, we'll also add a background color. If you want to select a specific color in SwiftUI, you can open the editor on the left here. And then add any color and you'll be able to select custom, and then select any color that you want with the color picker. In my case, I selected a dark blue color. Then I'll also set the preferred color scheme to dark. Now let's code the WelcomeView. So let's remove the text here and add a VStack. The VStack will have a frame with a maxWidth of infinity, as well as a maxHeight of infinity as well, so that it takes the whole screen. Inside of this first VStack we'll add another one. So VStack with the spacing of 20 pixels. Inside of this, we'll add a first Text that says Welcome to the weather app. Let's make this bold, with the font title. Then let's add another subtitle that will say Please share your current location to get the weather in your area. Let's add some padding to this text. On the VStack we'll add two modifiers. The first one will be the multiline text alignment to center, because we want everything to be centered. The second one will be some padding surrounding this VStack. Right below this VStack, we will add a LocationButton. So LocationButton and you might run into an error that says that Xcode cannot find LocationButton in scope. To fix this error, at the top, we need to import CoreLocationUI, because the location button comes in this framework. So let's add an argument in the parentheses of the location button, which will be .shareCurrentLocation. Then we need to add a closure right after the location button. Inside of this closure will be the action that we want to perform when the user clicks on that. So we'll call our location manager dot request location. And you can see that we have our location button here. So let's change it a bit. We'll add a border radius of 30 pixels, a symbol variant of fill, so that means that the symbol here will be filled, and we'll add a forgone color of white. Now if you play the preview, you will see that the white will become sharper and it will not be a white with some opacity as if when you're not playing the preview. Now, we finished our WelcomeView. Let's go back to the content view. And we'll display the coordinates of the user to see if our code works. If we take a look at the location manager, you'll see that we're saving the location in the location variable. We can access it in our content view by doing location manager dot location. So right before calling our WelcomeView, let's add an if statement, so if let location equals to location manager dot location, then we will display a text that says your coordinates are and we'll get the location dot longitude, as well as the location dot latitude. We need to write an if statement, because the location is optional. So if we don't have a location, we won't display the coordinates. Let's also add an else statement. Inside, we'll check that if location manager dot isLoading, we'll display a progress view. Else, we'll display our WelcomeView, so let's move that inside of the else statement. Now let's add some modifiers to the progress view. As we will be reusing the same component, we can create a new view with only a progress view. So under the views folder, we will create a new file. And it will be a SwiftUI View. We'll call this file a LoadingView. Let's create it. And instead of displaying a text that says Hello world, we'll call the progress view, and add a few modifiers to it. The first one will be the progress view style. It will be a circular progress view style, with a tint. And the tint should be a color, so it will be white, because we set our preferred theme to dark. Next we'll also add a frame with a maxWidth infinity and a maxHeight of infinity as well. So right now we cannot see the progress view because it's white on white. Let's go back to content view. And instead of calling the progress view here, we will call the loading view. And if we click on the share current location button, you can see our loading indicator here, and it will always show the weather app, because we cannot get the current location on the simulator. To fix this, and in order to test if we really do get the location of the user, connect your device to your Mac and instead of targeting iPhone 13, we'll target our iOS device. So in my case, I will select my phone. And if you don't see your phone in the list here, simply click on Add additional simulators and you will be able to select your device in the list. So I'll simply select this and build this application on my phone. So right now I have my screen here on my phone. And I will click on share current locations. And you can see it in the screen recording, but I have an alert asking me if I want to share my location with this application, so I will click on okay. And it will share my location. Then I will click again on this button. And you can see that I have my coordinates printed on my screen. Right now, the UI is not perfect, but we will be fixing that later on. So congratulations! We can now get the weather with these coordinates. Otherwise, if you don't see your coordinates, simply go back to the previous steps and see if you missed a step or forgot to add something in the code. Next, let's get the weather information. Now that we got the coordinates, we can easily get the weather information at that location with the open weather API. You will first need to create a free account. Once you're in your account, click on your username and under your API keys, copy your API key. So you can copy the default one or generate a new one and copy that one. Now, let's go back to Xcode. Under the Managers folder, we'll create a new file. This file will be a Swift file and we'll call this file weather manager, because this is where we'll call the API to get the weather. At the top, after importing Foundation, we also need to import CoreLocation. Next, let's create a class called WeatherManager. Inside of it, we'll just add one function called getCurrentWeather. So we'll do func getCurrentWeather. And this function will accept two arguments. The first one will be the latitude, which is of CLLocationDegrees. We'll also have a longitude, which is also of CLLocationDegrees. This function will use the new async await method introduced at WWDC21. So let's add async and it might also throw an error, so we'll add a throws keyword to this function. You can learn more about async await by heading over to the HTTP request with async await section of this handbook. Inside of this function, we will add a guard statement at the top. So guard let URL and we'll create a URL from a string. If we don't have a URL, we'll throw a fatal error that says Missing URL. So what should this URL be? This will be the URL endpoint that we will call in order to get the data from the API. Let's go to the API page on the open weather website and click on the API doc related to the current weather data. In here, you can see that we can query the API by city name. But since we have the coordinates of the location, on the right side, we will select the by geographic coordinates. And here is the URL that we'll call in order to get the weather data. So let's copy this. And paste it inside of the double quotes of the URL. So we need to provide the latitude, a longitude as well as an API key. Let's remove the placeholders and add these variables. So the latitude will be the latitude variable that we passed to this function and the longitude will be the longitude that we passed this function. Finally, we have the API key. Simply paste the API key that you copied before on the open weather website. In my case, I was able to write abcde for now. And I also want to use the metric units, so I will add and units equals metric. That means that the weather will be in Celsius rather than in Fahrenheit. Now we need to create a URL request. So we'll do let urlRequest is equals to a URLRequest with the URL that we created on line 13. Right below that, we need to try await the URL session dot shared dot data for the URL request. Let's save what we get in a data, as well as the response variables. Next, we need to guard to make sure that we have a response. And we need to make sure that the status code is 200 OK. Else, we'll throw another fatal error that says Error fetching weather data. And then, we need to decode the JSON data into SwiftUI data. So you can find the response body in the text content of this section. I simply pasted it below in the same file. You can copy the response body that you can find in the text content, or you can create your own by calling the endpoint and seeing what the JSON object contains. This is a model of the response that we get from the Open Weather Map API. So we'll try the JSON decoder dot decode. And we want to decode that into our response body, from the data variable that we got by calling the URL session. Let's save that in a decodedData variable and return the decoded data. Now you might run into an error that says Unexpected non-void return value in void function. We simply needs to add a return, and this function will return a response body. Great! Now we finished coding our WeatherManager. Let's go to ContentView and we'll create a new instance of the weather manager at the top. So var weatherManager will be equals to WeatherManager with parentheses. Right below that we'll create a state called weather. And it will be of type ResponseBody that we created in our weather manager file. Inside of the if let location equals to location manager dot location statement, we'll remove the taste and add another if else statement inside. So if let weather equals weather because it's optional, so we need to unwrap that optional, we'll display a Text that says Weather data fetched. Else, we'll call our loading view. On the loading view, we'll add a modifier and it will be the task modifier. The task modifier, as you can see here, it adds an asynchronous task to perform when this view appears. So, this is pretty similar to the onAppear, but it works with asynchronous code, since the function that we created in our weather manager is asynchronous. So we'll wrap everything in a do catch statement. In the catch, we will print Error getting weather and we'll print the error that we got. In the do, we need to try await and then we'll call the weather manager dot get current weather. We need to provide a latitude. To access the latitude, we need to do location dot latitude. The location is the location we got from our location manager. We also need a longitude, so we'll call location dot longitude. And if you remember, our get current weather function returns us the weather. So we can save that in the weather state that we created in our content view. Now let's build our application on our device. And see if we do get the text, weather data fetched. If you run into an error that the URL is not supported, remember to add the HTTPS at the beginning of the URL in your weather manager. So let's build our application. And we'll click on the share current location button. So now we see our loading screen. And sweet! We got our weather data fetched. So that means that we got our data and we can now display that in a nice UI. We'll be creating a new view, so let's create a new view under the views folder. New file. It will be a SwiftUI View and we'll call this view, WeatherView. This view will require a variable called weather and it will be of ResponseBody type. Right now you might run into an error that says missing argument for parameter weather in call. That means that our preview needs a weather variable as well in order to work. To fix that, we will copy the load function from the section Data from JSON of the SwiftUI Advanced handbook. You can find the link to this section in the text content of this section. Under the preview content folder, we'll create a new file and it will be a swift file, we will call this ModelData. And we'll paste our load function in here. Next we need to create another file, still under the preview content folder. We'll search for an empty file and select the one under the other category. This will be our JSON file in which we will store some dummy weather data. So let's call this weather data dot JSON. Now you can find the API endpoint to the current weather in New York. Simply replace what comes after the app ID by your own API key, and you will be able to see the weather conditions right now in New York. So I'll simply copy everything in the JSON. And paste it in the weatherData.json file in my Xcode. To format the JSON data, you simply need to do command A to select everything and then click on control I and it will format the entire JSON for you. Now back in ModelData, we will create a variable called preview weather. It will be of response body type and we'll call the load function and load the data from the weatherData.json file. Now let's go back to the weather view and we can now pass a weather variable to our weather view preview. And it will be the preview weather. Now let's go back to content view. And instead of displaying the text Weather data fetched, we will call our weather view. Remember to pass the weather to our view, and this will silence the warning that we had before, because we weren't using the weather. Now, as the data we will fetch from the Open Weather API will return us doubles. We need to round the doubles to zero decimals. So let's create a new file under the WeatherDemo folder. This will be a Swift file and then we'll call it extensions. Inside of this extensions file, we will need to create an extension on Double. So we will do extension double. Inside, we'll create a function that is called roundDouble and it will return us a string. Inside of the function, we'll simply return a string in the format percent dot zero F on self. That means that we will round the double, on which we will call the roundDouble function, to zero decimals. Now, let's code the body of the WeatherView. First let's remove the text Hello world and we'll add a ZStack, with some alignment to leading. We'll also add a few modifiers to the ZStack, like edges ignoring safe area dot bottom, so that our background color that we'll add will also cover the bottom safe area. Next, we want to add some background color, and we want the same background color that we added in content view. So let's go to content view and copy the background color, as well as the preferred color scheme to dark. And then add these modifiers to our ZStack. Now let's take care of the top half of this screen first. Let's add a VStack inside of the ZStack, with some padding and with a frame with a maxWidth of infinity and alignment to leading. Inside of the VStack, we'll add another VStack. This VStack will have an alignment to leading as well and some spacing of five pixels, meaning that each element inside of this VStack will be spaced out with five pixels. We'll also add to frame modifier. It will have in maxWidth of infinity and an alignment to leading. Inside of the VStack, we will add two elements. The first one will be a text that will display the city name in which we currently are. If we take a look at the JSON data that the Open Weather API returns us, you can see at the bottom here that we have a name key, which is New York. Since we queried the weather in New York, the name here will be the name of the city. So in the text, we will display weather dot name, which will be New York in this case, because it's calling the preview data that we added to our project. Now, let's add some boldness to this text and a front of title. Right below that, we'll add another text. And it will display today's date. So we will create a new date and we'll call the new formatted function introduced in iOS 15, that allows us to easily format dates. We will call dot date time and we want to show the month, the date, the hour, as well as the minute. So today it's December 1st and right now it is 3:06 PM. Let's add some fontWeight light to our date. Right below the title VStack, we'll add a spacer to space everything out. And you can see that right now, my dark blue background is taking the entire screen. Right below this spacer, we'll add another VStack. This VStack will contain the icon of the current weather condition, the current weather, the feels like temperature, as well as an image of a city using the new async image introduced in iOS 15. So the VStack will have a frame with a maxWidth of infinity. Then inside of that, we will add an HStack. The HStack will contain the feels like temperature. So let's add a text and we'll call weather dot main dot feelsLike. And you can see in the JSON data here, that the main dot feels like returns as a double, so we need it to call the roundDouble extension that we just created before. So we will do dot roundDouble and we'll do plus, and we'll add the degrees symbol. And you can see at the bottom here, we have our nine degrees displayed. Let's make it bigger, so we'll add a font with a system size of 100 pixels, some fontWeight of bold. As well as some padding around this number. Right before this text, we'll add a VStack with some spacing of 20 pixels. We'll also add a frame with a width of 150 pixels and some alignment to leading. We'll display an image with a system name. So the system name takes the name from SF Symbols. So I will open SF Symbols. And if you don't have SF Symbols installed on your computer, I highly encourage you to download it in your computer. If we take a look at the conditions right now, you can see that we have a clear sky. So back in SF Symbols, let's search for weather and find an icon that represents a clear sky. So I would choose the sun max symbol, so I will right click and copy the name. And I will add a string here, which is sun dot max. We'll also change the font with a system size of 40 pixels. Right below the image, we will display the weather conditions, so it will be weather dot weather and we'll get the first item of the array. If we take a look at the JSON, you can see that the weather is an array. So we're taking the first item and we want the dot main, which is clear. Dot main. So you can see right now, I have a sun and a clear at the bottom, with the fields like temperature on the right side. Now, right between the VStack and the feels like temperature, we will add a spacer. We will add a spacer, so everything is stretched out and on the sides of the screen. Now that we're done with our HStack, we'll add another spacer right below, with a frame of a height of 80 pixels. Then we'll add an image of a city. So I will be adding this image that I found online, but you can add any other image that you want. Let's call the new async image introduced in iOS 15 and it requires a URL. So let's create the URL from a string, and I just copied and pasted it here. And we can have a closure with async image to get the image and add some modifiers to this image. So we'll do image dot resizable, dot aspectRatio. The aspect ratio content mode will be fit and we'll have a frame with a width of 350 pixels. Remember to also add a placeholder and we'll add a progress view. The placeholder is what the user sees while the data of the image is being downloaded. And then, right after our async image, we will add a spacer. Next, let's code the bottom part of the UI, which will be a white rounded rectangle at the bottom that lets the user know more information about the current weather at their location. First, we'll need to add another extension in the extensions file. So right below the double extension, we'll create another one. And this time, the extension will be on view. And inside of the extension and we'll create a function called cornerRadius. Basically, it will create a clip shape with only some specific corners that will be rounded. You can find this function in the text content of the section. And right below this extension on View, we'll also needs to add a struct called rounded corner. And right now you can see that we have a bunch of errors that they cannot find type view, they cannot find type shape and etc. To fix this, we simply needs to import SwiftUI at the top. Now let's go back to WeatherView and we'll add a VStack. This second VStack will be right below the first VStack parent that we created before. So scroll down to the bottom and it should be right before the edges ignoring safe area. So let's create the VStack here. Inside of that, we will add a spacer at the top and another VStack at the bottom. This child VStack will have an alignment to leading and a spacing of 20 pixels. We'll also add a few modifiers to this. VStack. So it will have a maxWidth of infinity. And an alignment of leading as well. Then we'll add some padding all around this VStack. And we'll add some bottom padding of 20 pixels. We'll also add the foreground color, which will be the same color as the dark blue background here. So let's simply copy the color of the background. And paste it for the foreground color. We also want the background to be white. And we want to add some corner radius at the top left and the top right corner. Luckily, we already created that extension in the extensions file, so we simply need to call, corner radius of 20 and we need to provide some corners. It will be an array. And we want the top left as well as the top right to be rounded. So you can see right now that we have rounded corners at the top. Now we'll need to create a new component called WeatherRow. So let's create a new folder. And this folder will be called Components. Inside of this folder, we'll create a new file and it will be SwiftUI view. We'll call it WeatherRow. You can copy and paste the entire weather row component that you can find in the text content of the section. This component will require a logo, a name as well as a value. So we have a circle with the logo, as well as the title that we want to display and the value. Now let's go back to WeatherView and right inside the VStack of the rounded corner rectangle, we'll add a Weather now title, so we'll do Text, Weather now and it will be some bold text with some padding at the bottom. Then we'll add an HStack. The HStack will contain a WeatherRow and the logo will be a string, that will take care after. We'll have a name and we'll show the minimum temperature. The value will be weather dot main dot tempMin. And we want to round this double. And we also want to add the degrees sign right after that. For the logo of the minimum temperature, let's take a look at SF Symbols. So we'll search for weather. And let's find a good icon for the minimum weather. Let's take the thermometer, copy the name and paste it at the logo here. Right below this weather row, we will add a spacer. And we'll copy and paste that weather row again. Instead of minimum temperature, it will be max temperature. So instead of calling weather dot main dot tempMin, we'll call tempMax. In New York, the minimum temperature will be seven degrees and the maximum temperature will be 11 degrees. Now let's copy this HStack and pasted below. Instead of showing the minimum temperature we'll show the wind speed. And the value, instead of weather dot main dot temp minimum, it will be weather dot wind dot speed. And we will call the roundDouble to round it to zero decimals. Instead of showing the degrees symbol, we'll add meters per second, because the wind that open network returns us is in meters per second. And we'll also replace the logo. So if we search for wind, you can see that we have a wind icon here. So let's copy this name. And paste it in the place of the thermometer. Finally, we will show the humidity, so let's call humidity. And the humidity can be accessed by calling weather dot main dot humidity and we'll roundDouble, instead of a degrees, it will be a percentage. Instead of a thermometer, let's display a humidity logo. So let's search for humidity. And we'll select this one. And paste it here. You can see that I have a typo here and I wrote Max temp temp, so I'll simply remove one. So congratulations! We are done with the coding part. We'll simply need to test out our app on our device. Build and run your application on your actual device and then share your current location. And you should see the current weather conditions at your location. Sweet! Great! You built a SwiftUI application from scratch from a Hello world screen, all the way to this beautiful UI weather app. We learned how to get the user's current location, call an API to fetch weather conditions of the location and display all the information in a beautiful UI. Since we want to keep this tutorial simple and not too long. There's a lot more to cover, of course. For example, we can add a dynamic icon for the current weather condition, instead of hard coding a sun SF symbol. We can also add a dynamic background color depending on the time of the day and the weather conditions. And there's also a lot more that you can do to improve this weather app, but I will live it to your imagination. I hope that you learned a lot in this video. And see you in the next one.
Info
Channel: DesignCode
Views: 22,248
Rating: undefined out of 5
Keywords: designcode, course for beginners, api, fetch, data, query, swiftui, ios, app dev, SwiftUI app dev, learn SwiftUI, online course, crash course, build a SwiftUI app, weather, app, open weather api, corelocation, location, coordinates, from scratch, tutorial, json
Id: X2W9MPjrIbk
Channel Id: undefined
Length: 37min 3sec (2223 seconds)
Published: Fri Dec 03 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.