iOS 7 Best Practices; A Weather App Case Study: Part 1/2

Learn various iOS 7 best practices in this 2-part tutorial series. You’ll master the theory and then practice by making a functional, beautiful weather app. By Ryan Nystrom.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Setting Up Your App’s Views

It’s time to make your app come to life. Download the images for this project here and unzip them to a convenient location. This package contains a background image by Flickr user idleformat and weather icons by Dribbble user heeyeun.

Note: The background image is of San Francisco since the iPhone Simulator defaults its geolocation to that city. If you want to personalize your app, feel free to substitute an image of your own hometown — or any image you choose, for that matter.

Switch back back to Xcode and click File\Add Files to “SimpleWeather”…. Locate the Images folder you just unzipped and select it. Check the Copy items into destination group’s folder (if needed) option and click Add.

Open WXController.h and add the following delegate protocols directly below the @interface line:

<UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>

Now open WXController.m. Protip: you can use the hotkey Control-Command-Up to quickly toggle between the .h and .m files.

Add the following import to the top of WXController.m:

#import <LBBlurredImage/UIImageView+LBBlurredImage.h>

LBBlurredImage.h is contained in the LBBlurredImage project you imported with Cocoapods; you’ll use this library to blur your background image.

There should be an empty private interface boilerplate for WXController beneath the imports. Fill it in with the following properties:

@interface WXController ()

@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *blurredImageView;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign) CGFloat screenHeight;

@end

Now it’s time to create and set the views in your project. “But wait,” you say. “Where are the IBOutlets?”

Be forewarned: all of the views are created and set up… in code! :]

Table Flip

Hang on — don’t freak out just yet. There are lots of ways to create views, and everyone has their favorite. We even held a debate on the RayWenderlich.com team on the different styles and what each person prefers. Storyboards, NIBs, and code all have their pros and cons.

The views in this app aren’t terribly complicated and don’t necessarily have the performance hit that Auto-Layout can cause. However, since this is a case study, you’ll stick to using code.

You’ll create three views and stack them on top of each other to create the effect that you saw in the GIF at the beginning of this tutorial. Here’s an exploded view of what you’ll make, bearing in mind that the table view will be transparent:

Exploded Screens

To provide the dynamic blur effect, you’ll change the alpha of the blurred image as you scroll through your app.

Open up WXController.m and replace the code in -viewDidLoad that sets the background color with the following code:

// 1
self.screenHeight = [UIScreen mainScreen].bounds.size.height;

UIImage *background = [UIImage imageNamed:@"bg"];

// 2
self.backgroundImageView = [[UIImageView alloc] initWithImage:background];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.backgroundImageView];

// 3
self.blurredImageView = [[UIImageView alloc] init];
self.blurredImageView.contentMode = UIViewContentModeScaleAspectFill;
self.blurredImageView.alpha = 0;
[self.blurredImageView setImageToBlur:background blurRadius:10 completionBlock:nil];
[self.view addSubview:self.blurredImageView];

// 4
self.tableView = [[UITableView alloc] init];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.2];
self.tableView.pagingEnabled = YES;
[self.view addSubview:self.tableView];

This is pretty straightforward code:

  1. Get and store the screen height. You’ll need this later when displaying all of the weather data in a paged manner.
  2. Create a static image background and add it to the view.
  3. Create a blurred background image using LBBlurredImage, and set the alpha to 0 initially so that backgroundImageView is visible at first.
  4. Create a tableview that will handle all the data presentation. WXController will be the delegate and data source, as well as the scroll view delegate. Note that you’re also setting pagingEnabled to YES.

Add the following code for the UITableView delegate and data source to the @implementation block of WXController.m:

// 1
#pragma mark - UITableViewDataSource

// 2
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // TODO: Return count of forecast
    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (! cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
    }

    // 3
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.detailTextLabel.textColor = [UIColor whiteColor];

    // TODO: Setup the cell

    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // TODO: Determine cell height based on screen
    return 44;
}

Although some of the above code is placeholders, here’s what you have so far:

  1. Pragma marks are a great way to help organize your code..
  2. Your table view has two sections, one for hourly forecasts and one for daily. You always return 2 for the number of table view sections.
  3. Forecast cells shouldn’t be selectable. Give them a semi-transparent black background and white text.
Note: Using the formatted comment // TODO: helps Xcode find code you need to complete later. You can even view TODO items using Show Document Items (Control-6).

Finally, add the following method to WXController.m:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    CGRect bounds = self.view.bounds;

    self.backgroundImageView.frame = bounds;
    self.blurredImageView.frame = bounds;
    self.tableView.frame = bounds;
}

Your view controller calls the above method in order to lay out its subviews in WXController.m.

Build and run your app to see how your views stack up — (pardon the pun!) :

Image Background

Look closely and you’ll see the individual cell separators for all of your empty table cells.

Still in -viewDidLoad, add the following code to set up your layout frames and margins:

// 1
CGRect headerFrame = [UIScreen mainScreen].bounds;
// 2
CGFloat inset = 20;
// 3
CGFloat temperatureHeight = 110;
CGFloat hiloHeight = 40;
CGFloat iconHeight = 30;
// 4
CGRect hiloFrame = CGRectMake(inset, 
                              headerFrame.size.height - hiloHeight,
                              headerFrame.size.width - (2 * inset),
                              hiloHeight);

CGRect temperatureFrame = CGRectMake(inset, 
                                     headerFrame.size.height - (temperatureHeight + hiloHeight),
                                     headerFrame.size.width - (2 * inset),
                                     temperatureHeight);

CGRect iconFrame = CGRectMake(inset, 
                              temperatureFrame.origin.y - iconHeight, 
                              iconHeight, 
                              iconHeight);
// 5
CGRect conditionsFrame = iconFrame;
conditionsFrame.size.width = self.view.bounds.size.width - (((2 * inset) + iconHeight) + 10);
conditionsFrame.origin.x = iconFrame.origin.x + (iconHeight + 10);

This is fairly routine setup code, but here’s what’s going on:

  1. Set the header of your table to be the same size of your screen. You’ll be taking advantage of UITableView’s paging which will page the header and the daily and hourly forecast sections.
  2. Create an inset (or padding) variable so that all your labels are evenly spaced and centered.
  3. Create and initialize the height variables for your various views. Setting these values as constants makes it easy to configure and changing your view setup if required.
  4. Create frames for your labels and icon view based on the constant and inset variables.
  5. Copy the icon frame, adjust it so the text has some room to expand, and move it to the right of the icon. You’ll see how this layout math works once we add the label to the view below.

Add the following code below the frame code in -viewDidLoad:

// 1
UIView *header = [[UIView alloc] initWithFrame:headerFrame];
header.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = header;

// 2
// bottom left
UILabel *temperatureLabel = [[UILabel alloc] initWithFrame:temperatureFrame];
temperatureLabel.backgroundColor = [UIColor clearColor];
temperatureLabel.textColor = [UIColor whiteColor];
temperatureLabel.text = @"0°";
temperatureLabel.font = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:120];
[header addSubview:temperatureLabel];

// bottom left
UILabel *hiloLabel = [[UILabel alloc] initWithFrame:hiloFrame];
hiloLabel.backgroundColor = [UIColor clearColor];
hiloLabel.textColor = [UIColor whiteColor];
hiloLabel.text = @"0° / 0°";
hiloLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:28];
[header addSubview:hiloLabel];

// top
UILabel *cityLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 30)];
cityLabel.backgroundColor = [UIColor clearColor];
cityLabel.textColor = [UIColor whiteColor];
cityLabel.text = @"Loading...";
cityLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
cityLabel.textAlignment = NSTextAlignmentCenter;
[header addSubview:cityLabel];

UILabel *conditionsLabel = [[UILabel alloc] initWithFrame:conditionsFrame];
conditionsLabel.backgroundColor = [UIColor clearColor];
conditionsLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
conditionsLabel.textColor = [UIColor whiteColor];
[header addSubview:conditionsLabel];

// 3
// bottom left
UIImageView *iconView = [[UIImageView alloc] initWithFrame:iconFrame];
iconView.contentMode = UIViewContentModeScaleAspectFit;
iconView.backgroundColor = [UIColor clearColor];
[header addSubview:iconView];

That’s quite a chunk of code, but it’s really just doing the heavy lifting of setting up the various controls in your view. In short:

  1. Set the current-conditions view as your table header.
  2. Build each required label to display weather data.
  3. Add an image view for a weather icon.

Build and run your app; you should see all of your views laid out before you. The screenshot below shows all of the labels along with their frames for a visual on manual layout:

Labels and Views

Give the table a nudge with your finger; it should bounce when you scroll it.

Ryan Nystrom

Contributors

Ryan Nystrom

Author

Over 300 content creators. Join our team.