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.