Apple announced SwiftUI at the WWDC 2019, and I couldn’t be more excited to try it. I’ve watched sessions and tried tutorials on the web. But I wanted to do more than a non-functionnal Avocado toast app. So I’ve written a weather app using SwiftUI. Here is how.
Get started with the API
There is a lot of free weather API you can use today. I’ve chosen the Dark Sky API for its simplicity. It is free and you will have to create an account to generate your key to make this app work. You can create your account here and generate your key.
You can try if it’s working by opening your terminal, and typing:
This command should return a JSON file containing the weather data for the city of Chambéry, France. You are now ready to configure the Xcode project.
Get started with Xcode
To play with SwiftUI, you’ll have to download the Xcode 11 beta on the Apple Developer website. Make sure you have an Apple Developer account and a good internet connection as Xcode 11 is a 5Gb to download.
Once you’ve completed this step, you can finally start your first SwiftUI project. Isn’t it exciting? 😃
Open Xcode, click on Create New Project (or go to File > New > Project…). Select Single View App. Then name your project “Weather” and be sure that the SwiftUI checkbox is checked. Click next to create your project.
You are now ready to dive into the code.
Create your model
The weather model
Now that you know the url to fetch data from a city from the Dark Sky API, let’s create our model to retrieve the data in our app.
Our weather model should look like this:
You can also create a WeatherManager that includes your Dark Sky API key.
Our model is pretty simple. It conforms to the Codable protocol that will make the task easier for us to convert the Dark Sky JSON to our Weather class. This is a classic model, nothing is very interesting in this part that’s why I will not explain it in details. Now let’s create our model for each city.
The city model
The city model is more interesting because it will introduce Binding.
Binding was first introduced to Mac OS X in interface builder. It allowed us to observe a variable and change the UI of our app automatically according to the model changes. No delegate, no completion handler. That’s binding. In Mac OS X, it was not very easy to use, and even worse to debug. These days, Apple tended to go back to data source delegate instead of binding for their components, mostly for reducing the difference with UIKit which doesn’t use binding.
Note: the concept of binding is not specific to Swift or Objective-C.
Today, with Combine and SwiftUI, Apple has made binding much more easier. Let’s see how by creating our city model.
At first, let’s create a City object that will contain a name and its weather. The City object should look like this:
This code contains some new stuff. Don’t panic, we’ll get into it step by step.
At first, you can see that City conforms to BindableObject which is a new protocol introduced in Combine. BindableObject allows our object to be observed when some properties change. It requires the didChange variable. You should call didChange.send(self) every time a data changes in your class. Here, you can see that it is called on the didSet of the weather variable. This means that every time the weather variable will be set, all the observer of the City object will be notified that this object has changed.
Note: I didn’t call didChange.send(self) on the name property, because we will suppose that the name of the city will never change once it is initialized.
The getWeather() method fetches the weather for this city with URLSession asynchronously once the city is initialized. When the data is fetched, it decodes the JSON and sets the fetched weather.
Important note: according to the Apple Documentation, didChange.send(self) should only be called on the main thread. That’s why I set the weather on a DispatchQueue.main because the URLSession task is executed on a background thread.
You’ve just created a bindable object. Great work.
Now, let’s create a CityStore that will contain our city list. This will also be a bindable object to observe when a city is deleted or added. And because you just understood how to make a bindable object, this will be super easy right?
Okay let’s synthesize this:
- The BindableObject protocol lets me observe changes in an object by calling didChange.send(self) on variables setters.
- We’ve created a City class that conforms to the BindableObject to observe changes in one city (here when the weather is loaded).
- We’ve created a CityStore class that conforms to the BindableObject to observe changes in our city list (here when you add or remove a city).
Create the UI
If you’ve never seen how to build UI using SwiftUI, I highly recommend you to get started with the Apple Documentation about it.
Creating user interfaces with SwiftUI is simple and fun. I’ve always been using Storyboard to build my UI, but SwiftUI has set the bar very high.
Storyboards no longer need to exist, because SwiftUI will preview your code live. If you change the code, the preview will be updated. But better: if you change the preview, your code will be updated. And that’s truly fantastic.
Now, let’s create our city list view.
The code should be pretty easy to understand, but there are some new things here. Again, let’s address them step by step.
Note: Swift 5.1 introduce a new feature called Property Wrappers. As Property Wrappers are a bit complex to understand, I recommend you to read this article on it.
var body: some View
This variable represents the body of your view. This is where you should build your interface.
@EnvironmentObject allow you to save an object that will be used across all your app. It will fetch an existing instance of this class and use it. This is useful for an object you want to fetch anywhere across your app such as a user, or here, our city list.
@State is a property that represents a state in our view. @State should only be used in one view and declared as private to avoid using them anywhere. For example, in our CityListView, there is a state to check if you are editing our list.
Binding is used to bind a @State variable. In some cases, you want to observe changes of a @State property in your child view. In that case, you can use @Binding in your child view.
This property is used to observe changes for objects that conform to the BindableObject protocol we saw earlier.
All these properties will allow us to keep our view up-to-date as the data will change. To make the CityListView the first view presented on the screen, go to SceneDelegate.swift, and edit the file like this:
Here, you instantiate a CityStore and pass it as an EnvironmentObject to our CityListView.
You can now create a weather view for each city.
Note: we use a @ObjectBinding in our CityView. It allows the user to go to this view even if the weather is not loaded. Once the weather is loaded, all the UI containing a reference to this object will be re-rendered. This works only if your object conforms to the BindableObject protocol.
And all the subviews:
Note: CollectionView is currently not an existing component in SwiftUI. To make the horizontal scrollable view, you have to use a ScrollView with an HStack inside that contains the hourly views.
Finally, you can add a view for adding a city. Let’s create our model that will fetch cities for a given text. Again, this will be a bindable object to observe the search results that will be updated asynchronously.
We use MapKit to suggest places according to our search.
Now, let’s create a modal view to add a new city.
This view is interesting for 2 reasons:
- It shows us how to display a modal controller. To do it, you have to pass a boolean variable to our modal controller and set it to false when it needs to be dismissed. At the moment, dismissing a modal view is a bit buggy.
- @EnvironmentObject is used to access our model. There is no data duplication. Only binding.
Here it is. You’ve built your weather app using SwiftUI!