Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all 1836 articles
Browse latest View live

Create a Weather App with Forecast – Project Setup

$
0
0

In March of this year, the company behind Dark Sky and Forecast.io introduced Forecast. Forecast is a simple weather API that provides both short- and longterm weather data. In this series, I will show you how to create a beautiful weather application for iOS powered by Forecast.


Introduction

Earlier this year, the Dark Sky Company introduced Forecast, a simple yet powerful weather API that provides short and longterm weather predictions. In this series we will create an iOS application that is powered by the Forecast API. Forecast is free for up to a thousand API calls per day, so feel free to sign up for a developer account and follow along with me.

Even though a number of open source wrappers for the Forecast API are available, in this series we will use the AFNetworking library to query the Forecast API. In the first part of this series, we will create the project’s foundation and implement a basic user interface. Even though our application is simple in scope, it will support multiple locations and that is what we will focus on in this article.


1. Project Setup

Step 1: Creating the Project

Fire up Xcode and create a new project based on the Empty Application template (figure 1). Name the application Rain and enable Automatic Reference Counting (figure 2).

Creating a Weather App for iOS with Forecast.io: Part 1 - Setting Up the Project
Figure 1: Setting Up the Project
Creating a Weather App for iOS with Forecast.io: Part 1 - Configuring the Project
Figure 2: Configuring the Project

Step 2: Adding Libraries

For this project, we will be using three open source libraries, SVProgressHUD created by Sam Vermette, AFNetworking created by Mattt Thompson, and ViewDeck created by Tom Adriaenssen.

Since I am an avid fan of CocoaPods, I will be using it to install and manage the libraries of our project. If you are not familiar with CocoaPods, then I recommend visiting the website of CocoaPods or reading my introduction to CocoaPods. You can also manually add each library to your project if you prefer to not use CocoaPods.

Close your project, browse to the project’s root, and create a file named Podfile. Open Podfile in your favorite text editor and replace its contents with the snippet below. In the project’s pod file, we specify the platform, deployment target, and the pods we want to include in the project.

platform :ios, '6.0'
pod 'ViewDeck', '~> 2.2.11'
pod 'AFNetworking', '~> 1.2.1'
pod 'SVProgressHUD', '~> 0.9.0'

Open the Terminal application, browse to the project’s root, and install the libraries by executing pod install. In addition to installing the three pods that we specified in the project’s pod file, CocoaPods has already created an Xcode workspace for us. Open the new workspace by executing the open Rain.xcworkspace command from the command line.

Step 3: Adding Dependencies

Before we can continue, we need to link our project against a handful of frameworks. The AFNetworking library depends on the Mobile Core Services and System Configuration frameworks and the ViewDeck library makes use of the Quartz Core framework. Select the project from the Project Navigator on the left, select the Rain target in the list of targets, open the Build Phases tab at the top, and expand the Link Binary With Libraries drawer. Click the plus button to link your project against the aforementioned frameworks. We will be using the Core Location framework a bit later in this article so now is good time to link your project against that framework as well (figure 3).

Creating a Weather App for iOS with Forecast.io: Part 1 - Linking the Project Against a Handful of Frameworks
Figure 3: Linking the Project Against a Handful of Frameworks

Step 4: Edit the Precompiled Header File

Before we start implementing the basic structure of our weather application, it is a good idea to edit the precompiled header file of our project. Add an import statement for each of the frameworks we added to our project a moment ago and do the same for the AFNetworking, SVProgressHUD, and ViewDeck libraries.

#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <QuartzCore/QuartzCore.h>
    #import <CoreLocation/CoreLocation.h>
    #import <MobileCoreServices/MobileCoreServices.h>
    #import <SystemConfiguration/SystemConfiguration.h>
    #import "AFNetworking.h"
    #import "SVProgressHUD.h"
    #import "IIViewDeckController.h"
#endif

2. Laying the Foundation

The concept and structure of the application is simple. The application manages three views, (1) a view in the center showing the current weather for a particular location, a view on the right showing the weather for the next few days, and a view on the left with a list of locations. The basic structure will make more sense once we’ve implemented it. To create this structure, we make use of the terrific ViewDeck library, created and maintained by Tom Adriaenssen. The ViewDeck library is one of the most powerful implementations of the sliding view design pattern originally introduced in Facebook for iOS.

Step 1: View Controllers

Before we put the ViewDeck library to use, we need to create the view controller classes that will manage the three views I mentioned in the previous paragraph. Create three UIViewController subclasses named MTWeatherViewController, MTForecastViewController, and MTLocationsViewController, respectively (figure 4). Don’t forget to create a user interface or XIB file for each class (figure 4).

Creating a Weather App for iOS with Forecast.io: Part 1 - Creating the Three Main View Controller Classes
Figure 4: Creating the Three Main View Controller Classes

Step 2: Creating a View Deck Controller

Open MTAppDelegate.m and import the header file the three UIViewController subclasses. Add a class extension and create a property of type IIViewDeckController and name it viewDeckController. The reason for this will become clear in a moment.

#import "MTAppDelegate.h"
#import "MTWeatherViewController.h"
#import "MTForecastViewController.h"
#import "MTLocationsViewController.h"
@interface MTAppDelegate ()
@property (strong, nonatomic) IIViewDeckController *viewDeckController;
@end

In application:didFinishLaunchingWithOptions:, we start by creating an instance of each of the three UIViewController subclasses. We then initialize an instance of the IIViewDeckController class and pass the view controller objects as arguments to initWithCenterViewController:leftViewController:rightViewController:. As the initializer indicates, the view deck controller that we create manages a center, left, and right view controller. The rest of the implementation of application:didFinishLaunchingWithOptions: should be familiar to you. We initialize the application window and set the view deck controller as its root view controller.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize View Controllers
    MTLocationsViewController *leftViewController = [[MTLocationsViewController alloc] initWithNibName:@"MTLocationsViewController" bundle:nil];
    MTForecastViewController *rightViewController = [[MTForecastViewController alloc] initWithNibName:@"MTForecastViewController" bundle:nil];
    MTWeatherViewController *centerViewController = [[MTWeatherViewController alloc] initWithNibName:@"MTWeatherViewController" bundle:nil];
    // Initialize View Deck Controller
    self.viewDeckController = [[IIViewDeckController alloc] initWithCenterViewController:centerViewController leftViewController:leftViewController rightViewController:rightViewController];
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    [self.window setRootViewController:self.viewDeckController];
    [self.window makeKeyAndVisible];
    return YES;
}

Build and run the application in the iOS Simulator or on a physical device to see the ViewDeck library in action. Even though the views of the view controllers are empty, the fundamental application structure is ready.


3. Adding Locations

As I mentioned in the introduction, in this tutorial we will also add the ability to add locations to the list of locations managed by the application. The MTLocationsViewController class is in charge of managing the list of locations and presenting them in a table view. The user can add the current location to the list of locations by tapping the table view’s first row, which will be labeled “Add Current Location”. Adding a new location with the Core Location framework is a responsibility of the MTWeatherViewController class as we will see in a few minutes.

This leaves us with a problem. How is the weather view controller notified when the user has tapped the first row of the locations view controller? Notification center? Delegation is a better choice in this context. Whenever I encounter the need for a one-way communication between two objects, I tend to choose for delegation in favor of notifications. A delegate protocol is much easier to wrap your head around and extending it is as simple as declaring another method.

Another option would be to pass a reference to the weather view controller to the locations view controller, but I don’t like this type of tight coupling. Tight coupling makes code less reusable and it results in unnecessarily complex object hierarchies when the code base grows over time. Delegation is the right choice for this problem.

Step 1: Declaring the Delegate Protocol

Open MTLocationsViewController.h and update the header file as shown below. We create a property for the view controller’s delegate and we declare the MTLocationsViewControllerDelegate protocol. The protocol defines two methods, (1) controllerShouldAddCurrentLocation:, which is invoked when the first row in the view controller’s table view is tapped, and (2) controller:didSelectLocation:, which is invoked when the user select a location from the list of locations.

#import <UIKit/UIKit.h>
@protocol MTLocationsViewControllerDelegate;
@interface MTLocationsViewController : UIViewController
@property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
@end
@protocol MTLocationsViewControllerDelegate <NSObject>
- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller;
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location;
@end

Step 2: Adding the Table View

Revisit MTLocationsViewController.h one more time. Create an outlet for the view controller’s table view and make sure to conform the MTLocationsViewController class to the UITableViewDataSource and UITableViewDelegate protocols.

#import <UIKit/UIKit.h>
@protocol MTLocationsViewControllerDelegate;
@interface MTLocationsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end
@protocol MTLocationsViewControllerDelegate <NSObject>
- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller;
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location;
@end

Open MTLocationsViewController.xib, add a table view to the view controller’s view, and set the table view’s dataSource and delegate outlets to the File’s Owner object. Select the File’s Owner object and connect its tableView outlet with the table view that we added to the view controller’s view (figure 5).

Creating a Weather App for iOS with Forecast.io: Part 1 - Adding a Table View to List the Locations
Figure 5: Adding a Table View to List the Locations

Step 3: Populating the Table View

Before we implement the UITableViewDataSource and UITableViewDelegate protocols, we need to create a property that will serve as the table view’s data source. Create a class extension at the top of MTLocationsViewController.m and create a property named locations of type NSMutableArray. It will store the locations managed by our application.

#import "MTLocationsViewController.h"
@interface MTLocationsViewController ()
@property (strong, nonatomic) NSMutableArray *locations;
@end

Implementing the UITableViewDataSource and the UITableViewDelegate protocols is pretty straightforward. We start by declaring a static string constant for the cell reuse identifier. In the view controller’s viewDidLoad method we invoke setupView, a helper method in which we configure the view controller’s user interface. In setupView, we tell the table view to use the UITableViewCell class to instantiate new table view cells for the reuse identifier we declared earlier.

static NSString *LocationCell = @"LocationCell";
- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
}
- (void)setupView {
    // Register Class for Cell Reuse
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:LocationCell];
}

Implementing the UITableViewDataSource and UITableViewDelegate protocols is trivial as you can see below. Two implementation details require a bit explaining. We store each location as a dictionary with four keys, (1) city, (2) country, (3) latitude, and (4) longitude. With this in mind, the implementation of configureCell:atIndexPath: should become a bit clearer. Note that configureCell:atIndexPath: is nothing more than another helper method.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return ([self.locations count] + 1);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LocationCell forIndexPath:indexPath];
    // Configure Cell
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        [cell.textLabel setText:@"Add Current Location"];
    } else {
        // Fetch Location
        NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
        // Configure Cell
        [cell.textLabel setText:[NSString stringWithFormat:@"%@, %@", location[@"city"], location[@"country"]]];
    }
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (indexPath.row == 0) {
        // Notify Delegate
        [self.delegate controllerShouldAddCurrentLocation:self];
    } else {
        // Fetch Location
        NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
        // Notify Delegate
        [self.delegate controller:self didSelectLocation:location];
    }
    // Show Center View Controller
    [self.viewDeckController closeLeftViewAnimated:YES];
}

The tableView:didSelectRowAtIndexPath: also requires a short explanation. If the user taps the first row, labeled Add Current Location, the delegate is notified that the current location should be added to the list of locations. If any other row in the table view is tapped, the corresponding location is passed as the second argument of controller:didSelectLocation:, another delegate method of the MTLocationsViewController delegate protocol. This informs the delegate that the application should set the new location as the application’t default location and the weather data for that location should be fetched.

The last line of tableView:didSelectRowAtIndexPath: is also worth mentioning. The IIViewDeckController instance assigns itself to the view controllers it manages. The viewDeckController property provides access to a view controller’s view deck controller, which is convenient if you need to access the view controller’s view deck. It works very much like the navigationController property of a view controller instance. In the last line of tableView:didSelectRowAtIndexPath:, we tell the view deck controller to close the left view, which means that the center view becomes visible again.

Step 4: Keys and Constants

Before we continue populating the locations table view, we need to pay attention to some best practices. We currently use string literals to access the values of the location dictionary. Even though this works perfectly fine, it is better and safer to use string constants for this purpose. To make all this easy and maintainable, we declare the string constants in a central location. Let me show you how this works.

Create a subclass of NSObject and name it MTConstants. Replace the contents of MTConstants.h and MTConstants.m with the snippets shown below. It should be clear that MTConstants is not an Objective-C class. It is nothing more than a central place to store a set of constants specific to our project.

#pragma mark -
#pragma mark User Defaults
extern NSString * const MTRainUserDefaultsLocation;
extern NSString * const MTRainUserDefaultsLocations;
#pragma mark -
#pragma mark Notifications
extern NSString * const MTRainDidAddLocationNotification;
extern NSString * const MTRainLocationDidChangeNotification;
#pragma mark -
#pragma mark Location Keys
extern NSString * const MTLocationKeyCity;
extern NSString * const MTLocationKeyCountry;
extern NSString * const MTLocationKeyLatitude;
extern NSString * const MTLocationKeyLongitude;
#import "MTConstants.h"
#pragma mark -
#pragma mark User Defaults
NSString * const MTRainUserDefaultsLocation = @"location";
NSString * const MTRainUserDefaultsLocations = @"locations";
#pragma mark -
#pragma mark Notifications
NSString * const MTRainDidAddLocationNotification = @"com.mobileTuts.MTRainDidAddLocationNotification";
NSString * const MTRainLocationDidChangeNotification = @"com.mobileTuts.MTRainLocationDidChangeNotification";
#pragma mark -
#pragma mark Location Keys
NSString * const MTLocationKeyCity = @"city";
NSString * const MTLocationKeyCountry = @"country";
NSString * const MTLocationKeyLatitude = @"latitude";
NSString * const MTLocationKeyLongitude = @"longitude";

To make MTConstants really useful, add an import statement for MTConstants.h to your project’s precompiled header file so the constants declared in MTConstants are available throughout the project.

#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <QuartzCore/QuartzCore.h>
    #import <CoreLocation/CoreLocation.h>
    #import <MobileCoreServices/MobileCoreServices.h>
    #import <SystemConfiguration/SystemConfiguration.h>
    #import "AFNetworking.h"
    #import "SVProgressHUD.h"
    #import "IIViewDeckController.h"
    #import "MTConstants.h"
#endif

We can now update configureCell:atIndexPath: (MTLocationsViewController.m) as shown below. Not only will this give use code completion and a very, very small performance gain, the true benefit of this best practice is that the compiler will warn us in case of typos. I’m sure I don’t have to tell you that typos are one of the most common causes of bugs in software development.

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        [cell.textLabel setText:@"Add Current Location"];
    } else {
        // Fetch Location
        NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
        // Configure Cell
        [cell.textLabel setText:[NSString stringWithFormat:@"%@, %@", location[MTLocationKeyCity], location[MTLocationKeyCountry]]];
    }
}

At the moment, the locations property is empty and so will the table view. In initWithNibName:bundle:, we invoke loadLocations, a helper method that loads the array of locations. In loadLocations, load the array of locations that is stored in the application’s user defaults database. Note that we use another string constant that we declared in MTConstants.h.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Load Locations
        [self loadLocations];
    }
    return self;
}
- (void)loadLocations {
    self.locations = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocations]];
}

Step 5: Delegate Assignment

As I mentioned earlier, the MTWeatherViewController instance will serve as the delegate of the locations view controller. Revisit MTAppDelegate.m and update application:didFinishLaunchingWithOptions: as shown below.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize View Controllers
    MTLocationsViewController *leftViewController = [[MTLocationsViewController alloc] initWithNibName:@"MTLocationsViewController" bundle:nil];
    MTForecastViewController *rightViewController = [[MTForecastViewController alloc] initWithNibName:@"MTForecastViewController" bundle:nil];
    MTWeatherViewController *centerViewController = [[MTWeatherViewController alloc] initWithNibName:@"MTWeatherViewController" bundle:nil];
    // Configure Locations View Controller
    [leftViewController setDelegate:centerViewController];
    // Initialize View Deck Controller
    self.viewDeckController = [[IIViewDeckController alloc] initWithCenterViewController:centerViewController leftViewController:leftViewController rightViewController:rightViewController];
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    [self.window setRootViewController:self.viewDeckController];
    [self.window makeKeyAndVisible];
    return YES;
}

By making this change, a warning should immediately pop up telling you that MTWeatherViewController does not conform to the MTLocationsViewControllerDelegate protocol. The compiler is right so let’s fix this.

Open MTWeatherViewController.h, import the header file of MTLocationsViewController, and conform MTWeatherViewController to the MTLocationsViewControllerDelegate protocol.

#import <UIKit/UIKit.h>
#import "MTLocationsViewController.h"
@interface MTWeatherViewController : UIViewController <MTLocationsViewControllerDelegate>
@end

Wait. Another warning? We haven’t implemented the two required methods of the delegate protocol yet hence the warning. Open MTWeatherViewController.m and add a stub implementation for each of the delegate methods.

- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

Step 6: Fetching the Current Location

It is finally time to fetch the current location of the device. Add a class extension at the top of MTWeatherViewController.m and declare two properties, (1) location (NSDictionary) to store the application’s default location and (2) locationManager (CLLocationManager), which we will use to fetch the device’s location. Conform the MTWeatherViewController class to the CLLocationManagerDelegate protocol and declare an instance variable named _locationFound of type BOOL. The purpose of _locationFound will become clear in a few minutes.

#import "MTWeatherViewController.h"
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
    BOOL _locationFound;
}
@property (strong, nonatomic) NSDictionary *location;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

In the class’s designated initializer, initWithNibName:bundle:, we initialize and configure the location manager. We assign the view controller as the location manager’s delegate and set the location manager’s accuracy property to kCLLocationAccuracyKilometer. There is no need for better accuracy as we only need the location for weather data.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialize Location Manager
        self.locationManager = [[CLLocationManager alloc] init];
        // Configure Location Manager
        [self.locationManager setDelegate:self];
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
    }
    return self;
}

The next piece of the puzzle is implementing, locationManager:didUpdateLocations:, one of the methods of the CLLocationManagerDelegate protocol, which is invoked each time the location manager has updated the device’s location. The second argument of locationManager:didUpdateLocations: is an array CLLocation instances. The implementation of locationManager:didUpdateLocations: also reveals the purpose of _locationFound. Despite the fact that we tell the location manager to stop updating the location as soon as locationManager:didUpdateLocations: is invoked, it is not uncommon that another update of the location invokes locationManager:didUpdateLocations: again even after sending the location manager a message of stopUpdatingLocation. If this were to happen, the same location would be added twice to the list of locations. The simple solution is to use a helper variable, _locationFound, that keeps track of the state that we’re in.

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    if (![locations count] || _locationFound) return;
    // Stop Updating Location
    _locationFound = YES;
    [manager stopUpdatingLocation];
    // Current Location
    CLLocation *currentLocation = [locations objectAtIndex:0];
    // Reverse Geocode
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    [geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
        if ([placemarks count]) {
            _locationFound = NO;
            [self processPlacemark:[placemarks objectAtIndex:0]];
        }
    }];
}

We extract the first location from the array of locations and use the CLGeocoder class to reverse geocode that location. Reverse geocoding simply means finding out the name of the (closest) city and the location’s country. The completion handler of reverseGeocodeLocation: returns an array of placemarks. A placemark object is nothing more than a container for storing location data for a coordinate.

- (void)processPlacemark:(CLPlacemark *)placemark {
    // Extract Data
    NSString *city = [placemark locality];
    NSString *country = [placemark country];
    CLLocationDegrees lat = placemark.location.coordinate.latitude;
    CLLocationDegrees lon = placemark.location.coordinate.longitude;
    // Create Location Dictionary
    NSDictionary *currentLocation = @{ MTLocationKeyCity : city,
                                       MTLocationKeyCountry : country,
                                       MTLocationKeyLatitude : @(lat),
                                       MTLocationKeyLongitude : @(lon) };
    // Add to Locations
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    NSMutableArray *locations = [NSMutableArray arrayWithArray:[ud objectForKey:MTRainUserDefaultsLocations]];
    [locations addObject:currentLocation];
    [locations sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:MTLocationKeyCity ascending:YES]]];
    [ud setObject:locations forKey:MTRainUserDefaultsLocations];
    // Synchronize
    [ud synchronize];
    // Update Current Location
    self.location = currentLocation;
    // Post Notifications
    NSNotification *notification2 = [NSNotification notificationWithName:MTRainDidAddLocationNotification object:self userInfo:currentLocation];
    [[NSNotificationCenter defaultCenter] postNotification:notification2];
}

In processPlacemark:, we extract the data that we’re looking for, city, country, latitude, and longitude, store it in a dictionary, and update the array of locations in the application’s user defaults database. Note that we sort the array of locations before updating the user defaults database. The view controller’s location property is updated with the new location and a notification is sent to notify any object interested in this event.

That’s not all, though. I have also overridden the setter of the view controller’s location property. Because the MTWeatherViewController class is in charge of adding new locations, we can delegate a few additional responsibilities to this class, such as updating the default location in the user defaults database. Because other parts of the application also need to know about a change in location, a notification with name MTRainLocationDidChangeNotification is posted. We also invoke updateView, another helper method that we will implement shortly.

- (void)setLocation:(NSDictionary *)location {
    if (_location != location) {
        _location = location;
        // Update User Defaults
        NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
        [ud setObject:location forKey:MTRainUserDefaultsLocation];
        [ud synchronize];
        // Post Notification
        NSNotification *notification1 = [NSNotification notificationWithName:MTRainLocationDidChangeNotification object:self userInfo:location];
        [[NSNotificationCenter defaultCenter] postNotification:notification1];
        // Update View
        [self updateView];
    }
}

Step 7: Adding a Label

We won’t spend much time on the user interface in this tutorial, but to make sure that everything works as we expect, it is good to have some visual feedback by adding a label to the weather view controller’s view displaying the selected location. Open MTWeatherViewController.h and create an outlet of type UILabel and name it labelLocation.

#import <UIKit/UIKit.h>
#import "MTLocationsViewController.h"
@interface MTWeatherViewController : UIViewController <MTLocationsViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
@end

Open MTWeatherViewController.xib, add a label to the view controller’s view, and connect the outlet with the newly added label (figure 6). In updateView (MTWeatherViewController.m), we update the label with the new location as shown below.

Creating a Weather App for iOS with Forecast.io: Part 1 - Adding the Location Label to the Weather View Controller
Figure 6: Adding the Location Label to the Weather View Controller
- (void)updateView {
    // Update Location Label
    [self.labelLocation setText:[self.location objectForKey:MTLocationKeyCity]];
}

Step 8: Implementing the Delegate Protocol

Thanks to the work we have done so far, implementing the two methods of the MTLocationsViewControllerDelegate protocol is easy. In controllerShouldAddCurrentLocation:, we tell the location manager to start updating the location. In controller:didSelectLocation:, we set the view controller’s location property to the location that the user selected in the locations view controller, which in turn invokes the setter method we overrode a bit earlier.

- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller {
    // Start Updating Location
    [self.locationManager startUpdatingLocation];
}
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location {
    // Update Location
    self.location = location;
}

4. Final Touches

Before wrapping up the first installment of this series, we need to add a few final touches. When the user launches the application, for example, the location property of the MTWeatherViewController class needs to be set to the location stored in the application’s user defaults. In addition, when the user launches our application for the very first time, a default location is not yet set in the application’s user defaults. This isn’t a big problem, but to offer a good user experience it would be better to automatically fetch the user’s current location when the application launches for the first time.

We can make both changes by amending the weather view controller’s viewDidLoad as shown below. The view controller’s location property is set to the location stored in the user defaults database. If no location is found, that is, self.location is nil, we tell the location manager to start updating the location. In other words, when the application is launched for the first time, the current location is automatically retrieved and stored.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Load Location
    self.location = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
    if (!self.location) {
        [self.locationManager startUpdatingLocation];
    }
}

There is one more loose end that we need to tie up. When a new location is added to the array of locations, the weather view controller posts a notification. We need to update the MTLocationsViewController class so that it adds itself as an observer for these notifications. By doing so, the locations view controller can update its table view whenever a new location is added.

Revisit MTLocationsViewController.m and update the initWithNibName:bundle: as shown below. We add the view controller as an observer for notifications with a name of MTRainDidAddLocationNotification. The implementation of didAddLocation: is straightforward, that is, we add the new location to the array of locations, sort the array by city, and reload the table view.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Load Locations
        [self loadLocations];
        // Add Observer
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didAddLocation:) name:MTRainDidAddLocationNotification object:nil];
    }
    return self;
}
- (void)didAddLocation:(NSNotification *)notification {
    NSDictionary *location = [notification userInfo];
    [self.locations addObject:location];
    [self.locations sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:MTLocationKeyCity ascending:YES]]];
    [self.tableView reloadData];
}

Don’t forget to remove the view controller as an observer in the view controller’s dealloc method. It is also good practice to set the view controller’s delegate property to nil in dealloc.

- (void)dealloc {
    // Remove Observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    if (_delegate) {
        _delegate = nil;
    }
}

Build and run the application to see how all this works together. You may want to run the application in the iOS Simulator instead of a on physical device, because the iOS Simulator supports location simulation (figure 7), which makes it much easier to test location based applications such as the one we created.

Creating a Weather App for iOS with Forecast.io: Part 1 - Simulating Locations with the iOS Simulator
Figure 7: Simulating Locations with the iOS Simulator

Conclusion

Even though we haven’t even touched the Forecast API, we did quite a bit of work in this article. I hope you have tried CocoaPods and are convinced of its power and flexibility. In the next installment of this series, we focus on the Forecast API and the AFNetworking library.


Connecting to an External Database With NuSOAP

$
0
0

Many mobile applications use external databases that are located remotely on the Internet. In the following tutorial, we will create a simple database, connect to it, and receive or send data from this source. To do this we will use a Windows Phone 7 Silverlight based application and a NuSOAP web service. Let’s begin!


1. Install the Software

Before we start we need to install the following programs:

Obviously, we will also need some sort of web hosting where we can create a database. Most of the free hosting options will be enough for us, but they may have some limitations which I will discuss later on in this tutorial.

We also need an FTP client. In this tutorial I will use a FireFTP add-on to Mozilla Firefox. But you can use anything you want.

When all the programs are installed, we can continue.


2. Creating an External Database

Step 1

To begin, check that the hosting uses phpMyAdmin because I use it in the examples. But if you choose to use something else all the commands should be similar.

First we need to create a simple database, in our case it will contain only one table and three attributes:

  • ID– this value identifies the record. It must be set as an autoincrement field.
  • FirstName
  • LastName

The table name is MyUsers.

To do this just click “create table”:

Step 2

After that, fill in the cells as shown in this screenshot:

The table structure should now look like this:

Step 3

At this step we must note a few important points:

  • Host address

As I wrote earlier, free hostings have limits, one of these is to possibly connect only from localhost, remember that!

  • Database user

Our username to log into the database:

  • Database password
  • Database name

3. NuSOAP Server – Starting Web Service

Step 1

Starting our web service is very simple:

First, we must copy some files to the ftp server. I recommend a ftp server because it is directly connected to our hosting because of the localhost issue.

Once we’re connected, we need to copy the nusoap.php file which was downloaded earlier. We also require a file that will contain specific functions written by us that is necessary for our application; I called it MyService.php.

Step 2

After we copy the files, our FTP root directory should look like the image below:

Now open the MyService.php file and write into it:

<?php
// Pull in the NuSOAP
code require_once('nusoap.php');
// Create the server instance
$server = new soap_server();
// Initialize WSDL support
(MyService is name of our service)
$server----->configureWSDL('MyService', 'urn:MyService');
        // Character encoding
        $server->soap_defencoding = 'utf-8';
        //-------------------------------------------------
        //Registrations of our functions
        //-------------------------------------------------
        //Our web service functions will be here.
        //-------------------------------------------------
        $HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
        $server->service($HTTP_RAW_POST_DATA);
?>

The most important points are explained in the upper comments to code.

Step 3

From now on our service should work. We can check this by typing in the web browser:

http://www.ourdomain.com/MyService.php

If all went well you should see something like this:

After we successfully started the web service, we can go to the next step.


4. Writing Web Service Functions

In our service we need two functions:

  • The first function adds data to the online database.
  • The second function receives data from it.

Step 1

Let’s open MyService.php. We need to register the new function, to do this we should type:

    $server->register(
        'InsertData',   //Name of function
      	array('FirstName' => 'xsd:string', 'LastName' => 'xsd:string'), //Input Values
      	array('return' =>'xsd:boolean'), //Output Values
    	  'urn:MyServicewsdl',  //Namespace
        'urn:MyServicewsdl#InsertData',  //SoapAction
        'rpc',       //style
        'literal',   //can be encoded but it doesn't work with silverlight
        'Some_comments_about_function'
    );

Remember that it needs to be placed before the body function and after the server directives.

Here is some code explanation:

      'FirstName' => 'xsd:string'

“FirstName” is the name of variable, “string” is the type of variable (i.e. it can be int, longint, boolean, etc.).

When the function registers, we need to write the body of it. Below is the code and explanation:

    function InsertData($FirstName, $LastName) {
    	$connect = mysql_pconnect("Host","UserName","UserPassword"));
    	if ($connect) {
    		if(mysql_select_db("DatabaseName", $connect)) {
          mysql_query("INSERT INTO MyUser SET FirstName='$FirstName', LastName='$LastName'");
    			return true;
    		}
    	}
    	return false;
    }
          InsertData($FirstName, $LastName)

Here’s the name of the function and its attributes. They must be the same as in the registration section.

          $connect = mysql_pconnect("Host","UserName","UserPassword");

Here we can insert the data we noticed when we created the database.

          if(mysql_select_db("DatabaseName", $connect)) {

And also here.

After that it is a simple MySQL query that adds data to our database:

          mysql_query("INSERT INTO MyUser SET FistName='$FirstName', LastName='$LastName'");

Step 2

Now it’s time to write the second function. The structure will be similar to the first.

Here is the code of method registration:

      $server->register(
          'GetData',
        	array('ID' => 'xsd:int'),
        	array('return' =>'xsd:string'),
      	  'urn:MyServicewsdl',
          'urn:MyServicewsdl#GetData',
          'rpc',
          'literal',
          'Some comments about function 2'
      );

The main differences are in the Input/Output values section (changed types of variables).

Here’s the body function code:

       function GetData($ID) {
      	$connect = mysql_pconnect("Host","UserName","UserPassword");
      	if ($connect) {
      		if(mysql_select_db("DatabaseName", $connect)) {
            $sql = "SELECT FirstName, LastName FROM MyUser WHERE ID = '$ID'";
      			$result = mysql_fetch_array(mysql_query($sql));
      			return $result['FirstName']."-".$result['LastName'];
      		}
      	}
      	return false;
      }

Here is a little code explanation:

         return $result['FirstName']."-".$result['LastName'];

This line states what must return to the Windows Phone application.

Step 3

After writing all the functions the MyService.php file should look like this:

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the server instance
$server = new soap_server();
// Initialize WSDL support
configureWSDL('MyService', 'urn:MyService');
                // Character encoding
                $server->soap_defencoding = 'utf-8';
                //-------------------------------------------------
                //Register InsertData function
                $server->register(
                        'InsertData',
                      	array('FirstName' => 'xsd:string', 'LastName' => 'xsd:string'),
                      	array('return' =>'xsd:boolean'),
                    	  'urn:MyServicewsdl',
                        'urn:MyServicewsdl#InsertData',
                        'rpc',
                        'literal',
                        'Some comments about function'
                    );
                //Register GetData function
                $server->register(
                        'GetData',
                      	array('ID' => 'xsd:int'),
                      	array('return' =>'xsd:string'),
                    	  'urn:MyServicewsdl',
                        'urn:MyServicewsdl#GetData',
                        'rpc',
                        'literal',
                        'Some comments about function 2'
                    );
                //-------------------------------------------------
                //Body InsterData function
                function InsertData($FirstName, $LastName) {
                	$connect = mysql_pconnect("Host","UserName","UserPassword");
                	if ($connect) {
                		if(mysql_select_db("DatabaseName", $connect)) {
                      mysql_query("INSERT INTO MyUser SET FirstName='$FirstName', LastName='$LastName'");
                			return true;
                		}
                	}
                	return false;
                }
                //Body GetData function
                function GetData($ID) {
                	$connect = mysql_pconnect("Host","UserName","UserPassword");
                	if ($connect) {
                		if(mysql_select_db("DatabaseName", $connect)) {
                      $sql = "SELECT FirstName, LastName FROM MyUser WHERE ID = '$ID'";
                			$result = mysql_fetch_array(mysql_query($sql));
                			return $result['FirstName']."-".$result['LastName'];
                		}
                	}
                	return false;
                }
                //-------------------------------------------------
                $HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
                $server->service($HTTP_RAW_POST_DATA);
        ?>

To validate the functions we can again type http://www.ourdomain.com/MyService.php in the browser. Now the site should look a little bit different, but similar to this:

Now we’re ready to go to next step.


5. Creating a Simple Layout for a Windows Phone Application

Step 1

Firstly, we must create a Windows Phone app. Let’s run Microsoft Visual Studio for Windows Phone 2010. After Visual Studio starts, click “File” then “New Project”. You should see a dialog window like in the screenshot below:

Our app will use Silverlight, so we must check this template. We can also change the project name, like localisations, etc. In my case, the project name is “MyApplication”. After you have done this, click the OK button.

Step 2

At this point we must note what we need in our app. We need:

  • Two text boxes for sending data to the database (First Name, Last Name)
  • One text box to receive the data (ID)
  • Two buttons to approve our actions

Adding objects (such as buttons) to our application is easy, just drag it from “ToolBox” and drop it onto preview of the app. Keep in mind that you have a free hand in setting your own layout.

This is how it looks on my app:

Another important aspect is the names of the elements used in Visual Studio (they’re used later in code).

To change it, just click on element. Then in properties you can see text like “Textbox1″, click on it, and change it to something that you can remember, that is crucial. I used these names for my elements:

  • “FirstNameBox”, “LastNameBox”, and “IdBox” for text boxes
  • “SendBTN” and “ReadBTN” for buttons

That’s all we need to do in this step, we can move on.


6. Connecting to Service

To connect to the web service we must right click on “Reference” in “Solution Explorer” dialog and select “Add Service Reference…”

Here how this looks:

After that a new window will appear. In it we must write the address of our web service and the name of namespace.

In our case an address will be created, like on this scheme: http://www.ourdomain.com/MyService.php?wsdl.

After entering an address and clicking “Go” you should see something like this:

If you see the operations called “GetData” and “InsertData” that means the connection was successfully created! Remember to type namespace, in my case it is “MyService”. Now click OK.


7. Writing Windows Phone Functions

We’re almost at the end of this tutorial; we only need to write two simple functions.

Step 1

The first function will be behind the “Send” button, so double click it. Now we must add at the top of the file a “using” directive of our service:

      using MyApplication.MyService;

Let’s look at the send function behind the “Send” button, now it is empty:

        private void SendBTN_Click(object sender, RoutedEventArgs e)
        {
        }

Our complete function should look like this:

        private void SendBTN_Click(object sender, RoutedEventArgs e)
        {
            //Creating new proxy object of our service
            MyServicePortTypeClient send = new MyServicePortTypeClient();
            send.InsertDataCompleted += new EventHandler<InsertDataCompletedEventArgs>(send_InsertDataCompleted);
            //Calling method, as a parameters we type text contained in FirstNameBox and LastNameBox
            //This data will be sent to web service and later to database
            send.InsertDataAsync(FirstNameBox.Text, LastNameBox.Text);
        }
        void send_InsertDataCompleted(object sender, InsertDataCompletedEventArgs e)
        {
            //If our server return true, that means we're added data to database...
            if (e.Result)
                MessageBox.Show("Successfully added!");
            //...if return false we aren't.
            else
                MessageBox.Show("Some problems occured!");
        }

The most important points are explained in the code comments, but we must be aware of the following:

Method “send_InsertDataCompleted” executes when we get an answer from the server. Also this function is not in the “SendBTN” object, it is outside.

Now it’s time to test our work! Start to debug and fill out the boxes with some data. Here I have entered John as a first name and Doe as a last name, then I clicked Send:

Let’s see how the database looks now:

Yes, the new record with ID = 1 has appeared and everything is going well.

Step 2

Now we’ve reached the final function for receiving. It is similar to the previous method. Double click on the “Read” button and copy the code:

      private void ReadBTN_Click(object sender, RoutedEventArgs e)
        {
            //Creating new proxy object of our service
            MyServicePortTypeClient read = new MyServicePortTypeClient();
            read.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(read_GetDataCompleted);
            //Calling method, as a parameters we type text contained in IdTextBox
            //but we must change text type of string to integer (ID in web service have integer type)
            read.GetDataAsync(Convert.ToInt32(IdBox.Text));
        }
        void read_GetDataCompleted(object sender, GetDataCompletedEventArgs e)
        {
            MessageBox.Show(e.Result);
        }

This is the same process as before, “read_GetDataCompleted” executes after getting data from the database. In this method we’ll use a message box to show our result, i.e. first and last name. There is one more step to do; we need to change the type of text in IdBox from string to integer because the variable called ID in the web service has an integer type. To do this I used a function called “Convert.ToIn32()”


8. Testing

Now we can see if this works. Enter ID into “IdTextBox”. I entered “1″, then clicked the “Read” button.

Everything is working! Our application is now complete!


Conclusion

In this tutorial we created a database using a Windows Phone 7 Silverlight based application and NuSOAP web service. This database is helpful to receive or send data. External databases are important because they are used by many mobile applications.

Reading NFC Tags with Android

$
0
0

Are you curious about what NFC is and how it can be integrated into your own Android applications? This tutorial will quickly introduce you to the topic before diving in and teaching you how to build a simple NFC reader app!


What is NFC?

NFC is the abbreviation for Near Field Communication. It is the international standard for contactless exchange of data. In contrast to a large range of other technologies, such as wireless LAN and Bluetooth, the maximum distance of two devices is 10cm. The development of the standard started in 2002 by NXP Semiconductors and Sony. The NFC Forum, a consortium of over 170 companies and members, which included Mastercard, NXP, Nokia, Samsung, Intel, and Google, has been designing new specifications since 2004.

There are various possibilities for NFC use with mobile devices; for example, paperless tickets, access controls, cashless payments, and car keys. With the help of NFC tags you can control your phone and change settings. Data can be exchanged simply by holding two devices next to each other.

In this tutorial I want to explain how to implement NFC with the Android SDK, which pitfalls exist, and what to keep in mind. We will create an app step by step, which can read the content of NFC tags supporting NDEF.


NFC Technologies

There are a variety of NFC tags that can be read with a smartphone. The spectrum ranges from simple stickers and key rings to complex cards with integrated cryptographic hardware. Tags also differ in their chip technology. The most important is NDEF, which is supported by most tags. In addidition, Mifare should be mentioned as it is the most used contactless chip technology worldwide. Some tags can be read and written, while others are read-only or encrypted.

Only the NFC Data Exchange Format (NDEF) is discussed in this tutorial.

girogo-card

Adding NFC Support in an App

We start with a new project and a blank activity. It is important to select a minimum SDK version of level 10, because NFC is only supported after Android 2.3.3. Remember to choose your own package name. I’ve chosen net.vrallev.android.nfc.demo, because vrallev.net is the domain of my website and the other part refers to the topic of this application.

<uses-sdk
		android:minSdkVersion="10"
        android:targetSdkVersion="17" />

The default layout generated by Eclipse is almost sufficient for us. I’ve only added an ID to the TextView and changed the text.

<TextView
        android:id="@+id/textView_explanation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/explanation" />

To get access to the NFC hardware, you have to apply for permission in the manifest. If the app won’t work without NFC, you can specify the condition with the uses-feature tag. If NFC is required, the app can’t be installed on devices without it and Google Play will only display your app to users who own a NFC device.

<uses-permission android:name="android.permission.NFC" /><uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

The MainActivity should only consist of the onCreate() method. You can interact with the hardware via the NfcAdapter class. It is important to find out whether the NfcAdapter is null. In this case, the Android device does not support NFC.

	package net.vrallev.android.nfc.demo;
	import android.app.Activity;
	import android.nfc.NfcAdapter;
	import android.os.Bundle;
	import android.widget.TextView;
	import android.widget.Toast;
	/**
	 * Activity for reading data from an NDEF Tag.
	 *
	 * @author Ralf Wondratschek
	 *
	 */
	public class MainActivity extends Activity {
		public static final String TAG = "NfcDemo";
		private TextView mTextView;
		private NfcAdapter mNfcAdapter;
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			setContentView(R.layout.activity_main);
			mTextView = (TextView) findViewById(R.id.textView_explanation);
			mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
			if (mNfcAdapter == null) {
				// Stop here, we definitely need NFC
				Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
				finish();
				return;
			}
			if (!mNfcAdapter.isEnabled()) {
				mTextView.setText("NFC is disabled.");
			} else {
				mTextView.setText(R.string.explanation);
			}
			handleIntent(getIntent());
		}
		private void handleIntent(Intent intent) {
			// TODO: handle Intent
		}
	}

If we start our app now, we can see the text whether NFC is enabled or disabled.

How to Filter for NFC Tags

We have our sample app and want to receive a notification from the system when we attach an NFC tag to the device. As usual, Android uses its Intent system to deliver tags to the apps. If multiple apps can handle the Intent, the activity chooser gets displayed and the user can decide which app will be opened. Opening URLs or sharing information is handled the same way.


NFC Intent Filter

There are three different filters for tags:

  1. ACTION_NDEF_DISCOVERED
  2. ACTION_TECH_DISCOVERED
  3. ACTION_TAG_DISCOVERED

The list is sorted from the highest to the lowest priority.

Now what happens when a tag is attached to the smartphone? If the system detects a tag with NDEF support, an Intent is triggered. An ACTION_TECH_DISCOVERED Intent is triggered if no Activity from any app is registered for the NDEF Intent or if the tag does not support NDEF. If again no app is found for the Intent or the chip technology could not be detected, then a ACTION_TAG_DISCOVERED Intent is fired. The following graphic shows the process:

nfc tag dispatch

In summary this means that each app needs to filter after the Intent with the highest priority. In our case, this is the NDEF Intent. We implement the ACTION_TECH_DISCOVERED Intent first to highlight the difference between priorities.


Tech Discovered Intent

We must specify the technology we are interested in. For this purpose, we create a subfolder called xml in the res folder. In this folder we create the file nfc_tech_filter.xml, in which we specify the technologies.

<?xml version="1.0" encoding="utf-8"?><resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><tech-list><tech>android.nfc.tech.Ndef</tech><!-- class name --></tech-list></resources><!--<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><tech-list><tech>android.nfc.tech.IsoDep</tech><tech>android.nfc.tech.NfcA</tech><tech>android.nfc.tech.NfcB</tech><tech>android.nfc.tech.NfcF</tech><tech>android.nfc.tech.NfcV</tech><tech>android.nfc.tech.Ndef</tech><tech>android.nfc.tech.NdefFormatable</tech><tech>android.nfc.tech.MifareClassic</tech><tech>android.nfc.tech.MifareUltralight</tech></tech-list></resources>
	-->

Now we must create an IntentFilter in the manifest, and the app will be started when we attach a tag.

<?xml version="1.0" encoding="utf-8"?><activity
		android:name="net.vrallev.android.nfc.demo.MainActivity"
        android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter><action android:name="android.nfc.action.TECH_DISCOVERED" /></intent-filter><meta-data
            android:name="android.nfc.action.TECH_DISCOVERED"
            android:resource="@xml/nfc_tech_filter" /></activity>

If no other app is registered for this Intent, our Activity will start immediately. On my device, however, other apps are installed, so the activity chooser gets displayed.

screenshot

NDEF Discovered Intent

As I mentioned before, the Tech Discovered Intent has the second highest priority. However, since our app will support only NDEF, we can use the NDEF Discovered Intent instead, which has a higher priority. We can delete the technology list again and replace the IntentFilter with the following one.

<intent-filter><action android:name="android.nfc.action.NDEF_DISCOVERED" /><category android:name="android.intent.category.DEFAULT" /><data android:mimeType="text/plain" /></intent-filter>

When we attach the tag now, the app will be started like before. There is a difference for me, however. The activity chooser does not appear and the app starts immediately, because the NDEF Intent has a higher priority and the other apps only registered for the lower priorities. That’s exactly what we want.


Foreground Dispatch

Note that one problem remains. When our app is already opened and we attach the tag again, the app is opened a second time instead of delivering the tag directly. This is not our intended behavior. You can bypass the problem by using a Foreground Dispatch.

Instead of the system having distributed the Intent, you can register your Activity to receive the tag directly. This is important for a particular workflow, where it makes no sense to open another app.

I’ve inserted the explanations at the appropriate places in the code.

	package net.vrallev.android.nfc.demo;
	import android.app.Activity;
	import android.app.PendingIntent;
	import android.content.Intent;
	import android.content.IntentFilter;
	import android.content.IntentFilter.MalformedMimeTypeException;
	import android.nfc.NfcAdapter;
	import android.os.Bundle;
	import android.widget.TextView;
	import android.widget.Toast;
	/**
	 * Activity for reading data from an NDEF Tag.
	 *
	 * @author Ralf Wondratschek
	 *
	 */
	public class MainActivity extends Activity {
		public static final String MIME_TEXT_PLAIN = "text/plain";
		public static final String TAG = "NfcDemo";
		private TextView mTextView;
		private NfcAdapter mNfcAdapter;
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			setContentView(R.layout.activity_main);
			mTextView = (TextView) findViewById(R.id.textView_explanation);
			mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
			if (mNfcAdapter == null) {
				// Stop here, we definitely need NFC
				Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
				finish();
				return;
			}
			if (!mNfcAdapter.isEnabled()) {
				mTextView.setText("NFC is disabled.");
			} else {
				mTextView.setText(R.string.explanation);
			}
			handleIntent(getIntent());
		}
		@Override
		protected void onResume() {
			super.onResume();
			/**
			 * It's important, that the activity is in the foreground (resumed). Otherwise
			 * an IllegalStateException is thrown.
			 */
			setupForegroundDispatch(this, mNfcAdapter);
		}
		@Override
		protected void onPause() {
			/**
			 * Call this before onPause, otherwise an IllegalArgumentException is thrown as well.
			 */
			stopForegroundDispatch(this, mNfcAdapter);
			super.onPause();
		}
		@Override
		protected void onNewIntent(Intent intent) {
			/**
			 * This method gets called, when a new Intent gets associated with the current activity instance.
			 * Instead of creating a new activity, onNewIntent will be called. For more information have a look
			 * at the documentation.
			 *
			 * In our case this method gets called, when the user attaches a Tag to the device.
			 */
			handleIntent(intent);
		}
		private void handleIntent(Intent intent) {
			// TODO: handle Intent
		}
		/**
		 * @param activity The corresponding {@link Activity} requesting the foreground dispatch.
		 * @param adapter The {@link NfcAdapter} used for the foreground dispatch.
		 */
		public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
			final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
			intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
			final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
			IntentFilter[] filters = new IntentFilter[1];
			String[][] techList = new String[][]{};
			// Notice that this is the same filter as in our manifest.
			filters[0] = new IntentFilter();
			filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
			filters[0].addCategory(Intent.CATEGORY_DEFAULT);
			try {
				filters[0].addDataType(MIME_TEXT_PLAIN);
			} catch (MalformedMimeTypeException e) {
				throw new RuntimeException("Check your mime type.");
			}
			adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
		}
		/**
		 * @param activity The corresponding {@link BaseActivity} requesting to stop the foreground dispatch.
		 * @param adapter The {@link NfcAdapter} used for the foreground dispatch.
		 */
		public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
			adapter.disableForegroundDispatch(activity);
		}
	}

Now, when you attach a tag and our app is already opened, onNewIntent is called and no new Activity is created.


Reading Data From an NDEF Tag

The last step is to read the data from the tag. The explanations are inserted at the appropriate places in the code once again. The NdefReaderTask is a private inner class.

	package net.vrallev.android.nfc.demo;
	import java.io.UnsupportedEncodingException;
	import java.util.Arrays;
	import android.app.Activity;
	import android.app.PendingIntent;
	import android.content.Intent;
	import android.content.IntentFilter;
	import android.content.IntentFilter.MalformedMimeTypeException;
	import android.nfc.NdefMessage;
	import android.nfc.NdefRecord;
	import android.nfc.NfcAdapter;
	import android.nfc.Tag;
	import android.nfc.tech.Ndef;
	import android.os.AsyncTask;
	import android.os.Bundle;
	import android.util.Log;
	import android.widget.TextView;
	import android.widget.Toast;
	/*
	 * ... other code parts
	 */
	private void handleIntent(Intent intent) {
		String action = intent.getAction();
		if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
			String type = intent.getType();
			if (MIME_TEXT_PLAIN.equals(type)) {
				Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
				new NdefReaderTask().execute(tag);
			} else {
				Log.d(TAG, "Wrong mime type: " + type);
			}
		} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
			// In case we would still use the Tech Discovered Intent
			Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
			String[] techList = tag.getTechList();
			String searchedTech = Ndef.class.getName();
			for (String tech : techList) {
				if (searchedTech.equals(tech)) {
					new NdefReaderTask().execute(tag);
					break;
				}
			}
		}
	}
	/**
	 * Background task for reading the data. Do not block the UI thread while reading.
	 *
	 * @author Ralf Wondratschek
	 *
	 */
	private class NdefReaderTask extends AsyncTask<Tag, Void, String> {
		@Override
		protected String doInBackground(Tag... params) {
			Tag tag = params[0];
			Ndef ndef = Ndef.get(tag);
			if (ndef == null) {
				// NDEF is not supported by this Tag.
				return null;
			}
			NdefMessage ndefMessage = ndef.getCachedNdefMessage();
			NdefRecord[] records = ndefMessage.getRecords();
			for (NdefRecord ndefRecord : records) {
				if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
					try {
						return readText(ndefRecord);
					} catch (UnsupportedEncodingException e) {
						Log.e(TAG, "Unsupported Encoding", e);
					}
				}
			}
			return null;
		}
		private String readText(NdefRecord record) throws UnsupportedEncodingException {
			/*
			 * See NFC forum specification for "Text Record Type Definition" at 3.2.1
			 *
			 * http://www.nfc-forum.org/specs/
			 *
			 * bit_7 defines encoding
			 * bit_6 reserved for future use, must be 0
			 * bit_5..0 length of IANA language code
			 */
			byte[] payload = record.getPayload();
			// Get the Text Encoding
			String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
			// Get the Language Code
			int languageCodeLength = payload[0] & 0063;
			// String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
			// e.g. "en"
			// Get the Text
			return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
		}
		@Override
		protected void onPostExecute(String result) {
			if (result != null) {
				mTextView.setText("Read content: " + result);
			}
		}
	}

The app now successfully reads the content.

screen

Useful Apps

To check whether data is read and written properly, I personally like to use following apps:

  • NFC TagInfo by NFC Research Lab for reading data
  • TagInfo by NXP SEMICONDUCTORS for reading data
  • TagWriter by NXP SEMICONDUCTORS for writing data

Conclusion

In this tutorial I have shown you how the data from a NDEF tag can be extracted. You could expand the example to other mime types and chip technologies; a feature to write data would be useful as well. The first step to work with NFC was made. However, the Android SDK offers much more possibilities, such as an easy exchange of data (called Android Beam).


About the Author

Ralf Wondratschek is a computer science student from Germany. In addition to his studies, Ralf works as a freelancer in the field of mobile computing. In the last few years he has worked with Java, XML, HTML, JSP, JSF, Eclipse, Google App Engine, and of course Android. He has published two Android apps to date which can be found here.

You can find out more about the author’s work on his homepage vrallev.net.


Sources

http://www.nfc-forum.org/home/n-mark.jpg

http://commons.wikimedia.org/wiki/File%3A%C3%9Cberlagert.jpg

http://developer.android.com/images/nfc_tag_dispatch.png


Create a Weather App with Forecast – API Integration

$
0
0

In the first article of this series, we laid the foundation of the project by setting up the project and creating the application’s structure. In this article, we leverage the AFNetworking library to interact with the Forecast API.


Introduction

In the first installment of this series, we laid the foundation of our weather application. Users can add their current location and switch between locations. In this tutorial, we will use the AFNetworking library to ask the Forecast API for the weather data of the currently selected location.

If you want to follow along, you will need a Forecast API key. You can obtain an API key by registering as a developer at Forecast. Registration is free, so I encourage you to try out the Forecast weather service. You can find your API key at the bottom of your dashboard (figure 1).

Create a Weather App with Forecast – Forecast Integration - Obtaining Your API Key
Figure 1: Obtaining Your API Key

1. Subclassing AFHTTPClient

As I wrote earlier in this article, we will be using the AFNetworking library for communicating with the Forecast API. There are several options when working with AFNetworking, but to make our application future proof, we will opt for the AFHTTPClient class. This class is designed for consuming web services, such as the Forecast API. Even though we will only access one API endpoint, it is still useful to make use of the AFHTTPClient as you will learn in a few moments.

It is recommended to create an AFHTTPClient subclass for each web service. Because we already added AFNetworking to our project in the previous tutorial, we can immediately start subclassing AFHTTPClient.

Step 1: Create the Class

Create a new Objective-C class, name it MTForecastClient, and make it a subclass of AFHTTPClient (figure 2).

Create a Weather App with Forecast – Forecast Integration - Subclassing AFHTTPClient
Figure 2: Subclassing AFHTTPClient

Step 2: Creating a Singleton Object

We will adopt the singleton pattern to make it easier to use the MTForecastClient class in our project. This means that only one instance of the class is alive at any one time for the lifetime of the application. Chances are that you are already familiar with singleton pattern as it is a common pattern in many object-oriented programming languages. At first glance, the singleton pattern seems very convenient, but there are a number of caveats to watch out for. You can learn more about singletons by reading this excellent article by Matt Gallagher.

Creating a singleton object is pretty straightforward in Objective-C. Start by declaring a class method in MTForecastClient.h to provide public access to the singleton object (see below).

#import "AFHTTPClient.h"
@interface MTForecastClient : AFHTTPClient
#pragma mark -
#pragma mark Shared Client
+ (MTForecastClient *)sharedClient;
@end

The implementation of sharedClient may look daunting at first, but it isn’t that difficult once you understand what’s going on. We first declare two static variables, (1) predicate of type dispatch_once_t and (2) _sharedClient of type MTForecastClient. As its name implies, predicate is a predicate that we use in combination with the dispatch_once function. When working with a variable of type dispatch_once_t, it is important that it is declared statically. The second variable, _sharedClient, will store a reference to the singleton object.

The dispatch_once function takes a pointer to a dispatch_once_t structure, the predicate, and a block. The beauty of dispatch_once is that it will execute the block once for the lifetime of the application, which is exactly what we want. The dispatch_once function doesn’t have many uses, but this is definitely one of them. In the block that we pass to dispatch_once, we create the singleton object and store a reference in _sharedClient. It is safer to invoke alloc and init separately to avoid a race condition that could potentially lead to a deadlock. Euh … what? You can read more about the nitty gritty details on Stack Overflow.

+ (MTForecastClient *)sharedClient {
    static dispatch_once_t predicate;
    static MTForecastClient *_sharedClient = nil;
    dispatch_once(&predicate, ^{
        _sharedClient = [self alloc];
        _sharedClient = [_sharedClient initWithBaseURL:[self baseURL]];
    });
    return _sharedClient;
}

The important thing to understand about the implementation of the sharedClient class method is that the initializer, initWithBaseURL:, is invoked only once. The singleton object is stored in the _sharedClient static variable, which is returned by the sharedClient class method.

Step 3: Configuring the Client

In sharedClient, we invoke initWithBaseURL:, which in turn invokes baseURL, another class method. In initWithBaseURL:, we set a default header, which means that the client adds this header to every request that it sends. This is one of the advantages of working with the AFHTTPClient class. In initWithBaseURL:, we also register an HTTP operation class by invoking registerHTTPOperationClass:. The AFNetworking library provides a number of specialized operations classes. One of these classes is the AFJSONRequestOperation class, which makes interacting with a JSON API very easy. Because the Forecast API returns a JSON response, the AFJSONRequestOperation class is a good choice. The registerHTTPOperationClass: method works similar to how the registerClass:forCellReuseIdentifier: of the UITableView class works. By telling the client what operation class we want to use for interacting with the web service, it will instantiate instances of that class for us under the hood. Why this is useful will become clear in a few moments.

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (self) {
        // Accept HTTP Header
        [self setDefaultHeader:@"Accept" value:@"application/json"];
        // Register HTTP Operation Class
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
    }
    return self;
}

The implementation of baseURL is nothing more than a convenience method for constructing the client’s base URL. The base URL is the URL that the client uses to reach the web service. It is the URL without any method names or parameters. The base URL for the Forecast API is https://api.forecast.io/forecast//. The API key is part of the URL as you can see. This may seem insecure and it actually is. It isn’t difficult for someone to grab the API key so it is advisable to work with a proxy to mask the API key. Because this approach is a bit more involved, I won’t cover this aspect in this series.

+ (NSURL *)baseURL {
    return [NSURL URLWithString:[NSString stringWithFormat:@"https://api.forecast.io/forecast/%@/", MTForecastAPIKey]];
}

You may have noticed in the implementation of baseURL that I have used another string constant for storing the API key. This might seem unnecessary since we only use the API key in one location. However, it is good practice to store application data in one location or in a property list.

#pragma mark -
#pragma mark Forecast API
extern NSString * const MTForecastAPIKey;
#pragma mark -
#pragma mark Forecast API
NSString * const MTForecastAPIKey = @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

Step 4: Adding a Helper Method

Before we move on, I would like to extend the MTForecastClient class by adding a helper or convenience method that will make it easier to query the Forecast API. This convenience method will accept a location and a completion block. The completion block is executed when the request finishes. To make working with blocks easier, it is recommended to declare a custom block type as shown below. If you still feel uncomfortable using blocks, then I recommend reading this great article by Akiel Khan.

The block takes two arguments, (1) a boolean indicating whether the query was successful and (2) a dictionary with the response from the query. The convenience method, requestWeatherForCoordinate:completion:, takes the coordinates of a location (CLLocationCoordinate2D) and a completion block. By using a completion block, we can avoid creating a custom delegate protocol or fall back to using notifications. Blocks are a perfect fit for this type of scenario.

#import "AFHTTPClient.h"
typedef void (^MTForecastClientCompletionBlock)(BOOL success, NSDictionary *response);
@interface MTForecastClient : AFHTTPClient
#pragma mark -
#pragma mark Shared Client
+ (MTForecastClient *)sharedClient;
#pragma mark -
#pragma mark Instance Methods
- (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion;
@end

In requestWeatherForCoordinate:completion:, we invoke getPath:success:failure:, a method declared in AFHTTPClient. The first argument is the path that is appended to the base URL that we created earlier. The second and third arguments are blocks that are executed when the request succeeds and fails, respectively. The success and failure blocks are pretty simple. If a completion block was passed to requestWeatherForCoordinate:completion:, we execute the block and pass a boolean value and the response dictionary (or nil in the failure block). In the failure block, we log the error from the failure block to the console to facilitate debugging.

- (void)requestWeatherForCoordinate:(CLLocationCoordinate2D)coordinate completion:(MTForecastClientCompletionBlock)completion {
    NSString *path = [NSString stringWithFormat:@"%f,%f", coordinate.latitude, coordinate.longitude];
    [self getPath:path parameters:nil success:^(AFHTTPRequestOperation *operation, id response) {
        if (completion) {
            completion(YES, response);
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        if (completion) {
            completion(NO, nil);
            NSLog(@"Unable to fetch weather data due to error %@ with user info %@.", error, error.userInfo);
        }
    }];
}

You may be wondering what the response object in the success blocks is or references. Even though the Forecast API returns a JSON response, the response object in the success block is an NSDictionary instance. The benefit of working with the AFJSONHTTPRequestOperation class, which we registered in initWithBaseURL:, is that it accepts the JSON response and automatically creates an object from the response data, a dictionary in this example.


2. Querying the Forecast API

Step 1: Amend setLocation:

Armed with the MTForecastClient class, it is time to query the Forecast API and fetch the weather data for the currently selected location. The most suitable place to do this is in the setLocation: method of the MTWeatherViewController class. Amend the setLocation: method as shown below. As you can see, all we do is invoke fetchWeatherData, another helper method.

- (void)setLocation:(NSDictionary *)location {
    if (_location != location) {
        _location = location;
        // Update User Defaults
        NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
        [ud setObject:location forKey:MTRainUserDefaultsLocation];
        [ud synchronize];
        // Post Notification
        NSNotification *notification1 = [NSNotification notificationWithName:MTRainLocationDidChangeNotification object:self userInfo:location];
        [[NSNotificationCenter defaultCenter] postNotification:notification1];
        // Update View
        [self updateView];
        // Request Location
        [self fetchWeatherData];
    }
}

Have you ever wondered why I use so many helper methods in my code? The reason is simple. By wrapping functionality in helper methods, it is very easy to reuse code in various places of a project. The main benefit, however, is that it helps battle code duplication. Code duplication is something you should always try to avoid as much as possible. Another advantage of using helper methods is that it makes your code much more readable. By creating methods that do one thing and providing a well chosen method name, it is easier to quickly read and process your code.

Step 2: Sending the Request

It is time to put the SVProgressHUD library to use. I really like this library because it is so simple to use without cluttering the project's code base. Take a look at the implementation of fetchWeatherData below. We start by showing the progress HUD and then pass a structure (CLLocationCoordinate2D) to the convenience method we created earlier, requestWeatherForCoordinate:completion:. In the completion block, we hide the progress HUD and log the response to the console.

- (void)fetchWeatherData {
    // Show Progress HUD
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
    // Query Forecast API
    double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
    double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
    [[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
        // Dismiss Progress HUD
        [SVProgressHUD dismiss];
        NSLog(@"Response > %@", response);
    }];
}

Before you build and run your application, import the header file of the MTForecastClient class in MTWeatherViewController.m.

#import "MTWeatherViewController.h"
#import "MTForecastClient.h"
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
    BOOL _locationFound;
}
@property (strong, nonatomic) NSDictionary *location;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

What happens when the device is not connected to the web? Have you thought about that scenario? In terms of user experience, it is good practice to notify the user when the application is unable to request data from the Forecast API. Let me show how to do this with the AFNetworking library.


3. Reachability

There are a number of libraries that provide this functionality, but we will stick with AFNetworking. Apple also provides sample code, but it is a bit outdated and doesn't support ARC.

AFNetworking has truly embraced blocks, which is definitely one of the reasons that this library has become so popular. Monitoring for reachability changes is as simple as passing a block to setReachabilityStatusChangeBlock:, another method of the AFHTTPClient class. The block is executed every time the reachability status changes and it accepts a single argument of type AFNetworkReachabilityStatus. Take a look at the updated initWithBaseURL: method of the MTForecastClient class.

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (self) {
        // Accept HTTP Header
        [self setDefaultHeader:@"Accept" value:@"application/json"];
        // Register HTTP Operation Class
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
        // Reachability
        __weak typeof(self)weakSelf = self;
        [self setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            [[NSNotificationCenter defaultCenter] postNotificationName:MTRainReachabilityStatusDidChangeNotification object:weakSelf];
        }];
    }
    return self;
}

To avoid a retain cycle, we pass a weak reference to the singleton object in the block that we pass to setReachabilityStatusChangeBlock:. Even if you use ARC in your projects, you still need to be aware of subtle memory issues like this. The name of the notification that we post is another string constant declared in MTConstants.h/.m.

extern NSString * const MTRainReachabilityStatusDidChangeNotification;
NSString * const MTRainReachabilityStatusDidChangeNotification = @"com.mobileTuts.MTRainReachabilityStatusDidChangeNotification";

The reason for posting a notification in the reachability status change block is to make it easier for other parts of the application to update when the device's reachability changes. To make sure that the MTWeatherViewController class is notified of reachability changes, instances of the class are added as an observer for the notifications sent by the Forecast client as shown below.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialize Location Manager
        self.locationManager = [[CLLocationManager alloc] init];
        // Configure Location Manager
        [self.locationManager setDelegate:self];
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
        // Add Observer
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
    }
    return self;
}

This also means that we need to remove the instance as an observer in the dealloc method. This is a detail that is often overlooked.

- (void)dealloc {
    // Remove Observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

The implementation of reachabilityStatusDidChange: is pretty basic at the moment. We will update its implementation once we create the application's user interface.

- (void)reachabilityStatusDidChange:(NSNotification *)notification {
    MTForecastClient *forecastClient = [notification object];
    NSLog(@"Reachability Status > %i", forecastClient.networkReachabilityStatus);
}

4. Refreshing Data

Before we wrap this post up, I want to add two additional features, (1) fetching weather data whenever the application becomes active and (2) adding the ability to manually refresh weather data. We could implement a timer that fetches fresh data every hour or so, but this is not necessary for a weather application in my opinion. Most users will launch the application, take a look at the weather and put the application in the background. It is therefore only necessary to fetch fresh data when the user launches the application. This means that we need to listen for UIApplicationDidBecomeActiveNotification notifications in the MTWeatherViewController class. As we did for monitoring reachability changes, we add instances of the class as observers of notifications of type UIApplicationDidBecomeActiveNotification.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialize Location Manager
        self.locationManager = [[CLLocationManager alloc] init];
        // Configure Location Manager
        [self.locationManager setDelegate:self];
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
        // Add Observer
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
        [nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
    }
    return self;
}

In applicationDidBecomeActive:, we verify that location is set (not nil) because this won't always be true. If the location is valid, we fetch the weather data.

- (void)applicationDidBecomeActive:(NSNotification *)notification {
    if (self.location) {
        [self fetchWeatherData];
    }
}

I have also amended fetchWeatherData to only query the Forecast API if the device is connected to the web.

- (void)fetchWeatherData {
    if ([[MTForecastClient sharedClient] networkReachabilityStatus] == AFNetworkReachabilityStatusNotReachable) return;
    // Show Progress HUD
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
    // Query Forecast API
    double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
    double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
    [[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
        // Dismiss Progress HUD
        [SVProgressHUD dismiss];
        // NSLog(@"Response > %@", response);
    }];
}

Let's add a button to the weather view controller that the user can tap to manually refresh the weather data. Create an outlet in MTWeatherViewController.h and create a refresh: action in MTWeatherViewController.m.

#import <UIKit/UIKit.h>
#import "MTLocationsViewController.h"
@interface MTWeatherViewController : UIViewController <MTLocationsViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
@end
- (IBAction)refresh:(id)sender {
    if (self.location) {
        [self fetchWeatherData];
    }
}

Open MTWeatherViewController.xib, add a button to the view controller's view with a title of Refresh, and connect the outlet and action with the button (figure 3). The reason for creating an outlet for the button is to be able to disable it when no network connection is available. For this to work, we need to update the reachabilityStatusDidChange: method as shown below.

Create a Weather App with Forecast – Forecast Integration - Adding a Refresh Button
Figure 3: Adding a Refresh Button
- (void)reachabilityStatusDidChange:(NSNotification *)notification {
    MTForecastClient *forecastClient = [notification object];
    NSLog(@"Reachability Status > %i", forecastClient.networkReachabilityStatus);
    // Update Refresh Button
    self.buttonRefresh.enabled = (forecastClient.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable);
}

It isn't necessary to temporarily disable the refresh button when a request is being processed in fetchWeatherData because the progress HUD adds a layer on top of the view controller's view that prevents the user from tapping the button more than once. Build and run the application to test everything out.


Bonus: Removing Locations

A reader asked me how to delete locations from the list so I am including it here for the sake of completeness. The first thing that we need to do is tell the table view which rows are editable by implementing tableView:canEditRowAtIndexPath: of the UITableViewDataSource protocol. This method returns YES if the row at indexPath is editable and NO if it is not. The implementation is simple as you can see below. Every row is editable except for the first row and the currently selected location.

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        return NO;
    }
    // Fetch Location
    NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
    return ![self isCurrentLocation:location];
}

To check whether location is the current location, we use another helper method, isCurrentLocation:, in which we fetch the current location and compare the locations coordinates. It would have been better (and easier) if we had assigned a unique identifier to each location stored in the user defaults database. Not only would it make it easier to compare locations, but it would also allow us to store the current location's unique identifier in the user defaults database and look it up in the array of locations. The problem with the current implementation is that locations with the exact same coordinates cannot be distinguished from one another.

- (BOOL)isCurrentLocation:(NSDictionary *)location {
    // Fetch Current Location
    NSDictionary *currentLocation = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
    if ([location[MTLocationKeyLatitude] doubleValue] == [currentLocation[MTLocationKeyLatitude] doubleValue] &&
        [location[MTLocationKeyLongitude] doubleValue] == [currentLocation[MTLocationKeyLongitude] doubleValue]) {
        return YES;
    }
    return NO;
}

When the user taps the delete button of a table view row, the table view data source is sent a tableView:commitEditingStyle:forRowAtIndexPath: message. In this method, we need to (1) update the data source, (2) save the changes to the user defaults database, and (3) update the table view. If editingStyle is equal to UITableViewCellEditingStyleDelete, we remove the location from the locations array and store the updated array in the user defaults database. We also delete the row from the table view to reflect the change in the data source.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Update Locations
        [self.locations removeObjectAtIndex:(indexPath.row - 1)];
        // Update User Defaults
        [[NSUserDefaults standardUserDefaults] setObject:self.locations forKey:MTRainUserDefaultsLocations];
        // Update Table View
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
    }
}

To toggle the table view's editing style, we need to add an edit button to the user interface. Create an outlet for the button in MTLocationsViewController.h and an action named editLocations: in MTLocationsViewController.m. In editLocations:, we toggle the table view's editing style.

#import <UIKit/UIKit.h>
@protocol MTLocationsViewControllerDelegate;
@interface MTLocationsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) id<MTLocationsViewControllerDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *editButton;
@end
@protocol MTLocationsViewControllerDelegate <NSObject>
- (void)controllerShouldAddCurrentLocation:(MTLocationsViewController *)controller;
- (void)controller:(MTLocationsViewController *)controller didSelectLocation:(NSDictionary *)location;
@end
- (IBAction)editLocations:(id)sender {
    [self.tableView setEditing:![self.tableView isEditing] animated:YES];
}

Open MTLocationsViewController.xib, add a navigation bar to the view controller's view, and add an edit button to the navigation bar. Connect the edit button with the outlet and action that we created a moment ago.

Create a Weather App with Forecast – Forecast Integration - Adding an Edit Button
Figure 4: Adding an Edit Button

You may be wondering why we created an outlet for the edit button. The reason is that we need to be able to change the title of the edit button from Edit to Done, and vice versa, whenever the editing style of the table view changes. In addition, when the user deletes the last location (except for the current location) in the table view, it would be nice to automatically toggle the table view's editing style. These features are not hard to implement which is why I leave them up to you as an exercise. If you run into problems or have questions, feel free to leave a comment in the comments below this article.

Conclusion

We have successfully integrated the Forecast API in our weather application. In the next tutorial, we will implement focus on the user interface and the design of the application.

Android SDK: Create a Barcode Reader

$
0
0

In this tutorial, we’ll use the ZXing (Zebra Crossing) library to carry out barcode scanning within an Android app. We’ll call on the resources in this open source library within our app, retrieving and processing the returned results.

Since we’re using the ZXing library, we don’t need to worry about users without the barcode scanner installed, because the integration classes provided will take care of this for us. By importing the ZXing integration classes into our app, we can make user scans easier and focus our development efforts on handling the scan results. In a follow-up series coming soon, we’ll develop a book scanning app where we’ll build on the app we created in this tutorial. We’ll also add support for Google Books API so that we can display information about scanned books.


1. Create a New Android Project

Step 1

In Eclipse, create a new Android project. Enter your chosen application, project, and package names. Let Eclipse create a blank activity for you, with the name of your choice for both the activity and its layout.

New Project

Step 2

Open your main layout file. With the default settings, Eclipse starts your layout with a Relative Layout object, which you can leave as is. Inside of it, replace the existing content (typically a Text View) with a button.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent" ><Button android:id="@+id/scan_button"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_centerHorizontal="true"
		android:text="@string/scan" /></RelativeLayout>

After the button, add two Text Views in which we will output scanning information.

<TextView
	android:id="@+id/scan_format"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:textIsSelectable="true"
	android:layout_centerHorizontal="true"
	android:layout_below="@id/scan_button" /><TextView
	android:id="@+id/scan_content"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:textIsSelectable="true"
	android:layout_centerHorizontal="true"
	android:layout_below="@id/scan_format" />

Add the button text string to your “res/values/strings” XML file.

<string name="scan">Scan</string>

The user will press the button to scan. When the app receives a result from the barcode scanning operation, it will display the scan content data and format name in the two Text Views.


2. Add ZXing to Your Project

Step 1

ZXing is an open source library that provides access to tested and functional barcode scanning on Android. Many users will already have the app installed on their devices, so you can simply launch the scanning Intents and retrieve the results. In this tutorial we are going to use the Scanning via Intent method to make scanning easier. This method involves importing a couple of classes into your app and lets ZXing take care of instances where the user does not have the scanner installed. If the user doesn’t have the barcode scanner installed, they’ll be prompted to download it.

Tip: Since ZXing is open source, you can import the source code into your projects in its entirety. However, this is really only advisable if you need to make changes to its functionality. You can also compile the project and include its JAR file in your own apps if you prefer. For most purposes, using Scanning via Intent is a reliable and easy to implement options, plus your users will have access to the most recent version of the ZXing app.

In Eclipse, add a new package to your project by right-clicking the “src” folder and choosing “New“, then “Package“, and entering “com.google.zxing.integration.android” as the package name.

New Package

Step 2

Eclipse offers several ways to import existing code into your projects. For the purposes of this tutorial, you’ll probably find it easiest to simply create the two required classes and copy the code from ZXing. Right-click your new package, choose “New” then “Class” and enter “IntentIntegrator” as the class name. You can leave the other default settings the way they are. Once you’ve created this class, do the same for the other class we’ll be importing, giving it “IntentResult” as its class name.

New Class

Copy the code from both classes in the ZXing library and paste it into the class files you created. These are IntentIntegrator and IntentResult. Refer to the source code download if you’re in any doubt about where the various files and folders should be or what should be in them.

Project Package

You can now import the ZXing classes into your main Activity class.

import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

Go ahead and add the other import statements we’ll use for this tutorial. Bear in mind that Eclipse may have already added some for you.

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

Feel free to have a look at the content of the two ZXing classes. It’s fairly straightforward, but the details of the barcode scanning processing are carried out elsewhere in the library. These two classes really act as an interface to the scanning functionality.


3. Do Some Scanning

Step 1

Let’s implement scanning when the user clicks the button we added. In your app’s main activity class, the default onCreate method entered by Eclipse should look something like this.

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
}

Above this method, add the following instance variables to represent the button and two Text Views we created in the layout file.

private Button scanBtn;
private TextView formatTxt, contentTxt;

In onCreate, after the existing code, instantiate these variables using the ID values we specified in the XML.

scanBtn = (Button)findViewById(R.id.scan_button);
formatTxt = (TextView)findViewById(R.id.scan_format);
contentTxt = (TextView)findViewById(R.id.scan_content);

Next, add a listener to the button so that we can handle presses.

scanBtn.setOnClickListener(this);

Extend the opening line of the class declaration to implement the OnClickListener interface.

public class MainActivity extends Activity implements OnClickListener

Step 2

Now we can respond to button clicks by starting the scanning process. Add an onClick method to your activity class.

public void onClick(View v){
//respond to clicks
}

Check whether the scanning button has been pressed inside this method.

if(v.getId()==R.id.scan_button){
//scan
}

Inside this conditional block, create an instance of the Intent Integrator class we imported.

IntentIntegrator scanIntegrator = new IntentIntegrator(this);

Now we can call on the Intent Integrator method to start scanning.

scanIntegrator.initiateScan();

At this point, the scanner will start if it’s installed on the user’s device. If not, they’ll be prompted to download it. The results of the scan will be returned to the main activity where scanning was initiated, so we’ll be able to retrieve it in the onActivityResult method.

Tip: When you call the initiateScan method, you can choose to pass a collection of the barcode types you want to scan. By default, the method will scan for all supported types. These include UPC-A, UPC-E, EAN-8, EAN-13, QR Code, RSS-14, RSS Expanded, Data Matrix, Aztec, PDF 417, Codabar, ITF, Codes 39, 93, and 128. The ZXing library also includes barcode scanning options that we’re not going to cover in this tutorial. You can check the project out at Google Code for more info.


4. Retrieve Scanning Results

Step 1

When the user clicks the scan button, the barcode scanner will launch. When they scan a barcode, it will return the scanned data to the onActivityResult method of the calling activity. Add the method to your main activity class.

public void onActivityResult(int requestCode, int resultCode, Intent intent) {
//retrieve scan result
}

Inside the method, try to parse the result into an instance of the ZXing Intent Result class we imported.

IntentResult scanningResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);

Step 2

As with any data being retrieved from another app, it’s vital to check for null values. Only proceed if we have a valid result.

if (scanningResult != null) {
//we have a result
}

If scan data is not received (for example, if the user cancels the scan by pressing the back button), we can simply output a message.

else{
	Toast toast = Toast.makeText(getApplicationContext(),"No scan data received!", Toast.LENGTH_SHORT);
	toast.show();
}

Back in the if block, let’s find out what data the scan returned. The Intent Result object provides methods to retrieve the content of the scan and the format of the data returned from it. Retrieve the content as a string value.

String scanContent = scanningResult.getContents();

Retrieve the format name, also as a string.

String scanFormat = scanningResult.getFormatName();

Step 3

Now your program has the format and content of the scanned data, so you can do whatever you want with it. For the purpose of this tutorial, we’ll just write the values to the Text Views in our layout.

formatTxt.setText("FORMAT: " + scanFormat);
contentTxt.setText("CONTENT: " + scanContent);

Run your app on a device instead of an emulator so that you can see the scan functioning. Try scanning a book or any other barcode you might have.

App During Scanning
When the scan is initiated, the user is taken to the ZXing app to scan a barcode.
Scan Result
The scan results are returned to the app.

Conclusion

In this tutorial, we’ve run through the process of facilitating barcode scanning within Android apps using the ZXing library. In your own apps, you might want to carry out further processing on the retrieved scan results, such as loading URLs or looking the data up in a third party data source. In the follow-up to this tutorial, we’ll use the barcode scanning functionality to create a book scanning app that will allow us to retrieve data about scanned books from the Google Books API.

iOS SDK: Advanced UIImage Techniques

$
0
0

In this tutorial, we’ll look at some of the advanced features and usage patterns of the humble UIImage class in iOS. By the end of this tutorial, you’ll have learned the following: how to make images in code, how to create resizable images for UI elements like callouts, and how to create animated images.


Theoretical Overview

If you’ve ever had to display an image in your iOS app, you’re probably familiar with UIImage. It’s the class that allows you to represent images on iOS. This is by far the most common way of using UIImage and is quite straightforward: you have an image file in one of several standard image formats (PNG, JPEG, BMP, etc.) and you wish to display it in your app’s interface. You instantiate a new UIImage instance by sending the class message imageNamed:. If you have an instance of UIImageView, you can set its image property to the UIImage instance, and then you can stick in the image view into your interface by setting it as a subview to your onscreen view:

 UIImage *img = [UIImage imageNamed:filename];
 UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
 // Set frame, etc. ...
 [self.view addSubview:imgView];

You can also carry out the equivalent of the above procedure directly in Interface Builder.

There are some other ways of instantiating an image, such as from a URL, or from an archived image that was stored as an NSData type, but we won’t focus on those aspects in this tutorial.

Before we talk about creating images in code, recall that at the most primitive level, we know what a 2D image really is: a two-dimensional array of pixel values. The region of memory representing the pixel array for an image is often referred to as its bitmap store. This is sometimes useful to keep in mind when making memory considerations. However, it is important to realize that a UIImage is really a higher level abstraction of an image than that of a pixel array, and one that has been optimized according to the demands and usage scenarios of a mobile platform. While it is theoretically possible to create an image by populating an entire pixel array with values, or to reach into an existing image’s bitmap and read or modify the value of an individual pixel, it is rather inconvenient to do so on iOS and is not really facilitated by the API. However since the majority of app developers seldom find real need to mess with images at the pixel level, it’s usually not an issue.

What UIImage (or more generally, UIKit and Core Graphics) does facilitate the developer to do is to create a new image by compositing existing images in interesting ways, or to generate an image by rasterizing a vector drawing constructed using UIKit‘s UIBezierPath class or Core graphic’s CGPath... functions. If you want to write an app that lets the user create a collage of their pictures, it’s easy to do with UIKit and UIImage. If you’ve developed, say, a freehand drawing app and you want to let the user save his creation, then the simplest approach would involve extracting a UIImage from the drawing context. In the first section of this tutorial, you’ll learn exactly how both of these ideas can be accomplished!

It is important to keep in mind that a UIImage constructed this way is no different than an image obtained by opening a picture from the photo album, or downloading from the Internet: it can be saved to the archive or uploaded to the photo album or displayed in a UIImageView.

Image resizing is an important type of image manipulation. Obviously you’d like to avoid enlarging an image, because that causes the image’s quality and sharpness to suffer. However, there are certain scenarios in which resizable images are needed and there actually are sensible ways to do so that don’t degrade the quality of the image. UIImage caters for this situation by permitting images that have an inner resizable area and “edge insets” on the image borders that resize in a particular direction, or don’t resize at all. Furthermore, the resizing can be carried out either by tiling or stretching the resizable portions for two somewhat different effects that can be useful in different situations.

The second part section of the implementation will show a concrete implementation of this idea. We’ll write a nifty little class that can display any amount of text inside a resizable image!

Finally, we’ll talk a bit about animating images with UIImage. As you can probably guess, this means “playing” a series of images in succession, giving rise to the illusion of animation much like the animated GIFs that you see on the Internet. While this might seem a bit limited, in simple situations UIImage‘s animated image support might be just what you need, and all it takes is a couple of lines of code to get up and running. That’s what we’ll look at in the third and final section of this tutorial! Time to roll up our sleeves and get to work!


1. Starting a New Project

Create a new iOS project in Xcode, with the “Empty Application” template. Call it “UIImageFun”. Check the option for Automatic Reference Counting, but uncheck the options for Core Data and Unit Tests.

Creating a new Xcode project
Creating a new Xcode project

A small note, before we proceed: this tutorial uses several sets of images, and to obtain these you’ll need to click where it says “Download Source Files” at the top of this page. After downloading and unzipping the archive, drag the folder named “Images” into the Project Navigator – the leftmost tab in the lefthand pane in Xcode. If the left pane isn’t visible, then press the key combination ⌘ + 0 to make it visible and ensure the leftmost tab – whose icon looks like a folder – is selected.

Drag and drop the Images folder into your project
Drag and drop the Images folder into your project
Ensure that the “Copy items into destination group’s folder (if needed)” option in the dialog box is checked”.
Ensure that the files get copied to your project folder
Ensure that the files get copied to your project folder

The downloaded file also contains the complete Xcode project with the images already added to the project, in case you get stuck somewhere.


2. Creating an Image in Code

Create a new file for an Objective-C class, call it ViewController and make it a subclass of UIViewController. Ensure that the options related to iPad and XIB are left unchecked.

Make a new view controller
Make a new view controller

Replace all the code in ViewController.m with the following:

#import "ViewController.h"
@interface ViewController ()
{
    UIImage *img;
    UIImageView *iv;
    NSMutableArray *ivs;
}
@end
@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    // (1) Creating a bitmap context, filling it with yellow as "background" color:
    CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
    [[UIColor yellowColor] setFill];
    UIRectFill(CGRectMake(0, 0, size.width, size.height));
    // (2) Create a circle via a bezier path and stroking+filling it in the bitmap context:
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(size.width/2, size.height/2) radius:140 startAngle:0 endAngle:2 * M_PI clockwise:YES];
    [[UIColor blackColor] setStroke];
    bezierPath.lineWidth = 5.0;
    [bezierPath stroke];
    [[UIColor redColor] setFill];
    [bezierPath fill];
    // (3) Creating an array of images:
    NSArray *rocks = @[[UIImage imageNamed:@"rock1"],
                       [UIImage imageNamed:@"rock2"],
                       [UIImage imageNamed:@"rock3"],
                       [UIImage imageNamed:@"rock4"],
                       [UIImage imageNamed:@"rock5"],
                       [UIImage imageNamed:@"rock6"],
                       [UIImage imageNamed:@"rock7"],
                       [UIImage imageNamed:@"rock8"],
                       [UIImage imageNamed:@"rock9"]];
    // (4) Drawing rocks in a loop, each chosen randomly from the image set and drawn at a random position in a circular pattern:
    for ( int i = 0; i < 100; i++)
    {
        int idx = arc4random() % rocks.count;
        NSLog(@"idx = %d", idx);
        int radius = 100;
        int revolution = 360;
        float r = (float)(arc4random() % radius);
        float angle = (float)(arc4random() % revolution);
        float x = size.width/2 + r * cosf(angle * M_PI/180.0);
        float y = size.height/2 + r * sinf(angle * M_PI/180.0);
        CGSize rockSize = ((UIImage *)rocks[idx]).size;
        [rocks[idx] drawAtPoint:CGPointMake(x-rockSize.width/2, y-rockSize.height/2)];
    }
    // (5) Deriving a new UIImage instance from the bitmap context:
    UIImage *fImg = UIGraphicsGetImageFromCurrentImageContext();
    // (6) Closing the context:
    UIGraphicsEndImageContext();
    // (7) Setting the image view's image property to the created image, and displaying
    iv = [[UIImageView alloc] initWithImage:fImg];
    [self.view addSubview:iv];
}
@end

Configure the App Delegate to use an instance of ViewController as the root view controller by replacing the code in AppDelegate.m with the following:

#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[ViewController alloc] init];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
@end

Let’s examine the code for viewDidLoad: where all the action happens. We’ll refer to the numbers in the code context.

  1. We want to start by drawing an image, which means we need a “canvas”. In proper terminology, this is called an image context (or bitmap context). We create one by calling the UIGraphicsBeginImageContextWithOptions() function. This function takes as arguments a CGSize, which we’ve set to the size of our view controller’s view, meaning the entire screen. The BOOL tell us whether the context is opaque or not. An opaque context is more efficient, but you can’t “see through” it. Since there’s nothing of interest underneath our context, we set it to YES. The scale factor, which is a float that we set to 0.0 (a value that ensures device-specific scale). Depending on whether the device has a Retina display, the scale factor will be set to 2.0 or 1.0 respectively. I’ll talk a bit more about the scale factor shortly, but for a comprehensive discussion, I’ll refer you to the official documentation (specifically, the “Points vs. Pixels” section in the Drawing and Printing Guide for iOS).

    Once we create an image context this way, it becomes the current context. This is important because to draw with UIKit, we must have a current drawing context where all the implicit drawing happens. We now set a fill color for the current context and fill in a rectangle the size of the entire context.

  2. We now create a UIBezierPath instance in the shape of a circle, which we stroke with a thick outline and fill with a different color. This concludes the drawing portion of our image creation.

  3. We create an array of images, with each image instantiated via the imageNamed: initializer of UIImage. It’s important to observe here that we have two sets of rock images: rock1.png, rock2.png,… and rock1@2x.png, rock2@2x.png, the latter being twice the resolution of the former. One of the great features of UIImage is that at runtime the imageNamed: method automatically looks for an image with suffix @2x presumed to be of double resolution on a Retina device. If one is available, it is used! If the suffixed image is absent or if the device is non-Retina, than the standard image is used. Note that we don’t specify the suffix of the image in the initializer. The use of single- and double-resolution images in conjunction with the device-dependent scale (as a result of setting scale to 0.0) ensures the actual size of the objects on screen will be the same. Naturally, the Retina images will be more crisp because of the higher pixel density.
  4. If you view the rock images, you’ll notice that the double-resolution images are flipped with respect to the single-resolution ones. I did that on purpose so we could confirm that at runtime the correct resolution images were being used – that’s all. Normally the two sets of images would be the same (apart from the resolution).
  5. We compose our image in a loop by placing a randomly chosen rock from our picture set at a random point (constrained to lie in a circle) in each iteration. The UIImage method drawAtPoint: draws the chosen rock image at the specified point into the current image context.
  6. We now extract a new UIImage object from the contents of the current image context, by calling UIGraphicsGetImageFromCurrentImageContext().
  7. The call to UIGraphicsEndImageContext() ends the current image context and cleans up memory.
  8. Finally, we set the image we created as the image property of our UIImageView and display it on screen.

Build and run. The output should look like the following, only randomized differently:

UIImage created by drawing and composition
UIImage created by drawing and composition

By testing on both Retina and non-Retina devices or by changing the device type in the Simulator under the Hardware menu, you can ensure that the rocks are flipped with one in respect to the other. Once again, I only did this so we could easily confirm that the right set of images would be being picked at runtime. Normally, there’s no reason for you to do this!

To recap – at the risk of belaboring the point – we created a new image (a UIImage object) by compositing together images we already have on top of a drawing we drew.

On to the next part of the implementation!


3. Resizable Images

Consider the figure below.

A callout (left), deconstructed to show how the image might be resized sensibly (right)
A callout (left), deconstructed to show how the image might be resized sensibly (right)

The left image shows a callout or “speech bubble” similar to the one seen in many messaging apps. Obviously, we would like the callout to expand or shrink according to the amount of text in it. Also, we’d like to use a single image from which we can generate callouts of any size. If we magnify the entire callout equally in all directions, the entire image gets pixellated or blurred depending on the resizing algorithm being used. However, note the way that the callout image has been designed. It can be expanded in certain directions without loss of quality simply by replicating (tiling) pixels as we go along. The corner shapes can’t be resized without changing image quality, but on the other hand, the middle is just a block of pixels of uniform colour that can be made any size we like. The top and bottom sides can be stretched horizontally without losing quality, and the left and right sides vertically. All this is shown in the image on the right hand side.

Luckily for us, UIImage has a couple of methods for creating resizable images of this sort. The one we’re going to use is resizableImageWithCapInsets:. Here the “cap insets” represent the dimensions of the non-stretchable corners of the image (starting from the top margin and moving counterclockwise) and are encapsulated in a struct of type UIEdgeInsets composed of four floats:

typedef struct {
 float top, left, bottom, right;
 } UIEdgeInsets;

The figure below should clarify what these numbers represent:


Let’s exploit resizable UIImages to create a simple class that lets us enclose any amount of text in a resizable image!

Create a NSObject subclass called Note and enter the following code into Note.h and Note.m respectively.

 #import <Foundation/Foundation.h>
@interface Note : NSObject
@property (nonatomic, readonly) NSString *text;
@property (nonatomic, readonly) UIImageView *noteView;
- (id)initWithText:(NSString *)text fontSize:(float)fontSize noteChrome:(UIImage *)img edgeInsets:(UIEdgeInsets)insets maximumWidth:(CGFloat)width topLeftCorner:(CGPoint)corner;
@end
#import "Note.h"
@implementation Note
- (id)initWithText:(NSString *)text fontSize:(float)fontSize noteChrome:(UIImage *)img edgeInsets:(UIEdgeInsets)insets  maximumWidth:(CGFloat)width topLeftCorner:(CGPoint)corner
{
    if (self = [super init])
    {
#define LARGE_NUMBER 10000 // just a large (but arbitrary) number because we don't want to impose any vertical constraint on our note size
        _text = [NSString stringWithString:text];
        CGSize computedSize = [text sizeWithFont:[UIFont systemFontOfSize:fontSize] constrainedToSize:CGSizeMake(width, LARGE_NUMBER) lineBreakMode:NSLineBreakByWordWrapping];
        UILabel *textLabel = [[UILabel alloc] init];
        textLabel.font = [UIFont systemFontOfSize:fontSize];
        textLabel.text = self.text;
        textLabel.numberOfLines = 0; // unlimited number of lines
        textLabel.lineBreakMode = NSLineBreakByWordWrapping;
        textLabel.frame = CGRectMake(insets.left, insets.top, computedSize.width , computedSize.height);
        _noteView = [[UIImageView alloc] initWithFrame:CGRectMake(corner.x, corner.y, textLabel.bounds.size.width+insets.left+insets.right, textLabel.bounds.size.height+insets.top+insets.bottom)];
        _noteView.image = [img resizableImageWithCapInsets:insets];
        [_noteView addSubview:textLabel];
    }
    return self;
}
@end

The initializer method for Note, -initWithText:fontSize: noteChrome:edgeInsets:maximumWidth:topLeftCorner: takes several parameters, including the text string to be displayed, the font size, the note “chrome” (which is the resizable UIImage that will surround the text), its cap insets, the maximum width the note’s image view may have, and the top-left corner of the note’s frame.

Once initialised, the Note class’ noteView property (of type UIImageView) is the user interface element that we’ll display on the screen.

The implementation is quite simple. We exploit a very useful method from the NSString‘s category on UIKit, sizeWithFont:constrainedToSize:lineBreakMode:, that computes the size that a block of text will occupy on the screen, given certain parameters. Once we’ve done that, we construct a text label (UILabel) and populate it with the provided text. By taking into account the inset sizes and the calculated text size, we assign the label an appropriate frame, as well as make our noteView‘s image large enough (using the resizableImageWithCapInsets method) so that the label fits comfortably on top of the interior area of the the image.

In the figure below, the image on the left represents what a typical note containing a few lines worth of text in it would look like.

Using the smallest possible image for constructing a resizable image
Using the smallest possible image for constructing a resizable image

Note that the interior has nothing of interest. We can actually “pare” the image to its bare minimum (as shown on the right) by getting rid of all the pixels in the interior with image editing software. In fact, in the documentation Apple recommends that for best performance, the interior area should be tiled 1 x 1 pixels. That’s what the funny little image on the right represents, and that’s the one we’re going to be passing to our Note initializer. Make sure that it got added to your project as squeezednote.png when you dragged the Images folder to your project.

In ViewController.m, enter the #import "Note.h" statement at the top. Comment out the previous viewDidLoad: form and enter the following:

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString *text1 = @"Hi!";
    NSString *text2 = @"I size myself according to my content!";
    NSString *text3 = @"Standard boring random text: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
    UIImage *noteChrome = [UIImage imageNamed:@"squeezednote"];
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(37, 12, 12, 12);
    Note *note1 = [[Note alloc] initWithText:text1 fontSize:25.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(10, 10)];
    Note *note2 = [[Note alloc] initWithText:text2 fontSize:30.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(200, 10)];
    Note *note3 = [[Note alloc] initWithText:text3 fontSize:16.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(10, 200)];
    [self.view addSubview:note1.noteView];
    [self.view addSubview:note2.noteView];
    [self.view addSubview:note3.noteView];
}

We’re simply creating Note objects with a different amount of text. Build, run, and observe how nicely the “chrome” around the note resizes to accommodate the text inside its boundaries.

Nicely resizing note
Nicely resizing note

For the sake of comparison, here’s what the output would look like if “squeezednote.png” were configured as a “normal” UIImage instantiated with imageNamed: and resized equally in all directions.

Badly resizing note

Admittedly, we wouldn’t actually use a “minimal” image like “squeezednote” unless we were using resizable images in the first place, so the effect shown in the previous screenshot is greatly exaggerated. However, the blurring problem would definitely be there.

On to the final part of the tutorial!


4. Animated Images

By animated image, I actually mean a sequence of individual 2D images that are displayed in succession. This is basically just the sprite animation that is used in most 2D games. UIImage has an initializer method animatedImageNamed:duration: to which you pass a string that represents the prefix of the sequence of images to be animated, so if your images are named “robot1.png”, “robot2.png”, …, “robot60.png”, you’d simply pass in the string “robot” to this method. The duration of the animation is also passed in. That’s pretty much it! When the image is added to a UIImageView, it continuously animates on screen. Let’s implement an example.

Comment out the previous version of viewDidLoad: and enter the following version.

 - (void)viewDidLoad
{
    [super viewDidLoad];
    ivs = [NSMutableArray array];
    img = [UIImage animatedImageNamed:@"explosion" duration:2.0];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(explosion:)];
    [self.view addGestureRecognizer:tap];
}
- (void)explosion:(UITapGestureRecognizer *)t
{
    CGPoint loc = [t locationInView:self.view];
    for (UIImageView *v in ivs)
    {
        if ([v pointInside:[v convertPoint:loc fromView:self.view] withEvent:nil])
        {
            [ivs removeObject:v];
            [v removeFromSuperview];
            return;
        }
    }
    UIImageView *v = [[UIImageView alloc] initWithImage:img];
    v.center = loc;
    [ivs addObject:v];
    [self.view addSubview:v];
}

We added a set of PNG images to our project, explosion1.png through explosion81.png which represent an animated sequence of a fiery explosion. Our code is quite simple. We detect a tap on the screen and either place a new explosion animation at the tap point, or if there was already an explosion going on at that point, we remove it. Note that the essential code consists of just creating an animated image via animatedImageNamed: to which we pass the string @"explosion" and a float value for the duration.

You’ll have to run the app on the simulator or a device yourself in order to enjoy the fireworks display, but here’s an image that captures a single frame of the action, with several explosions going on at the same time.

Animated explosions
Animated explosions

Admittedly, if you were developing a high-paced action game such as a shoot ‘em up or a side scrolling platformer, then UIImage‘s support for animated images would seem quite primitive. They wouldn’t be your go-to approach for implementing animation. That’s not really what the UIImage is built for, but in other less demanding scenarios, it might be just the ticket! Since the animation runs continuously until you remove the animated image or the image view from the interface, you can make the animations stop after a prescribed time interval by sending a delayed message with – performSelector:withObject:afterDelay: or use an NSTimer.


Conclusion

In this tutorial, we looked at some useful but less well known features of the UIImage class that can come in handy. I suggest you take a look at the UIImage Class Reference because some of the features we discussed in this tutorial have more options. For example, images can be composited using one of several blending options. Resizable images can be configured in one of two resizing modes, tiling (which is the one we used implicitly) or stretching. Even animated images can have insets. We didn’t talk about the underlying CGImage opaque type that UIImage wraps around. You need to deal with CGImages if you program at the Core Graphics level, which is the C-based API that sits one level below UIKit in the iOS framework. Core Graphics is more powerful than UIKit to program with, but not quite as easy. We also didn’t talk about images created with data from a Core Image object, as that would make more sense in a Core Image tutorial.

I hope you found this tutorial useful. Keep coding!

Create a Sound Based Memory Game – Tuts+ Premium

$
0
0

This is the second installment in our Corona SDK Sound Memory tutorial. In today’s tutorial, we’ll add to our interface and the game interaction. Read on!


Tutorial Preview


Get the Full Series!

This tutorial series is available to Tuts+ Premium members only. Read a preview of this tutorial on the Tuts+ Premium web site or login to Tuts+ Premium to access the full content.


Joining Tuts+ Premium. . .

For those unfamiliar, the family of Tuts+ sites runs a premium membership service called Tuts+ Premium. For $19 per month, you gain access to exclusive premium tutorials, screencasts, and freebies from Mobiletuts+, Nettuts+, Aetuts+, Audiotuts+, Vectortuts+, and CgTuts+. You’ll learn from some of the best minds in the business. Become a premium member to access this tutorial, as well as hundreds of other advanced tutorials and screencasts.

What’s Happening in Android: Google I/O and the Developer Console

$
0
0

From Android Studio to the recent changes made in the Google Play Developer Console, the last few months have revealed a load of big changes for developers working on Android applications! This article provides an overview of what you need to know to keep up!


Android Studio

For most developers Android Studio is by far the biggest news to come out of Google I/O this year. If you’ve tried designing, developing, and testing an app for Android using Eclipse with ADT, you’ll know what a headache fragmentation can give you. By shifting to an IDE that is tailor-made for Android, based on the IntelliJ Idea, rather than an existing and long-standing Java IDE with an Android plug-in, Android development should become a more streamlined process.

Android Studio is currently available for download as what’s being referred to as an early access preview, so if you are trying it out expect bugs. Android Studio uses the Gradle build system, with refactoring and fix tools designed specifically for Android, plus Lint tools putting app creators in a much improved position to develop for performance. However, the biggest difference for most developers is likely to be the ability to create user interfaces more effectively for the vast range of device screen sizes.

The Android Studio interface itself is designed to facilitate this process much more easily, with wizards letting you build common layout objects quickly and a hugely improved graphical editor for your layouts, letting you drag and drop components to arrange your interfaces visually, which was difficult at best before. In general, it’s going to be a lot easier to build and preview your app screens during development.

At the time of writing I must admit I haven’t had much of a chance to play around with Android Studio, but from first impressions the development process doesn’t seem hugely different in terms of the application/logic programming (i.e. Java) side of things. Your project structure is presented in much the same way, have a browse through the folders and you find everything you expect in there, with only very slight differences. Class structure navigation is also very similar, and you can toggle which views are available depending on how you like your workspace to look. The big difference is layout design.

Project in Android Studio

When you edit a layout file in design view, the IDE displays the Palette, presenting you with the range of Android UI components, including layouts, widgets, text-fields, and more. You can drag and drop these straight onto your layout. The Component Tree shows the overall structure of your layout, while the Properties view allows you to edit the details of your layout items without switching to the XML text editor.

Android Studio Palette
Android Studio Properties

When you have a layout open in Android Studio, selecting “Preview All Screen Sizes” gives you a real-time indicator of your UI on actual device screen sizes. When you see this in action, you really start to appreciate how clunky the design process has been up until now.

Android Studio Previewing Screen Sizes

Migrating from Eclipse

At the moment, the official developer guide is still instructing new Android developers to download and install Eclipse with the ADT plug-in, so in practice, the shift to Android Studio may be a way into the future yet. However, it’s probably a sensible move for existing developers to start getting acquainted with the new environment. If you want to work on existing apps in Android Studio, you need to migrate them from Eclipse.

Before you attempt to migrate any projects from Eclipse to Android Studio, make sure your ADT plug-in for Eclipse is up to date. When you export the project, select the option to Generate Gradle Build Files. You can then import the project straight into Android Studio.


Google Play Developer Console

As anyone with published Android apps will already know, the Google Play Developer Console has had an overhaul over the past few months. The new console was introduced in October 2012, became default in March 2013, and the old version has been retired completely. Google I/O this year did touch on the console, but let’s recap some of the most notable changes you will already be experiencing when it comes to publishing, updating and monitoring the performance of any Android apps you publish through the Google Play store.

Launch

The new console was launched in October 2012. Here’s the Google Hangout in which the developer console team introduced some of the major features:

There are lots of minor changes, such as general visual improvements to the user interface and the search and navigation functions; these are particularly useful for developers with many published apps. The more significant developments concern statistics, app listings, and the publishing process.

Statistics

Performance measurement and app optimization within the developer console have been significantly enhanced with improved statistical reports. You can filter these reports to retrieve detailed graphical overviews of an app’s performance in terms of installs, ratings and errors. For example, you can view a graph tracking installs over time with optional filters for devices, app versions, Android versions, countries, carriers and language.

Ratings and Filters

As well as installs, the Statistics section can display daily and cumulative average ratings using the same filters. One of the most potentially useful applications of this is the ability to check for any changes in an app’s performance after an upgrade, filtering the stats by app version.

In the new developer console you can also view error reports by app version, drilling right down to the stack traces for crashes, including any user messages. This means that you can identify any issues introduced in a new version of an app, letting you address the problem promptly.

Listings

Control over the listings for your apps has also been extensively developed, particularly when it comes to language. You can add listing text for specific languages, letting you control exactly what will appear in the Google Play store for users speaking a different language. This includes the ability to use language-specific graphics, which is vital if your app listing uses graphics with linguistic elements in them. You will also be able to get access to professional translations from within the console itself.

Any languages without specific listing text will now be automatically translated by Google. It goes without saying that such translations are not always accurate, but they are surely better than having nothing if you haven’t been able to translate the listing properly. Whenever you update a listing, the automated translation will update along with it. You may have used the translation utility before, which you had to opt into manually, but now it is done automatically.

Potential users will simply see the translation of your app listing in their language, whether it’s a translation you provided or the Google translation. Language support has also been extended, now including UK English among other language variants.

Listing Languages

Publishing

The publishing process in Google Play Developer Console has vastly improved. It has clarified what information needs to be input as you work through the publishing screen, and you can save without having to complete everything in one go.

It’s easier to spot differences between APK versions if you’re upgrading an existing app; the APK Upload tool highlights changes from the previous version, giving you the chance to rectify any accidental errors you’ve made in terms of supported devices, permissions, etc. I must admit, I’ve always found the process of publishing or upgrading an app in the console to be a bit of a headache, so I’m pleased to see these improvements.

APK Upload

In the APK section for an app in the console, you can now see a lengthy list of actual devices together with an indication of your app’s is compatibility. The Device Compatibility window displays a series of devices by manufacturer and model, with a clear graphical indicator of compatibility for each. Rather than scrolling through the whole lot, you can search for any particular device you are interested in.

Device List

Comments

Surely, one of the most requested developments to the console has been the ability to respond to user comments. The one-way nature of the comment and ratings system has been a frustrating aspect of publishing apps in the Google Play store for many developers. The good news is that the ability to reply to comments regarding your apps is being introduced, albeit in a relatively restricted form.

Over the past few months, this feature has been rolled out completely to all developers, so you should have the ability to do this now via your Developer Console. Essentially, you have the opportunity to publicly respond once to any single comment. The user will be sent an email, including links through which they can update their original comment or contact the app developer for support.

The Google support information on this feature outlines some fairly specific rules on developer responses to comments, making it clear that this ability may be revoked if a developer gives inappropriate or unprofessional responses, so take a deep breath before you draft your replies to any rude commenters! You can alter your developer responses to user comments any time you like, so if a user you’ve responded to alters their comment as a result, you can update your response accordingly.

The ability to respond to comments on your apps is likely to be most useful with apps for which you are trying to build long term relationships with users – it’s clearly intended as a communication medium for resolving issues with your apps rather than a system for exercising your right-to-reply. However, this new feature (combined with the already implemented requirement for commenters to sign in with Google+) does seem to indicate a commitment to improving quality in the comment system for Android apps, something all developers will likely welcome.


Beta Testing and Rollouts

Staged rollouts have been another of the most desired facilities for Android developers. The Developer Console now provides developers with alpha and beta testing/ graduated rollout options. This has been big news for many developers, particularly those with an existing client base they can use to get feedback early in the development lifecycle. With the new rollout options you can release an app version to a selected group of users, minimizing the impact if you use server resources and giving you control over who you get feedback from, hopefully improving the quality of that data.

Previously, you either had to arrange your own testing/ rollout process independently of Google Play, use a third-party service or simply wait until you had a version you were happy to release through the official publication channels and get feedback that way, potentially damaging your app listings. The ratings system on Google Play made this problematic for many development teams, as a poorly received early version of an app could cause a serious blow to your app’s ratings that was difficult to counter in the long term.

The beta testing facility within Google Play is provided via Google+, with the company trying hard to make it the default communication medium for all things Android. With Google+ Groups and Communities, you will be able to communicate privately with beta testers, rather than through the existing comments system in which everything said is instantly in the public domain. This lets developers try out new apps and features without the risk of damaging their standings in the public listings – potentially very valuable in the sometimes harsh context of Google Play!


Conclusion

Both Android Studio and the new Google Play Developer Console genuinely seem to represent a real effort to meet developer needs and wants. With access to more detailed information about apps in terms of multiple performance aspects, the new console provides a whole new world of optimization abilities. However, the really big news is Android Studio. At the moment it’s hard to say what impact the new IDE will have, but the limited view of it that we have now is already making it look vastly superior as an Android development environment. For most Android developers, the biggest disadvantage to creating apps for the platform is fragmentation, and this IDE represents a serious effort to counter, or at least alleviate that.

Let us know what you think of either the new Developer Console or the announcements at Google I/O in the comments!


Build a Monster Smashing Game with Cocos2D: Project Setup

$
0
0

This tutorial will teach you how to use the Cocos2D iOS framework in order to create simple yet advanced 2D games aimed at all iOS devices. It’s designed for both novice and advanced users. Along the way, you’ll learn about the Cocos2D core concepts, touch interaction, menus and transitions, actions, particles, and collisions. Read on!

The Cocos2D tutorial will be divided into three parts in order to completely cover each section. After the three part tutorial, you’ll be able to create an interesting 2D game using Cocos2D!

Each part will produce a practical result, and the sum of all parts will produce the final game. Despite the fact that each part can be read independently, for better understanding and guidance we suggest that the tutorial be followed step by step. We’ve also included the source code for each part separately, providing a way to start the tutorial at any point of the tutorial without having to read the prior portions.


Series Structure

Before we start the tutorial, we’d like to thank Rodrigo Bellão for providing us with the images of the monsters.


1. Installation and Basics

If this is your first time working with Cocos2D, you’ll need to download and install the SDK from the Cocos2D for iPhone Webpage or from the GitHub Repository. Once you’ve downloaded it, you’ll need to install the Xcode project templates. Unzip the package and open up a terminal window on the directory you downloaded Cocos2D to.

Enter the following command.


./install-templates -f -u

This command will only work if Xcode is installed within the default directory.


2. Monster Smashing!

Now we have Cocos2D installed. To start our game, we need to launch Xcode and go to File Menu > New > Project. We’ll see something like the following image.

Figure 1: Choosing Cocos2D template
Illustration of the template chooser (Xcode).

We need to choose the “Cocos2D iOS” template (from the Cocos2D v2.x lateral list). This template creates what we need to start our project, and includes the libraries required to run Cocos2D. The project is created with three classes (AppDelegate, HelloWorldLayer, and IntroLayer). Today, you will be working with HelloWorldLayer.


3. Pixel and Point Notation

A pixel is the smallest controllable element of a picture represented on the screen. There are several display resolutions in the iDevice family. Thus, the Cocos2D framework introduces a notation to help developers positioning objects in the screen. This is called point notation.

The device coordinate space is normally measured in pixels. However, the logical coordinate space is measured in points. The next table presents the direct relation between devices, points, and pixels.

DevicePointsPixel
iPhone 3G480×320480×320
iPhone Retina480×320960×640
iPad 31024×7682048×1536

From Cocos2D version 0.99.5-rc0 and above, the user can place objects in the screen using only point notation. This will offer a great advantage since the user knows that the object placement will be the same despite the end device where the application will run.

Nevertheless, if the user wants to, it’s possible to place objects using pixel notation simply by modifying the type of message that we pass to the object.

// Director
CCDirector *director [CCDirector sharedDirector];
CGSize objectA = [director winSize]; // points notation
CGSize objectB = [director winSizeInPixels]; // pixel notation
// Node
CCNode *node = [...];
CGPoint posA = [node position];  // points notation
CGPoint posB = [node positionInPixels]; // pixel notation
// Sprite
CCSprite *sprite = [...];
CGRect rectA = [sprite rect]; // points notation
CGRect rectB = [sprite rectInPixels]; // pixel notation

Note that the object setters can also be used in both notations.

[sprite setPosition:ccp(x,y)]; // points notation
[sprite setPositionInPixels:(x,y)] // pixel notation

We recommend using the point notation since we know that the positions will be the same in all devices.

Now that we know how the pixel and point notation work, the next step is to understand where the objects are positioned when we pass a specific coordinate. In Codos2D, the point (0,0) is in the bottom left corner of the screen. Thus, the top-right corner has the (480,320) point.

Figure 2: x-,y- axis coordination system
Illustration of pixel and dot notation using the iPhone simulator.

For example, to position a sprite you had to do:

sprite.position = ccp(480,320); // at the top-right corner
sprite.position = ccp(0,320); // at the top-left corner
sprite.position = ccp(480,0); // at the bottom-right corner
sprite.position = ccp(240,160); // at the center of the screen

4. Background and Logo

Cocos2D introduced the concept of scenes. Each scene can be seen as a different screen, which is used to incorporate menus, levels, credits, and everything else that you see during the game. Note that only one scene can be active at a given time.

Inside the scenes, you can have several layers (like Photoshop). The layers can have several properties, and we can have a large number of them on the screen simultaneously. They can have several properties such as background color, animation, a menu, or even a shadow.

Sprites are simply 2D images that can be moved, rotated, scaled, animated, and so on. In the next step, you will change the project source code and add a sprite.

Before you add a sprite, some artwork, which is available in the resources folder, is required.

All resources used in this tutorial are designed for the original iPad resolution.

Once you have downloaded the resources, you will find six images. Copy them to the resources folder of your project and make sure that the option “Copy items into destination goups’s folder (if needed)” is checked.

Now, in the HelloWorldLayer.m you can delete everything inside the condition if( (self=[super init])). The sprite creation can be achieved in code using the following snippet.

CGSize winSize = [CCDirector sharedDirector].winSize;
CCSprite *backgroundImage = [CCSprite spriteWithFile:@"WoodRetroApple_iPad_HomeScreen.jpg"];
backgroundImage.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:backgroundImage z:-2];

With the above-mentioned snippet you can change the default background image of that class. The code is simple to understand, and to summarize.

  • Ask for the window size
  • Initialize a CCSprite with the picture we want to load
  • Center the sprite on screen
  • Add the CCSprite to the scene

Note that we put the z = -2 because the background needs to stay behind all objects.

Let’s add a logo. Looking at the above-mentioned code, we already know how to achieve it. The process is simple and straightforward. If you need some help, you can use the following code.

CCSprite *logo = [CCSprite spriteWithFile:@"MonsterSmashing.png"];
logo.scale = 1.2;
logo.position =  ccp(winSize.width /2 , 800 );
[self addChild: logo];

It’s now time to run the project and see if everything is working as expected. You should see similar to the next screen.

Figure 3: Monster Smashing Logo and Background
Illustration of the first screen. The screen contains the default game name and logo.

5. Creating the Menu

The main objective for this step is to add a default menu. The menu will have two options:

  • Play
  • Sound

The Play option will start a new game, while the Sound option is a toggle button that will provide us a way to stop or start the sound engine (in this case the background music).

To implement the Play button (using the CCMenuItem class), we just need to add a line (for now). This line represent an Item of the Menu. The option is represented using an image (added previously to the resources)

CCMenuItem *startGameButtonImage = [CCMenuItemImage itemFromNormalImage:@"playButton.png" selectedImage:@"playButtonSelected.png" target:self selector:@selector(onStartGamePressed)];

To create and describe the Play Button we need to pass it through several messages. The itemFromNormalImage is the image location information, and the selectedImage is the image that appears when our finger is hovering over the item. The selector is the method that will be called when the user lifts their finger from the menu item, which in this case is the method onStartgamePressed. We’ll talk about that later.

The toggle button is more complex than the play button since it uses two states, on and off. We need to define the two states and then add them to the final toggle button, CCMenuItemToogle. The on and off button is defined as the play button.

After we create the two options, we need to join the options. Use the snippet below for this.

CCMenuItem *startGameButtonImage = [CCMenuItemImage itemFromNormalImage:@"playButton.png" selectedImage:@"playButtonSelected.png" target:self selector:@selector(onStartGamePressed)];
CCMenuItem *_soundOn = [[CCMenuItemImage  itemFromNormalImage:@"soundOn.png"
                                                         selectedImage:@"soundOnSelected.png" target:nil selector:nil] retain];
CCMenuItem *_soundOff = [[CCMenuItemImage itemFromNormalImage:@"soundOff.png"
                                                         selectedImage:@"soundOffSelected.png" target:nil selector:nil] retain];
CCMenuItemToggle *toggleItem = [CCMenuItemToggle itemWithTarget:self
                                                               selector:@selector(soundButtonTapped:) items:_soundOn, _soundOff, nil];

After adding the above snippet, we need to create the real menu with the inherent options. We need to use a CCMenu and send several CCMenuItems through the method menuWithItems. As with any other object, we must define its position and its padding, which in this case is vertical. Here’s the code.

CCMenu *menu = [CCMenu menuWithItems:startGameButtonImage, toggleItem, nil];
menu.position = ccp(size.width * 0.5f, size.height * 0.4f);
[menu alignItemsVerticallyWithPadding:15];
//Add the menu as a child to this layer
[self addChild:menu];

Previously, we said that the selector parameter called a method. We call two methods, onStartGamePressed and soundButtonTapped. The first one will replace the current scene, while the second will activate or deactivate the game sound.

Before we go to the method implementations, we need to create a new scene. So, go to File > New > File… and select a CCNode class and name it “MonsterRun“. Two new files will be added to your project, MonsterRun.h and MonsterRun.m). Make sure that the class uses the following code.

+(CCScene *) scene {
  CCScene *scene = [CCScene node];
  MonsterRun *layer = [MonsterRun node];
  [scene addChild: layer];
  return scene;
}

Now, we can go back to the HelloWorldLayer.m and reference the MonsterRun. We use the following line to create it.

#import "MonsterRun.h"

For now, we’ll leave this class and its code alone. It’ll be covered in the next tutorial. Let’s rewind a bit for the menus and the two methods that we talked about before, onStartGamePressed and soundButtonTapped.

- (void)soundButtonTapped:(id)sender {
    // Covered in Part 3
}
- (void)onStartGamePressed {
  CCScene *MonsterRunScene = [MonsterRun scene];
  CCTransitionFlipAngular *transition = [CCTransitionFlipAngular transitionWithDuration:0.5f scene:helloWorldScene];
  [[CCDirector sharedDirector] replaceScene:MonsterRunScene];
}

Now run the project in order to see if everything is working correctly. You will see something similar to the next image.

Figure 4: Monster Smashing Logo and Background
Illustration of the first screen. The screen contains the default game name, logo, and options menu.

6. Results

At this point, you have to be able to understand and perform the following tasks:

  • Install the Cocos2D framework.
  • Configure a Cocos2D project.
  • Implement scenes, layers, sprites and use the director.
  • Pixel and point differences.
  • Screen positioning and x-, y- axis orientation.
  • Define and use menus.

Create a Weather App with Forecast – User Interface

$
0
0

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 Vector Tuts+, 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.

extern NSString * const MTRainWeatherDataDidChangeChangeNotification;
extern NSString * const MTRainTemperatureUnitDidChangeNotification;
NSString * const MTRainWeatherDataDidChangeChangeNotification = @"com.mobileTuts.MTRainWeatherDataDidChangeChangeNotification";
NSString * const MTRainTemperatureUnitDidChangeNotification = @"com.mobileTuts.MTRainTemperatureUnitDidChangeNotification";

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.

#import "MTWeatherViewController.h"
#import "MTForecastClient.h"
@interface MTWeatherViewController () <CLLocationManagerDelegate> {
    BOOL _locationFound;
}
@property (strong, nonatomic) NSDictionary *location;
@property (strong, nonatomic) NSDictionary *response;
@property (strong, nonatomic) NSArray *forecast;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

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.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Initialize Location Manager
        self.locationManager = [[CLLocationManager alloc] init];
        // Configure Location Manager
        [self.locationManager setDelegate:self];
        [self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
        // Add Observer
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
        [nc addObserver:self selector:@selector(reachabilityStatusDidChange:) name:MTRainReachabilityStatusDidChangeNotification object:nil];
        [nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil];
        [nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil];
    }
    return self;
}
- (void)weatherDataDidChangeChange:(NSNotification *)notification {
    // Update Response & Forecast
    [self setResponse:[notification userInfo]];
    [self setForecast:self.response[@"hourly"][@"data"]];
    // Update View
    [self updateView];
}
- (void)temperatureUnitDidChange:(NSNotification *)notification {
    // Update View
    [self updateView];
}

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.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Add Observer
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(weatherDataDidChangeChange:) name:MTRainWeatherDataDidChangeChangeNotification object:nil];
        [nc addObserver:self selector:@selector(temperatureUnitDidChange:) name:MTRainTemperatureUnitDidChangeNotification object:nil];
    }
    return self;
}
#import "MTForecastViewController.h"
@interface MTForecastViewController ()
@property (strong, nonatomic) NSDictionary *response;
@property (strong, nonatomic) NSArray *forecast;
@end
- (void)weatherDataDidChangeChange:(NSNotification *)notification {
    // Update Response & Forecast
    [self setResponse:[notification userInfo]];
    [self setForecast:self.response[@"daily"][@"data"]];
    // Update View
    [self updateView];
}
- (void)temperatureUnitDidChange:(NSNotification *)notification {
    // Update View
    [self updateView];
}
- (void)updateView {
}

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.

#import <UIKit/UIKit.h>
#import "MTLocationsViewController.h"
@interface MTWeatherViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, MTLocationsViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *buttonLocation;
@property (weak, nonatomic) IBOutlet UIButton *buttonRefresh;
@property (weak, nonatomic) IBOutlet UILabel *labelDate;
@property (weak, nonatomic) IBOutlet UILabel *labelTemp;
@property (weak, nonatomic) IBOutlet UILabel *labelTime;
@property (weak, nonatomic) IBOutlet UILabel *labelWind;
@property (weak, nonatomic) IBOutlet UILabel *labelRain;
@property (weak, nonatomic) IBOutlet UILabel *labelLocation;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@end

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.

- (IBAction)openLeftView:(id)sender {
    [self.viewDeckController toggleLeftViewAnimated:YES];
}

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.

- (void)fetchWeatherData {
    if ([[MTForecastClient sharedClient] networkReachabilityStatus] == AFNetworkReachabilityStatusNotReachable) return;
    // Show Progress HUD
    [SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
    // Query Forecast API
    double lat = [[_location objectForKey:MTLocationKeyLatitude] doubleValue];
    double lng = [[_location objectForKey:MTLocationKeyLongitude] doubleValue];
    [[MTForecastClient sharedClient] requestWeatherForCoordinate:CLLocationCoordinate2DMake(lat, lng) completion:^(BOOL success, NSDictionary *response) {
        // Dismiss Progress HUD
        [SVProgressHUD dismiss];
        if (response && [response isKindOfClass:[NSDictionary class]]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                // Post Notification on Main Thread
                NSNotification *notification = [NSNotification notificationWithName:MTRainWeatherDataDidChangeChangeNotification object:nil userInfo:response];
                [[NSNotificationCenter defaultCenter] postNotification:notification];
            });
        }
    }];
}

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.

- (void)updateView {
    // Update Location Label
    [self.labelLocation setText:[self.location objectForKey:MTLocationKeyCity]];
    // Update Current Weather
    [self updateCurrentWeather];
    // Reload Collection View
    [self.collectionView 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.

extern NSString * const MTRainUserDefaultsTemperatureUnit;
NSString * const MTRainUserDefaultsTemperatureUnit = @"temperatureUnit";

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
#import <Foundation/Foundation.h>
@interface NSUserDefaults (Helpers)
#pragma mark -
#pragma mark Temperature
+ (BOOL)isDefaultCelcius;
+ (void)setDefaultToCelcius;
+ (void)setDefaultToFahrenheit;
@end

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.

+ (BOOL)isDefaultCelcius {
    return [[NSUserDefaults standardUserDefaults] integerForKey:MTRainUserDefaultsTemperatureUnit] == 1;
}
+ (void)setDefaultToCelcius {
    // Update User Defaults
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setInteger:1 forKey:MTRainUserDefaultsTemperatureUnit];
    [ud synchronize];
    // Post Notification
    [[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil];
}
+ (void)setDefaultToFahrenheit {
    // Update User Defaults
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setInteger:0 forKey:MTRainUserDefaultsTemperatureUnit];
    [ud synchronize];
    // Post Notification
    [[NSNotificationCenter defaultCenter] postNotificationName:MTRainTemperatureUnitDidChangeNotification object:nil];
}

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
#import <Foundation/Foundation.h>
@interface MTSettings : NSObject
#pragma mark -
#pragma mark Convenience Methods
+ (NSString *)formatTemperature:(NSNumber *)temperature;
@end
#import "NSUserDefaults+Helpers.h"
+ (NSString *)formatTemperature:(NSNumber *)temperature {
    float value = [temperature floatValue];
    if ([NSUserDefaults isDefaultCelcius]) {
        value = (value  -  32.0)  *  (5.0 / 9.0);
    }
    return [NSString stringWithFormat:@"%.0f°", value];
}

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.

#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <QuartzCore/QuartzCore.h>
    #import <CoreLocation/CoreLocation.h>
    #import <MobileCoreServices/MobileCoreServices.h>
    #import <SystemConfiguration/SystemConfiguration.h>
    #import "AFNetworking.h"
    #import "SVProgressHUD.h"
    #import "IIViewDeckController.h"
    #import "MTSettings.h"
    #import "MTConstants.h"
#endif

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.

- (void)updateCurrentWeather {
    // Weather Data
    NSDictionary *data = [self.response objectForKey:@"currently"];
    // Update Date Label
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    [dateFormatter setDateFormat:@"EEEE, MMM d"];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]];
    [self.labelDate setText:[dateFormatter stringFromDate:date]];
    // Update Temperature Label
    [self.labelTemp setText:[MTSettings formatTemperature:data[@"temperature"]]];
    // Update Time Label
    NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
    [timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    [timeFormatter setDateFormat:@"ha"];
    [self.labelTime setText:[timeFormatter stringFromDate:[NSDate date]]];
    // Update Wind Label
    [self.labelWind setText:[NSString stringWithFormat:@"%.0fMP", [data[@"windSpeed"] floatValue]]];
    // Update Rain Label
    float rainProbability = 0.0;
    if (data[@"precipProbability"]) {
        rainProbability = [data[@"precipProbability"] floatValue] * 100.0;
    }
    [self.labelRain setText:[NSString stringWithFormat:@"%.0f%%", rainProbability]];
}

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).

#import <UIKit/UIKit.h>
@interface MTHourCell : UICollectionViewCell
@property (strong, nonatomic) UILabel *labelTime;
@property (strong, nonatomic) UILabel *labelTemp;
@property (strong, nonatomic) UILabel *labelWind;
@property (strong, nonatomic) UILabel *labelRain;
@end
#import "MTHourCell.h"
#define kMTLabelBottomWidth 40.0
#define kMTLabelBottomHeight 40.0
@interface MTHourCell ()
@end
@implementation MTHourCell
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Helpers
        CGSize size = self.contentView.frame.size;
        // Initialize Label Time
        self.labelTime = [[UILabel alloc] initWithFrame:CGRectMake(30.0, 0.0, 50.0, 40.0)];
        // Configure Label Time
        [self.labelTime setBackgroundColor:[UIColor clearColor]];
        [self.labelTime setTextColor:[UIColor whiteColor]];
        [self.labelTime setFont:[UIFont fontWithName:@"GillSans-Light" size:18.0]];
        [self.labelTime setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.labelTime];
        // Initialize Label Temp
        self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 46.0, 80.0, 44.0)];
        // Configure Label Temp
        [self.labelTemp setBackgroundColor:[UIColor clearColor]];
        [self.labelTemp setTextAlignment:NSTextAlignmentCenter];
        [self.labelTemp setTextColor:kMTColorGray];
        [self.labelTemp setFont:[UIFont fontWithName:@"GillSans-Bold" size:40.0]];
        [self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.labelTemp];
        // Initialize Label Wind
        self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(0.0, size.height - kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)];
        // Configure Label Wind
        [self.labelWind setBackgroundColor:[UIColor clearColor]];
        [self.labelWind setTextAlignment:NSTextAlignmentCenter];
        [self.labelWind setTextColor:kMTColorGray];
        [self.labelWind setFont:[UIFont fontWithName:@"GillSans-Light" size:16.0]];
        [self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
        [self.contentView addSubview:self.labelWind];
        // Initialize Label Rain
        self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelBottomWidth, size.height - kMTLabelBottomHeight, kMTLabelBottomWidth, kMTLabelBottomHeight)];
        // Configure Label Rain
        [self.labelRain setBackgroundColor:[UIColor clearColor]];
        [self.labelRain setTextAlignment:NSTextAlignmentCenter];
        [self.labelRain setTextColor:kMTColorGray];
        [self.labelRain setFont:[UIFont fontWithName:@"GillSans-Light" size:16.0]];
        [self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
        [self.contentView addSubview:self.labelRain];
        // Background View
        UIImage *backgroundImage = [[UIImage imageNamed:@"background-hour-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(40.0, 10.0, 10.0, 10.0)];
        UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
        [backgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
        [backgroundView setImage:backgroundImage];
        [self setBackgroundView:backgroundView];
    }
    return self;
}
@end
#define kMTColorGray [UIColor colorWithRed:0.737 green:0.737 blue:0.737 alpha:1.0]
#define kMTColorGreen [UIColor colorWithRed:0.325 green:0.573 blue:0.388 alpha:1.0]
#define kMTColorOrange [UIColor colorWithRed:1.000 green:0.306 blue:0.373 alpha:1.0]

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

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.forecast ? 1 : 0;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.forecast count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    MTHourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:HourCell forIndexPath:indexPath];
    // Fetch Data
    NSDictionary *data = [self.forecast objectAtIndex:indexPath.row];
    // Initialize Date Formatter
    NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
    [timeFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    [timeFormatter setDateFormat:@"ha"];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]];
    // Configure Cell
    [cell.labelTime setText:[timeFormatter stringFromDate:date]];
    [cell.labelTemp setText:[MTSettings formatTemperature:data[@"temperature"]]];
    [cell.labelWind setText:[NSString stringWithFormat:@"%.0fMP", [data[@"windSpeed"] floatValue]]];
    float rainProbability = 0.0;
    if (data[@"precipProbability"]) {
        rainProbability = [data[@"precipProbability"] floatValue] * 100.0;
    }
    [cell.labelRain setText:[NSString stringWithFormat:@"%.0f%%", rainProbability]];
    return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(80.0, 120.0);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0);
}

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.

#import "MTHourCell.h"
static NSString *HourCell = @"HourCell";
- (void)viewDidLoad {
    [super viewDidLoad];
    // Load Location
    self.location = [[NSUserDefaults standardUserDefaults] objectForKey:MTRainUserDefaultsLocation];
    if (!self.location) {
        [self.locationManager startUpdatingLocation];
    }
    // Configure Collection View
    [self.collectionView setBackgroundColor:[UIColor clearColor]];
    [self.collectionView registerClass:[MTHourCell class] forCellWithReuseIdentifier:HourCell];
}

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.

#import <UIKit/UIKit.h>
@interface MTForecastViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end

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
#import <UIKit/UIKit.h>
@interface MTDayCell : UITableViewCell
@property (strong, nonatomic) UILabel *labelDay;
@property (strong, nonatomic) UILabel *labelDate;
@property (strong, nonatomic) UILabel *labelTemp;
@property (strong, nonatomic) UILabel *labelWind;
@property (strong, nonatomic) UILabel *labelRain;
@end
#import "MTDayCell.h"
#define kMTCalendarWidth 44.0
#define kMTCalendarHeight 80.0
#define kMTCalendarMarginLeft 60.0
#define kMTLabelRightWidth 30.0
#define kMTLabelRightHeight 14.0
@interface MTDayCell ()
@property (strong, nonatomic) UIImageView *imageViewCalendar;
@end
@implementation MTDayCell
#pragma mark -
#pragma mark Initialization
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Helpers
        CGSize size = self.contentView.frame.size;
        // Configure Table View Cell
        [self setSelectionStyle:UITableViewCellSelectionStyleNone];
        // Initialize Image View Clock
        self.imageViewCalendar = [[UIImageView alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 0.0, kMTCalendarWidth, kMTCalendarHeight)];
        // Configure Image View Clock
        [self.imageViewCalendar setContentMode:UIViewContentModeCenter];
        [self.imageViewCalendar setImage:[UIImage imageNamed:@"background-calendar-day-cell"]];
        [self.imageViewCalendar setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.imageViewCalendar];
        // Initialize Label Day
        self.labelDay = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 10.0, kMTCalendarWidth, 20.0)];
        // Configure Label Day
        [self.labelDay setTextColor:[UIColor whiteColor]];
        [self.labelDay setTextAlignment:NSTextAlignmentCenter];
        [self.labelDay setBackgroundColor:[UIColor clearColor]];
        [self.labelDay setFont:[UIFont fontWithName:@"GillSans" size:14.0]];
        [self.labelDay setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.labelDay];
        // Initialize Label Date
        self.labelDate = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarMarginLeft, 20.0, kMTCalendarWidth, 60.0)];
        // Configure Label Date
        [self.labelDate setTextColor:kMTColorGray];
        [self.labelDate setTextAlignment:NSTextAlignmentCenter];
        [self.labelDate setBackgroundColor:[UIColor clearColor]];
        [self.labelDate setFont:[UIFont fontWithName:@"GillSans" size:24.0]];
        [self.labelDate setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.labelDate];
        // Initialize Label Wind
        self.labelWind = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelRightWidth, (size.height / 2.0) - kMTLabelRightHeight, kMTLabelRightWidth, kMTLabelRightHeight)];
        // Configure Label Wind
        [self.labelWind setTextColor:kMTColorGray];
        [self.labelWind setTextAlignment:NSTextAlignmentCenter];
        [self.labelWind setBackgroundColor:[UIColor clearColor]];
        [self.labelWind setFont:[UIFont fontWithName:@"GillSans" size:12.0]];
        [self.labelWind setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin)];
        [self.contentView addSubview:self.labelWind];
        // Initialize Label Rain
        self.labelRain = [[UILabel alloc] initWithFrame:CGRectMake(size.width - kMTLabelRightWidth, (size.height / 2.0), kMTLabelRightWidth, kMTLabelRightHeight)];
        // Configure Label Rain
        [self.labelRain setTextColor:kMTColorGray];
        [self.labelRain setTextAlignment:NSTextAlignmentCenter];
        [self.labelRain setBackgroundColor:[UIColor clearColor]];
        [self.labelRain setFont:[UIFont fontWithName:@"GillSans" size:12.0]];
        [self.labelRain setAutoresizingMask:(UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin)];
        [self.contentView addSubview:self.labelRain];
        // Initialize Label Temp
        self.labelTemp = [[UILabel alloc] initWithFrame:CGRectMake(kMTCalendarWidth + kMTCalendarMarginLeft + 12.0, 0.0, size.width - kMTCalendarWidth - kMTCalendarMarginLeft - kMTLabelRightWidth - 12.0, size.height)];
        // Configure Label Temp
        [self.labelTemp setTextColor:kMTColorGray];
        [self.labelTemp setTextAlignment:NSTextAlignmentCenter];
        [self.labelTemp setBackgroundColor:[UIColor clearColor]];
        [self.labelTemp setFont:[UIFont fontWithName:@"GillSans-Bold" size:40.0]];
        [self.labelTemp setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
        [self.contentView addSubview:self.labelTemp];
    }
    return self;
}
@end

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.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.forecast ? 1 : 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.forecast count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MTDayCell *cell = [tableView dequeueReusableCellWithIdentifier:DayCell forIndexPath:indexPath];
    // Fetch Data
    NSDictionary *data = [self.forecast objectAtIndex:indexPath.row];
    // Initialize Date Formatter
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:[data[@"time"] doubleValue]];
    // Configure Cell
    [dateFormatter setDateFormat:@"EEE"];
    [cell.labelDay setText:[dateFormatter stringFromDate:date]];
    [dateFormatter setDateFormat:@"d"];
    [cell.labelDate setText:[dateFormatter stringFromDate:date]];
    float tempMin = [data[@"temperatureMin"] floatValue];
    float tempMax = [data[@"temperatureMax"] floatValue];
    [cell.labelTemp setText:[NSString stringWithFormat:@"%.0f°/%.0f°", tempMin, tempMax]];
    [cell.labelWind setText:[NSString stringWithFormat:@"%.0f", [data[@"windSpeed"] floatValue]]];
    float rainProbability = 0.0;
    if (data[@"precipProbability"]) {
        rainProbability = [data[@"precipProbability"] floatValue] * 100.0;
    }
    [cell.labelRain setText:[NSString stringWithFormat:@"%.0f", rainProbability]];
    return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 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.

#import "MTDayCell.h"
static NSString *DayCell = @"DayCell";
- (void)viewDidLoad {
    [super viewDidLoad];
    // Configure Table View
    [self.tableView registerClass:[MTDayCell class] forCellReuseIdentifier:DayCell];
}
- (void)updateView {
    // Reload Table View
    [self.tableView reloadData];
}

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
#import <UIKit/UIKit.h>
@interface MTLocationCell : UITableViewCell
@property (strong, nonatomic) UIButton *buttonDelete;
@property (strong, nonatomic) UILabel *labelLocation;
@end
#import "MTLocationCell.h"
#define kMTButtonDeleteWidth 44.0
#define kMTLabelLocationMarginLeft 44.0
@implementation MTLocationCell
#pragma mark -
#pragma mark Initialization
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Helpers
        CGSize size = self.contentView.frame.size;
        // Initialize Delete Button
        self.buttonDelete = [UIButton buttonWithType:UIButtonTypeCustom];
        // Configure Delete Button
        [self.buttonDelete setFrame:CGRectMake(0.0, 0.0, kMTButtonDeleteWidth, size.height)];
        [self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateNormal];
        [self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateSelected];
        [self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateDisabled];
        [self.buttonDelete setImage:[UIImage imageNamed:@"button-delete-location-cell"] forState:UIControlStateHighlighted];
        [self.buttonDelete setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin)];
        [self.contentView addSubview:self.buttonDelete];
        // Initialize Location Label
        self.labelLocation = [[UILabel alloc] initWithFrame:CGRectMake(kMTLabelLocationMarginLeft, 0.0, size.width - kMTLabelLocationMarginLeft, size.height)];
        // Configure Text Label
        [self.labelLocation setTextColor:kMTColorGray];
        [self.labelLocation setBackgroundColor:[UIColor clearColor]];
        [self.labelLocation setFont:[UIFont fontWithName:@"GillSans" size:20.0]];
        [self.labelLocation setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin)];
        [self.contentView addSubview:self.labelLocation];
    }
    return self;
}
@end

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).

#import "MTLocationsViewController.h"
#import "MTLocationCell.h"
#import "NSUserDefaults+Helpers.h"
@interface MTLocationsViewController ()
@property (strong, nonatomic) NSMutableArray *locations;
@end
static NSString *AddLocationCell = @"AddLocationCell";
static NSString *LocationCell = @"LocationCell";
static NSString *SettingsCell = @"SettingsCell";

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

- (void)setupView {
    // Setup Table View
    [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    // Register Class for Cell Reuse
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:AddLocationCell];
    [self.tableView registerClass:[MTLocationCell class] forCellReuseIdentifier:LocationCell];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:SettingsCell];
}

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.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        return [self.locations count] + 1;
    }
    return 2;
}

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:.

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    switch (section) {
        case 0: {
            return NSLocalizedString(@"Locations", nil);
            break;
        }
        default: {
            return NSLocalizedString(@"Temperature", nil);
            break;
        }
    }
    return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    // Header Text
    NSString *text = [self tableView:tableView titleForHeaderInSection:section];
    // Helpers
    CGRect labelFrame = CGRectMake(12.0, 0.0, tableView.bounds.size.width, 44.0);
    // Initialize Label
    UILabel *label = [[UILabel alloc] initWithFrame:labelFrame];
    // Configure Label
    [label setText:text];
    [label setTextColor:kMTColorOrange];
    [label setFont:[UIFont fontWithName:@"GillSans" size:20.0]];
    [label setBackgroundColor:[UIColor clearColor]];
    // Initialize View
    UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, tableView.bounds.size.width, 34.0)];
    [backgroundView setBackgroundColor:[UIColor clearColor]];
    [backgroundView addSubview:label];
    return backgroundView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 40.0;
}

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:.

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    // Initialize View
    UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, tableView.bounds.size.width, 34.0)];
    [backgroundView setBackgroundColor:[UIColor whiteColor]];
    return backgroundView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    if (section == 0) {
        return 40.0;
    }
    return 0.0;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = nil;
    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            cell = [tableView dequeueReusableCellWithIdentifier:AddLocationCell forIndexPath:indexPath];
        } else {
            cell = [tableView dequeueReusableCellWithIdentifier:LocationCell forIndexPath:indexPath];
        }
    } else {
        cell = [tableView dequeueReusableCellWithIdentifier:SettingsCell forIndexPath:indexPath];
    }
    // Configure Cell
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

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.

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    // Helpers
    UIFont *fontLight = [UIFont fontWithName:@"GillSans-Light" size:18.0];
    UIFont *fontRegular = [UIFont fontWithName:@"GillSans" size:18.0];
    // Background View Image
    UIImage *backgroundImage = [[UIImage imageNamed:@"background-location-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(10.0, 0.0, 0.0, 10.0)];
    // Configure Table View Cell
    [cell.textLabel setFont:fontLight];
    [cell.textLabel setTextColor:kMTColorGray];
    [cell.textLabel setBackgroundColor:[UIColor clearColor]];
    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            [cell.textLabel setText:@"Add Current Location"];
            [cell.imageView setContentMode:UIViewContentModeCenter];
            [cell.imageView setImage:[UIImage imageNamed:@"icon-add-location"]];
            // Background View Image
            backgroundImage = [[UIImage imageNamed:@"background-add-location-cell"] resizableImageWithCapInsets:UIEdgeInsetsMake(10.0, 0.0, 0.0, 10.0)];
        } else {
            // Fetch Location
            NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
            // Configure Cell
            [[(MTLocationCell *)cell buttonDelete] addTarget:self action:@selector(deleteLocation:) forControlEvents:UIControlEventTouchUpInside];
            [[(MTLocationCell *)cell labelLocation] setText:[NSString stringWithFormat:@"%@, %@", location[MTLocationKeyCity], location[MTLocationKeyCountry]]];
        }
    } else {
        if (indexPath.row == 0) {
            [cell.textLabel setText:NSLocalizedString(@"Fahrenheit", nil)];
            if ([NSUserDefaults isDefaultCelcius]) {
                [cell.textLabel setFont:fontLight];
                [cell.textLabel setTextColor:kMTColorGray];
            } else {
                [cell.textLabel setFont:fontRegular];
                [cell.textLabel setTextColor:kMTColorGreen];
            }
        } else {
            [cell.textLabel setText:NSLocalizedString(@"Celsius", nil)];
            if ([NSUserDefaults isDefaultCelcius]) {
                [cell.textLabel setFont:fontRegular];
                [cell.textLabel setTextColor:kMTColorGreen];
            } else {
                [cell.textLabel setFont:fontLight];
                [cell.textLabel setTextColor:kMTColorGray];
            }
        }
    }
    if (backgroundImage) {
        // Background View
        UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, cell.frame.size.width, cell.frame.size.height)];
        [backgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
        [backgroundView setImage:backgroundImage];
        [cell setBackgroundView:backgroundView];
    }
}

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.

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}

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.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            // Notify Delegate
            [self.delegate controllerShouldAddCurrentLocation:self];
        } else {
            // Fetch Location
            NSDictionary *location = [self.locations objectAtIndex:(indexPath.row - 1)];
            // Notify Delegate
            [self.delegate controller:self didSelectLocation:location];
        }
    } else {
        if (indexPath.row == 0 && [NSUserDefaults isDefaultCelcius]) {
            [NSUserDefaults setDefaultToFahrenheit];
        } else if (![NSUserDefaults isDefaultCelcius]) {
            [NSUserDefaults setDefaultToCelcius];
        }
        // Update Section
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
    }
    // Show Center View Controller
    [self.viewDeckController closeLeftViewAnimated:YES];
}

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.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 50.0;
}

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.

- (void)deleteLocation:(id)sender {
    UITableViewCell *cell = (UITableViewCell *)[[(UIButton *)sender superview] superview];
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    // Update Locations
    [self.locations removeObjectAtIndex:(indexPath.row - 1)];
    // Update User Defaults
    [[NSUserDefaults standardUserDefaults] setObject:self.locations forKey:MTRainUserDefaultsLocations];
    // Update Table View
    [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}

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.

Android SDK: Create an Interactive Screen Saver with Daydream

$
0
0

Daydream interactive screen savers are a new feature in Android 4.2 (API Level 17). With Daydream, you can create a screen saver with animation, interaction, and just about anything else you would include in an Android Activity. Daydream screen savers are displayed while the user device is charging. Users with devices running Jelly Bean 4.2 can select and configure a Daydream screen saver in their display settings. In this tutorial we’ll go through the required steps to create a Daydream screen saver for Android.

The sample Daydream we’ll create in this tutorial will display a simple animation featuring the Android robot image used as default launcher icon. The user will be able to stop and start the animation by tapping the robots. The Daydream will display a grid with multiple instances of the rotating robot and a button the user can use to dismiss the Daydream, which will be placed randomly within the grid.

In your own projects, you can choose interactive screen saver elements that will reuse the views you have in existing apps, or web views that provide links to your projects. The code in this tutorial is simply intended to familiarize you with the process of creating a functioning interactive Daydream, but these potential applications can enhance user engagement with any existing projects you may have.

Tip: Daydream is only available on devices running version 4.2 of Android, which is API Level 17. If you don’t have a device running API 17, you’ll only be able to test your Daydream apps on the emulator. As long as your Android development environment has version 17 installed, you should be able to create an AVD (Android Virtual Device) on which the Daydream will run. During development, you can create a test Activity in which you place the same content as your Daydream apps. The Eclipse and Android development resources are particularly useful for Activities.


1. Create a Project

Step 1

Create a new Android Project in Eclipse. The app will have one class in it, which will be a Service. When you go through the process of creating your new Android project in Eclipse, select API Level 17 as both target and minimum SDK for your project. There’s no need to let Eclipse create an Activity or layout file. You won’t need one. You can, however, let it create a launcher icon. In the New Android Application “Configure Project” screen, uncheck “Create Activity” but leave “Create Custom Launcher Icon” checked. Choose the default Android robot icon; we are going to use it within the Daydream.

Step 2

Open your project Manifest file to configure the app as a Daydream. Include the following code within the Manifest application element.

<service
	android:name=".RobotDaydream"
	android:exported="true"
	android:label="@string/daydream_name"><intent-filter><action android:name="android.service.dreams.DreamService" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service>

This allows your app to be listed in the Daydream options within the user’s display settings. You can change the name of the service class “RobotDaydream” if you like, as long as you give your service class the same name when we create it. Add the specified label string to your application’s “res/values/strings” file(s).

<string name="daydream_name">Spinning Androids</string>

This name will appear in the device display settings when selecting a Daydream.

Daydream Settings

Tip: The Manifest code above is complete for a functioning Daydream. However, you can opt to add a metadata element, linking to a resource where you can specify a settings Activity class for your app.


2. Define an Animation

Step 1

Before we get started on the Daydream Service class, we’ll define an animation set resource. The little robot icons we display will rotate back and forth when the Daydream runs, so let’s define this animation first. In your project “res” folder, create a new folder named “animator” and create a new file in it named “android_spin.xml“. Inside the new XML file, define the animation.

<set xmlns:android="http://schemas.android.com/apk/res/android"
	android:interpolator="@android:anim/linear_interpolator"
	android:ordering="together" ><objectAnimator
	android:duration="5000"
	android:propertyName="rotation"
	android:repeatCount="infinite"
	android:repeatMode="reverse"
	android:valueTo="360"
	android:valueType="floatType" /></set>

You can alter the animation properties if you wish. This simply defines an animation that will rotate the target image 360 degrees in one direction over five seconds and back in the opposite direction. It will repeat continuously.

Daydream Started
The image views in the grid will rotate back and forth continuously while the Daydream is visible.

3. Create a Daydream Service

Step 1

In your project, create a new class in the main package. If Eclipse did not create a default package when you created the project, add one now by selecting the “src” folder and choosing File > New > Package and entering your package name. Add your new class to the package, naming it “RobotDaydream” or whatever you included in the Manifest service name attribute. Extend the opening line of the class declaration.

public class RobotDaydream extends DreamService implements OnClickListener

You will need the following imports in the class for the remainder of the code in this tutorial.

import java.util.Random;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.graphics.Color;
import android.graphics.Point;
import android.service.dreams.DreamService;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.ImageView;

Step 2

When you extend the Daydream class, there are a number of methods you can override to control the appearance and behavior of your screen saver. Inside your Service class, add the method outline for when the Daydream starts.

@Override
public void onDreamingStarted() {
//daydream started
}

Now add the method outline for when Daydreaming stops.

@Override
public void onDreamingStopped(){
//daydream stopped
}

Add the method outline for when your Daydream is initially attached. You’ll include setup tasks for the screen saver.

@Override
public void onAttachedToWindow() {
//setup daydream
}

Any tidying up from setup will be placed inside the method for when the Daydream is detached, so add that next.

@Override
public void onDetachedFromWindow() {
//tidy up
}

Lastly, add the method outline for handling clicks. Depending on their functionality, your Daydream apps may not always require all of these methods.

public void onClick(View v){
//handle clicks
}

We will be working with each of these methods.


4. Prepare for Daydreaming

Step 1

At the top of your Daydream Service class, add some instance variables that we’ll use to implement the animation and interaction. First you’ll add a button so that the user can stop the Daydream.

private Button dismissBtn;

If you set your Daydream up to be interactive, which we’ll do here, you’ll need to manually implement a way to stop the Daydream. Otherwise, the default behavior for a Daydream is to stop when the user touches the screen. We won’t be using the default behavior because we want to allow the user to interact with the views in our Daydream. We’ll provide a button that allows the user to dismiss the Daydream instead.

Next, add two arrays.

private ImageView[] robotImgs;
private AnimatorSet[] robotSets;

These will store the robot image views and animator sets for them. We’ll use a constant to set the number of robots to display, using the number of rows and columns we want in the grid.

private final int ROWS_COLS=5;
private final int NUM_ROBOTS=ROWS_COLS*ROWS_COLS;

Finally, we’ll use a random number to determine where the stop button will be placed in the grid.

private int randPosn;

Step 2

The onAttachedToWindow method gives us the opportunity to carry out setup tasks for the Daydream. Call the superclass method inside the method.

super.onAttachedToWindow();

Set the Daydream to be interactive and occupy the full screen, hiding the status bar.

setInteractive(true);
setFullscreen(true);

Step 3

Get a random number to determine where the stop button will be.

Random rand = new Random();
randPosn = rand.nextInt(NUM_ROBOTS);

Create a grid layout for the Daydream, setting the number of rows and columns using the constant.

GridLayout ddLayout = new GridLayout(this);
ddLayout.setColumnCount(ROWS_COLS);
ddLayout.setRowCount(ROWS_COLS);

Initialize the arrays.

robotSets = new AnimatorSet[NUM_ROBOTS];
robotImgs = new ImageView[NUM_ROBOTS];

Determine the width and height for each robot image in the grid.

Point screenSize = new Point();
getWindowManager().getDefaultDisplay().getSize(screenSize);
int robotWidth = screenSize.x/ROWS_COLS;
int robotHeight = screenSize.y/ROWS_COLS;

Step 4

Now we can loop through the arrays, adding the stop button and robots to the grid.

for(int r=0; r<NUM_ROBOTS; r++){
//add to grid
}

Inside the loop, create some layout parameters using the width and height we calculated.

GridLayout.LayoutParams ddP = new GridLayout.LayoutParams();
ddP.width=robotWidth;
ddP.height=robotHeight;

Check to make sure we are at the index allocated for the stop button.

if(r==randPosn){
//stop button
}
else{
//robot image view
}

Inside the if block, create and add the stop button to the layout, setting display properties and listening for clicks.

dismissBtn = new Button(this);
dismissBtn.setText("stop");
dismissBtn.setBackgroundColor(Color.WHITE);
dismissBtn.setTextColor(Color.RED);
dismissBtn.setOnClickListener(this);
dismissBtn.setLayoutParams(ddP);
ddLayout.addView(dismissBtn);

Inside the else block, create an Image View and set the launcher icon as its drawable, adding it to the layout.

robotImgs[r] = new ImageView(this);
robotImgs[r].setImageResource(R.drawable.ic_launcher);
ddLayout.addView(robotImgs[r], ddP);

Inside the else, create and add an animator set to the array, referencing the animation resource we created. You can alter the drawable image to suit one of your own.

robotSets[r] = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.android_spin);

Set the current robot image as target for the animation and listen for clicks on it.

robotSets[r].setTarget(robotImgs[r]);
robotImgs[r].setOnClickListener(this);

After the for loop (but still inside the onAttachedToWindow method), set the content view to the layout we created and populated.

setContentView(ddLayout);

5. Handle Clicks

Step 1

Inside your onClick method, find out whether a robot image view or the stop button was clicked.

if(v instanceof Button && (Button)v==dismissBtn){
//stop button
}
else {
//robot image
}

Step 2

In the if block, finish the Daydream.

this.finish();

You must implement this for any interactive Daydreams you create (in which you call setInteractive(true)). The user will not be able to dismiss the screen saver the default way.

Step 3

Inside the else block, add a loop to iterate through the robot image views.

for(int r=0; r<NUM_ROBOTS; r++){
//check array
}

Inside the loop, make sure that the index is not in the position designated for the stop button. If this is the case, the image view array entry will be empty.

if(r!=randPosn){
//check image view
}

Inside the if block, find out if the current view is the one just clicked.

if((ImageView)v==robotImgs[r]){
//is the current view
}

Inside this if, stop or start the animation depending on whether it’s currently running.

if(robotSets[r].isStarted()) robotSets[r].cancel();
else robotSets[r].start();

This lets the user turn the animations on and off for each robot image view. Now that we’ve responded to the click, break out of the loop.

break;
Daydream Interaction
Clicking the image views will start and stop the animations, clicking the stop button will dismiss the Daydream.

6. Starting and Stopping Daydreaming

Step 1

In your onDreamingStarted method, call the superclass method.

super.onDreamingStarted();

Next, loop through the animations and start them.

for(int r=0; r<NUM_ROBOTS; r++){
	if(r!=randPosn)
		robotSets[r].start();
}

Step 2

In your onDreamingStopped method, stop the animations.

for(int r=0; r<NUM_ROBOTS; r++){
	if(r!=randPosn)
		robotSets[r].cancel();
}

Now call the superclass method.

super.onDreamingStopped();

Step 3

In the onDetachedFromWindow method, we can get rid of anything we set up in onAttachedToWindow. In this case we’ll just stop listening for clicks.

for(int r=0; r<NUM_ROBOTS; r++){
	if(r!=randPosn)
		robotImgs[r].setOnClickListener(null);
}

Finally, call the superclass method.

super.onDetachedFromWindow();

Conclusion

This completes the basic Daydream app! You can test yours on the emulator or on a real device running Jelly Bean 4.2. Browsing to the Settings > Display section of the device menu will let you select the Daydream from the available list. Once it’s selected, you can choose “Start Now” to see it in action. In your own projects, you can implement pretty much the same interactive and informational functions you would in an Activity, so you can use Daydreams to provide engaging access points for your existing apps. However, be aware that if your screen saver is using too much of the available processing resources, the Android system will stop it from running to allow the device to charge properly. Other options to explore in your Daydream apps include setting the screen brightness and providing a settings activity for user configuration.

Designing a Meaningful Social Layer for Mobile Applications

$
0
0

Applications with social features have become the norm, but, as a designer, it’s important that you question how these social features add value for the user. In this article, we’ll explore how to improve an application by designing a meaningful social layer.

social layer

When we talk about social layers, there’s a lot involved. Basically, we’re building an interactive experience where people can create, share, and exchange information within a virtual network. The importance of user-generated content has grown enormously. Even a simple output of a social layer can create value for people, like Facebook’s simple “Like” feature. On the flip side, social layers can have negative effects on our lives. They often create an unwanted (and unconscious) pressure to be successful not just in our work and our lives, but within the social media apps we’re active using.

That being said, it’s important to make sure that the social aspect we design and build is meaningful and fun. Even better, try to ignore the social layer if it isn’t relevant to your application. The assumption that the social aspect is mandatory is wrong. When present, social interaction needs to be thoughtfully utilized.


A Subtle Difference

The social layer is more extensive than social media. Social media is intended to connect people with people, but a social layer is much more extensive. Social media became a social layer because it connected us with brands, experiences, products, feelings, applications, and games. When we try to connect to something or someone, it’s a much larger part of our lives than it was just a few years ago. Social media has evolved into a social layer of life and our digital data online, and we spread it through our actions. It has become increasingly valuable for developers to use this layer in their applications.

Understanding the power of a social layer creates a range of possibilities for empowering your application. Social aspects are a cheap way to promote your application and they can improve the features of your application if they’re relevant to what your application will do.


Qualities of a Good Social Layer

social layer
Dribbble Connections by Bruce Spang on Dribbble

Generally, when we’re talking about social layers we’re talking about two important characteristics. They either help the user or help the community.

When we speak of helping the user, there are a lot of obvious solutions. We can offer them ways to express themselves by creating and sharing content. These features are just an example, but there are numerous options. Still, it’s important to understand that you shouldn’t force a social layer in your application if it doesn’t create any value. Users might not want to share content or compare themselves to other users. Try to understand what the social aspect means for your target audience.

Another important part of the social layer is the sense of community it creates. These can be obvious things, like helping each other through content or building ways to interact with each other.

The interaction you create should be simple, fun, and worth the user’s time. Try to analyze your own behavior on social media and see how it influences you. Use that as an example for the features you are planning in your application. Generally, people give value to their actions on social media. This is why it’s important to add social features to your applications in a rewarding way.


Designing The Social Experience

social layer
Diversity by Andy Mangold on Dribbble

Start by defining your community and understanding what the added value of that community is.

When you’re dealing with applications, everything starts with the features. Do you believe that a social layer enhances the experience of using your application? What are the advantages and disadvantages?

It’s also necessary to understand how individuals participate in the community. What action or features are connected with the community? How much effort does such an action cost and what are the rewards? Specify what your social layer looks like. What are the important actions and how do they link to each other? Make a scheme for yourself.

Once you’ve figured out how you’d like to add a social layer to your application, think about the technical aspects. How will you handle user data? How will you create your features? How will you integrate every aspect of your social layer into a coherent application? It’s often very difficult to make everything a streamlined experience, but it’s important to keep that streamlined user experience in mind even if it costs features in the end.


Best Practices

This article has been purely theoretical thus far, so let’s take a look at some applications and see how they made their social aspects a success.

social layer
Foursquare

One of my favorite examples is Foursquare. They’ve made the community the most important contributor in their application while also integrating the user’s personal relationships. They also made all the user interactions within the social layer simple, fun, and accessible.

In addition, FourSquare also did a great job at rewarding the user’s actions and keeping the application competitive between friends by resetting check-in points weekly. Most of all, it offers useful content and just about everyone can contribute to the experience. They understand how a social layer should work and the social aspect of their application has made it a huge success.

social layer
Flipboard

Flipboard isn’t as popular as Foursquare, but they know how to make interaction with content fun. By creating simple navigation gestures and combining them with the ability to integrate social media accounts, Flipboard has made social media interaction much more fun. It’s still possible to like, comment, or share and it’s much less cluttered than most social media sites like Facebook.

The social layer they’ve created is hidden, and it’s so well done that it doesn’t even feel like a real social layer. Flipboard is like reading a newspaper that you can interact with. A hidden social layer can work just as well as an obvious one.

What are some of your personal favorite applications which handle the social aspects of applications very well?


Some Final Tips and Tricks

  • Start on paper. Brainstorm, use feedback, and weigh out your pros and cons.
  • Don’t assume that every user wants to interact. Never force it.
  • If you plan to work with accounts, make registering and logging in very simple and quick.
  • Add a low-level form of interacting (a like button or checking in)
  • Add a high-level form of interacting (commenting or leaving tips)
  • Use a social layer to indirectly promote your application. Sharing is a great example of this.
  • As a developer, make use of the social layer yourself to interact with your community.
  • Do some usability tests. It’s a great idea to use a beta version to find out how streamlined the interaction is.
  • Finally, if a social layer doesn’t create any added value for your application, just leave it out.

Conclusion

Social layers have grown and gained importance. As always, carefully weigh the pros and cons when deciding to use one within your application. Make interaction fun and rewarding, and users will gladly participate in the community you build. Good luck!

Check Out the New Recommended Resources on Mobiletuts+

$
0
0

We’ve added a new page to the site, which will help mobile design and development professionals grab top quality software and tools. It’s filled with our favorite resources that we recommend for mobile app developers. You can jump straight over to our Recommended Resources page here on Mobiletuts+ or read on for further information.


Hand Picked Resources for Mobile App Professionals

Our Tuts+ editorial team has hand-picked these resources, which feature core applications, hosting recommendations, editors, testing, and deployment services. Our goal here is to feature the highest quality, useful resources that we highly recommend and in most cases use ourselves. It’s a quick stop to finding the best of the best when you have an urgent need to fill as a mobile creative professional.

Keep an eye out for more of these site sections as our Resource pages roll out across the Tuts+ network.

Recommended Resources

Jump over to the Mobiletuts+ Recommended Resources Page


What Mobile App Development Tools Do You Recommend?

This is version 1.0 of this Mobiletuts+ Resources page. We’ll continue to add to it and grow this section of the site. We could use your help with that!

Are there any awesome apps, tools or services that you feel we missed? Bounce into the discussion below and leave a comment about what resource you recommend to fellow mobile development experts.

Build a Monster Smashing Game with Cocos2D: Movement & Animations

$
0
0

This tutorial will teach you how to use the Cocos2D iOS framework in order to create simple yet advanced 2D games aimed at all iOS devices. It’s designed for both novice and advanced users. Along the way, you’ll learn about the Cocos2D core concepts, touch interaction, menus and transitions, actions, particles, and collisions. Read on!

This tutorial is the second entry in the three-part Monster Smashing series. Make sure you’ve completed the previous section before beginning.


Series Structure

In today’s tutorial we’ll program the game board for Monster Smashing. This part focuses on several things, including the MonsterRun class, movements, animations, touches, and so on. We’ll explain everything in the tutorial below.


1. Monsters

Monsters are the main objects of the game. Each monster has a few unique attributes like sprites, movement, velocity, and kill action. To represent an object we can use a specific class, so we’ve created a Monster class to represent a monster.

Usually, in Objective-C, we create a new NSObject subclass. In this case, we need to use the CCNode class, which is a class that includes the Foundation.h and the cocos2d.h headers. To create a new class in Xcode, choose File -> New -> New File…. In the left-hand table of the panel that appears, select Cocos2D from the iOS section. Select CCNode class from the upper panel and hit next. We’ll name the class “Monster”.

Figure 1: CCNode Class
Illustration of the Template Chooser (Xcode).

Now we need to declare the instance variables. In Monster.h, add five instance variables:

  • NSString *monsterSprite
  • NSString *splashSprite
  • int movement
  • float minVelocity
  • float maxVelocity
  • int killMethod

Now, as with every oriented object language, we need to implement the setters and getters. In objective-C, properties are a convenient alternative to writing out accessors for instance variables. It saves a lot of typing and it makes your class files easier to read. Add the above-mentioned properties on the .h file.

@property(nonatomic,readwrite,retain) NSString *monsterSprite;
@property(nonatomic,readwrite,retain) NSString *splashSprite;
@property(nonatomic, readwrite) int movement;
@property(nonatomic, readwrite) float minVelocity;
@property(nonatomic, readwrite) float maxVelocity;
@property(nonatomic, readwrite) int killMethod;

2. Initializing the Gameboard

Before continuing, you’ll need to initialize several things. we can add a background to our game just like we did in the first section of the tutorial. We can use the same one in the menu scene. If you don’t remember how to do that, you can use the following code on the -(id) init method (MonsterRun.m).

self.isTouchEnabled = YES;
// Add background
        winSize = [CCDirector sharedDirector].winSize;
        CCSprite *background = [CCSprite spriteWithFile:@"WoodRetroApple_iPad_HomeScreen.jpg"];
        background.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:background z:-2];

Now we need to initialize some “monsters”. Before you start adding monsters, we need to create a NSMutableArray in order to store them. Once again, the images for the monster sprites are available in the resources folder.

We’d also like to thank Rodrigo Bellão for providing us with the images for the monsters.

In MonsterRun.m add two NSMutableArray objects: _monsters and _monstersOnScreen. Then, in the init method, we must initialize those arrays in order to use them. The following snippet can help us achieve that.

        //initializing monsters
        _monsters = [[NSMutableArray alloc] init];
        _monstersOnScreen = [[NSMutableArray alloc] init];
        Monster *m1 = [[Monster alloc] init];
        [m1 setTag:1];
        [m1 setMonsterSprite:[[NSString alloc] initWithString:@"monsterGreen.png"]];
        [m1 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterGreen.png"]];
        [m1 setMinVelocity:2.0];
        [m1 setMaxVelocity:8.0];
        [m1 setMovement:1];
        [m1 setKillMethod:1];
        [_monsters addObject:m1];
        Monster *m2 = [[Monster alloc] init];
        [m2 setTag:2];
        [m2 setMonsterSprite:[[NSString alloc] initWithString:@"monsterBlue.png"]];
        [m2 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterBlue.png"]];
        [m2 setMinVelocity:2.0];
        [m2 setMaxVelocity:8.0];
        [m2 setKillMethod:2];
        [m2 setMovement:1];
        [_monsters addObject:m2];
        Monster *m3 = [[Monster alloc] init];
        [m3 setTag:3];
        [m3 setMonsterSprite:[[NSString alloc] initWithString:@"monsterRed.png"]];
        [m3 setSplashSprite:[[NSString alloc] initWithString:@"splashMonsterRed.png"]];
        [m3 setMinVelocity:3.0];
        [m3 setMaxVelocity:6.0];
        [m3 setKillMethod:1];
        [m3 setMovement:2];
        [_monsters addObject:m3];

These arrays should be used to store all the monsters for a given level.


3. Adding Monsters to the Screen

Now that we have some monsters on memory, the next step is to put them on screen. To do that, we just need a few lines.

First we need to create a scheduler to invoke a method to a specific action which, in this case, is the monster location and movement. Right next to the monster’s allocation, we can create the scheduler as the next line presents.

[self schedule:@selector(addMonster:) interval:1.0];

The selector indicates the method that will be called addMonster. At the moment, we don’t have that method, but it will be created soon. The scheduler can also receive a time interval that represents the time when a specific sprite is updated.

The addMonster method is the one responsible for the monster movement. Since we’ve added three different monsters, we will also create three different movement patterns. The addMonster method is depicted below.

- (void) addMonster:(ccTime)dt {
    //select a random monster from the _monsters Array
    int selectedMonster = arc4random() % [_monsters count];
    //get some monster caracteristics
    Monster *monster = [_monsters objectAtIndex:selectedMonster];
    int m = [monster movement];
    //!IMPORTANT -- Every Sprite in Screen must be an new CCSprite! Each Sprite can only be one time on screen
    CCSprite *spriteMonster = [[CCSprite alloc] initWithFile:[monster monsterSprite]];
    spriteMonster.tag = [monster tag];
    //BLOCK 1 - Determine where to spawn the monster along the Y axis
    CGSize winSize = [CCDirector sharedDirector].winSize;
    int minX = spriteMonster.contentSize.width / 2;
    int maxX = winSize.width - spriteMonster.contentSize.width/2;
    int rangeX = maxX - minX;
    int actualY = (arc4random() % rangeX) + minX;
    //BLOCK 2 - Determine speed of the monster
    int minDuration = [monster minVelocity];
    int maxDuration = [monster maxVelocity];
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;
    if(m == 1){ //STRAIGHT MOVIMENT
        //BLOCK 3 - Create the monster slightly off-screen along the right edge,
        // and along a random position along the Y axis as calculated above
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
        //BLOCK 4 - Create the actions
        CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp( actualY,-spriteMonster.contentSize.height/2)];
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
        }];
        [spriteMonster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
        [_monstersOnScreen addObject:spriteMonster];
    }
    else if(m == 2){ //ZIGZAG-SNAKE MOVIMENT
        /* Create the monster slightly off-screen along the right edge,
         and along a random position along the Y axis as calculated above
         */
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
        }];
        // ZigZag movement Start
        NSMutableArray *arrayBezier = [[NSMutableArray alloc] init];
        ccBezierConfig bezier;
        id bezierAction1;
        float splitDuration = actualDuration / 6.0;
        for(int i = 0; i< 6; i++){
            if(i % 2 == 0){
                bezier.controlPoint_1 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
            else{
                bezier.controlPoint_1 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
            [arrayBezier addObject:bezierAction1];
        }
        [arrayBezier addObject:actionMoveDone];
        id seq = [CCSequence actionsWithArray:arrayBezier];
        [spriteMonster runAction:seq];
        // ZigZag movement End
        [_monstersOnScreen addObject:spriteMonster];
    }
}

You may have noticed that there are comments within the code. The comments will guide us (both programmers and readers) to implement and learn each instruction. In this tutorial, the monsters are chosen randomly (however, in part three we’ll show how use pList to put the monsters on the screen).

The first line of our code does exactly that. We get the total number of monsters on our array and then we randomly choose which one we will put onscreen next. After the monster is selected, we define some properties such as movement, velocity, range over the XX axis and its tag.

We defined three Blocks that define specific properties.

  • Block 1 determines where on screen we will span the monsters. Note that the monster will only spawn in a visible area of screen. For that, we get the range of the XX axis and then randomly place the monster.
  • Block 2 determines the speed of the monster. Note that each monster’s type will have the minimum and maximum speed defined. The speed of the monster is the duration in seconds that the monster will pass through the screen. This duration will be between the minDuration and the maxDuration defined on the monster object on the init method
  • Block 3 creates the monster slightly off-screen along the right edge and along a random position along the XX axis, calculated above
  • Block 4 creates the actions for the straight movement. Two actions are created, the movement itself CCMoveTo * actionMove and one that is called when the sprite leaves the screen (CCCallBlockN * actionMoveDone). To create an action with straight movement, we use the CCMoveTo object, which receives the duration of the action and the final position. We also use the CCCallBlockN. This particular object will detect when a monster gets off the screen. Using the CCCallBlockN is advantageous because Cocos2D already has some mechanisms in place to detect if a particular resource leaves the screen.

We have defined two movements: straight and zig-zag. The straight one can be analyzed in Block 4, mentioned above. The zig-zag uses the bezier path to create the zig-zag effect and will be depicted below.


4. The Zig-Zag (Snake) Movement

To make the zig-zag movement we use the CCBezier. This class allow us to create an action that will move the target with a cubic bezier curve to a destination point.

The created curves will always move 100 pixels to the left or right, and at the same time the object will move 200 pixels below. The for loop will create the path of the monster. Since the screen, which is the normal iPad resolution, has 1024 pixels in the vertical position, we need to run the loop six times (200px * 6 = 1200px). The condition inside the for loop will determine if the iteration is odd or pair. When the number is odd, the monster move to the left. If the number is pair, the monster moves to the right.

Now each condition will calculate the controlPoint1 and controlPoint2, which are used to control and guide the curve on screen. The endPosition, as the name suggests, is the final position that each monster will end up in after each curve. We concatenate all these actions into an array and create a sequence from it. Just as we did with the straight movement, we’ll run the action and add the monster to the array.

It’s now time to build and run the project. Your game should look similar to the image below.

Figure 2: Monster on Screen
Illustration of the game with several monsters.

5. Touch Interaction

A game with monsters that cannot be killed is not a game, right?

It’s time to add some interaction between the screen and the user. We’ll follow the same philosophy we used above, code and explanation.

Cocos2D provides us with several mechanisms for touch interaction. For now, we’ll only focus on one touch. For that, we need to add a specific method called ccTouchesBegan. The method receives an event and will act accordingly to that event. Obviously, the event is a touch event. The snippet is presented below.

-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
    UITouch * touch = [[touches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    for (CCSprite *monster in _monstersOnScreen) {
        if (CGRectContainsPoint(monster.boundingBox, touchLocation)) {
            [monstersToDelete addObject:monster];
            //add animation with fade - splash
            Monster *m = [_monsters objectAtIndex:(monster.tag-1)];
            CCSprite *splashPool = [[CCSprite alloc] initWithFile:[m splashSprite]];
            if([m killMethod] == 1){
                splashPool.position = monster.position;
                [self addChild:splashPool];
                CCFadeOut *fade = [CCFadeOut actionWithDuration:3];  //this will make it fade
                CCCallFuncN *remove = [CCCallFuncN actionWithTarget:self selector:@selector(removeSprite:)];
                CCSequence *sequencia = [CCSequence actions: fade, remove, nil];
                [splashPool runAction:sequencia];
                //finish splash
            }
            if([m killMethod] == 2){
                // in Part 3 - Particles section
            }
            break;
        }
    }
     for (CCSprite *monster in monstersToDelete) {
         [monster stopAllActions];
         [_monstersOnScreen removeObject:monster];
         [self removeChild:monster cleanup:YES];

The first line of this method is the allocation of an auxiliary array monstersToDelete that defines what monsters will be deleted at each action.

The next step is to know the touch interaction location. With that location, we’ll loop all the sprites on the screen and test to see if the touch location is inside an invisible box that contains a monster. We’ll use f (CGRectContainsPoint(monster.boundingBox, touchLocation)). If it is true, we’ll add the monsters to the auxiliary array. Plus, we’ll add two splash animations when a monster is killed. The first one is a simple splash, while the second is a particle. We’ll discuss that in part three. To create the splash, we verify the monster type and then we add that monster to a splash pool. CCFadeOut will create a fade effect on the splash image, and the CCCallFuncN will call another method to remove the Monster sprite.

Now we just need one little thing. In the CCCallFuncN *remove, the call method removeSprite needs to be created and the objective is to remove that sprite. Add that method using the following code.

-(void) removeSprite:(id)sender {
    [self removeChild:sender cleanup:YES];
}

Finally, when the loop is over, the touch interaction is tested against all objects in the scene we have in another loop, for (CCSprite *monster in monstersToDelete). It will iterate over monstersToDelete and remove the object.

It’s now time to build and run the project. We should see some monsters popping up onscreen. Now we can start killing them! You will notice that the red monsters don’t have any splash when they die. This will be covered in the next part. The final image in this part is the following.

Figure 1: Monster on Screen with splash animation
Illustration of the game with several monsters with splash animation.

6. Conclusion

At this point you should be able to understand and perform the following tasks:

  • Add sprites to the screen.
  • Define sprite properties.
  • Define custom classes.
  • Initialize the game board.
  • Define custom movements and actions.
  • Know how to use touch interaction.

In the next tutorial we’ll learn about sound and game mechanics!

Android SDK: Create a Book Scanning App

$
0
0

With the ZXing library, you can create Android applications with barcode scanning functionality. In Android SDK: Create a Barcode Reader, we implemented basic barcode reading with the library in a simple app. In this tutorial series, we will build on what we learned to create an app that will scan books and retrieve related information about them from Google Books API.

Here is a preview of the app we are working toward:

Book Scanner App

This series on Creating a Book Scanner App will be in three parts:

In this first part of the series, we will get the app set up for scanning – you will be able to use most of the code from the previous tutorial, with any changes necessary outlined below. We will also get setup for accessing Google Books API. In the second part we will create the rest of the user interface and carry out a book search via Google Books API. In the final part we will retrieve the results of the book search and present them within the interface.

When the app runs, the user will be able to scan books on clicking a button. When the app receives the scan result, it will check that the format is EAN. If it is, the app will build a search query for Google Books which it will attempt to execute. When the app receives the JSON book search results, it will pick particular details out and display them within the interface. We will use two inner AsyncTask classes, one for retrieving the book search results and one for retrieving an image thumbnail of the book, if one is available. We will also create a second class with a Web View in it, in which we will use the Embedded Viewer API to display a preview of the scanned book, again if one is available. We will use an asset file for the Web View, and will provide a link to the Web page for the book on Google Books site. The final app will also include steps to preserve the data displayed on state changes.


1. Create a Scanner Project

Step 1

If you have not already completed the previous tutorial Android SDK: Create a Barcode Reader, you may wish to do so first. If you did complete that tutorial, you can either create a new app for this tutorial or amend the code in the scanner app you already created. We will not be explaining the functionality outlined in that first tutorial again but will run through the code necessary for the book scanning app.

If you are working with the app you created during the previous tutorial you may be able to omit some of the steps below – just read through them and make sure you have the code outlined. We will be using the scan button as before, but do not need the two Text Views we created for that app as we will be using different views to display the book info – you can leave most of the Activity class the way it is, but should replace your XML layout file content with the code indicated below.

If you are creating a new Android Project, let Eclipse create a blank Activity plus layout for you. Create a second package in your project for the ZXing classes as we did in the previous tutorial, giving the package the name “com.google.zxing.integration.android” and copying the two scanning via Intent classes into it. In your main Activity class, add the following import statements (omitting any Eclipse has already added):

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

We will be adding code to the main Activity class later in this tutorial and throughout the series, as well as creating a second class and adding to some resource files including the layout. For the moment a new blank Activity class should contain the default content, setting the layout file created when Eclipse created the project:

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
}

Step 2

Let’s start getting our user interface set up. Open your layout file and replace the content with the following Relative Layout contained within a Scroll View:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:background="#ffffffff" ><RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:padding="10dp" ></RelativeLayout></ScrollView>

Inside the Relative Layout, add a Button for scanning as we did in the previous tutorial:

<Button android:id="@+id/scan_button"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_centerHorizontal="true"
	android:padding="10dp"
	android:background="#ff333333"
	android:textColor="#ffcccccc"
	android:text="@string/scan"
	/>

Add the specified string to your “res/values/strings” XML file:

<string name="scan">Scan a Book</string>

Step 3

Let’s turn to the Activity class. If you are working with the code you created for the previous scanning tutorial, you can remove all lines referencing the two Text Views we used in the Activity (formatTxt and contentTxt). Otherwise you can leave the Activity as it was when you completed that tutorial.

If you are creating the app from scratch, extend the opening Activity declaration line to implement the click listener interface:

public class MainActivity extends Activity implements OnClickListener

Add the outline of the onClick method to your class:

public void onClick(View v){
//handle clicks
}

At the top of the class declaration, before onCreate, add an instance variable for the scan button:

private Button scanBtn;

Inside onCreate, retrieve a reference to the button after the existing code:

scanBtn = (Button)findViewById(R.id.scan_button);
scanBtn.setOnClickListener(this);

Step 4

Let’s initiate scanning when the user presses the button, as we did last time. In the onClick method, check for clicks on the scan button:

if(v.getId()==R.id.scan_button){
	IntentIntegrator scanIntegrator = new IntentIntegrator(this);
	scanIntegrator.initiateScan();
}

As before, we are scanning via Intent, which will prompt the user to download the ZXing barcode scanner if they do not already have it installed. The scanning result will be returned to onActivityResult, so add it to your class:

public void onActivityResult(int requestCode, int resultCode, Intent intent) {
	//retrieve result of scanning - instantiate ZXing object
	IntentResult scanningResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
	//check we have a valid result
	if (scanningResult != null) {
		//get content from Intent Result
		String scanContent = scanningResult.getContents();
		//get format name of data scanned
		String scanFormat = scanningResult.getFormatName();
	}
	else{
		//invalid scan data or scan canceled
		Toast toast = Toast.makeText(getApplicationContext(),"No book scan data received!", Toast.LENGTH_SHORT);
		toast.show();
	}
}

This is virtually identical to the code we used in the last tutorial except for the reference to the two Text Views which we have removed. We will output information about scanned books in user interface items we create later in the series. For the moment you can add the following line to your onActivityResult method, inside the if block, outputting the scan content and format to the log if you wish to run the app in the meantime:

Log.v("SCAN", "content: "+scanContent+" - format: "+scanFormat);

2. Setup Google Books API Access

Step 1

We are going to use Google Books API to retrieve information about books scanned by the user. Sign into your Google account and browse to the APIs Console. Select “Services” and scroll to the Books API listing. Click to turn Books API on for your account, accepting the terms if prompted.

Books API

You will need your API key to access the Books API in the app. In your Google APIs Console, select “API Access”. If you already have a key it should be listed under “Key for browser apps”. If not you can create one by selecting “Create new Browser key” and following the instructions. Copy your key for later reference – we will use it in the next tutorial.

Step 2

To use the Google Books API service, we will need Internet access, so finally add the permission to your app Manifest:

<uses-permission android:name="android.permission.INTERNET" ></uses-permission>

Conclusion

In this first part of the series we have set the app up to use scanning functionality via ZXing. We have also retrieved an API key for accessing Google Books data. In the next part we will build a book search query and execute it. Over the second and third parts of the series we will fetch and retrieve information about scanned books. Check the source code download to ensure you have everything in the right place before you complete the next part.


iOS SDK: Creating a Custom Accordion Menu

$
0
0

This tutorial will teach you how to create a custom Accordion Menu. This animated menu will allow you to collect input from the user in an engaging and streamlined fashion. Read on to learn more!


Tutorial Preview


About the Accordion Menu

The accordion menu’s initial position will be at the center of the target view it appears on. When the menu appears, half of it will move towards the top of the target view, while the other half will move towards the bottom of the view, expanding to its full allowed height. During use, both the Y origin point and the height are going to be modified so that the desired effect can take place. The menu itself will consist of a tableview. It will provide us with great flexibility regarding the number of options added to the menu. The tableview is going to exist as a subview on a view and will appear on a target view. The main view of the accordion menu’s view controller will work as a cover to the subviews existing at the back, so the user is unable to tap on anything else except for our menu options.

Let’s go ahead and bring this idea to life. Here’s an image of what the final product will look like.

final_accordion_menu

1. Create the Accordion Menu View Controller

Step 1

The first thing we must do is create a new view controller for our accordion menu. Prior to that, we’ll create a new group in the Xcode to keep everything neat and tidy.

In the Project Navigator pane on the left side of the Xcode, Control + Click (right click) on the CustomViewsDemo group. Select the New Group option from the popup menu.

create_group_2

Set Accordion Menu as the name.

group_name_2

Step 2

Let’s create the view controller now. Control + Click (right click) on the Accordion Menu group. Select the New File… option from the popup menu.

create_file_2

Select the Objective-C class as the template for the new file and click next.

new_file_template_2

Use the name AccordionMenuViewController in the class field and make sure that in the Subclass of field the UIViewController value is selected. Don’t forget to leave the “With XIB for user interface” option checked.

accmenu_view_controller

Finally, click on the create button. Make sure that the Accordion Menu is the selected group, as shown in the next image.



2. Configure the interface

The first thing we need to do with our new view controller is setup the interface in the Interface Builder, which should be pretty simple. Most of the work will be done in code.

Step 1

Click on the AccordionMenuViewController.xib file to turn the Interface Builder on. Click on the default view and turn the Autolayout feature off so it works on versions of iOS prior to 6.

  • Click on the Utilities button at the Xcode toolbar to show the Utilites pane if it is not visible.
  • Click on File Inspector.
  • Scroll towards the bottom a litte bit and click on the “Use Autolayout” option to turn it off.
autolayout_2

Next, go to the Attributes Inspector. In the Simulated Metrics section, set the Size value to None so that it will work on a 3.5″ screen as well.

view_size_2

Step 2

Go ahead and add a new view, but make sure that you don’t add it as a subview to the default view. Do the following:

  • Go to the Simulated Metrics section of the Attributes Inspector and set the Size to None.
  • Change the background color to dark grey.
second_view_config

Step 3

Grab a tableview from the Object Library and add it as a subview to the view we added in the previous step. Complete the next configuration in the Attributes Inspector section.

  • Set Shows Horizontal Scrollers to off.
  • Set Shows Vertical Scrollers to off.
  • Set Scrolling Enabled to off.
  • Change the background color to clear.
tableview_options

This is what your interface should look like at this point.

ib_sample_2

3. Set the IBOutlet Properties

Step 1

Next, we’re going to create two IBOutlet properties for the extra view and the tableview we added to the project earlier. First, we should make the Assistant Editor appear. Click on the middle button in the Editor section on the Xcode toolbar to reveal it.

assistant_editor2_1

Step 2

To connect the view to a new IBOutlet property, go to Show Document Outline > Control + Click (right click) > New Referencing Outlet. Drag and drop into the Assistant Editor.

insert_outlet_2

I named the property viewAccordionMenu and I recommend that you use the same name to avoid any problems while coding. Set the Storage option to Strong instead of the default Weak value.

iboutlet_name_2

Step 3

Now let’s add an IBOutlet property for the tableview. Just as we did above, create a new property named tblAccordionMenu. Set the Storage option to Strong as well.

Here are the two IBOutlet properties.

@property (strong, nonatomic) IBOutlet UIView *viewAccordionMenu;
@property (strong, nonatomic) IBOutlet UITableView *tblAccordionMenu;

4. Do Some Code Level Setup

So far so good! We’ve created the view controller for the accordion menu, we’ve setup the interface, and we’ve created the two required IBOutlet properties for the views we added to the builder. Now it’s time to begin writing some code.

Step 1

First of all, we should set our view controller as the delegate and datasource of the tableview we created in the Interface Builder. Our view controller should adopt the respective protocols. Click on the AccordionMenuViewController.h file and right next to the @interface header add the following.

@interface AccordionMenuViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

Step 2

Click on the AccordionMenuViewController.m file and implement a simple init method. Set the view controller as the delegate and datasource of the tableview.

-(id)init{
    self = [super init];
    if (self) {
        [_tblAccordionMenu setDelegate:self];
        [_tblAccordionMenu setDataSource:self];
    }
    return self;
}

To make the menu look better, let’s add a border. In the init, add the highlighted code.

...
if (self) {
        [_tblAccordionMenu setDelegate:self];
        [_tblAccordionMenu setDataSource:self];
        // Add some border around the tableview.
        [[_tblAccordionMenu layer] setBorderColor:[UIColor whiteColor].CGColor];
        [[_tblAccordionMenu layer] setBorderWidth:2.0];
    }
...

Make sure to add this to the top of the file.

#import <QuartzCore/QuartzCore.h>

Set the background color of the default view to light grey with transparency.

...
if (self) {
        ...
        [self.view setBackgroundColor:[UIColor colorWithRed:0.33 green:0.33 blue:0.33 alpha:0.75]];
    }
...

Step 3

It’s a good idea to define the row height of the tableview as a constant. Apart from the tableView:heightForRowAtIndexPath method, we are going to use it in other methods. Right after the #import commands, add the following.

#define ROW_HEIGHT          40.0    // The height of each option of the accordion menu.

The accordion menu will use animation to appear and disappear. We can set the duration of the animation as a constant.

#define ANIMATION_DURATION  0.25    // The duration of the animation.

From now on, if we want to change the row height or the animation duration we’ll do it once without changing the values in each method.

Step 4

We should declare two private variables and an NSArray. The variables regard the accordion menu width and height, meaning the width and height of the parent view of our tableview. The NSArray is the array that will keep the menu options, and it will be the datasource of the tableview.

At the top of the AccordionMenuViewController.m file add the next lines in the @interface section.

@interface AccordionMenuViewController (){
    CGFloat menuViewWidth;
    CGFloat menuViewHeight;
}
@property (nonatomic, strong) NSArray *optionsArray;
@end

Make sure you don’t forget the curly brackets!


5. Implement the Public Methods

There are at least three public methods that should be implemented in order for the accordion menu to work properly. The first method will be used to set the options of the menu, the second method will make the accordion menu appear, and the third method will make it disappear.

Step 1

First of all, we should declare the methods I mentioned above into the .h file. So, click on the AccordionMenuViewController.h file and add the following.

-(void)setMenuOptions:(NSArray *)options;
-(void)showAccordionMenuInView:(UIView *)targetView;
-(void)closeAccordionMenuAnimated:(BOOL)animated;

Step 2

Let’s implement the first method. Click on the AccordionMenuViewController.m file and write or copy/paste the following code.

-(void)setMenuOptions:(NSArray *)options{
    NSMutableArray *tempArray = [NSMutableArray arrayWithArray:options];
    [tempArray addObject:@"Close"];
    _optionsArray = [[NSArray alloc] initWithArray:(NSArray *)tempArray copyItems:YES];
}

Even though this is a really simple method, let me explain it a little bit. I find it easier for the user and the programmer to provide an array with menu options only. You don’t need to be concerned about the so-called close menu option. That’s really only useful when someone is going to use the accordion menu in more than one case. I use the tempArray mutable array to put together both the user options and the close option. If you know that the _optionsArray is not mutable, you’ll understand that it cannot accept new objects after creation. I initialize the _optionsArray array. It’s your choice to avoid using this logic or change the title of the close menu option.

Step 3

Let’s move on to the next method. It’ll concern the way the accordion menu appears on screen. A lot of different steps take place in it, so I’ll present and discuss it in parts.

First of all, we need to take the status bar under consideration when the accordion menu is about to appear. This is because we’ll use the target view where the menu will appear and its frame as a base to configure our views. Thus, it’s really important to handle the status bar correctly. If the status bar is hidden, there’s no problem at all. If it’s visible, however, an empty space equal to the status bar height will be created when we make our view appear. So, as a first step, we need to check if the status bar is visible and store its height to fix the offset created by this.

-(void)showAccordionMenuInView:(UIView *)targetView{
    // STEP 1: THE STATUS BAR OFFSET
    CGFloat statusBarOffset;
    if (![[UIApplication sharedApplication] isStatusBarHidden]) {
        CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
        if (statusBarSize.width < statusBarSize.height) {
            statusBarOffset = statusBarSize.width;
        }
        else{
            statusBarOffset = statusBarSize.height;
        }
    }
    else{
        statusBarOffset = 0.0;
    }
...

This is the approach we followed during the previous tutorial in the custom text input view.

Next, we should specify what the width and height of the target view is depending on the orientation. Depending again on the orientation, we should “tell” whether the offset that should be moved by our default view is on the X or the Y axis.

-(void)showAccordionMenuInView:(UIView *)targetView{
    ...
    CGFloat width, height, offsetX, offsetY;
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft ||
        [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) {
        // If the orientation is landscape then the width
        // gets the targetView's height value and the height gets
        // the targetView's width value.
        width = targetView.frame.size.height;
        height = targetView.frame.size.width;
        offsetX = -statusBarOffset;
        offsetY = 0.0;
    }
    else{
        // Otherwise the width is width and the height is height.
        width = targetView.frame.size.width;
        height = targetView.frame.size.height;
        offsetX = 0.0;
        offsetY = -statusBarOffset;
    }
   ...

The next step is easy. We’ll simply set up the default view, self.view, by setting its frame and the appropriate offset, its alpha value, and finally adding it as a subview to the target view.

-(void)showAccordionMenuInView:(UIView *)targetView{
    ...
    // STEP 3 : SETUP THE SELF.VIEW
    [self.view setFrame:CGRectMake(targetView.frame.origin.x, targetView.frame.origin.y, width, height)];
    [self.view setFrame:CGRectOffset(self.view.frame, offsetX, offsetY)];
    [self.view setAlpha:0.0];
    [targetView addSubview:self.view];
    ...

Now it’s time to configure the view that contains the tableview. We have to specify its size here, keeping in mind that it will occupy a portion of the screen. I set its width to 260.0 px, but you can change it according to your desires. Its height will be calculated based on the total options and the height of each row. That means that the height will be equal to the total rows multiplied by the height of each row. In the case of too many options, and a menu height greater than the target view height in either orientation, we should manually shrink the menu and enable scrolling on the tableview. To achieve that, we’ll use a temporary variable that will keep the height in each case.

In order to achieve the accordion effect, we need to set its frame twice. At first, we’ll give it its normal frame and we’ll center it according to the target view center point. Then we’ll store the Y origin point and set the frame again. We’ll do this by setting the Y origin point to the Y origin point of its center and setting its height to 0.0. When the Y origin point and the height get restored to their original values, we’ll have a great accordion effect.

-(void)showAccordionMenuInView:(UIView *)targetView{
    ...
    // STEP 4: SETUP THE MENU VIEW
    menuViewWidth = 260.0;
    // The height is the height of each row multiplied by the number
    // of options.
    menuViewHeight = ROW_HEIGHT * [_optionsArray count];
    // Declare and use a local, temporary variable for the height of the menu view.
    CGFloat tempMenuHeight;
    if (menuViewHeight > height) {
        // If the menuViewHeight as set above is greater than the height of the target view
        // then set the menu view's height to targetview's height - 50.0.
        // Also enable scrolling on tableview.
        tempMenuHeight = height - 50.0;
        [_tblAccordionMenu setScrollEnabled:YES];
    }
    else{
        // Otherwise if the menu view height is not greater than the targetView's height
        // then the tempMenuHeight simply equals to the menuViewHeight.
        // The scrolling doesn't have to be enabled.
        tempMenuHeight = menuViewHeight;
        [_tblAccordionMenu setScrollEnabled:NO];
    }
    // Set the initial frame of the menu view. Note that we're not
    // interested in the x and y origin points because they'll be automatically
    // set right after, so set it to 0.0.
    [_viewAccordionMenu setFrame:CGRectMake(0.0, 0.0, menuViewWidth, tempMenuHeight)];
    // Set its center to the self.view's center.
    [_viewAccordionMenu setCenter:self.view.center];
    // Store temporarily the current y origin point of the menu view.
    CGFloat yPoint = _viewAccordionMenu.frame.origin.y;
    // Now set the center.y point as the y origin point of the menu view and its height to 0.0.
    [_viewAccordionMenu setFrame:CGRectMake(_viewAccordionMenu.frame.origin.x, _viewAccordionMenu.center.y, _viewAccordionMenu.frame.size.width, 0.0)];
    // Add the menu view to the targetView view.
    [targetView addSubview:_viewAccordionMenu];
    ...

It’s time to animate the menu. There’s really nothing special to discuss here. We simply change the alpha value of the self.view and set the final frame to the menu view.

-(void)showAccordionMenuInView:(UIView *)targetView{
    ...
    // STEP 5: ANIMATE
    [UIView beginAnimations:@"" context:nil];
    [UIView setAnimationDuration:ANIMATION_DURATION];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [self.view setAlpha:0.5];
    // Set the yPoint value as the y origin point of the menu view
    // and the tempMenuHeight value as its height.
    [_viewAccordionMenu setFrame:CGRectMake(_viewAccordionMenu.frame.origin.x,
                                            yPoint,
                                            _viewAccordionMenu.frame.size.width,
                                            tempMenuHeight)];
    [UIView commitAnimations];
    ...

Finally, reload the table data so the menu options appear on the tableview. Note that the method ends here.

-(void)showAccordionMenuInView:(UIView *)targetView{
    ...
    // STEP 6: RELOAD THE TABLEVIEW DATA
    [_tblAccordionMenu reloadData];
    ...
}

Step 4

Let’s write the next method regarding the menu closing. There’s nothing I really need to mention here. I’ll only emphasize that it uses a boolean parameter that specifies whether the closing should be animated or not.

-(void)closeAccordionMenuAnimated:(BOOL)animated{
    if (animated) {
        [UIView beginAnimations:@"" context:nil];
        [UIView setAnimationDuration:ANIMATION_DURATION];
        [UIView setAnimationCurve:UIViewAnimationCurveLinear];
        [self.view setAlpha:0.0];
        [_viewAccordionMenu setFrame:CGRectMake(_viewAccordionMenu.frame.origin.x, _viewAccordionMenu.center.y, _viewAccordionMenu.frame.size.width, 0.0)];
        [UIView commitAnimations];
        [_viewAccordionMenu performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:ANIMATION_DURATION + 0.5];
        [self.view performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:ANIMATION_DURATION + 0.5];
    }
    else{
        [_viewAccordionMenu removeFromSuperview];
        [self.view removeFromSuperview];
    }
}

6. Consider Orientation Changes

We’ve succeeded in making our menu appear correctly when called. What happens when the user rotates the device, though? Nothing! This is because we haven’t written anything concerning this, so let’s do it now. We’ll implement the viewWillLayoutSubviews method, which gets called every time the orientation is changed. You can read more about it at Apple’s developer website.

Here’s the short version of what we’re going to do. First, we’ll set the menu view’s frame, based on the menuViewWidth and the menuViewHeight variables we set earlier. We’ll center it according to the self.view center point. Next, depending on the device orientation, we’ll calculate the height of the superview. Finally, we’ll check whether the view’s height is greater than the superview’s height. If that’s true, we’ll manually shrink it and make scrolling enabled, just like we did in the -(void)showAccordionMenuInView:(UIView *)targetView method. Otherwise, we’ll simply turn scrolling off.

-(void)viewWillLayoutSubviews{
    // Get the current orientation.
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    // Set the menu view frame and center it.
    [_viewAccordionMenu setFrame:CGRectMake(_viewAccordionMenu.frame.origin.x,
                                            _viewAccordionMenu.frame.origin.y,
                                            menuViewWidth,
                                            menuViewHeight)];
    [_viewAccordionMenu setCenter:self.view.center];
    // Get the superview's height. In landscape mode the height is the width.
    CGFloat height;
    if (orientation == UIInterfaceOrientationLandscapeLeft ||
        orientation == UIInterfaceOrientationLandscapeRight) {
        height = self.view.superview.frame.size.width;
    }
    else{
        height = self.view.superview.frame.size.height;
    }
    // Check if the menu view's height is greater than the superview's height.
    if (_viewAccordionMenu.frame.size.height > height) {
        // If that's true then set the menu view's frame again by setting its height
        // to superview's height minus 50.0.
        [_viewAccordionMenu setFrame:CGRectMake(_viewAccordionMenu.frame.origin.x,
                                                _viewAccordionMenu.frame.origin.y,
                                                menuViewWidth,
                                                height - 50.0)];
        // Center again.
        [_viewAccordionMenu setCenter:self.view.center];
        // Also allow scrolling.
        [_tblAccordionMenu setScrollEnabled:YES];
    }
    else{
        // In that case the menu view's height is not greater than the superview's height
        // so set scrolling to NO.
        [_tblAccordionMenu setScrollEnabled:NO];
    }
}

7. Implement the Tableview Methods

Step 1

Here are the minimum required methods needed to make the tableview work. Note that in the -(UITableViewCell *)tableView:cellForRowAtIndexPath: method, we’ll check to see whether the current row is the last one or not.

-(int)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}
-(int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return [_optionsArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    if ([indexPath row] < [_optionsArray count] - 1) {
        [[cell contentView] setBackgroundColor:[UIColor colorWithRed:204.0/255.0 green:204.0/255.0 blue:204.0/255.0 alpha:1.0]];
        [[cell textLabel] setTextColor:[UIColor blackColor]];
        [[cell textLabel] setShadowColor:[UIColor whiteColor]];
        [[cell textLabel] setShadowOffset:CGSizeMake(1.0, 1.0)];
    }
    else{
        [[cell contentView] setBackgroundColor:[UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:104.0/255.0 alpha:1.0]];
        [[cell textLabel] setTextColor:[UIColor whiteColor]];
        [[cell textLabel] setShadowColor:[UIColor blackColor]];
        [[cell textLabel] setShadowOffset:CGSizeMake(1.0, 1.0)];
    }
    [[cell textLabel] setFont:[UIFont fontWithName:@"Georgia" size:17.0]];
    [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
    CGRect rect = CGRectMake(0.0, 0.0, self.view.bounds.size.width, [self tableView:tableView heightForRowAtIndexPath:indexPath]);
    [[cell textLabel] setFrame:rect];
    [[cell textLabel] setTextAlignment:NSTextAlignmentCenter];
    [[cell textLabel] setText:[_optionsArray objectAtIndex:[indexPath row]]];
    return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return ROW_HEIGHT;
}

Step 2

We also need to handle the tapping on the rows of the tableview. Note in the following method that we remove the selection from the tapped row.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO];
}

We’ll get back to this method pretty soon.


8. Protocol Definition

When the user taps on a row, or in other words a menu option, we want the caller view controller to be notified about the selected choice.

Step 1

Click on the AccordionMenuViewController.h file and write the following code, right before the @interface header.

@protocol AccordionMenuViewControllerDelegate
-(void)userSelectedOptionWithIndex:(NSUInteger)index;
@end

Step 2

Now, declare a delegate property.

@interface AccordionMenuViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) id<AccordionMenuViewControllerDelegate> delegate;
@property (strong, nonatomic) IBOutlet UIView *viewAccordionMenu;
@property (strong, nonatomic) IBOutlet UITableView *tblAccordionMenu;
-(void)setMenuOptions:(NSArray *)options;
-(void)showAccordionMenuInView:(UIView *)targetView;
-(void)closeAccordionMenuAnimated:(BOOL)animated;
@end

Step 3

When should the userSelectedOptionWithIndex delegate method be used? Every time a menu option gets selected. Go back in the -(void)tableView:didSelectRowAtIndexPath: method and add the following lines.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO];
    [self.delegate userSelectedOptionWithIndex:[indexPath row]];
}

9. The Accordion Menu in Action

The accordion menu is now ready. It’s time to see it in action. Make the required preparations in the ViewController class.

Step 1

First of all, the ViewController class should adopt the AccordionMenuViewControllerDelegate protocol. Open the ViewController.h file, import the AccordionMenuViewController.h class and add the protocol in the @interface header.

#import <UIKit/UIKit.h>
#import "CustomTextInputViewController.h"
#import "AccordionMenuViewController.h"
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, CustomTextInputViewControllerDelegate, AccordionMenuViewControllerDelegate>

Step 2

Open the ViewController.m file and go to the private part of the @interface at the top of the file. In there, add an NSArray that will be used for the options we’ll provide the accordion menu with, as well as an AccordionMenuViewController object.

@interface ViewController (){
...
@property (nonatomic, strong) NSArray *menuOptionsArray;
@property (nonatomic, strong) AccordionMenuViewController *accordionMenu;
@end

Step 3

Inside the viewDidLoad method, we need to initialize both the array and the object we declared in the previous step.

- (void)viewDidLoad
{
    [super viewDidLoad];
    ...
    // Set the options that will appear in the accordion menu.
    _menuOptionsArray = [[NSArray alloc] initWithObjects:@"Edit",
                         @"Delete",
                         @"Option 1",
                         @"Option 2",
                         @"Option 3",
                         @"Option 4",
                         @"Option 5",
                         nil];
    // Init the accordion menu view controller.
    _accordionMenu = [[AccordionMenuViewController alloc] init];
    // Set self as its delegate.
    [_accordionMenu setDelegate:self];
    // Set the menu options.
    [_accordionMenu setMenuOptions:_menuOptionsArray];
}

We’ll only use two options from the list above. For the time being, the rest are for demonstration purposes only.

Step 4

Go to the -(void)tableView:didSelectRowAtIndexPath: method and add the following.

// Make the accordion menu appear.
[_accordionMenu showAccordionMenuInView:self.view];

If you’re continuing the project from the previous tutorial, delete or comment out any existing content.

Step 5

Finally, we just need to implement the -(void)userSelectedOptionWithIndex:(NSUInteger)index delegate method. This is where any actions are taken when the user taps on the menu options.

-(void)userSelectedOptionWithIndex:(NSUInteger)index{
    if (index != [_menuOptionsArray count]) {
        NSIndexPath *indexPath = [_table indexPathForSelectedRow];
        switch (index) {
            case 0:
                [_textInput showCustomTextInputViewInView:self.view
                                                 withText:[_sampleDataArray objectAtIndex:[indexPath row]]
                                             andWithTitle:@"Edit item"];
                // Set the isEditingItem flag value to YES, indicating that
                // we are editing an item.
                isEditingItem = YES;
                break;
            case 1:
                [_sampleDataArray removeObjectAtIndex:[indexPath row]];
                [_table reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            default:
                break;
        }
    }
    [_accordionMenu closeAccordionMenuAnimated:YES];
}

We’re finished! Run the app on the simulator or on a device and check out the menu. Play around with it, and don’t hesitate to improve it or change it according to your needs.


Conclusion

Providing users with menu options that are different than the usual predefined ones is always a great challenge for a programmer. As you now know, we can achieve a nice result without using any difficult or extreme techniques. The accordion menu presented in this tutorial is a pretty nice way to display options to the user and, most importantly, it is reusable. I hope it will become a useful tool to everyone who uses it!

Android SDK: Create a Book Scanning App – Displaying Book Information

$
0
0

With the ZXing library, you can create Android applications with barcode scanning functionality. In Android SDK: Create a Barcode Reader, we implemented basic barcode reading with the library in a simple app. In this tutorial series, we will build on what we learned to create an app that will scan books and retrieve related information about them from Google Books API.

This series on Creating a Book Scanner App is in three parts:

Here is a preview of the app we are working toward:

Book Scanning App

When the final app runs, the user will be able to scan book barcodes. The app will present a selection of data about the scanned books, including where they’re available for purchase. The app will also provide a link to the page for a scanned book on Google Books.


1. Parse the Search Results

Step 1

In the last section, we executed the Google Books search query using the scanned ISBN (EAN) number, then brought the returned data into the app as a string. Now let’s parse the returned string, which is a JSON feed. We will do this in the onPostExecute method of the inner AsyncTask class we created. Add the method outline after the doInBackground method we completed last time.

protected void onPostExecute(String result) {
//parse search results
}

When you parse a JSON feed or any other incoming data from an external data source, you need to first familiarize yourself with the structure of the incoming data. Have a look at the Search and Volume sections on the Google Books API documentation to see the structure of what the search query will return.

We will be using the Java JSON libraries to process the search results. Add the following imports to your main activity class.

import java.io.BufferedInputStream;
import java.net.URL;
import java.net.URLConnection;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

Step 2

Let’s start working through the search result JSON. In your onPostExecute method, add try and catch blocks to cope with any input exceptions.

try{
//parse results
}
catch (Exception e) {
//no result
}

In the catch block, respond to cases where there has not been a valid search result.

e.printStackTrace();
titleText.setText("NOT FOUND");
authorText.setText("");
descriptionText.setText("");
dateText.setText("");
starLayout.removeAllViews();
ratingCountText.setText("");
thumbView.setImageBitmap(null);
previewBtn.setVisibility(View.GONE);

We simply set all of the user interface elements to indicate that a valid search result was not returned. This may happen if a scanned book is not listed on Google Books or if something else goes wrong when inputting the data.

Step 3

Back in the try block, set the preview button to be visible, assuming we have a valid result:

previewBtn.setVisibility(View.VISIBLE);

Now start to retrieve the JSON objects from the returned string.

JSONObject resultObject = new JSONObject(result);
JSONArray bookArray = resultObject.getJSONArray("items");

As you can see from the structure outlined in the search instructions on the Google Books API documentation, the query result will include an array named “items” containing any results matching the passed ISBN number. Since we are only expecting a single result for the book scanned, let’s get the first item in the array.

JSONObject bookObject = bookArray.getJSONObject(0);

Now we need the “volumeInfo” object which contains some of the data we want to display.

JSONObject volumeObject = bookObject.getJSONObject("volumeInfo");

This object will give us access to most of the book data items we are looking for.

Step 4

In some cases you’ll find that the Google Books search result will return some but not all of the data items, so we’ll use repeated try and catch blocks to handle cases where individual data values are missing. Start with the title.

try{ titleText.setText("TITLE: "+volumeObject.getString("title")); }
catch(JSONException jse){
	titleText.setText("");
	jse.printStackTrace();
}

If the string can be retrieved, we’ll display it in the relevant Text View. Otherwise, we’ll set it to display an empty string, with the catch block allowing the app to continue processing the search results in spite of this missing item.

Next let’s process the author, which is represented as an array in case there is more than one. We’ll build the entire string using a loop.

StringBuilder authorBuild = new StringBuilder("");
try{
	JSONArray authorArray = volumeObject.getJSONArray("authors");
	for(int a=0; a<authorArray.length(); a++){
		if(a>0) authorBuild.append(", ");
		authorBuild.append(authorArray.getString(a));
	}
	authorText.setText("AUTHOR(S): "+authorBuild.toString());
}
catch(JSONException jse){
	authorText.setText("");
	jse.printStackTrace();
}

Authors will be separated by commas if there’s more than one, otherwise the single author name will appear. Now parse the publication date.

try{ dateText.setText("PUBLISHED: "+volumeObject.getString("publishedDate")); }
catch(JSONException jse){
	dateText.setText("");
	jse.printStackTrace();
}

Next, we’ll process the description.

try{ descriptionText.setText("DESCRIPTION: "+volumeObject.getString("description")); }
catch(JSONException jse){
	descriptionText.setText("");
	jse.printStackTrace();
}

Now let’s deal with the star rating. Remember that we created a layout and declared an array to hold the star Image Views. Go back to your onCreate method for a moment and instantiate the array after the existing code.

starViews=new ImageView[5];
for(int s=0; s<starViews.length; s++){
	starViews[s]=new ImageView(this);
}

There can be a maximum of five stars. Back in onPostExecute after the last catch block we added for the description, add try and catch blocks for the stars.

try{
//set stars
}
catch(JSONException jse){
	starLayout.removeAllViews();
	jse.printStackTrace();
}

When we retrieve the number of stars awarded to the book, we’ll add the Image Views dynamically to the layout we created for the star rating. If the star rating number cannot be retrieved, we’ll remove any previously added views in the catch block. In this try block, retrieve the number of stars from the returned JSON and cast it to an integer.

double decNumStars = Double.parseDouble(volumeObject.getString("averageRating"));
int numStars = (int)decNumStars;

Set the number of stars currently displayed as the layout object tag. We will use this later when dealing with saving the app state. Remove any existing views from the layout for repeated scans.

starLayout.setTag(numStars);
starLayout.removeAllViews();

Now we can simply loop to add the relevant number of stars.

for(int s=0; s<numStars; s++){
	starViews[s].setImageResource(R.drawable.star);
	starLayout.addView(starViews[s]);
}

Remember that we added the star image to the application drawables last time. After the star section catch block, we can deal with the rating count.

try{ ratingCountText.setText(" - "+volumeObject.getString("ratingsCount")+" ratings"); }
catch(JSONException jse){
	ratingCountText.setText("");
	jse.printStackTrace();
}

Now let’s check to see if the book has a preview on Google Books. Set the preview button to be enabled or disabled accordingly.

try{
	boolean isEmbeddable = Boolean.parseBoolean
		(bookObject.getJSONObject("accessInfo").getString("embeddable"));
	if(isEmbeddable) previewBtn.setEnabled(true);
	else previewBtn.setEnabled(false);
}
catch(JSONException jse){
	previewBtn.setEnabled(false);
	jse.printStackTrace();
}

We’ll deal with what happens when pressing the preview button later. Next get the URL for the book’s page on Google Books and set it as the tag for the link button.

try{
	linkBtn.setTag(volumeObject.getString("infoLink"));
	linkBtn.setVisibility(View.VISIBLE);
}
catch(JSONException jse){
	linkBtn.setVisibility(View.GONE);
	jse.printStackTrace();
}

We’ll implement clicks on this later. The only remaining piece of information we still want to process is the thumbnail image, which is a little more complex. We want to use an additional AsyncTask class to retrieve it in the background.


2. Retrieve the Thumbnail Image

Step 1

After the “GetBookInfo” class, add another for retrieving the book thumbnail.

private class GetBookThumb extends AsyncTask<String, Void, String> {
//get thumbnail
}

We will pass the thumbnail URL string from the other AsyncTask when we retrieve it from the JSON. Inside this new AsyncTask class, add the doInBackground method.

@Override
protected String doInBackground(String... thumbURLs) {
//attempt to download image
}

Step 2

Inside doInBackground, add try and catch blocks in case of I/O exceptions.

try{
//try to download
}
catch(Exception e) {
	e.printStackTrace();
}

In the try block, attempt to make a connection using the passed thumbnail URL.

URL thumbURL = new URL(thumbURLs[0]);
URLConnection thumbConn = thumbURL.openConnection();
thumbConn.connect();

Now get input and buffered streams.

InputStream thumbIn = thumbConn.getInputStream();
BufferedInputStream thumbBuff = new BufferedInputStream(thumbIn);

We want to bring the image into the app as a bitmap, so add a new instance variable at the top of the activity class declaration.

private Bitmap thumbImg;

Back in doInBackground for the “GetBookThumb” class, after creating the buffered input stream, read the image into this bitmap.

thumbImg = BitmapFactory.decodeStream(thumbBuff);

Close the streams.

thumbBuff.close();
thumbIn.close();

After the catch block, return an empty string to complete the method.

return "";

After doInBackground, add the onPostExecute method to show the downloaded image.

protected void onPostExecute(String result) {
	thumbView.setImageBitmap(thumbImg);
}

3. Execute the Retrieving and Parsing Classes

Step 1

Now let’s pull these strands together. In your onActivityResult method, after the line in which you created the search query string, create and execute an instance of the AsyncTask class to fetch the search results.

new GetBookInfo().execute(bookSearchString);

In the onPostExecute method of the “GetBookInfo” class, after the catch block for the link button, add a final section for fetching the thumbnail using the JSON data and the other AsyncTask class.

try{
	JSONObject imageInfo = volumeObject.getJSONObject("imageLinks");
	new GetBookThumb().execute(imageInfo.getString("smallThumbnail"));
}
catch(JSONException jse){
	thumbView.setImageBitmap(null);
	jse.printStackTrace();
}

4. Handle the Remaining Clicks

Step 1

Let’s deal with the preview and link buttons. Start with the link button. We’ve already set the class as click listener for the buttons, so add a new section to your onClick method after the conditional for the scan button.

else if(v.getId()==R.id.link_btn){
	//get the url tag
	String tag = (String)v.getTag();
	//launch the url
	Intent webIntent = new Intent(Intent.ACTION_VIEW);
	webIntent.setData(Uri.parse(tag));
	startActivity(webIntent);
}

This code retrieves the URL from the button tag, which we set when we processed the JSON. The code then launches the link in the browser.

In order to launch the preview, we need the book ISBN number. Go back to the onActivityResult method for a moment. Add a line before the line in which you built the search query string. Set the scanned ISBN as a tag for the preview button.

previewBtn.setTag(scanContent);

Now back in onClick, add a conditional for the preview button in which you retrieve the tag.

else if(v.getId()==R.id.preview_btn){
	String tag = (String)v.getTag();
	//launch preview
}

Step 2

We are going to create a new activity for the book preview. Add a new class to your project in the same package as the main activity, naming it “EmbeddedBook“. Give your class the following outline.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class EmbeddedBook extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}
}

Create a new layout file in your app’s “res/layout” folder, giving it the name “embedded_book.xml“. Enter the following layout.

<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/embedded_book_view"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent" />

We will present the book preview in the WebView. Back in the new class, set this as content view in onCreate after the existing code.

setContentView(R.layout.embedded_book);

Add an instance variable at the top of the class.

private WebView embedView;

In onCreate, get a reference to the WebView we included in the layout.

embedView = (WebView)findViewById(R.id.embedded_book_view);

Set JavaScript to be enabled.

embedView.getSettings().setJavaScriptEnabled(true);

When we launch the activity, we will pass the ISBN. Retrieve this passed data next in onCreate.

Bundle extras = getIntent().getExtras();

Now check to see whether we have any extras.

if(extras !=null) {
//get isbn
}

Inside the conditional, attempt to retrieve the ISBN.

String isbn = extras.getString("isbn");

We are going to include the WebView content in an HTML formatted asset file. Add try and catch blocks for loading it.

try{
//load asset
}
catch(IOException ioe){
	embedView.loadData
	("<html><head></head><body>Whoops! Something went wrong.</body></html>","text/html", "utf-8");
	ioe.printStackTrace();
}

If the file cannot be loaded, we’ll output an error message. We’ll return to the try block soon. First, create a new file in your app’s “assets” folder, naming it “embedded_book_page.html“. Locate the new file in your Package Explorer, right-click and select “Open With” and then “Text Editor“. Enter the following code.

<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="content-type" content="text/html; charset=utf-8"/><title>Google Books Embedded Viewer API Example</title><script type="text/javascript" src="https://www.google.com/jsapi"></script><script type="text/javascript">
		google.load("books", "0");
		function initialize() {
			var viewer = new google.books.DefaultViewer(document.getElementById('viewerCanvas'));
			viewer.load('ISBN:$ISBN$');
		}
		google.setOnLoadCallback(initialize);</script></head><body><div id="viewerCanvas" style="width: 600px; height: 500px"></div></body></html>

This is the standard markup code used to display a Google Books Preview with the Embedded Viewer API. Notice that the page includes “$ISBN$“. We’ll use this to pass the ISBN number in from the activity.

Back to the try block in your “EmbeddedBook” class. Try to load the page using the following algorithm.

InputStream pageIn = getAssets().open("embedded_book_page.html");
BufferedReader htmlIn = new BufferedReader(new InputStreamReader(pageIn));
StringBuilder htmlBuild = new StringBuilder("");
String lineIn;
while ((lineIn = htmlIn.readLine()) != null) {
	htmlBuild.append(lineIn);
}
htmlIn.close();

Next, pass the ISBN, replacing the section we included for it in the HTML.

String embeddedPage = htmlBuild.toString().replace("$ISBN$", isbn);

Complete the new activity by loading the page.

embedView.loadData(embeddedPage, "text/html", "utf-8");

Step 3

Back in the main activity class onClick method, in the conditional block you added for the preview button, after the line in which you retrieved the tag, launch the new activity, passing the ISBN.

Intent intent = new Intent(this, EmbeddedBook.class);
intent.putExtra("isbn", tag);
startActivity(intent);

Add the new class to your application manifest file, after the main activity element.

<activity android:name="com.example.barcodescanningapp.EmbeddedBook" />

Alter the package name to suit your own if necessary.


5. Preserve the Application State

Step 1

You can run the app at this stage and it should function. However, let’s take care of the application state changing first. In your main activity class, add the following method.

protected void onSaveInstanceState(Bundle savedBundle) {
	savedBundle.putString("title", ""+titleText.getText());
	savedBundle.putString("author", ""+authorText.getText());
	savedBundle.putString("description", ""+descriptionText.getText());
	savedBundle.putString("date", ""+dateText.getText());
	savedBundle.putString("ratings", ""+ratingCountText.getText());
	savedBundle.putParcelable("thumbPic", thumbImg);
	if(starLayout.getTag()!=null)
		savedBundle.putInt("stars", Integer.parseInt(starLayout.getTag().toString()));
	savedBundle.putBoolean("isEmbed", previewBtn.isEnabled());
	savedBundle.putInt("isLink", linkBtn.getVisibility());
	if(previewBtn.getTag()!=null)
		savedBundle.putString("isbn", previewBtn.getTag().toString());
}

We won’t go into detail about saving the state because it’s not the focal point of this tutorial. Look over the code to ensure you understand it. We are simply saving the displayed data so that we can preserve it when the application state changes. In onCreate after the existing code, retrieve this information.

if (savedInstanceState != null){
	authorText.setText(savedInstanceState.getString("author"));
	titleText.setText(savedInstanceState.getString("title"));
	descriptionText.setText(savedInstanceState.getString("description"));
	dateText.setText(savedInstanceState.getString("date"));
	ratingCountText.setText(savedInstanceState.getString("ratings"));
	int numStars = savedInstanceState.getInt("stars");//zero if null
	for(int s=0; s<numStars; s++){
		starViews[s].setImageResource(R.drawable.star);
		starLayout.addView(starViews[s]);
	}
	starLayout.setTag(numStars);
	thumbImg = (Bitmap)savedInstanceState.getParcelable("thumbPic");
	thumbView.setImageBitmap(thumbImg);
	previewBtn.setTag(savedInstanceState.getString("isbn"));
	if(savedInstanceState.getBoolean("isEmbed")) previewBtn.setEnabled(true);
	else previewBtn.setEnabled(false);
	if(savedInstanceState.getInt("isLink")==View.VISIBLE) linkBtn.setVisibility(View.VISIBLE);
	else linkBtn.setVisibility(View.GONE);
	previewBtn.setVisibility(View.VISIBLE);
}

Again, we will not go into detail on this. The code simply carries out what the code already does immediately after scanning. In this case, it’s following a state change. Extend the opening tag of your main activity element in the project manifest to handle configuration changes.

android:configChanges="orientation|keyboardHidden|screenSize"

That completes the app! You should be able to run it at this point. When it launches, press the button and scan a book. If the book is listed in Google Books, the relevant information should appear within the interface. If certain data items are missing, the app should still display the others. If there is a preview available, the preview button should be enabled and should launch the embedded book activity.

Book Scanner App Preview

Conclusion

In this series we built on the foundation from Android SDK: Create a Barcode Scanner to create a book scanner app in conjunction with the ZXing scanning library and Google Books API. Experiment with the app by trying to retrieve and display different items of data from the returned JSON feed or by responding to scans of different barcode types, rather than just EANs. Using external resources via APIs is a key skill in any development discipline, as it allows you to make the best use of existing data and functionality in order to focus on the unique details of your own apps.

iOS SDK: Creating a Custom Alert View

$
0
0

Welcome to the third and last part of this session, where we’ve created some useful custom views. We’ve implemented a custom text input view, a custom accordion menu, and now we’re going to build a simple custom alert view. Read on!


Final Effect Preview


Custom Alert View Overview

The custom Alert View is going to be an alternative, simple, and nice alert view that will serve our need to display simple messages to the user. It will mainly consist of the following parts.

  • A label upon which the message will appear.
  • A toolbar below the label.
  • An okay button on the right side of the toolbar.
  • A cancel button on the left side of the toolbar.

The above will reside together into a UIView, and the view will be contained into the main view of the view controller that we’ll build for the purpose of this custom alert view.

The main view of the view controller is going to be semi-transparent, which will prevent users from tapping on anything else in the background while the alert view is up.

Furthermore, the alert view will slide in from the top side of the screen when it’s about to appear, and it’ll slide out with the reverse move when it’s about to go offscreen. It’ll contain two buttons, an okay button and a cancel button. They can be visible or not depending on the needs of the message about to appear.

Here is a sample of the final result.

[Final product demo movie here]

1. The Custom Alert View Controller

Step 1

Let’s begin our work by creating a new group in the Xcode for the custom alert view. This way we can keep our custom views tidy and clear.

On the Project Navigator pane on the left side of the Xcode, Control + Click (right click) on the CustomViewsDemo group and select the New Group option from the popup menu.

new_group_create

Set the Custom Alert View as the name of the new Group.

new_group_name

Step 2

Let’s keep going by creating a new view controller. Control+Click (right click) on the Custom Alert View group and select the New File… option from the popup menu.

new_file_option

Inside the new window, select the Objective-C class as the template for the new file and click next.

new_vc_template

In the next step, complete the following tasks.

  1. Set the name CustomAlertViewController as the content of the Class field.
  2. In the Subclass of field select the UIViewController value.
  3. Make sure that the “With XIB for user interface” is checked.

After completing all of these steps, click on the next button.

new_vc_options

This is the last step before our new view controller is created. The only thing we need to do here is make sure that Custom Alert View is the selected group. Then, click on the create button.

new_vc_create

2. Interface Setup

Step 1

Now that the new view controller is ready, let’s begin working with the interface. Click on the CustomAlertViewController.xib file to let the Interface Builder show onscreen. Turn the Autolayout feature off in case somebody wants to create the alert view for iOS versions prior to 6. Here is how you’ll accomplish that.

  1. Click on the Utilities button at the Xcode toolbar to show the Utilites pane if not visible.
  2. Click on File Inspector.
  3. Scroll towards the bottom a little bit and click on the “Use Autolayout” option to turn it off.
autolayout_3

We also need to make sure the alert view works on 3.5″ screen devices, so go to the Attributes Inspector and under the Simulated Metrics section set the Size value to None.

view_size_3

Step 2

It’s time to add some views to the interface. Grab the following from the Object library and set some properties, just as described here:

  1. UIView
    • Frame: X:0.0, Y:0.0, Width: 320.0, Height: 180.0
    • Background color: R:204, G:204, B:204
    • Autosizing: Flexible Width, Flexible Margin Top
      autosizing
  2. UILabel
    • Font: Custom
    • Family: Chalkboard SE
    • Style: Regular
    • Size: 17
    • Alignment: Center
    • Lines: 4
    • Line breaks: Word Wrap
    • Shadow: White Color
    • Shadow offset: Horizontal: 1, Vertical: 1
    • Frame: X: 20.0, Y: 7.0, Width: 280.0, Height: 123.0
    • Autosizing: Flexible Width, Flexible Margin Top
      autosizing
  3. UIToolbar
    • Tint: R: 204, G: 204, B: 204
    • Frame:: X: 0.0, Y: 136.0, Width: 320.0, Height: 44.0
  4. UIBarButtonItem
    • Position: Left side of the toolbar
    • Tint: Black Color
    • Identifier: Custom
    • Title: Cancel
  5. UIBarButtonItem
    • Identifier: Flexible Space
  6. UIBarButtonItem
    • Position: Right side of the toolbar
    • Tint: Black Color
    • Identifier: Custom
    • Title: Okay

Here is what your interface should look like.

ib_sample_3

3. IBOutlet Properties and IBAction Methods

Step 1

Now that the interface is ready, we need to create some IBOutlet properties to connect our views, as well as create a couple of IBAction methods so that our UIBarButtonItem buttons work. In order to do that, we need to have the Assistant Editor visible.

While you’re in the Interface Builder, click on the middle button of the Editor section in the Xcode toolbar to reveal the Assistant Editor and make the appropriate connections.

assistant_editor_3

Step 2

To create IBOutlet properties and connect them to the views, make sure that you have the Document Outline visible and:

  1. Control + Click (right click) on a view.
  2. On the popup menu, click on the New Referencing Outlet.
  3. Drag and drop into the Assistant Editor window.
iboutlet_create

Then just specify a name for the new property.

iboutlet_name

In the screenshots above, I demonstrated how to create an IBOutlet property for the UIView that contains all the other subviews. All the IBOutlet properties you need to create following the above procedure are inside the next code snippet. UIBarButtonItems btnOK and btnCancel, you must select the Strong storage property instead of the Weak, which is the default value. I’ll tell you later why we do that.

@property (weak, nonatomic) IBOutlet UIView *viewMessage;
@property (weak, nonatomic) IBOutlet UILabel *lblMessage;
@property (weak, nonatomic) IBOutlet UIToolbar *toolbar;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *btnOK;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *btnCancel;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *flexSpace;

Step 3

Let’s create the necessary IBAction methods now. In order to create a new IBAction method for a UIBarButtonItem button, we’ll do the following:

  1. Control + Click (right click) on the bar button item.
  2. Click on the Selector option in the Sent Actions section of the popup menu.
  3. Drag and drop into the Assistant Editor window.
ibaction_create

Set a name for the new method.

ibaction_name

There are two UIBarButtonItems in our interface, so you have to do this procedure twice. You’ll do it once for the Okay button and once for the Cancel button. Here are both of the IBAction methods.

- (IBAction)btnOkayTap:(id)sender;
- (IBAction)btnCancelTap:(id)sender;

They will both be implemented later.


4. Code Level Setup

Step 1

It would be a great idea to distinguish the two UIBarButtonItems (buttons) we previously added using their tag values. We didn’t set any tag value in the Interface Builder, because I personally prefer doing so programmatically. As you will find out by yourself later, the tag values are the key to making the buttons of the alert view work properly.

For the time being, let’s define two constants that will represent the tag values for each UIBarButtonItem (the Okay button and the Cancel button) of the alert view.

Open the CustomAlertViewController.m file and at the top of it, right after the #import... line, add the following:

#define OK_BUTTON_TAG       888
#define CANCEL_BUTTON_TAG   999

The alert view will use animations to appear and disappear from the screen, so it’s a good idea to define a constant for the duration of the animation. Right after the two constants we just added, write this one:

#define OK_BUTTON_TAG       888
#define CANCEL_BUTTON_TAG   999
#define ANIMATION_DURATION  0.25

Step 2

There are a couple of initializations that need to be done, but prior to doing so let’s create a custom init method, preferably right after the @implementation CustomAlertViewController line.

-(id)init{
    self = [super init];
    if (self) {
    }
return self;
}

There are four things we need to do here:

  1. Set the background color and the alpha value of the default view (self.view).
  2. Programatically calculate the message label’s height, just in case the message view’s height change in Interface Builder.
  3. Set the tag value of the Okay button.
  4. Set the tag value of the Cancel button.

Let’s set the background of the default view to a semi-transparent, grey color.

[self.view setBackgroundColor:[UIColor colorWithRed:0.33 green:0.33 blue:0.33 alpha:0.75]];

If you want to change the height of the viewMessage property, don’t worry about the exact height of the contained UILabel (the lblMessage property). We’ll calculate it programatically.

[_lblMessage setFrame:CGRectMake(_lblMessage.frame.origin.x,
                                         _lblMessage.frame.origin.y,
                                         _lblMessage.frame.size.width,
                                        _viewMessage.frame.size.height - _toolbar.frame.size.height)];

Finally, let’s set the tag values for the two buttons we have.

[_btnOK setTag:OK_BUTTON_TAG];
[_btnCancel setTag:CANCEL_BUTTON_TAG];

The init method is ready. Our next step is to declare and implement the public methods of the view controller.


5. Implementing the Public Methods

Step 1

There are a great number of public methods that can be implemented, and many of them depend on the imagination of the programmer or the level of the functionalities expected from a custom alert view of this kind. However, in our example, we are going to implement only methods regarding the following issues:

  • Appearance of the alert view on a parent view.
  • Disappearance from the parent view.
  • Disappearance from the parent view instantly.
  • The state toggle of the Okay button (remove/insert).
  • The state toggle of the Cancel button (remove/insert).
  • Checking whether the Okay button is removed.
  • Checking whether the Cancel button is removed.

Click on the CustomAlertViewController.h to bring it on. Add the next declaration into it.

-(void)showCustomAlertInView:(UIView *)targetView withMessage:(NSString *)message;
-(void)removeCustomAlertFromView;
-(void)removeCustomAlertFromViewInstantly;
-(void)removeOkayButton:(BOOL)shouldRemove;
-(void)removeCancelButton:(BOOL)shouldRemove;
-(BOOL)isOkayButtonRemoved;
-(BOOL)isCancelButtonRemoved;

Let’s begin implementing the above methods one by one, but first let’s switch to the CustomAlertViewController.m file.

Step 2

The showCustomAlertInView:withMessage is perhaps the most important method, as it is responsible for showing the custom alert view in the target view specified by the first parameter. There are some major points to be noted, however.

First, the status bar; the alert view is going to slide in and out from the top of the screen, so the space the status bar occupies if it is visible is accountable. We can easily find out if it’s visible and get its size. But there is a small trap in case the status bar is visible and we have to deal with both portrait and landscape orientation. Why? Because in portrait mode, the status bar size is in the form Width x Height (e.g. 320.0 x 20.0) and in landscape mode the size is in the form Height x Width (e.g. 20.0 x 480.0). The easiest way for us to determine the status bar height is to check for the minimum value between the width and the height.

Secondly, to present the alert view correctly we have to check the orientation every time this method is called and set the view’s frame accordingly. It is important to note that when we are in landscape mode the width of the screen is the height for the view and the height of the screen is the width for the view. Also, when the status bar is visible (and in our example it is), a padding is created (at the left side of the view in landscape mode, at the top in portrait mode), which needs to be eliminated. For that reason, we’ll simply use the CGRectOffset method to move the alert view’s frame for an offset value equal to the status bar height, either towards the top or the left of the parent view.

As I have already said, the message view is going to be offscreen initially, which means that the Y origin point of the viewMessage property is going to be set to -_viewMessage.frame.size.height. When we use animation to show it onscreen, we’ll simply set the Y origin point to the 0.0 value, so a smooth slide-in effect will be achieved. Finally, the message string passed as the second parameter to the method will be set to the label, and we’re done.

After having said all of that, here is the whole method:

-(void)showCustomAlertInView:(UIView *)targetView withMessage:(NSString *)message{
    CGFloat statusBarOffset;
    if (![[UIApplication sharedApplication] isStatusBarHidden]) {
        // If the status bar is not hidden then we get its height and keep it to the statusBarOffset variable.
        // However, there is a small trick here that needs to be done.
        // In portrait orientation the status bar size is 320.0 x 20.0 pixels.
        // In landscape orientation the status bar size is 20.0 x 480.0 pixels (or 20.0 x 568.0 pixels on iPhone 5).
        // We need to check which is the smallest value (width or height). This is the value that will be kept.
        // At first we get the status bar size.
        CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
        if (statusBarSize.width < statusBarSize.height) {
            // If the width is smaller than the height then this is the value we need.
            statusBarOffset = statusBarSize.width;
        }
        else{
            // Otherwise the height is the desired value that we want to keep.
            statusBarOffset = statusBarSize.height;
        }
    }
    else{
        // Otherwise set it to 0.0.
        statusBarOffset = 0.0;
    }
    // Declare the following variables that will take their values
    // depending on the orientation.
    CGFloat width, height, offsetX, offsetY;
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft ||
        [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) {
        // If the orientation is landscape then the width
        // gets the targetView's height value and the height gets
        // the targetView's width value.
        width = targetView.frame.size.height;
        height = targetView.frame.size.width;
        offsetX = -statusBarOffset;
        offsetY = 0.0;
    }
    else{
        // Otherwise the width is width and the height is height.
        width = targetView.frame.size.width;
        height = targetView.frame.size.height;
        offsetX = 0.0;
        offsetY = -statusBarOffset;
    }
    // Set the view's frame and add it to the target view.
    [self.view setFrame:CGRectMake(targetView.frame.origin.x, targetView.frame.origin.y, width, height)];
    [self.view setFrame:CGRectOffset(self.view.frame, offsetX, offsetY)];
    [targetView addSubview:self.view];
    // Set the initial frame of the message view.
    // It should be out of the visible area of the screen.
    [_viewMessage setFrame:CGRectMake(0.0, -_viewMessage.frame.size.height, _viewMessage.frame.size.width, _viewMessage.frame.size.height)];
    // Animate the display of the message view.
    // We change the y point of its origin by setting it to 0 from the -height value point we previously set it.
    [UIView beginAnimations:@"" context:nil];
    [UIView setAnimationDuration:ANIMATION_DURATION];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [_viewMessage setFrame:CGRectMake(0.0, 0.0, _viewMessage.frame.size.width, _viewMessage.frame.size.height)];
    [UIView commitAnimations];
    // Set the message that will be displayed.
    [_lblMessage setText:message];
}

Step 3

The next method we have to implement is the -(void)removeCustomAlertFromView, which simply makes the alert view disappear from the screen. There is nothing particular to mention about here, as it uses a simple animation to alter the Y origin point of the message view and make it that way go away using a slide-out effect. Pay attention to the last command only, which removes the default view from the superview with some delay and after the animation has finished.

-(void)removeCustomAlertFromView{
    // Animate the message view dissapearing.
    [UIView beginAnimations:@"" context:nil];
    [UIView setAnimationDuration:ANIMATION_DURATION];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [_viewMessage setFrame:CGRectMake(0.0, -_viewMessage.frame.size.height, _viewMessage.frame.size.width, _viewMessage.frame.size.height)];
    [UIView commitAnimations];
    // Remove the main view from the super view as well after the animation is finished.
    [self.view performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:ANIMATION_DURATION];
}

The -(void)removeCustomAlertFromViewInstantly method is the easiest, as it simply removes the default view from the superview.

-(void)removeCustomAlertFromViewInstantly{
    [self.view removeFromSuperview];
}

Step 4

Next, we need to write the -(BOOL)isOkayButtonRemoved and the -(BOOL)isCancelButtonRemoved methods. Both of them play an important role in the functionality of the alert view and you can easily understand from their names why. These methods could be private, as I don’t call any of them directly from the demo app. However, in a real application, they might be useful, so let them be in public scope.

The functionality is similar in both of these methods. We simply check in each one if the respective UIBarButtonItem (button) is found in the toolbar’s items, and we return the appropriate value. So, here are both of them:

-(BOOL)isOkayButtonRemoved{
    if ([[_toolbar items] indexOfObject:_btnOK] == NSNotFound) {
        return YES;
    }
    else{
        return NO;
    }
}
-(BOOL)isCancelButtonRemoved {
    if ([[_toolbar items] indexOfObject:_btnCancel] == NSNotFound) {
        return YES;
    }
    else{
        return NO;
    }
}

Step 5

Next, let’s implement both the -(void)removeOkayButton:(BOOL)shouldRemove and -(void)removeCancelButton:(BOOL)shouldRemove methods. These ones are identical, just like the previous methods. The first one makes the Okay button of the alert view appear or get removed from the view, while the other one deals with the Cancel button.

There is one thing that I should point out. Both of these methods do the same job for different buttons, so it would be pointless (and really bad practice) to write the procedure of showing/removing each button to/from the view twice. Therefore, we’ll implement in the next step a private method which does the real job. Its name is -(void)addOrRemoveButtonWithTag:andActionToPerform:, and this is the one that we call in both of our current methods.

Here they are:

-(void)removeOkayButton:(BOOL)shouldRemove{
    if ([self isOkayButtonRemoved] != shouldRemove) {
        [self addOrRemoveButtonWithTag:OK_BUTTON_TAG andActionToPerform:shouldRemove];
    }
}
-(void)removeCancelButton:(BOOL)shouldRemove {
    if ([self isCancelButtonRemoved] != shouldRemove) {
        [self addOrRemoveButtonWithTag:CANCEL_BUTTON_TAG andActionToPerform:shouldRemove];
    }
}

Note that in the methods above we provide the private method (not implemented yet) with the shouldRemove parameter flag, and nothing more gets done in any of them.


6. The Private Method

Step 1

Now that the public methods are implemented, we are halfway to our destination. Beyond that, we just saw that we need to create another method, which doesn’t need to be public, responsible for showing or hiding each button of our alert view.

At first, we need to declare this method. Go to the private part of the interface, add the highlighted line and you are done:

@interface CustomAlertViewController ()
-(void)addOrRemoveButtonWithTag:(int)tag andActionToPerform:(BOOL)shouldRemove;
@end

Step 2

Before we go further into the implementation of the method we just declared, let me explain how it will work. First, we’ll get the items of the toolbar into a mutable array, so we are able to add or remove objects. Next, depending on the tag of the button provided in the parameter, we’ll specify the index of the button into the array with the toolbar items. For that purpose, we are going to use the index of the flexible space we added into the Interface Builder. If we are about to show or hide the Cancel button, then we want to access the index 0, otherwise we want to access the index right after the index of the flexible space.

After we have the index of the button into the items array specified, the rest is easy. Depending on the flag, we either remove the button from the items, or we add it to the appropriate position. Finally, after all the work has been done, we set the modified items array as the items of the toolbar.

Here it is. Pay attention to the fact that we are using the tag values we previously defined as constants.

-(void)addOrRemoveButtonWithTag:(int)tag andActionToPerform:(BOOL)shouldRemove{
    NSMutableArray *items = [[_toolbar items] mutableCopy];
    int flexSpaceIndex = [items indexOfObject:_flexSpace];
    int btnIndex = (tag == OK_BUTTON_TAG) ? flexSpaceIndex + 1 : 0;
    if (shouldRemove) {
        [items removeObjectAtIndex:btnIndex];
    }
    else{
        if (tag == OK_BUTTON_TAG) {
            [items insertObject:_btnOK atIndex:btnIndex];
        }
        else{
            [items insertObject:_btnCancel atIndex:btnIndex];
        }
    }
    [_toolbar setItems:(NSArray *)items];
}

It’s time to explain why we used the strong property for the btnOk and the btnCancel IBOutlet properties. We did it because of this method. When using the strong property, the reference to the IBOutlet property doesn’t get lost even when the button is removed from the toolbar. It’s just like we have the retain in case we don’t use the ARC. On the contrary, if we keep the weak property, when we remove a button from the toolbar the reference to it gets removed as well. As a result, when we go to add it to the toolbar again, the only thing that is found is a nil object and not the UIBarButtonItem we want. This is a really simple explanation, and you should do some more reading about these attributes if you want or need to get a more general knowledge regarding these things. For testing purposes, you can change the properties in the CustomAlertViewController.h file to weak and watch the message in the debugger, but only after we have the alert view completed.


7. Protocol Definition

Now that both the public and the private method(s) are ready, we need to define the protocol which allows the delegate class that adopts it (in our case the ViewController) to implement the necessary methods required to handle the Okay and Cancel button taps.

So, go to the CustomAlertViewController file and define a protocol, along with the next delegate methods:

@protocol CustomAlertViewControllerDelegate
-(void)customAlertOK;
-(void)customAlertCancel;
@end

Then, right after the @interface header, add the highlighted line:

@interface CustomAlertViewController : UIViewController
@property (nonatomic, retain) id<CustomAlertViewControllerDelegate> delegate;
...

8. Implementing the IBAction Methods

At this point, we are just a step away from having the custom alert view ready. We simply need to implement the two IBAction methods we defined earlier. Every time that the Okay button gets tapped, the -(void)customAlertOK delegate method will be called. Similarly, every time that the Cancel button gets tapped, the -(void)customAlertCancel delegate method will be called.

- (IBAction)btnOkayTap:(id)sender {
    [self.delegate customAlertOK];
}
- (IBAction)btnCancelTap:(id)sender {
    [self.delegate customAlertCancel];
}

9. Prepare the Alert View for Action

Our brand new custom view is finally ready. What we need to do now is watch it in action, and here is how we are going to try it out:

  • When we delete an item, a confirmation alert will show up.
  • When we use the dummy Option 1, the Cancel button will get removed and the Okay will remain on the toolbar.
  • When we use the dummy Option 2, the Okay button will get removed and the Cancel will remain on the toolbar.
  • When we use the dummy Option 3, both the Okay and Cancel buttons will get removed from the toolbar and the alert view will go away a few seconds later.
  • When we use the dummy Options 4 and 5, the Okay button will remain only on the toolbar.

Step 1

Open the ViewController.h file and import the alert view:

#import "CustomAlertViewController.h"

Also, make the ViewController class the delegate of the CustomAlertViewController in the interface header:

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, CustomTextInputViewControllerDelegate, AccordionMenuViewControllerDelegate,
 CustomAlertViewControllerDelegate>

Step 2

Switch to the ViewController.m file and declare a CustomAlertViewController object at the private part of the interface (along with the rest custom views’ objects). Also, add a BOOL flag named isDeletingItem, which will indicate whether we delete an item from the list or not:

@interface ViewController (){
    BOOL isEditingItem;
    BOOL isDeletingItem;
}
@property (nonatomic, strong) NSMutableArray *sampleDataArray;
@property (nonatomic, strong) NSArray *menuOptionsArray;
@property (nonatomic, strong) CustomTextInputViewController *textInput;
@property (nonatomic, strong) AccordionMenuViewController *accordionMenu;
@property (nonatomic, strong) CustomAlertViewController *customAlert;
@end

Step 3

The object and the flag have been declared, but they have not been initialized yet. So, head to the viewDidLoad method and add the following lines before its closing:

// Initialize the custom alert view controller object
// and set self as its delegate.
_customAlert = [[CustomAlertViewController alloc] init];
[_customAlert setDelegate:self];
// Set the inital value of the isDeleting flag.
isDeletingItem = NO;

Step 4

Most of the work will take place inside the -(void)userSelectedOptionWithIndex:(NSUInteger)index delegate method of the custom Accordion Menu. For instance, when the user chooses to delete an item, instead of directly deleting the item in the respective case inside the method, we’ll call the custom alert view to show a confirmation message. Similarly, when the user selects any of the other options, we’ll show the appropriate messages with the appropriate buttons visible, just as I previously described.

Note the use of the removeOkayButton and the removeCancelButton methods and how we play with them. When we set the NO value as their parameter, the respective button is removed from the toolbar. On the contrary, when we set the YES value, the respective button is visible on the toolbar again. You can also see that when we show the confirmation message to the user the isDeletingItem flag turns to YES.

Here is the whole method. What we care about here is from case 1 and so forth:

-(void)userSelectedOptionWithIndex:(NSUInteger)index{
    if (index != [_menuOptionsArray count]) {
        NSIndexPath *indexPath = [_table indexPathForSelectedRow];
        switch (index) {
            case 0:
                [_textInput showCustomTextInputViewInView:self.view
                                                    withText:[_sampleDataArray objectAtIndex:[indexPath row]]
                                                    andWithTitle:@"Edit item"];
                // Set the isEditingItem flag value to YES, indicating that
                // we are editing an item.
                isEditingItem = YES;
                break;
            case 1:
                // Make sure that both the Okay and the Cancel button will be shown on the alert view.
                [_customAlert removeOkayButton:NO];
                [_customAlert removeCancelButton:NO];
                // Show the custom alert view.
                [_customAlert showCustomAlertInView:self.view
                                        withMessage:@"Hey, wait!\nAre you sure you want to delete this item?"];
                isDeletingItem = YES;
                break;
            case 2:
                // Show only the Okay button and remove the Cancel button.
                [_customAlert removeOkayButton:NO];
                [_customAlert removeCancelButton:YES];
                // Show the custom alert view.
                [_customAlert showCustomAlertInView:self.view
                                        withMessage:@"Where is the Cancel button?"];
                break;
            case 3:
                // Show only the Cancel button and remove the Okay button.
                [_customAlert removeOkayButton:YES];
                [_customAlert removeCancelButton:NO];
                // Show the custom alert view.
                [_customAlert showCustomAlertInView:self.view
                                        withMessage:@"Where is the Okay button?"];
                break;
            case 4:
                // Remove both of the buttons.
                [_customAlert removeOkayButton:YES];
                [_customAlert removeCancelButton:YES];
                // Show the custom alert view.
                [_customAlert showCustomAlertInView:self.view
                                        withMessage:@"This is an alert without buttons.\nThis message will go away in about 3 seconds."];
                [_customAlert performSelector:@selector(removeCustomAlertFromView) withObject:nil afterDelay:3.0];
                break;
            case 5:
            case 6:
                // Remove the Cancel button.
                [_customAlert removeOkayButton:NO];
                [_customAlert removeCancelButton:YES];
                // Show the custom alert view.
                [_customAlert showCustomAlertInView:self.view
                                        withMessage:@"This is another option of your great menu!"];
                break;
            default:
                break;
        }
    }
    [_accordionMenu closeAccordionMenuAnimated:YES];
}

Step 5

As a final step, we need to implement the delegate methods of the CustomAlertViewController class and add the desired behaviour of the demo app in them. Let’s begin with the -(void)customAlertOK. In this method, note that we first check if we are deleting an item or not. If we are, then after doing all the necessary actions to delete the item from the list, we instantly remove the alert view from the parent view; at the same time the isDeletingItem flag becomes false again. Otherwise, we simply remove the alert view from the view.

-(void)customAlertOK{
    if (isDeletingItem) {
        NSIndexPath *indexPath = [_table indexPathForSelectedRow];
        [_sampleDataArray removeObjectAtIndex:[indexPath row]];
        [_table reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
        isDeletingItem = NO;
        [_customAlert removeCustomAlertFromViewInstantly];
    }
    else{
        [_customAlert removeCustomAlertFromView];
    }
}

The next delegate method is simple enough. We have to check the isDeletingItem flag value here too and make it false, just in case the user tapped the Cancel button to avoid deleting the item.

-(void)customAlertCancel{
    [_customAlert removeCustomAlertFromView];
    if (isDeletingItem) {
        isDeletingItem = NO;
    }
}

We are ready! Go and give it a try in the Simulator. Use the custom alert view in both portrait and landscape orientation and watch how the buttons work in each case.


Conclusion

As we’ve proved once again, it is not hard to create custom views using simple techniques. In this case, we created a custom alert view, a reusable component that is pretty useful in case someone doesn’t want to use the default alert view that the iOS provides. For the purpose of this tutorial, only two buttons have been added to the alert view. Naturally, it could be enriched with more buttons and even more functionalities! Everything is up to your imagination and your needs! This tutorial was the final part in a trilogy where three important, useful and reusable custom views have been built. I hope they all serve their cause and become useful tools in the hands of every iOS programmer!

Create a Physics-Based Puzzle Game – Tuts+ Premium

$
0
0

In this tutorial series, I’ll be showing you how to create a physics-based puzzle game in Corona SDK. You’ll learn about physics manipulation and touch controls. The objective of the game is to get the ball into the container by tapping the correct boxes. Read on!


Tutorial Preview

Interface
Credits View

Get the Full Series!

This tutorial series is available to Tuts+ Premium members only. Read a preview of this tutorial on the Tuts+ Premium web site or login to Tuts+ Premium to access the full content.


Joining Tuts+ Premium. . .

For those unfamiliar, the family of Tuts+ sites runs a premium membership service called Tuts+ Premium. For $19 per month, you gain access to exclusive premium tutorials, screencasts, and freebies from Mobiletuts+, Nettuts+, Aetuts+, Audiotuts+, Vectortuts+, and CgTuts+. You’ll learn from some of the best minds in the business. Become a premium member to access this tutorial, as well as hundreds of other advanced tutorials and screencasts.

iOS SDK: Detecting Network Changes with Reachability

$
0
0

Most mobile applications access the web for some reason or another. This implies that these applications will – or should – behave differently when no network connection is available. In this quick tip, I will show you how to detect network interface changes using Tony Million‘s Reachability class.


Solutions

Even though Apple provides sample code for monitoring changes in network availability, Apple’s Reachability class is a bit outdated and doesn’t support ARC (Automatic Reference Counting). If you use AFNetworking, then you may want to consider AFHTTPClient, which also lets you monitor network interface changes.

However, my preferred solution is the wonderful Reachability class created and maintained by Tony Million. It supports ARC and leverages GCD (Grand Central Dispatch). Let me show you how to integrate Tony’s Reachability class.


1. Setting Up the Project

Create a new project in Xcode by selecting the Single View Application template from the list of templates (figure 1). Name your application Reachability, enter a company identifier, set iPhone for the device family, and check Use Automatic Reference Counting. The rest of the checkboxes can be left unchecked for this project (figure 2). Tell Xcode where you want to save the project and click Create.

iOS Quick Tip: Detecting Network Changes with Reachability - Choosing a Project Template
Figure 1: Choosing a Project Template
iOS Quick Tip: Detecting Network Changes with Reachability - Configuring the Project
Figure 2: Configuring the Project

2. Integrating Reachability

Step 1: Adding the Reachability Class

Integrating Tony Million’s Reachability class is trivial. Visit the project’s GitHub page, download the latest version, and drag Reachability.h/.m into your Xcode project (figure 3). If you take this path, make sure to copy the class files into your Xcode project (figure 4). You can also use CocoaPods to to add Reachability to your project.

iOS Quick Tip: Detecting Network Changes with Reachability - Adding the Reachability Class to Your Project
Figure 3: Adding the Reachability Class to Your Project
iOS Quick Tip: Detecting Network Changes with Reachability - Make Sure to Copy the Class Files into Your Project
Figure 4: Make Sure to Copy the Class Files into Your Project

Step 2: Linking Against the System Configuration Framework

The Reachability class depends on the System Configuration framework for some of its functionality. With your project selected in the Project Navigator, select the Reachability target in the list of targets, open the Build Phases tab, and expand the Link Binary with Libraries drawer. Click the plus button and search for SystemConfiguration.framework (figure 5).

iOS Quick Tip: Detecting Network Changes with Reachability - Link Your Project Against the System Configuration Framework
Figure 5: Link Your Project Against the System Configuration Framework

3. Reachability Basics

The Reachability class provides two ways to monitor changes in network availability: blocks and notifications. Let me show you how this works.

Step 1: Importing Reachability.h

To keep things simple, we will add the reachability logic to the application delegate. Start by adding an import statement for Reachability.h to MTAppDelegate.m as shown below.

#import "MTAppDelegate.h"
#import "Reachability.h"
#import "MTViewController.h"

Step 2: Blocks

We first need to create an instance of the Reachability class in application:didFinishLaunchingWithOptions: (MTAppDelegate.m) as shown below. We then set the reachableBlock and unreachableBlock on the Reachability instance. The reachableBlock is invoked when the network availability changes from unreachable to reachable. The reverse is true for the unreachableBlock. It is key to send the Reachability instance a message of startNotifier so that it knows that it should start monitoring for reachability changes. It is also important to know that the call to startNotifier causes the Reachability instance to retain itself, which means that you don’t need to store a reference to the Reachability object. However, I am not a fan of this approach and will show you an alternative solution a bit later.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize Reachability
    Reachability *reachability = [Reachability reachabilityWithHostname:@"www.google.com"];
    reachability.reachableBlock = ^(Reachability *reachability) {
        NSLog(@"Network is reachable.");
    };
    reachability.unreachableBlock = ^(Reachability *reachability) {
        NSLog(@"Network is unreachable.");
    };
	// Start Monitoring
    [reachability startNotifier];
    // Initialize View Controller
    self.viewController = [[MTViewController alloc] initWithNibName:@"MTViewController" bundle:nil];
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    [self.window setRootViewController:self.viewController];
    [self.window makeKeyAndVisible];
    return YES;
}

Before we take a look at notifications, I need to stress that the reachable and unreachable blocks are invoked on a background thread. Keep this in mind when you need to update your application’s user interface when the network interface changes.

Step 3: Notifications

The advantage of using notifications for reachability changes is that any object in your application can register itself as an observer for these notifications. In contrast to the use of blocks, the reachability notifications are posted and delivered on the main thread. As you can see below, in application:didFinishLaunchingWithOptions:, we create an instance of the Reachability class and tell it to start monitoring for network changes.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize Reachability
    Reachability *reachability = [Reachability reachabilityWithHostname:@"www.google.com"];
	// Start Monitoring
    [reachability startNotifier];
    // Initialize View Controller
    self.viewController = [[MTViewController alloc] initWithNibName:@"MTViewController" bundle:nil];
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    [self.window setRootViewController:self.viewController];
    [self.window makeKeyAndVisible];
    return YES;
}

As an example, if the view controller needs to be informed of changes in network availability, then we need to add it as an observer of the notifications the Reachability instance posts. Open MTViewController.m, add an import statement for Reachability.h, and update initWithNibName:bundle: as shown below.

#import "MTViewController.h"
#import "Reachability.h"
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Add Observer
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityDidChange:) name:kReachabilityChangedNotification object:nil];
    }
    return self;
}

Every time the network interface changes, reachabilityDidChange: is invoked and the view controller can respond appropriately. The object property of the notification is the Reachability instance that posted the notification. The Reachability class provides a number of useful instance methods, such as isReachable, isReachableViaWWAN, and isReachableViaWiFi. You can even tell the Reachability instance whether it should consider WWAN as unreachable by setting the reachableOnWWAN property accordingly.

- (void)reachabilityDidChange:(NSNotification *)notification {
    Reachability *reachability = (Reachability *)[notification object];
    if ([reachability isReachable]) {
        NSLog(@"Reachable");
    } else {
        NSLog(@"Unreachable");
    }
}

4. Reachability Manager

For applications that require a network connection, I usually use an alternative approach for managing reachability. I create a separate class named ReachabilityManager that adopts the singleton pattern. The singleton object manages a Reachability instance and provides a number of useful class methods. Let me walk you through the internals of this class.

Step 1: Interface

As I mentioned, the MTReachabilityManager class adopts the singleton pattern and it provides access to the singleton object through the sharedManager class method. This is useful if an object needs direct access to the reachability instance that the singleton object manages.

Through a number of class methods objects can ask the reachability manager about the current network interface. In addition, objects can still add themselves as observers for the kReachabilityChangedNotification notifications as we saw earlier.

#import <Foundation/Foundation.h>
@class Reachability;
@interface MTReachabilityManager : NSObject
@property (strong, nonatomic) Reachability *reachability;
#pragma mark -
#pragma mark Shared Manager
+ (MTReachabilityManager *)sharedManager;
#pragma mark -
#pragma mark Class Methods
+ (BOOL)isReachable;
+ (BOOL)isUnreachable;
+ (BOOL)isReachableViaWWAN;
+ (BOOL)isReachableViaWiFi;
@end

Step 2: Implementation

The implementation of MTReachabilityManager isn’t too surprising. If you are unfamiliar with the singleton pattern, then the implementation of sharedManager might seem a bit odd. Even though the MTReachabilityManager class makes use of the singleton pattern, it is technically possible to create instances of the class. We could prevent this by checking the value of _sharedManager in the init, but I assume that whoever uses the MTReachabilityManager class has taken a look at the class’s interface file revealing that it adopts the singleton pattern.

#import "MTReachabilityManager.h"
#import "Reachability.h"
@implementation MTReachabilityManager
#pragma mark -
#pragma mark Default Manager
+ (MTReachabilityManager *)sharedManager {
    static MTReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [[self alloc] init];
    });
    return _sharedManager;
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
    // Stop Notifier
    if (_reachability) {
        [_reachability stopNotifier];
    }
}
#pragma mark -
#pragma mark Class Methods
+ (BOOL)isReachable {
    return [[[MTReachabilityManager sharedManager] reachability] isReachable];
}
+ (BOOL)isUnreachable {
    return ![[[MTReachabilityManager sharedManager] reachability] isReachable];
}
+ (BOOL)isReachableViaWWAN {
    return [[[MTReachabilityManager sharedManager] reachability] isReachableViaWWAN];
}
+ (BOOL)isReachableViaWiFi {
    return [[[MTReachabilityManager sharedManager] reachability] isReachableViaWiFi];
}
#pragma mark -
#pragma mark Private Initialization
- (id)init {
    self = [super init];
    if (self) {
        // Initialize Reachability
        self.reachability = [Reachability reachabilityWithHostname:@"www.google.com"];
        // Start Monitoring
        [self.reachability startNotifier];
    }
    return self;
}
@end

If you decide to adopt this alternative approach and use a reachability manager that manages an instance of the Reachability class, then don’t forget to instantiate the singleton object when your application launches. You can do so by calling sharedManager on the MTReachabilityManager class.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Instantiate Shared Manager
    [MTReachabilityManager sharedManager];
    // Initialize View Controller
    self.viewController = [[MTViewController alloc] initWithNibName:@"MTViewController" bundle:nil];
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    [self.window setRootViewController:self.viewController];
    [self.window makeKeyAndVisible];
    return YES;
}

Conclusion

Notifying the user or updating the application’s user interface when the network interface changes not only results in a better user experience, Apple requires that you do this if your application relies on network connectivity. It does take some effort, but the Reachability class makes it a lot easier.

Viewing all 1836 articles
Browse latest View live