Create a Weather App with Forecast – User Interface

In the last article of this series, I will show you how to create the user interface of our weather application. Thanks to the work of Chris Carey, a regular contributor of Vectortuts+, we have a beautiful design that we can work with!


Introduction

For the design of our weather application, I collaborated with Chris Carey, a regular contributor of Vectortuts+. Chris crafted a beautiful design that we can use to create the application's user interface. After Chris handed me the design as a vector illustration, I sliced the artwork and prepared it for use in our project. You can find the sliced artwork in the source files of this article.

Create a Weather App with Forecast – User Interface - Chris Carey's Design
Figure 1: Chris Carey's Design

Due to the limited customizability of UIKit, we will have to make a few compromises when implementing Chris' design. The final result, however, will very much look like the design that Chris created for us. Customizing UIKit's UISwitch class, for example, is very limited and we will therefore use a different approach to implement the temperature setting. Chris used two custom fonts for his design, Maven Pro and Mission Gothic. Even though iOS supports custom fonts, we will only use fonts available on iOS.


1. Notifications

Step 1: Creating String Constants

Whenever the weather view controller receives weather data from the Forecast API, its own view and the right view need to be updated. This means that we need to notify the forecast view controller and send it the weather data. There are several ways to notify the forecast view controller of such an event. Posting a notification using NSNotificationCenter is the simplest solution and provides us with the most flexibility. We can use the same solution for updating the user interface after the user has toggled the temperature setting. By sending a notification, every object that is interested in this event can register itself as an observer. I have updated MTConstants.h/.m to create a string constant for each notification.

Step 2: Weather View Controller

After receiving a response from the Forecast API, we need to store it so that we can use it later to update the view. Create two private properties in MTWeatherViewController.m, (1) response (of type NSDictionary) that will hold the response of the Forecast API and (2) forecast (of type NSArray) that will hold a subset of the response, the weather data for the next hours.

To receive notifications, we need to update initWithNibName:bundle: as shown below (MTWeatherViewController.m). In weatherDataDidChangeChange:, we store the response of the Forecast API in response and forecast and we update the view controller's view. In temperatureUnitDidChange, we only need to update the view to reflect the changed setting.

Step 3: Forecast View Controller

The steps are almost identical in the MTForecastViewController class. We update initWithNibName:bundle: as shown below, create two properties (response and forecast), and implement weatherDataDidChangeChange: and temperatureUnitDidChange:. The difference is the weather data stored in forecast. We will implement updateView a bit later in this tutorial, but it is good practice to create a stub implementation to get rid of any compiler warnings.


2.User Interface Center View

Step 1: Outlets and Actions

Even though the center view contains a lot of information, it isn't that difficult to implement. Let's start by creating a number of outlets and one new action. Update MTWeatherViewController.h as shown below. Revisit Chris's design to better understand the location and purpose of each user interface element. The difference with Chris's design is that we will replace the calendar icon in the top right with the refresh button that we created in the previous tutorial. The weather data for the next hours will be presented in a collection view, which implies that the MTWeatherViewController class needs to conform to the UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout protocols.

We also need to add an action for the location button in the top left. Open MTWeatherViewController.m and add a new action named openLeftView:. In openLeftView:, we tell the view controller's view deck controller to toggle the left view. Remember that it is also possible to swipe from left to right to open the left view.

Step 2: Creating the User Interface

Open MTWeatherViewController.xib and create the user interface as shown in figure 2. While creating the user interface, it is important to verify that the user interface displays correctly on both the 3.5" screen and the iPhone 5's 4" screen. You can test this by selecting the view controller's view and changing the Size attribute in the Attributes Inspector. To accomplish the desired result, you need to tweak the autolayout constraints of the user interface elements. The goal is to make the weather data stick to the top of the view, while the collection view is glued to the bottom. The icons next to the time, wind, and rain labels are instances of UIImageView.

Create a Weather App with Forecast – User Interface - Creating the User Interface of the Weather View Controller
Figure 2: Creating the User Interface of the Weather View Controller

Configure the labels and buttons as shown in figure 2. This includes properly aligning the text of the labels, setting the types of both buttons to Custom, making the File's Owner the collection view's dataSource and delegate, and setting the scroll direction of the collection view flow layout to horizontal. I am a fan of Gill Sans so that is the font I chose to use for this project. Before switching back to the implementation file of the weather view controller, connect the outlets and action that we created earlier. In addition to the labels and buttons, I've also added an image view to the view controller's view to display the background image.

As I mentioned in the introduction, you can find the application's artwork in the source files of this tutorial. Create a folder named Artwork in your Xcode project and drag the artwork in this folder.

Step 3: Populating the User Interface

We currently log the response of the Forecast API to Xcode's Console. To start using the weather data, we need to update the fetchWeatherData method as shown below. In the completion block of requestWeatherForCoordinate:completion:, we hide the progress HUD and send a notification on the main thread. We make use of the dispatch_async function and pass the queue of the main thread as the first argument. The notification's userInfo dictionary is the response of the request.

The weather and forecast view controllers are both observers of MTRainWeatherDataDidChangeChangeNotification notifications. The weather view controller invokes weatherDataDidChangeChange:, which in turn invokes updateView. In updateView, we invoke updateCurrentWeather and update the collection view by sending it a message of reloadData.

Before we implement updateCurrentWeather, I want to take a small detour. We will be displaying temperature values in various places of the application and because we support both Fahrenheit and Celsius this can become cumbersome. It is therefore useful to create a class that centralizes this logic so that we don't need to clutter our code base with if statements and temperature conversions.

Step 4: Creating the Settings Class

Before we create the class that handles temperature conversions, we need to be able to store the current temperature setting in the user defaults database. Revisit MTConstants.h/.m and declare a string constant with a name of MTRainUserDefaultsTemperatureUnit.

To make working with settings easier, I often create a category on NSUserDefaults that allows me to quickly and elegantly access the application's settings. Let me show you what I mean. Create a new Objective-C category (figure 3), name the category Helpers, and make it a category on NSUserDefaults (figure 4). In NSUserDefaults+Helpers.h, we declare three class methods as shown below.

Create a Weather App with Forecast – User Interface - Creating a New Objective-C Category
Figure 3: Creating a New Objective-C Category
Create a Weather App with Forecast – User Interface - Creating a Category on NSUserDefaults
Figure 4: Creating a Category on NSUserDefaults

Even though these methods aren't magical, they are very useful. The first method, isDefaultCelcius, tells us if the temperature unit is set to Celsius or not. The other two methods make it very easy to switch between Fahrenheit and Celsius. Not only do we update the user defaults database, we also post a notification that informs observers of the change.

It is time to create the settings class that I wrote about earlier. The solution is surprisingly easy. Create a new Objective-C class, name it MTSettings, and make it a subclass of NSObject (figure 5). In MTSettings.h, we declare one class method, formatTemperature:. In MTSettings.m, we import the header of the category we created a moment ago and implement formatTemperature: as shown below. The method accepts an instance of NSNumber, converts it to a float, and returns a formatted string based on the temperature setting.

Create a Weather App with Forecast – User Interface - Creating the MTSettings Class
Figure 5: Creating the MTSettings Class

Before we continue, add an import statement for the MTSettings class to the project's precompiled header file so that we can use it throughout the project.

It is now time to implement updateCurrentWeather in the MTWeatherViewController class. The data for the current weather is a subset of the response we received from the Forecast API. The implementation of updateCurrentWeather is pretty straightforward. The only caveat to watch out for is the precipitation probability. If this value is equal to 0, the key precipProbability is not included in the response. That is the reason that we first check for the existence of the key precipProbability in the response dictionary.

Step 5: Populating the Collection View

To populate the collection view, we first need to create a UICollectionViewCell subclass. Create a new Objective-C class, name it MTHourCell, and make it a subclass of UICollectionViewCell (figure 6). Creating custom table or collection view cells can be laborious and painstaking. If you want to learn more about creating custom table and collection view cells, then I suggest you take a look at a tutorial I wrote a few weeks ago.

Create a Weather App with Forecast – User Interface - Creating a Custom Collection View
Figure 6: Creating a Custom Collection View

In the interface of MTHourCell, we declare four properties of type UILabel. We don't do much magic in MTHourcell.m as you can see below. To better understand the initWithFrame: method, revisit the design I showed you at the beginning of this article. I won't discuss the implementation of initWithFrame: in detail, but I do want to point out that I use a preprocessor define for the text color of the labels. I added the preprocess define to MTConstants.h to make it available to the entire project (see below).

As you can see below, implementing the UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout protocols is very similar to implementing the UITableViewDataSource and UITableViewDelegate protocols.

To make all this work, we need to (1) import the header file of MTHourCell, (2) declare a static string constant that serves as the cell reuse identifier, and (3) tell the collection view to use the MTHourCell class to instantiate new cells. In viewDidLoad, we also set the collection view's background color to transparant.


3.User Interface Right View

Step 1: Outlets

Even though the right view displays a lot of data, the implementation of the MTForecastViewController class isn't that complex. We start by creating an outlet for the table view in MTForecastViewController.h and conform the class to the UITableViewDataSource and UITableViewDelegate protocols.

Step 2: Creating the User Interface

Creating the user interface is as simple as adding a table view to the view controller's view, connecting the outlet we created a moment ago, and setting the File's Owner as the table view's dataSource and delegate (figure 7).

Create a Weather App with Forecast – User Interface - Creating the User Interface of the Forecast View Controller
Figure 7: Creating the User Interface of the Forecast View Controller

Step 3: Populating the Table View

Before we populate the table view with data, we need to create a UITableViewCell subclass. Create a new Objective-C class, name it MTDayCell, and make it a subclass of UITableViewCell (figure 8). Open MTDayCell.h and declare five outlets of type UILabel. As with the MTHourCell class, the implementation of MTDayCell isn't too difficult as you can see below.

Create a Weather App with Forecast – User Interface - Creating a Custom Table View Cell
Figure 8: Creating a Custom Table View Cell

The implementation of the table view data source protocol is very similar to that of the collection view data source protocol that we saw earlier. We also implement one method of the table view delegate protocol, tableView:heightForRowAtIndexPath:, to set the row height to 80.0.

To make all this work, we need to (1) import the header file of the MTDayCell class, (2) declare a static string constant for the cell reuse identifier, and (3) register the MTDayCell as the class for that cell reuse identifier in viewDidLoad. In updateView, we reload the table view.


4.User Interface Right View

Step 1: Updating the User Interface

It is clear that we need to make some significant changes to the locations view controller to implement Chris's design. The table view of the locations view controller will include two sections instead of one. The top section will display the stored locations while the bottom section is reserved for the temperature setting. Start by opening MTLocationsViewController.xib and remove the navigation bar that we added in the previous article (figure 9). This means that we can also delete the outlet for the edit button in MTLocationsViewController.h and the editLocations action in MTLocationsViewController.m.

Create a Weather App with Forecast – User Interface - Updating the User Interface of the Locations View Controller
Figure 9: Updating the User Interface of the Locations View Controller

Step 2: Creating the Location Cell

The cells that display the locations have a delete button on the left. To make this work, we need to create a custom table view cell. Create another UITableViewCell subclass and name it MTLocationCell (figure 10). Open MTLocationCell.h and create two properties, (1) buttonDelete (UIButton) and (2) labelLocation (UILabel). As you can see, the implementation of MTLocationCell is less complex than those of MTHourCell and MTDayCell.

Create a Weather App with Forecast – User Interface - Creating a Custom Table View Cell
Figure 10: Creating a Custom Table View Cell

Step 3: Updating the Table View Data Source Protocol

To implement the design, we need to update the UITableViewDataSource and UITableViewDelegate protocols. Start by importing the header file of the MTLocationCell class and the category on NSUserDefaults that we created earlier. The table view will contain three types of cells and we need to declare a reuse identifier for each type (see below).

In setupView, we configure the table view by (1) setting its separatorStyle property to UITableViewCellSeparatorStyleNone and (2) registering a class for each reuse identifier.

The UITableViewDataSource protocol changes significantly and the implementations of the various methods can seem daunting at first. Most of the complexity, however, is due to nested if statements.

We also implement tableView:titleForHeaderInSection: and tableView:viewForHeaderInSection: to replace the default section headers with a custom design that matches the application's design. When implementing tableView:viewForHeaderInSection:, it is important to also implement tableView:heightForHeaderInSection:.

We make use of tableView:heightForFooterInSection: to create some whitespace between the top and bottom sections. This means that we also need to implement tableView:viewForFooterInSection:.

The implementation of tableView:cellForRowAtIndexPath: is a bit more complex due to the three cell types in the table view.

In configureCell:atIndexPath:, we configure each cell. As I wrote earlier, the complexity is primarily due to nested if statements. Tapping the delete button in a location cell sends a message of deleteLocation: to the locations view controller. We will implement deleteLocation: shortly.

Because locations can now be deleted by tapping the delete button in the location cells, the rows themselves no longer need to be editable. This means that the implementation of tableView:canEditRowAtIndexPath: can be reduced to returning NO and the implementation of tableView:commitEditingStyle:forRowAtIndexPath: can be removed altogether.

Step 4: Updating the Table View Delegate Protocol

In tableView:didSelectRowAtIndexPath:, we add a bit more complexity due to the inclusion of the second section containing the temperature setting. Thanks to our category on NSUserDefaults, the implementation is simple and concise.

The rows in Chris's design are slightly taller than the default height of 44 points. To put this detail into practice, we implement tableView:heightForRowAtIndexPath: as shown below.

Step 5: Deleting Locations (Revisited)

The last thing that we need to do is implement the deleteLocation: method that is invoked when the delete button is tapped in a location cell. The implementation is more verbose than you might expect. Inferring the row number from the cell to which the delete button belongs isn't as trivial as it should be. However, once we have the index path of the cell to which the button belongs, we only need to update the array of locations, update the user defaults database, and update the table view.


5. Finishing Touch

To finish our project, we need to replace the default launch images with the ones provided by Chris Carey. The launch images are also included in the source files of this article.

Build and run the application to see the final result in action. Even though we have spent quite some time creating and designing the application, there are still some rough edges. It would also be nice to implement a caching mechanism so that we can show cached data to the user as long as a request to the Forecast API hasn't returned. These are a few examples of refinements that we can add to our application.

Conclusion

Creating the user interface was quite a bit of work, but the code involved wasn't all that complicated. I hope this tutorial has shown you what a great design can do for an application. Consumer applications in particular really benefit from an appealing, fresh design like the one we used in this tutorial.

Tags:

Comments

Related Articles