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

Making a Blackjack Game in Corona – Creating the Interface

$
0
0

In this tutorial I’ll be showing you how to create a Blackjack game for the iPad using the Corona SDK. Let’s get started!

Want to learn how this game was designed? This tutorial series is based on the work of Andrei Marius, who originally published an in-depth Illustrator tutorial demonstrating how to design the game on Vectortuts+.


1. New Project

Open the Corona Simulator and choose “New Project“.


On the screen that follows, choose the following settings.

Project Settings

Press the “Next” button, then choose open in editor. This will open “Main.lua” in your default text editor.


2. Project Configuration

Open “Config.lua” delete everything and change it to the following.

application = {
   content = {
     width = 768,
     height = 1024,
     scale = "letterBox",
     fps = 30,
   }
}

This sets the project’s default width, height, scale, and FPS. The “letterBox” scale setting means the app will scale up in both directions as uniformly as possible, and if necessary show the game “Letter Boxed”, like you see in some DVD movies.


3. Hiding the Status Bar

We don’t want the status bar showing in our app, so enter the following in “Main.lua

display.setStatusBar(display.HiddenStatusBar);

4. Local Variables

Add the following beneath the code you entered in the step above.

local cardTable = display.newImage("table.png",true);
local money10; -- The $10.00
local money25; --The $25.00 chip
local money50; --The $50.00 chip
local dealBtn; -- the deal buttons
local hitBtn; -- the hit button
local standBtn; -- the stand button
local instructionsText; -- Give Instructions and show winner
local suits = {"h","d","c","s"}; -- hearts = h,diamonds =d,clubs =c,spades=s
local deck; -- The deck of Cards
local coinContainer; -- a group that will hold the chips
local dealerGroup; -- we put the dealer cards in this group
local playerGroup; -- we put the player cards in this group
local dealTo = "player"; -- who is currently being dealt to
local playerHand ={}; -- a table to hold the players cards
local dealerHand ={}; -- a table to hold the dealers cards
local allCards = {} -- a table to hold all cards
local betAmount = 0; -- how much the player is betting Total
local money; -- how much money the player has
local blackJack = false; -- whether player or dealer has blackjack
local firstDealerCard = ""; -- a reference to the first card the dealer is dealt
local playerYields = false; -- whether or not the player has stood on his hand
local winner=""; -- who the winner of the round is
local bet=0; -- how much the player is adding to betAmount variable
local bankText; -- shows the players money
local betText; -- shows how much the player is betting
local dealerCardsY = 120; -- Y position of dealer cards
local playerCardsY = 810; -- Y position of player cards

These are all of the variables we’ll be using in this game. Read the comments to understand what they’re for.


5. Setup()

The setup function will be called when the app first loads. Enter the following beneath the code you entered in the step above.

function Setup()
end

Now call the setup function right below where you declared it.

Setup()

6. SetupCoins()

Add the following above where you are calling Setup() in the step above.

function setupCoins()
    money10 = display.newImage("money10.png",50,960);
    money10.betAmount = 10;
    money25 = display.newImage("money25.png",110,960);
    money25.betAmount = 25;
    money50 = display.newImage("money50.png",170,960);
    money50.betAmount = 50;
end

This sets up our money images and adds a betAmount key.

Now call this function inside Setup().

function Setup()
    setupCoins();
end

7. SetupButtons()

Add the following beneath the setupCoins() function you declared in the step above.

function setupButtons()
    dealBtn = display.newImage("deal_btn.png",250,960);
    dealBtn.isVisible = false;
    hitBtn = display.newImage("hit_btn.png",400,960);
    hitBtn.isVisible = false;
    standBtn = display.newImage("stand_btn.png",550,960);
    standBtn.isVisible = false;
end

This sets up our buttons and makes them all invisible.

Call this function inside Setup().

function Setup()
    setupCoins();
    setupButtons();
end

8. SetupTextFields()

Add the following beneath the setupButtons() function you entered in the step above.

function setupTextFields()
    instructionsText = display.newText( "Place Your Bet", 300, 300, native.systemFont, 30 );
    instructionsText:setTextColor( 0,0,0)
    bankText = display.newText("Your Bank:$ ",10,905,native.systemFont, 30 );
    bankText:setTextColor(0,0,0)
    betText = display.newText("",650,906,native.systemFont,30); betText:setTextColor(0,0,0);
end

This sets up the TextFields and sets the text color to black.

Add this to the Setup() function.

function Setup()
    setupCoins();
    setupButtons();
    setupTextFields()
end

9. SetupGroups()

Add the following beneath the setupTextFields() function.

function setupGroups()
    coinContainer = display.newGroup()
    coinContainer.x = 250;
    coinContainer.y = 600;
    dealerGroup = display.newGroup();
    playerGroup = display.newGroup();
end

This sets up the groups that will be used to hold the cards and the money the player bets

Add this to the Setup() function just like you’ve been doing in the previous steps.


10. Check Progress

If you test the app, you should see the interface running properly.

Game Interface

11. AddListeners()

Add the following beneath the setupGroups() function.

function addListeners()
    money10:addEventListener('touch', betHandler);
    money25:addEventListener('touch',betHandler);
    money50:addEventListener('touch',betHandler);
    dealBtn:addEventListener('touch',deal);
    hitBtn:addEventListener('touch',hit);
    standBtn:addEventListener('touch',stand);
end

This adds touch listeners to our interface elements so that the user can interact with them. We need to create the functions that will be called when the user clicks on them.


12. BetHandler()

Enter the following beneath the addListeners() function.

function betHandler( event )
end

This function will handle the betting. It’ll make sure that the user doesn’t try to bet more than he has in his bank.


13. Deal()

Enter the following beneath the code you entered in the step above.

function deal()
end

This function is where the heart of the game lies. All of the logic of the game will be handled in this function.


14. Hit()

Add the following beneath the deal() function.

function hit(event)
end

This function will be called when the user presses the “hit” button.


15. Stand()

Enter the following beneath the code you entered in the step above.

function stand()
end

When the player decides to stand, this function will be called.


16. CreateDeck()

Add the following beneath the createDeck() function.

function createDeck()
    deck = {};
      for i=1, 4 do
     	 for j=1, 13 do
    	    local tempCard = suits[i]..j;
    	    table.insert(deck,tempCard);
     	end
    end
end

This resets the deck table and creates a fresh deck. It runs through each value in the suits table and appends the number 1 through 13 to them. We set the variable tempCard equal to the result, then insert it into the deck table.

Now call this in the Setup() function.

function Setup()
    setupCoins();
    setupButtons();
    setupTextFields();
    setupGroups();
    createDeck();
end

We’ll be getting some random cards from the deck, so ensure that it’s truly random we’ll need to seed the random generator. If we don’t do this, every time the game starts it’ll generate the same randomness. Add the following createDeck().

function Setup()
    setupCoins();
    setupButtons();
    setupTextFields();
    setupGroups();
    math.randomseed(os.time());
    createDeck();
end

Conclusion

This brings part one of this tutorial to a close. In the next part of the series we will begin incorporating the gameplay. Thanks for reading. Stay tuned for part two!


Android SDK: Making Remote API Calls

$
0
0

This tutorial will explain communicating with an external data source via an Android application, to be used when logging into a web based service or backing up data to an external source, among other things.

The external data can be stored in any of the many databases available today, which in turn can be hosted by any of the operating systems available. With this in mind, the theory becomes as important as the code.

We want to be able to send information from one platform to another completely separate platform and vice versa. It sounds simple, but there’s a language barrier to overcome and there’s not always an existing solution. We need a data type that both platforms can work with and some code in the middle to handle the requests. This is the API. The below diagram illustrates this better than words.


For this tutorial, I will be using a standard LAMP stack. The external database will be MySQL, and I will use PHP to make the API. The PHP file will write and read to the MySQL database based on requests received from the application. It will also send the results of the requests back to the app. The requests and responses will be in JSON as both parties understand this format. The goal is to enter a person’s name from the Android application and retrieve the person’s age from the external database. I know it’s simple, but it’s a broad area and it’s a good way to illustrate the process.

Note: I’m using a LAMP stack because it’s free and accessible. I will give further instructions in the download code about setting one up for those that require it, but I won’t be going into great detail about this side. I will show the code and explain its function, but it would be a very long read to go into detail about PHP, SQL, JSON and so forth.
Also I am not touching upon security in this tutorial, I will most likely cover it separately in a follow up.

Creating the MySQL Database and Table

Connect to your MySQL instance however you prefer and perform the following.

create database android_remoteDB
use android_remoteDB
create table users(
   _id int(10) primary key auto_increment,
   user_name varchar(25) not null unique,
   user_age int(2) not null
);

Insert the Test Data

insert into users (user_name,user_age) values ("green ranger",27);
insert into users (user_name,user_age) values ("red ranger",24);
insert into users (user_name,user_age) values ("blue ranger",23);
insert into users (user_name,user_age) values ("black ranger",29);
insert into users (user_name,user_age) values ("yellow ranger",22);
insert into users(user_name,user_age)  values ("pink ranger",21);

Create the API

For the purpose of this tutorial, I will be using a single PHP file called android_api.php. This needs to be placed somewhere where it can be accessed via HTTP request. It might be in the www directory on a local LAMP or WAMP setup, or on a hosted server that you can access.

The app will send a tag to this page along with any additional parameters. The API will perform an action based on the content of the tag. In this basic example there is only one tag, one parameter, and one action, so it wasn’t really necessary. Still, doing it like this will help you understand how to perform multiple tasks from the API.

Note: In practice you may well split things across several PHP files, so you might have one file for config parameters, another to handle database requests, and so on. In the interest of keeping things both simple and quick, I will stick with a single file.
<?php
// check that the tag is present
if (isset($_POST['tag']) && $_POST['tag'] != '') {
    $tag = $_POST['tag'];
    // Create a connection the the database and table
    $error = 'Could not connect to the database';
    //Depending on your setup you might need to change the below for your own credentials
	mysql_connect('localhost','root','') or die ($error);
	mysql_select_db('remote_db') or die ($error);
    // check if tag = 'getAge'
    if ($tag == 'getAge') {
        //Get name
        $name = mysql_real_escape_string($_POST['name']);
		//Select records from the database
		$find = mysql_query("SELECT * FROM users WHERE user_name='$name'");
		//check that records exist
        if (mysql_num_rows($find)>0) {
          while ($find_row = mysql_fetch_assoc($find))
		{
			//Get user age
			$age = $find_row['user_age'];
		}
			//Return the response as a success, including age.
		    $response["success"] = 1;
            $response["user"]["name"] = $name;
            $response["user"]["age"] = $age;
            echo json_encode($response);
            }
         else {
			//Return error
			$response["success"] = 0;
            $response["error"] = 1;
            $response["error_msg"] = "User could not be found";
             echo json_encode($response);
		 }
    } else {
        echo "No action for the tag";
    }
} else {
    echo "Unknown error";
}
?>

Creating the Application

In the IDE, create a new Android project by going to File → New → Android Application Project. Next, name the project. I went with remoteDB. From here on out, you can get away with clicking next on the screens you’re presented with.



Creating the JSON Class

To create a new class, go to your new project and open the src folder. Right click on the package, expand new and select class.  Make sure to name it JSONParser.


The class itself is a fairly generic and widely used one. It makes a post request to the provided URL and converts the response to JSON format.

Next, open the class and insert the following.

package com.example.remotedb;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;
import android.util.Log;
public class JSONParser {
	static InputStream is = null;
	static JSONObject json = null;
	static String outPut = "";
	// constructor
	public JSONParser() {
	}
	public JSONObject getJSONFromUrl(String url, List<NameValuePair> params) {
		// Making the HTTP request
		try {
			DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(new UrlEncodedFormEntity(params));
			HttpResponse httpResponse = httpClient.execute(httpPost);
			HttpEntity httpEntity = httpResponse.getEntity();
			is = httpEntity.getContent();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			BufferedReader in = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = in.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			outPut = sb.toString();
			Log.e("JSON", outPut);
		} catch (Exception e) {
			Log.e("Buffer Error", "Error converting result " + e.toString());
		}
		try {
			json = new JSONObject(outPut);
		} catch (JSONException e) {
			Log.e("JSON Parser", "Error parsing data " + e.toString());
		}
		// return JSON String
		return json;
	}
}

Creating the Layout File

Open up the activity_main.xml file in Res → Layout…

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"><!-- User Name Label --><TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="User Name:"
            android:padding="10dip"
            android:textColor="#33B5E5" /><!--  User Name Text Field --><EditText
            android:id="@+id/Enter"
            android:hint="Please Enter a Name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" /><!--  Text View to display the returned age or a error message --><TextView   android:id="@+id/Results"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:text="User Age:"
                    android:textColor="#99CC00"
                    android:padding="10dip"
                    android:textStyle="bold"/><!-- Button to fire the getAge Method--><Button
            android:id="@+id/GetAge"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dip"
            android:text="Get Age" /></LinearLayout>
Note: It’s normally the best practice to have your text in the strings.xml file.

Main Activity

Now for the main activity, which is in Src → Packagename → MainActicity.java…

Again, this is fairly simple. We have a method called getAge, which is called when the button is clicked. It passes paramaters such as URL and name over to the JSONParser and creates a JSON object from the response. This is then read and either the age or an error message is displayed in the results text view depending on whether the query was a success or not.

package com.example.remotedb;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
	private JSONParser jsonParser;
	//10.0.2.2 is the address used by the Android emulators to refer to the host address
	// change this to the IP of another host if required
	private static String ageURL = "http://10.0.2.2/android_api.php";
	private static String getAge = "getAge";
	private static String jsonResult = "success";
	String uname;
	String age_res;
	TextView Results;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Invoke the Json Parser
        jsonParser = new JSONParser();
     final EditText Enter = (EditText) findViewById(R.id.Enter);
     Results = (TextView) findViewById(R.id.Results);
     Button GetAge = (Button) findViewById(R.id.GetAge);
    //Get Age Button
    GetAge.setOnClickListener(new View.OnClickListener() {
        public void onClick(View view) {
        	//Get the contents of the edit text
        	uname = Enter.getText().toString();
            //Pass the name to the JSON method and create a JSON object from the return
        	JSONObject json = getAge(uname);
			// check the success of the JSON call
			try {
				if (json.getString(jsonResult) != null) {
					Results.setText("");
					String res = json.getString(jsonResult);
					if(Integer.parseInt(res) == 1){
						//If it's a success create a new JSON object for the user element
						JSONObject json_user = json.getJSONObject("user");
						//Set the results text to the age from the above JSON object
						Results.setText("User Age: " + json_user.getString("age"));
					}else{
						//If the user could not be found
						Results.setText("User could not be found");
					}
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}
        }
    });
    }
    //The below passes the tag and the user name over to the JSON parser class
    public JSONObject getAge(String name){
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("tag", getAge));
		params.add(new BasicNameValuePair("name", name));
		JSONObject json = jsonParser.getJSONFromUrl(ageURL, params);
		return json;
	}
}

Add the Internet Permission

One requirement of the app is the android.permission.INTERNET permission. This needs to be added to the AndroidManifest.xml file.


The line is:

uses-permission android:name="android.permission.INTERNET"

When added, the manifest should look something like the code below.

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.remotedb"
    android:versionCode="1"
    android:versionName="1.0" ><uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" /><uses-permission android:name="android.permission.INTERNET" /><application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" ><activity
            android:name="com.example.remotedb.MainActivity"
            android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>

You should be good to go at this stage. Try running the application.


Conclusion

Yes, the end result is extremely underwhelming. We’ve made a number appear on the screen. We have actually done much more than that, though. We’ve taken information from one platform and sent it to a completely foreign platform. The foreign platform was able to perform a task based on this information and to retrieve information from a database which the original platform had no knowledge of or possible access to. The second platform was able to send information back to the original which was then able to display the information to the user.

When it’s put like that, it sounds pretty cool and infinitely useful! You can see how such techniques allow multiple platforms to share data, and how different data sources can be combined.

Simple text objects such as JSON are very similar to morse code. They transcend other languages and can be universally understood. This makes them extremely useful and versatile. It’s the simple things that often solve the complex problems.

iOS Quick Tip: Managing Configurations With Ease

$
0
0

Have you ever felt the need to be able to quickly and easily switch between configurations without messing with compiler flags or manually modifying variables in your project? In this quick tip, I’d like to show you a clever solution to this problem by leveraging Xcode schemes and custom project configurations.


The Problem

For some iOS projects, you need the ability to quickly switch between different configurations or environments. A common scenario is when an iOS application communicates with an API or a web service. During development, you need to work in a development or staging environment. Before releasing an update, however, you most likely want to test your application in a staging or production environment. Switching between configurations or environments can be cumbersome especially if you need make this switch frequently.

The Solution

The easiest approach is to manually modify the configuration each time you switch environments. This means modifying a compiler flag or manually modifying values in your project. This approach is error prone, tedious, and far from ideal. A better solution is to create a custom configuration for each environment. This involves creating a configuration file that centralizes environment variables and custom Xcode schemes. Let me show you how this works by creating a sample project.


1. Project Setup

Create a new project in Xcode by selecting the Empty Application template from the list of templates (figure 1). Name your application Configurable, 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: Managing Configurations With Ease - Choosing a Project Template
Figure 1: Choosing a Project Template
iOS Quick Tip: Managing Configurations With Ease - Configuring the Project
Figure 2: Configuring the Project

2. Edit Info.plist

Step 1: Add Custom Configurations

The key idea of this approach is to know what the current configuration is. What is a configuration and where are they defined? You can see a list of all the project’s configurations by selecting your project in the Project Navigator and opening the Info tab at the top. Make sure that you have selected your project in the left sidebar and not a target from the list of targets (figure 3).

iOS Quick Tip: Managing Configurations With Ease - Project Configurations
Figure 3: Project Configurations

In this tutorial, we’re going to assume that we have a development, a staging, and a production environment that we need to work with. Start by creating a new configuration for each environment by clicking the plus button below the list of configurations. Select the Duplicate “Debug” Configuration option for each new configuration (figure 4) and give the configuration an appropriate name (figure 5).

iOS Quick Tip: Managing Configurations With Ease - Duplicate Debug Configuration
Figure 4: Duplicate Debug Configuration
iOS Quick Tip: Managing Configurations With Ease - Three Custom Configurations
Figure 5: Three Custom Configurations

Step 2: Edit Info.plist

When the application is running, we need to be able to find out what the current configuration is. We can do this by adding a new entry to the target’s Info.plist file. Select the target’s Info.plist file and create a new key-value pair. Set the key to Configuration and the value to ${CONFIGURATION} (figure 6). The CONFIGURATION identifier identifies the build configuration (e.g., Development or Staging) that the target uses to generate the product.

iOS Quick Tip: Managing Configurations With Ease - Adding a New Entry to Info.plist
Figure 6: Adding a New Entry to Info.plist

Step 3: Fetch Current Configuration

With these changes made, we can now fetch the current configuration in our application. To try this out, open MTAppDelegate.m and update application:didFinishLaunchingWithOptions: as shown below. To access the information in Info.plist, we ask the main application bundle for its infoDictionary. From the info dictionary, we grab the value of the Configuration key that we added and log it to the Xcode console. Build and run your application to see what is logged to the Xcode console.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSString *configuration = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"Configuration"];
    NSLog(@"Current Configuration > %@", configuration);
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Despite the fact that we created three custom configurations, the current configuration is still set to Debug. Let’s remedy that in the next section.


2. Custom Xcode Schemes

When building an application, an Xcode scheme is used. An Xcode scheme defines a number of variables to use when building the product. One of those variables is the configuration that should be used.

The current Xcode scheme is displayed in the top left of Xcode’s toolbar. To easily switch between the configurations (Development, Staging, etc.) that we created earlier, I recommend creating an Xcode scheme for each configuration. Click the current scheme, select New Scheme from the menu that appears, and name the new scheme Development. With the new scheme selected, click the scheme and select Edit Scheme from the menu. Select Run Configurable from the left pane, open the Info tab at the top, and set the Build Configuration to Development (figure 7). Create an Xcode scheme for the Staging and Production configurations by repeating the above steps.

iOS Quick Tip: Managing Configurations With Ease - Creating a Custom Xcode Scheme
Figure 7: Creating a Custom Xcode Scheme

3. Create a Configuration File

To make managing configuration settings easy, we will create a custom property list that groups the various configuration settings. Create a new property list and name it Configurations.plist (figure 8). The property list is a dictionary with an entry for each configuration. Each entry in the property list holds another dictionary with information specific for that configuration (figure 9).

iOS Quick Tip: Managing Configurations With Ease - Creating a Property List for the Configuration Settings
Figure 8: Create a New Property List
iOS Quick Tip: Managing Configurations With Ease - Configuration Property List
Figure 9: Configuration Property List

As you can see, you can add whatever variables to Configurations.plist that you like. You only need to make sure that each entry in the property list contains the same variables (keys).


4. Configuration Class

You now have all the necessary elements to quickly switch between configurations. However, our work isn’t done yet. The current implementation isn’t very user (or developer) friendly. When adopting this approach, I always create a configuration class that gives me easy access to the variables defined in Configurations.plist. The configuration class fetches the current configuration, loads Configurations.plist, and provides easy access to the variables. Take a look at the MTConfiguration class below to see what I mean.

#import <Foundation/Foundation.h>
@interface MTConfiguration : NSObject
#pragma mark -
+ (NSString *)configuration;
#pragma mark -
+ (NSString *)APIEndpoint;
+ (BOOL)isLoggingEnabled;
@end
#import "MTConfiguration.h"
#define MTConfigurationAPIEndpoint @"MTAPIEndpoint"
#define MTConfigurationLoggingEnabled @"MTLoggingEnabled"
@interface MTConfiguration ()
@property (copy, nonatomic) NSString *configuration;
@property (nonatomic, strong) NSDictionary *variables;
@end
@implementation MTConfiguration
#pragma mark -
#pragma mark Shared Configuration
+ (MTConfiguration *)sharedConfiguration {
    static MTConfiguration *_sharedConfiguration = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedConfiguration = [[self alloc] init];
    });
    return _sharedConfiguration;
}
#pragma mark -
#pragma mark Private Initialization
- (id)init {
    self = [super init];
    if (self) {
        // Fetch Current Configuration
        NSBundle *mainBundle = [NSBundle mainBundle];
        self.configuration = [[mainBundle infoDictionary] objectForKey:@"Configuration"];
        // Load Configurations
        NSString *path = [mainBundle pathForResource:@"Configurations" ofType:@"plist"];
        NSDictionary *configurations = [NSDictionary dictionaryWithContentsOfFile:path];
        // Load Variables for Current Configuration
        self.variables = [configurations objectForKey:self.configuration];
    }
    return self;
}
#pragma mark -
+ (NSString *)configuration {
    return [[MTConfiguration sharedConfiguration] configuration];
}
#pragma mark -
+ (NSString *)APIEndpoint {
    MTConfiguration *sharedConfiguration = [MTConfiguration sharedConfiguration];
    if (sharedConfiguration.variables) {
        return [sharedConfiguration.variables objectForKey:MTConfigurationAPIEndpoint];
    }
    return nil;
}
+ (BOOL)isLoggingEnabled {
    MTConfiguration *sharedConfiguration = [MTConfiguration sharedConfiguration];
    if (sharedConfiguration.variables) {
        return [[sharedConfiguration.variables objectForKey:MTConfigurationLoggingEnabled] boolValue];
    }
    return NO;
}
@end

The MTConfiguration class provides easy access to the variables stored in Configurations.plist. Switching between configurations is now as easy as selecting the correct Xcode scheme. Even though it may look like quite a bit of work up front, I can assure you that it will save you a tremendous amount of time – and frustration – down the road.

To test our solution, import the header file of the MTConfiguration class in MTAppDelegate.m and update the application:didFinishLaunchingWithOptions: method as shown below.

#import "MTAppDelegate.h"
#import "MTConfiguration.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"Configuration > %@", [MTConfiguration configuration]);
    NSLog(@"API Endpoint > %@", [MTConfiguration APIEndpoint]);
    NSLog(@"Is Logging Enabled > %i", [MTConfiguration isLoggingEnabled]);
    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Configure Window
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Conclusion

Custom configurations and Xcode schemes can really help organize a project and streamline your development workflow. I hope I’ve been able to convince you of the value of this solution especially for complex projects with multiple environments.

Implementing Container Containment – Sliding Menu Controller

$
0
0

In this tutorial, we’ll implement a minimalistic version of the Facebook/Path-style UI. The objective will be to understand how to utilize view controller containment in order to implement custom flow in your app.


Theoretical Overview

View controllers are an essential part of any iOS application, no matter how small, big, simple, or complex. They provide the “glue-logic” between the data model of your app and the user interface.

Broadly speaking, there are two kinds of view controllers:

  • Content view controllers: These are responsible for displaying and managing visible content.
  • Container controllers: These manage content view controllers, and they’re responsible for the overall structure and flow of the app.

A container controller may have some visible component of its own, but basically functions as a host for content view controllers. Container controllers serve to “traffic” the comings and goings of content view controllers.

UINavigationController, UITabBarController and UIPageViewController are examples of container view controllers that ship with the iOS SDK. Consider how the three are different in terms of the application flows that they give rise to. The navigation controller is great for a drill-down type app, where the selection the user makes in one screen affects what choices he’s presented with in the next screen. The tab bar controller is great for apps with independent pieces of functionality, allowing convenient toggling by simply pressing a tab button. Finally, the page view controller presents a book metaphor, allowing the user to flip back and forth between pages of content.

The key thing to keep in mind here is that an actual screenful of content being presented through any of these container view controllers itself needs to be managed, both in terms of the data it is derived from (the model) and the on-screen presentation (the view), which would again be the job of a view controller. Now we’re talking about content view controllers. In some apps, particularly on the iPad because its larger screen allows more content to be shown at once, different views on the screen might even need to be managed independently. This requires multiple view controllers onscreen at once. All of this implies that the view controllers in a well-designed app should be implemented in a hierarchical manner with both container and content view controllers playing their respective roles.

Prior to iOS 5, there were no means of declaring a hierarchical (i.e., parent-child) relationship between two view controllers and therefore no “proper” way of implementing a custom application flow. One either had to make do with the built-in types, or do it in a haphazard way, which basically consisted of sticking views managed by one view controller into the view hierarchy of the view managed by another view controller. This would create inconsistencies. For example, a view would end up being in the view hierarchy of two controllers without either of these controllers acknowledging the other, which sometimes leads to strange behaviour. Containment was introduced in iOS 5 and refined slightly in iOS 6, and it allows the notion of parent and child view controllers in a hierarchy to be formalized. Essentially, correct view controller containment demands that if view B is a subview (child) of view A and if they’re not under the management of the same view controller, then B’s view controller must be made A’s view controller’s child.

You might ask whether there is any concrete benefit offered by view controller containment besides the advantage of the hierarchical design we discussed. The answer is yes. Keep in mind that when a view controller comes on screen or goes away, we might need to set up or tear down resources, clean up, fetch or save information from/to the file system. We all know about appearance callbacks. By explicitly declaring the parent-child relationship, we ensure that the parent controller will forward callbacks to its children whenever one comes on or goes off the screen. Rotation callbacks need to be forwarded too. When the orientation changes, all the view controllers on the screen need to know so they can adjust their content appropriately.

What does all this imply, in terms of code? View controllers have an NSArray property called childViewControllers and our responsibilities include adding and removing child view controllers to and from this array in the parent by calling appropriate methods. These methods include addChildViewController (called on the parent) and removeFromParentViewController (called on the child) when we seek to make or break the parent-child relationship. There are also a couple of notification messages sent to the child view controller at the start and at the end of the addition/removal process. These are willMoveToParentViewController: and didMoveToParentViewController:, sent with the appropriate parent controller as argument. The argument is nil, if the child is being removed. As we’ll see, one of these messages will be sent for us automatically while the other will be our responsibility to send. This will depend on whether we’re adding or removing the child. We’ll study the exact sequence shortly when we implement things in code. The child controller can respond to these notifications by implementing the corresponding methods if it needs to do something in preparation of these events.

We also need to add/remove the views associated with the child view controller to the parent’s hierarchy, using methods such as addSubview: or removeFromSuperview), including performing any accompanying animations. There’s a convenience method (-)transitionFromViewController:toViewController:duration:options:animations:completion: that allows us to streamline the process of swapping child view controllers on-screen with animations. We’ll look at the exact details when we write the code – which is next!


1. Creating a New Project

Create a new iOS app in Xcode based on the “Empty Application” template. Make it an iOS App with ARC enabled. Call it VCContainmentTut.

Creating a new project
Creating a new project

2. Implementing the Container View Controller

Create a new class called RootController. Make it a UIViewController subclass. Ensure that any checkboxes are deselected. This will be our container view controller subclass.

newvc

Replace the code in RootViewController.h with the following.

#import <UIKit/UIKit.h>
@interface RootController : UIViewController<UITableViewDataSource, UITableViewDelegate> // (1)
- (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)titles; // (2)
@end

Our container controller will have a table view that functions as our menu, and tapping any cell will replace the currently visible view controller by the one selected through the user’s tap.

Referring to the points in the code,

  1. Our root controller doubles as the menu’s (i.e., the table view’s) delegate and datasource.
  2. We are offering an extremely simple API (as far as our container class user is concerned) consisting of one initializer that takes a list of view controllers that our root controller shall contain, and a list of strings representing the titles for each view controller in the menu. This is so we can concentrate on the basics. Once you understand these you can make the API as customizable as you like.

Let’s take a peek ahead to see what our finished product will look like so that you have a mental picture to associate the implementation with.

newvc

It will help to realize that our container view controller is quite similar to a tab view controller. Each item in the menu corresponds to an independent view controller. The difference between our “sliding menu” controller and the tab controller is visual for the most part. The phrase “sliding menu” is a bit of a misnomer because it’s actually the content view controller that slides to hide or reveal the menu underneath.

Moving on to the implementation, replace all the code in RootController.m with the following code.

#define kExposedWidth 200.0
#define kMenuCellID @"MenuCell"
#import "RootController.h"
@interface RootController()
@property (nonatomic, strong) UITableView *menu;
@property (nonatomic, strong) NSArray *viewControllers;
@property (nonatomic, strong) NSArray *menuTitles;
@property (nonatomic, assign) NSInteger indexOfVisibleController;
@property (nonatomic, assign) BOOL isMenuVisible;
@end
@implementation RootController
- (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)menuTitles
{
    if (self = [super init])
    {
        NSAssert(self.viewControllers.count == self.menuTitles.count, @"There must be one and only one menu title corresponding to every view controller!");    // (1)
        NSMutableArray *tempVCs = [NSMutableArray arrayWithCapacity:viewControllers.count];
        self.menuTitles = [menuTitles copy];
        for (UIViewController *vc in viewControllers) // (2)
        {
            if (![vc isMemberOfClass:[UINavigationController class]])
            {
                [tempVCs addObject:[[UINavigationController alloc] initWithRootViewController:vc]];
            }
            else
                [tempVCs addObject:vc];
            UIBarButtonItem *revealMenuBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Menu" style:UIBarButtonItemStylePlain target:self action:@selector(toggleMenuVisibility:)]; // (3)
            UIViewController *topVC = ((UINavigationController *)tempVCs.lastObject).topViewController;
            topVC.navigationItem.leftBarButtonItems = [@[revealMenuBarButtonItem] arrayByAddingObjectsFromArray:topVC.navigationItem.leftBarButtonItems];
        }
        self.viewControllers = [tempVCs copy];
        self.menu = [[UITableView alloc] init]; // (4)
        self.menu.delegate = self;
        self.menu.dataSource = self;
    }
    return self;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.menu registerClass:[UITableViewCell class] forCellReuseIdentifier:kMenuCellID];
    self.menu.frame = self.view.bounds;
    [self.view addSubview:self.menu];
    self.indexOfVisibleController = 0;
    UIViewController *visibleViewController = self.viewControllers[0];
    visibleViewController.view.frame = [self offScreenFrame];
    [self addChildViewController:visibleViewController]; // (5)
    [self.view addSubview:visibleViewController.view]; // (6)
    self.isMenuVisible = YES;
    [self adjustContentFrameAccordingToMenuVisibility]; // (7)
    [self.viewControllers[0] didMoveToParentViewController:self]; // (8)
}
- (void)toggleMenuVisibility:(id)sender // (9)
{
    self.isMenuVisible = !self.isMenuVisible;
    [self adjustContentFrameAccordingToMenuVisibility];
}
- (void)adjustContentFrameAccordingToMenuVisibility // (10)
{
    UIViewController *visibleViewController = self.viewControllers[self.indexOfVisibleController];
    CGSize size = visibleViewController.view.frame.size;
    if (self.isMenuVisible)
    {
        [UIView animateWithDuration:0.5 animations:^{
            visibleViewController.view.frame = CGRectMake(kExposedWidth, 0, size.width, size.height);
        }];
    }
    else
        [UIView animateWithDuration:0.5 animations:^{
            visibleViewController.view.frame = CGRectMake(0, 0, size.width, size.height);
        }];
}
- (void)replaceVisibleViewControllerWithViewControllerAtIndex:(NSInteger)index // (11)
{
    if (index == self.indexOfVisibleController) return;
    UIViewController *incomingViewController = self.viewControllers[index];
    incomingViewController.view.frame = [self offScreenFrame];
    UIViewController *outgoingViewController = self.viewControllers[self.indexOfVisibleController];
    CGRect visibleFrame = self.view.bounds;
    [outgoingViewController willMoveToParentViewController:nil]; // (12)
    [self addChildViewController:incomingViewController]; // (13)
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; // (14)
    [self transitionFromViewController:outgoingViewController // (15)
                      toViewController:incomingViewController
                              duration:0.5 options:0
                            animations:^{
                                outgoingViewController.view.frame = [self offScreenFrame];
                            }
                            completion:^(BOOL finished) {
                                [UIView animateWithDuration:0.5
                                                 animations:^{
                                                     [outgoingViewController.view removeFromSuperview];
                                                     [self.view addSubview:incomingViewController.view];
                                                     incomingViewController.view.frame = visibleFrame;
                                                     [[UIApplication sharedApplication] endIgnoringInteractionEvents]; // (16)
                                }];
                                [incomingViewController didMoveToParentViewController:self]; // (17)
                                [outgoingViewController removeFromParentViewController]; // (18)
                                self.isMenuVisible = NO;
                                self.indexOfVisibleController = index;
   }];
}
// (19):
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.menuTitles.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kMenuCellID];
    cell.textLabel.text = self.menuTitles[indexPath.row];
    return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self replaceVisibleViewControllerWithViewControllerAtIndex:indexPath.row];
}
- (CGRect)offScreenFrame
{
    return CGRectMake(self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height);
}
@end

Now for an explanation of the code. The parts that I’ve highlighted for emphasis are especially relevant to the containment implementation.

  1. First, the initializer does a simple check to ensure that each view controller has been given a menu title. We haven’t done any type checking to ensure that each of the two arrays passed to the initializer contain the right kind of objects, UIViewController and NSString types respectively. You might consider doing it. Note that we’re maintaining arrays for each of these, called viewControllers, and menuTitles.
  2. We want there to be a button that, when tapped, displays or hides the menu. My simple solution as to where the button should be was to put every view controller we received from the initializer inside a navigation controller. This gives us a free navigation bar that a button can be added to, unless the passed in view controller was already a navigation controller, in which case we don’t do anything extra.
  3. We create a bar button item that triggers the menu’s appearance or hiding by sliding the view controller that’s currently visible. We add it to the navigation bar by moving any existing buttons on the navigation bar to the right. We do this because the added view controller is already a navigation controller with pre-existing bar buttons.
  4. We instantiate our menu as a table view and assign the root controller itself as the delegate and data source.
  5. In viewDidLoad, after configuring and adding the menu table view to our root controller’s view, we usher into our app the first view controller in the viewControllers array. By sending the addChildViewController: message to our root controller, we carry out our first containment-related responsibility. You should know that this causes the message willMoveToParentViewController: to be called on the child controller.
  6. Note that we explicitly need to add our child controller’s view to the parent’s view hierarchy!
  7. We set the menu to be visible initially and call a method that adjusts the visible content view controller’s frame, taking the menu’s visibility into account. We’ll look into this method’s details shortly.
  8. Once the child view controller’s view is sitting comfortably in the parent’s view hierarchy, we send the didMoveToParentViewController message to the added child controller, with self, the RootController instance, as the argument. In our child controller, we can implement this method if we need to.
  9. A simple method connected to our menu bar button’s action that toggles the menu’s visibility by adjusting the overlaying view controller’s view appropriately.
  10. As the name indicates, adjustContentFrameAccordingToMenuVisibility lets us adjust the content view controller’s frame to tell us whether the menu is hidden or not. If yes, then it overlays the superview. Otherwise, it is shifted to the right by kExposedWidth. I’ve set that to 200 points.
  11. Again, as clearly indicated by the name, replaceVisibleViewControllerWithViewControllerAtIndex allows us to swap out view controllers and the corresponding views from the hierarchy. To pull off our animation, which consists of sliding the replaced view controller offscreen to the right and then bringing in the replacement controller from the same place, we define some rectangular frames.
  12. willMoveToParentViewController with nil. Once we complete this step, this view controller will cease to receive appearance and rotation callbacks from the parent. This makes sense because it's no longer an active part of the app.
  13. We add the incoming view controller as a child to the root controller, similar to what we did in the beginning.
  14. We begin ignoring user interaction events to let our view controller swap over smoothly.
  15. This convenience method allows us to animate the removal of the outgoing controller and the arrival of the incoming one while performing the required sequence of events involved in the child view controller addition and removal process. We animate the outgoing VC's view to slide offscreen to the right, and after the animation is completed we remove it from the view hierarchy. We then animate the incoming view controller to slide in from the same place offscreen and occupy the place previously taken up by the outgoing controller's view.
  16. We enable our app to accept incoming interaction events, since our view controller swap has been completed.
  17. We notify the incoming view controller that it's been moved to the container controller by sending it the didMoveToParentViewController message with self as the argument.
  18. We remove the outgoing controller from the container controller by sending it the removeFromParentViewController message. You should know that didMoveToParentViewController: with nil as an argument gets sent for you.
  19. We implement the menu table view's delegate and data source protocol methods, which are pretty straightforward. This includes triggering the view controller swapping step (11) when a new item's cell is tapped in the menu via the -tableView:didSelectRowAtIndexPath: method.

You may have found the sequence of calls related to controller containment a bit confusing. It helps to summarize.

    When adding a child view controller to a parent:
  • Call addChildViewController: on the parent with the child as the argument. This causes the message willMoveToParentViewController: to be sent to the child with the parent as the argument.
  • Add the child's view as a subview of the parent's view.
  • Explicitly call didMoveToParentViewController: on the child with the parent as the argument.
    When removing a child view controller from its parent:
  • Call willMoveToParentViewController: on the child with nil as the argument.
  • Remove the child's view from its superview.
  • Send removeFromParentViewController to the child. The causes the message didMoveToParentViewController with nil as the argument to be sent to the child on your behalf.

3. Testing

Let's test the different types of view controllers added to our root controller! Create a new subclass of UIViewController called ViewController, keeping any options unchecked.

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

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)willMoveToParentViewController:(UIViewController *)parent
{
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
- (void)didMoveToParentViewController:(UIViewController *)parent
{
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    NSLog(@"%@ (%p) - %@", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
@end

There is nothing special about our view controller itself, except that we've overridden the various callbacks so that we can log them whenever our ViewController instance becomes a child to our root controller and an appearance or rotation event occurs.

In all of the previous code, _cmd refers to the selector corresponding to the method that our execution is inside. NSStringFromSelector() converts it to a string. This is a quick and easy way to get the name of the current method without having to manually type it out.

Let's throw a navigation controller and a tab controller into the mix. This time we'll use Storyboards.

Create a new file, and under iOS > User Interface, choose storyboard. Set the device family to iPhone, and name it NavStoryBoard.

storyboard

From the objects library, drag and drop a Navigation Controller object into the canvas. Drag and drop a bar button item into the left side of the navigation bar in the table view controller designated as "Root View Controller". This contains the table view in the canvas. Give it any name. I've named it "Left". Its purpose is to verify the code we wrote to make the menu bar's hide/reveal button take its place as the leftmost button on the navigation bar, pushing any already present buttons to the right. Finally, drag a View Controller instance and place it to the right of the controller titled "Root View Controller" in the canvas.

Click where it says "Table View" in the center of the second controller, and in the attributes inspector change the content from "Dynamic Prototype" to "Static Cells".

Changing cell content type from  dynamic to static
Changing cell content type from dynamic to static

This will cause three static table view cells to appear in the interface builder. Delete all but one of these table view cells, and while holding down Control, click and drag from the remaining cell to the view controller on the far right and release. Select "push" under Selection Segue. All this does is cause a segue to the right view controller when you tap on the lone cell from the table view. If you want, you can drop a UILabel onto the table cell to give it some text. Your storyboard should look similar to the photo below.

NavStoryBoard
NavStoryBoard

Finally, let's add a tab bar controller. Just as you did previously, create a storyboard file and call it TabStoryBoard. Drag and drop a tab bar controller item from the object library into the canvas. It comes preconfigured with two tabs and, if you like, you can change the background color of the two tabbed view controllers by clicking on the view corresponding to either view controller and changing the "background" option in the Attributes Inspector. This way, you can verify that view controller selection through the tab is working correctly.

TabStoryBoard
Your story board should look like this.

4. Configuring the App Delegate

Now it's time to set everything up in the AppDelegate.

Replace the code in AppDelegate.m with the following code.

#import "AppDelegate.h"
#import "RootController.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    UIStoryboard *tabStoryBoard = [UIStoryboard storyboardWithName:@"TabStoryboard" bundle:nil];
    UIStoryboard *navStoryBoard = [UIStoryboard storyboardWithName:@"NavStoryboard" bundle:nil];
    UINavigationController *navController = [navStoryBoard instantiateViewControllerWithIdentifier:@"Nav Controller"];
    UITabBarController *tabController = [tabStoryBoard instantiateViewControllerWithIdentifier:@"Tab Controller"];
    ViewController *redVC, *greenVC;
    redVC = [[ViewController alloc] init];
    greenVC = [[ViewController alloc] init];
    redVC.view.backgroundColor = [UIColor redColor];
    greenVC.view.backgroundColor = [UIColor greenColor];
    RootController *menuController = [[RootController alloc]
                                      initWithViewControllers:@[tabController, redVC, greenVC, navController]
                                      andMenuTitles:@[@"Tab", @"Red", @"Green", @"Nav"]];
    self.window.rootViewController = menuController;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

All we did was create instances of ViewController and instantiate the navigation and tab controller from the two storyboards. We passed them in an array to our RootController's instance. That's the container controller we implemented at the start. We did this along with an array of strings to name the view controllers in the menu. Now we'll simply designate our initialized Root Controller instance as the window's rootViewController property.

Build and run the app. You've just implemented container containment! Tap on the various table cells in the menu to replace the visible slide with the new one sliding in from the right. Notice how, for the navigation controller instance (named "NavC" in the menu), the "Left" button has shifted one place to the right and the menu bar button has taken up the leftmost position. You can change the orientation to landscape and verify that everything looks proper.

Simulator Screen shots
Simulator screen shots

Conclusion

In this introductory tutorial, we looked at how view controller containment is implemented in iOS 6. We developed a simple version of a custom app interface that has gained much popularity and is often seen in highly-used apps such as Facebook and Path. Our implementation was as simple as possible, so we were able to dissect it easily and get the basics right. There are many sophisticated open-source implementations of this type of controller that you can download and study. A quick Google search turns up JASidePAnels and SWRevealViewController, among others.

Here are some ideas for you to work on.

  • Make the implementation more flexible and the API more customizable.
  • Make the interface prettier. You can customize the table view cell appearance, or let your view controller's view throw a shadow on the menu to lend the interface some depth.
  • Make the interface adapt better to the orientation. Remember that your child view controllers will be receiving rotation notifications, so that's where you start!
  • Implement gesture recognition so that you can drag a view controller left and right across the screen as an alternate to clicking on the menu button.
  • Design and develop a completely new and novel app flow and realize the UI in code. Chances are you'll need to make use of view controller containment!

One thing I'd like to mention here is that in Xcode 4.5 onwards, there is a new interface builder object called "Container View" which can display the contents of a view controller, and thus be used to implement containment directly in your storyboard! Happy coding!

Tuts+ Premium Mid-Year Sale – 25% off Yearly Subscriptions

$
0
0

2013 is flying past – it’s already July! But there’s still plenty of time left to skill up and really make the most of 2013.

Tuts+ Premium has a huge collection of courses, tutorials, eBooks and guides on hundreds of creative and technical topics. And right now you can get 25% off a yearly subscription– a cumulative saving of $93 on the monthly price!


Join Tuts+ Premium

It’s not too late to learn new skills in 2013! Be it developing your own web app, drawing like a professional artist, or hundreds of other topics and techniques, our extensive library of educational material can help you make the most of the rest of your year. Our dedicated team adds new content weekly so there’s always something fresh to sink your teeth into.

With Tuts+ Premium you learn from expert instructors in every field, such as:

  • Designer Justin Maller (Nike, Verizon, DC Shoe Co.)
  • Illustrator Russell Tate (McDonald’s, Coca-Cola)
  • Developer Burak Guzel (Software Engineer at Facebook)
  • Simply sign up before 12pm on July 22nd, 2013 (AEST) to save 25% on a yearly membership. For just $135, you’ll have access to our huge collection of courses, tutorials, eBooks and guides for an entire year – a cumulative saving of $93 off the monthly price.

    Join Tuts+ Premium now and save 25% on a yearly subscription!

Making a Blackjack Game in Corona – Implementing Gameplay

$
0
0

In the previous part of this series, we put our interface together for a Blackjack game and created the deck of cards. In this part of the tutorial, we’ll add the necessary Blackjack game logic. Let’s get started!


17. CreateDataFile()

We need a way to store the player’s money between game sessions, and to do this we’ll use a simple text file. Add the following code beneath the createDeck() function.

function createDataFile()
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
local fh, errStr = io.open( path, "r" ) -- r means read mode
if fh then
	print("DataFile Exists Already") -- already exists so we just return from this function
   return
else
   print( "Reason open failed: " .. errStr )  -- display failure message in terminal
   -- create file because it doesn't exist yet
   fh = io.open( path, "w" ) -- w means write mode
   if fh then
       local money = 500
   	   fh:write(money)
   else
        print( "Create file failed!"..errStr )
   end
   io.close(fh)
end
end

Here we are creating a file named “data.txt” and writing 500 to it. The player will start the game with $500.00. It’s important to make sure you always call io.close() when you’re finished with your operations.

You can learn more about creating this data file in the documentation on the Corona site.


18. ReadFile()

Now that we have a way to create our data file, we need a method to read its contents. Enter the following beneath the createDataFile() function you entered in the step above.

function readMoney()
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
local fh, errStr = io.open( path, "r" )
    if fh then
    local theMoney = fh:read( "*n" )
    return theMoney
	else
   	 print( "Reason open failed: " .. errStr )  -- display failure message in terminal
	end
   io.close(fh)
end

We open the file using the same method, then we use read("*n") to get the value out of the text file. The “*n” means read as a number.


19. SaveMoney()

To complete our file handling operations, we need a way to save. Enter the following beneath the code you entered in the step above.

function saveMoney(money)
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
local fh, errStr = io.open( path, "w" )
    if fh then
    	fh:write(money)
	else
   	 print( "Reason open failed: " .. errStr )  -- display failure message in terminal
	end
	io.close(fh)
end

Here we open the file for writing as denoted by the “w” in the open() method. We then write money to the file that was passed in as a parameter.


20. Setting the Initial Balance

We now need to create the initial balance when the game first starts. Add the following to the top of Setup().

function Setup()
    createDataFile();
    setupCoins();
    setupButtons();
    setupTextFields();
    setupGroups();
    createDeck();
end

If you open the Corona terminal and run the app twice you should see “DataFile Exists Already” printed out to the terminal. I left the print() messages in the file handling code so you could see the steps and any errors. If all is working well, feel free to remove them.


21. Showing Initial Balance

So now that we have the balance set, let’s show it in our app. Change the following code within the setupTextFields() function.

function setupTextFields()
    instructionsText = display.newText( "Place Your Bet", 300, 300, native.systemFont, 30 );
    instructionsText:setTextColor( 0,0,0)
    bankText = display.newText("Your Bank:$ "..readMoney(),10,905,native.systemFont, 30 );
    bankText:setTextColor(0,0,0)
    betText = display.newText("",650,906,native.systemFont,30);
    betText:setTextColor(0,0,0);
end

Note that we are appending the balance onto “Your Bank:$” by calling the readMoney() function.


22. Betting

Now that we have the money in place we can add the code to our betHandler() function. We created this function in the previous part of the tutorial, so make sure you add to it instead of redefining it!

local betHandler = function( event )
    local theMoney = readMoney();
    if event.phase == "began" then
        local t = event.target
	if(bet + t.betAmount > theMoney)then
           print("Trying to bet more than have");
           print("Money is"..theMoney);
           return;
        else
        bet = bet + t.betAmount
        local tempImage = display.newImage("money"..t.betAmount..".png");
	    local randomX = (math.random() * 150);
	    local randomY = (math.random() * 100);
	    tempImage.x = randomX;
	    tempImage.y = randomY;
	    coinContainer:insert(tempImage);
	    dealBtn.isVisible = true;
	    instructionsText.text = "";
	    betText.text = "Your Bet: $"..bet;
	    end
     end
end

Here we first read how much money the player has. If they are trying to bet more than they have, the function simply returns. I’ve left the print() statements in the code to help with debugging. We set a dynamic key, betAmount, when we set up the money. If they are not trying to bet too much, we add the amount to the bet variable.

Next we create a tempImage, generate two random numbers, set the images x and y to the random numbers, and finally add the image to the coin container. You’ll notice that we are using "money"..t.betAmount..".png" for the image URL. Our images for the money are named “money10.png“, “money25.png” and “money50.png“, so all we are doing here is concatenating them together to form the image string.

Lastly, we set the dealBtn to be visible, clear out the instructionsText and set the betText equal to the bet.

Now we need to add the addListeners() function to our Setup() code. Add the following code at the bottom.

function Setup()
    createDataFile()
    setupCoins();
    setupButtons();
    setupTextFields();
    setupGroups();
    createDeck();
    addListeners();
end

If you test the app now, you should be able to bet some money.


23. Getting Hand Values

We need a way to get the hand value of the player’s hand and the dealer’s hand. Enter the following beneath the createDeck() function.

function getHandValue(theHand)
local handValue = 0;
local hasAceInHand=false;
    for i=1,#theHand do
	 local cardsValue =  tonumber(string.sub(theHand[i],2,3));
	     if (cardsValue > 10) then
		cardsValue = 10;
	     end
		handValue = handValue + cardsValue;
		if (cardsValue == 1)then
		    hasAceInHand = true;
		end
	end
    if (hasAceInHand and handValue <= 11)then
	handValue = handValue + 10;
    end
    return handValue;
end

We set up a handValue variable and a hasAceInHand variable. Next, we loop through theHand which will either be the playerHand or the dealerHand. We create a variable cardsValue, casting it to a number by getting a substring of the current card. If cardsValue is greater than 10 we set it to 10. Jacks, Queens, and Kings are represented by 11, 12, and 13. We then add the value to handValue. If the card’s value is equal to 1 then we know they have an ace in their hand. If they have an ace and their handValue is less than or equal to 11 we add 10 to it.


24. Deal()

We now have everything in place for a deal, so now we’ll animate the cards to make the game more interesting. This function is quite large because it’s where all of the game’s logic takes place. We’ll divide it into several steps. Add the following code within the deal() function you created in the first part of this series.

   money10.isVisible = false;
   money25.isVisible = false;
   money50.isVisible = false;
   dealBtn.isVisible = false;
   local randIndex = math.random(#deck)
   local tempCard = display.newImage("card_front.png",630,55);
   table.insert(allCards,tempCard);
   local whichPosition;
   local whichArray={};
   local whichGroup;
	if(dealTo == "player") then
	    whichArray = playerHand;
	    whichPosition = playerCardsY;
	    whichGroup = playerGroup;
	else
	    whichArray = dealerHand;
	    whichPosition = dealerCardsY;
	    whichGroup = dealerGroup;
	end
	table.insert(whichArray,deck[randIndex]);
	local xPos = 20+#whichArray*35
    transition.to(tempCard, {time=1000,x=xPos,y=whichPosition,onComplete=function()
		 if(dealTo == "dealer" and #dealerHand == 1) then
			firstDealerCard = deck[randIndex];
			dealerGroup:insert(tempCard);
        else
        tempCard:removeSelf();
        tempCard = display.newImage(deck[randIndex]..".png",xPos-45,whichPosition-60);
        whichGroup:insert(tempCard);
        end
        table.remove(deck,randIndex);
		if(#dealerHand < 2)then
			if(dealTo == "player")then
				dealTo = "dealer"
			else
				dealTo = "player"
			end
	       deal();
	       end
    end
	});

Here we set our money to be invisible, and our deal button to visible. Next we generate a randIndex from the deck table. We then generate a new image tempCard, and insert the tempCard into the allImages table. We set up three local variables, whichPosition, whichArray, and whichGroup. We check who is currently being dealt to initialize these variables as appropriate.

We then insert deck[randIndex] into whichArray, which is either the playerHand or the dealerHand table. Remember that our deck is composed of strings, so deck[randIndex] would be something like “h5“,”d10“.

We set a local variable xPos equal to 20+#whichArray*35, which sets it to 20 plus the table’s length + 35. The first time through the table length would be 1, so 20+1*35. The next time through the table length would be 2 so it would be 20+2*35. All of this is to allow us to space our cards evenly along the X axis.

We use coronas transition.to method to move the tempCard. When the card completes its transition, we check whether we are dealing to the dealer and if his hand length is 1. If so, we set firstDealerCard equal to deck[randIndex], and insert the card into the dealerGroup. We need a reference to the dealer’s first card so that we can show it later.

If this was not the dealer’s first card, then we remove the tempCard, generate a new image by using deck[randIndex], and insert it into the appropriate group (player or dealer). The reason we subtract 45 and 60 respectively is because Corona sets the reference point of images to the center by default, and our images are 90 x 120. Consequently, we take half of that.

Lastly we remove the card at position randIndex from the deck table and check whether dealerHand length is less than 2. If it is, we change dealTo to its opposite (player or dealer) and then deal again.

Finally, you can test the app, bet some money and get the first two cards dealt.


25. Deal() Continued…

Add the following code beneath where we called deal() in the step above.

    if(#dealerHand < 2)then
	if(dealTo == "player")then
	    dealTo = "dealer"
	else
	    dealTo = "player"
	end
	deal();
	elseif(#dealerHand == 2 and #playerHand == 2) then
	    if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then
		 doGameOver(true);
	   else
            standBtn.isVisible = true;
	    hitBtn.isVisible = true;
            end
       end

Here we are checking if both dealerHand and playerHand‘s length are equal to 2. If they are, we’ll check to see if either of their hands are equal to 21. If either of their hands are equal to 21, the game is over. We call doGameOver(true) which will award winnings and start a new game. The true parameter is true for blackjack. Otherwise, it will be false.


26. DoGameOver()

The doGameOver() function awards the winnings and starts a new game. We’ll also be coding this function in several steps. For now we can use it to test the blackjack part. Enter the following code beneath the deal function.

function doGameOver(hasBlackJack)
	local playerHandValue = getHandValue(playerHand);
	local dealerHandValue = getHandValue(dealerHand);
	local tempCardX = allCards[2].x;
	local tempCardY= allCards[2].y;
	allCards[2]:removeSelf();
	local tempCard = display.newImage(firstDealerCard..".png",tempCardX-45,tempCardY-60);
	dealerGroup:insert(tempCard);
	tempCard:toBack();
	if(hasBlackJack)then
	    if(playerHandValue > dealerHandValue)then
		money = money + bet*1.5;
		instructionsText.text = "You Got BlackJack!";
		winner = "player"
	     else
		money = money - bet;
		instructionsText.text = "Dealer got BlackJack!";
		winner = "dealer"
        end
    end
end

Here we get the player’s and the dealer’s hand value. We get a reference to allCards[2], x, and y, which is the dealer’s first card, and then we remove it from the display. We then generate a tempCard by using the firstDealerCard variable we setup earlier. Once again we subtract 45 and 60. We then insert this new card into the dealerGroup. When we do this, it’s on top of the second card, so we send it to the back by calling toBack().

We check to see if hasBlackJack is true, and if it is we then check whether the player’s hand is greater than the dealer’s hand. If it is, we award some money, set the instructionsText accordingly, and change winner to “player“.


27. Initializing the Money

We need to remember to initialize the money variable before we do anything with it. Add the following within the Setup() function.

function Setup()
   createDataFile();
   money = readMoney();
   setupCoins();
   .....
end

28. Testing for Blackjack

We are at the point where we can test to see if the player or dealer has blackjack. We’ll temporarily change some code to test, but we’ll change it back. First, in the deal function, change the following.

 elseif(#dealerHand == 2 and #playerHand == 2) then
     if(true)then
      doGameOver(true);

Then within the doGameOver() function change the first two line like so.

local playerHandValue = 21--getHandValue(playerHand);
local dealerHandValue = 18--;getHandValue(dealerHand);

Now go ahead and test the app. You should see that the player gets blackjack.

Now change the first two lines inside the doGameOver to the following

local playerHandValue = 18--getHandValue(playerHand);
local dealerHandValue = 21--;getHandValue(dealerHand);

Now if you test, you should see the dealer has gotten blackjack.


29. Resetting From Testing

Now that we’ve tested, we should set our variables back. Inside the deal function change the following.

elseif(#dealerHand == 2 and #playerHand == 2) then
    if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then
     doGameOver(true);

Then within the doGameOver() function, change the first two lines back to their previous state.

local playerHandValue = getHandValue(playerHand);
local dealerHandValue = getHandValue(dealerHand);

If you test once more, and neither you nor the dealer gets blackjack, the deal button should become invisible and the hit and stand buttons should become visible.


30. Hit()

We need to allow the player to hit or stand once the first two cards have been dealt. Enter the following within the hit() function you entered in the previous part of this series.

function hit(event)
    if("began" == event.phase)then
	dealTo = "player";
	deal();
	end
end

If you test now, you should be able to hit.

After some quality control testing, you might have noticed that the player can rapidly press the hit button over and over, dealing many cards at once. This isn’t how the game should work. To fix it, we need to add a condition to make sure that they can only hit at the appropriate time. Add the following to the bottom of your variable declarations.

local canBet=true;

Now, change the hit() function to the following.

function hit(event)
    if("began" == event.phase)then
      if(canBet)then
      dealTo = "player";
      deal();
      canBet = false;
      end
    end
end

Within the deal() function, add the following line of code.

transition.to(tempCard, {time=1000,x=xPos,y=whichPosition,onComplete=function()
    	 canBet = true;

Now the player can only hit once, the card has finished being dealt.


31. Stand()

Next we need to allow the player to stand. Add the following within the stand() function you entered during the previous part of this series.

function stand()
    playerYields = true;
    standBtn.isVisible = false;
    hitBtn.isVisible = false;
    if(getHandValue(dealerHand) < 17)then
	dealTo = "dealer"
	deal();
    else
	  doGameOver(false);
    end
end

Here we indicate that the player is “holding.” Set the standBtn and hitBtn to invisible. We check to see if the dealer’s hand is less than 17, and if it is we change dealTo to the dealer and deal. If his hand is not less than 17, then we call doGameOver(). The dealer has to stand on 17 or greater.

If you test now, the player can get the hand they want and then press stand. There are a couple of problems, however. If the player busts, he can continue drawing cards and the dealing pauses at the dealer. We need the dealer to continue drawing cards until he gets over 17 or over, or until he busts. We will fix these issues when we finish off our deal() function in the next two steps.


32. Deal() Continued…

Add the following within the deal() function.

if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then
    doGameOver(true);
       else
         standBtn.isVisible = true;
         hitBtn.isVisible = true;
    end
end
 if( #dealerHand >=3 and(getHandValue(dealerHand) < 17))then
	deal();
        elseif( playerYields and getHandValue(dealerHand)>=17)then
        standBtn.isVisible = false;
        hitBtn.isVisible = false;
        doGameOver(false);
 end

Here we check to see if the dealerHand‘s length is greater than or equal to 3 and that the dealer’s hand is less than 17. If his hand is less than 17 he has to draw a card. Otherwise, we check to see if the player has yielded and if the dealer’s hand is greater than or equal to 17. If so, the game is over. It’s possible for dealer to have 17 or greater with the first two cards.


33. Finishing the Deal() Function

Enter the following code within the deal() function.

if( #dealerHand >=3 and(getHandValue(dealerHand) < 17))then
   deal();
   elseif( playerYields and getHandValue(dealerHand)>=17)then
   standBtn.isVisible = false;
   hitBtn.isVisible = false;
   doGameOver(false);
end
if(getHandValue(playerHand)>21)then
    standBtn.isVisible = false;
    hitBtn.isVisible = false;
    doGameOver(false);
end

If the player hits and draws a card that puts him over 21, the game is over.


34. GameOver() Continued…

In this step we’ll continue coding the gameOver() function. As of now, the deal function only determines who wins when either the player or dealer has blackjack. We need to handle all of the other possible outcomes. Enter the following within the doGameOver() function.

else
   money = money - bet;
   instructionsText.text = "Dealer got BlackJack!";
   winner = "dealer"
end
else
   if (playerHandValue > 21)then
        instructionsText.text = "You Busted!";
         money = money - bet;
         winner = "dealer";
    elseif (dealerHandValue > 21)then
         money = money + bet;
         instructionsText.text ="Dealer Busts. You Win!";
         winner = "player";
    elseif (dealerHandValue > playerHandValue)then
         money = money - bet;
         instructionsText.text ="You Lose!";
         winner = "dealer"
    elseif (dealerHandValue == playerHandValue)then
	 money = money - bet;
	 instructionsText.text = "Tie - Dealer Wins!";
	 winner = "tie"
    elseif (dealerHandValue < playerHandValue)then
	 money = money +bet;
	 instructionsText.text="You Win!";
	 winner = "player"
   end
end

If you test the code now, you should be able to play a full game. The instruction text will show the outcome. Play a few rounds and make sure everything seems correct. If you really want to test the game throughly by entering different hand values, you can use the same technique we used earlier in this tutorial to test for blackjack.


35. GameOver() Continued…

Enter the following at the bottom of the doGameOver() function

elseif (dealerHandValue < playerHandValue)then
    money = money +bet;
    instructionsText.text="You Win!";
    winner = "player"
  end
end
if(money < 10)then
   money = 500
  end
saveMoney(money)

After each round we should save the player’s money. If their money is less than 10 we’ll consider that bankrupt and reset their money to 500. As an exercise, see if you can get an alert to pop up saying something like “You’ve gone bankrupt, the dealer is awarding you $500.00.”


36. Finishing GameOver()

After each round we move the coins to the winner and then start a new game. Enter the following beneath the code you entered in the step above.

saveMoney(money)
local tweenTo;
    if(winner == "player")then
	tweenTo = playerCardsY;
    else
	tweenTo = dealerCardsY
    end
    transition.to(coinContainer, {time=1000,y=tweenTo,onComplete=function()
	for i=coinContainer.numChildren,1,-1 do
		local child = coinContainer[i]
		child:removeSelf()
		child = nil;
	end
	timer.performWithDelay( 2000, newGame);
	coinContainer.y = 600;
	end
   });

Here we see who won the round, and then we animate the coins to them. When the animation completes we remove all the coins from the coinContainer, and set them to nil since we are done with them. Lastly, we call newGame() after two seconds, we also reset our coinContainer position.


37. NewGame()

Enter the following beneath the doGameOver() function.

function newGame()
   instructionsText.text = "PLACE YOUR BET";
   betText.text = "";
   money10.isVisible = true;
   money25.isVisible = true;
   money50.isVisible = true;
   bankText.text = "Your Bank: $"..readMoney()
  for i=dealerGroup.numChildren,1,-1 do
     local child = dealerGroup[i]
     child:removeSelf()
     child = nil;
 end
 for i=playerGroup.numChildren,1,-1 do
    local child = playerGroup[i]
    child:removeSelf()
   child = nil;
end
   dealTo = "player";
   playerHand = {};
   dealerHand = {};
   allCards = {};
   createDeck();
   firstDealerCard="";
   playerYields = false;
   winner = "";
   bet = 0;
   canBet = true;
end

Here we set the money to visible, remove the cards from both the player and the dealer groups, and reset all of our variables.


Conclusion

We’ve coded a fun and interesting blackjack game using the Corona SDK. Thanks for reading, I hope you found this tutorial helpful!

Preparing for Firefox OS

$
0
0

Firefox OS is an entire mobile operating system built around open web technologies! The OS has been built from the ground up to allow HTML5 and Javascript to unleash the full potential of the device hardware. Read on to learn more about Firefox OS and how you can start porting your existing web apps to the platform today!

Firefox OS Logo

Firefox OS

Firefox OS is a mobile operating system created from scratch by Mozilla. It evolved from an empty GitHub repository into a fully-blown open sourced system in more than a year, which alone is quite impressive. Built from the bare idea to the final product, shipped by the companies worldwide. Let’s see why this is so special and why should you even care.

The most important thing about Firefox OS is the technology used – it’s just the Web! All the front-end developers and JavaScript programmers can now easily create HTML5 applications directly for the platform. The mobile web now has the technologies and hardware access it deserves. Web APIs allow you to “talk” directly to the device hardware using JavaScript, making it possible to take photos, send messages, or initiate calls.

Let’s be clear about the target audience – it’s not that Firefox OS will directly battle high-end devices with iOS and Android on board in highly developed countries. The main target is to provide smartphone experience to those who normally wouldn’t have the chance to buy such a device. Phones with Firefox OS will be a cheap alternative to those who would like to switch from feature phones and dive into the web using modern browsers. There’s a huge demand for this and Mozilla want to fill the gap!

It’s the “new old” ecosystem as the web technologies are already well-known for thousands of JavaScript programmers and front-end developers. You don’t have to learn a new language or development environment. Everything you know about the web can be used to create stunning Firefox OS applications. Porting your game is very simple and requires minimum efforts to achieve.

In this article I’ll walk you through the whole process from having just a regular mobile HTML5 game, optimizing it for the Firefox OS device, and publishing the outcome in the Firefox Marketplace – I’ll use Captain Rogers as an example.

Testing Firefox OS

There are two ways to test your game for Firefox OS – using a software simulator or by plugging in the actual device. Both are useful and you should start with the Simulator to see if the game is working. After that, pushing the game onto the device will ultimately battle-test it and help you get rid of all the bugs. Then you can focus on the publishing process.

Simulator

Firefox OS Simulator

There is a way to test your game or application without the need of having an actual device in your hand. It’s the Firefox OS Simulator and you can simply install it as a plugin in your Firefox browser. You won’t be able to test touch controls, but at least you can see if the game works and behaves as it should. Using the simulator you can also push your application directly to the device.

Available Devices

Firefox OS Keon

If you really need your own device you can get it from Geeksphone. They are providing two developer preview phones – Keon and Peak. Those two devices are very similar to the final products which will be sold worldwide. First commercial devices are already announced: ZTE Open and Alcatel OneTouch Fire are offered by Movistar in Spain for a low price of 69 Euro (no contract, with 4 GB microSD card and 30 Euro for calls). Next on the list are Poland, Columbia, and Venezuela with more countries coming soon.

Firefox Marketplace

Firefox OS Marketplace

When you have an operating system on your device already it would be nice to install some apps on it. This is where the Firefox Marketplace comes in – Mozilla’s store providing everything you need. The difference between iOS or Android stores and the Firefox Marketplace is that the last one is open and free. Everybody can run his own marketplace, you’re not limited to the one and only place for distributing apps and games. You don’t even have to direct people to the marketplace itself if you want them to install your game on their device – there’s an API for that! Using the Open Web App API you can create a simple “install this app” button using an HTML button calling a few lines of JavaScript. End users will then be able to install the game directly on their device.

Marketplaces for Everybody

Mobile operators will provide marketplaces for their own clients with the apps targeted especially for them. This will also give them an opportunity to start earning money again and stop being just the data providers. It is important to understand that all players in the Firefox OS space can benefit from supporting the system.

Discoverability

There is a huge potential in the way web applications are built – you can search for content inside of them without the need to download them. As it’s just HTML, CSS, and JavaScript you can look for anything you’re interested in and you won’t be limited to the title or description – it is indexable and searchable just like any web site is now.

Already Available

The best thing is you can add your apps and games already – the Marketplace is coming out of the beta stage and opening for customers, but it was available for developers for some time now. Both sides had the time to adjust accordingly to the environment and prepare themselves for the platform. There is almost no competition – I’ve found around 500 games in the Marketplace. It’s hard even trying to compare it to the Apple’s App Store where you can find hundreds of thousands of apps. Now is the best time to seize the opportunity, the Firefox Marketplace is awaiting your content!

Case Study – Captain Rogers

Enclave Games - Captain Rogers

As I mentioned earlier I will show you the whole process of converting your regular HTML5 game to the fully working one optimized for the Firefox OS device and available for free in the Firefox Marketplace. The game Captain Rogers was built using ImpactJS and is very simple – you are the brave Captain Rogers and have to fly through an asteroid field to escape from the evil Kershan fleet. It was created as a collaboration between Enclave Games (Andrzej Mazur– coding) and Blackmoon Design (Robert Podgórski– graphics). The main focus was on making it small and simple yet fully functional and polished.

Optimize Your Game for Firefox OS

There are plenty of ways to optimize your game for the Firefox OS platform and most of them can be also applied to regular HTML5 games – it’s web technology after all, right? Below you’ll see some of the basic techniques that will quickly speed up the performance, make the game more flexible, or just make it look better.

Moz-opaque

The easiest way to speed up canvas rendering of the game on Firefox OS is to add the moz-opaque attribute to it:

<canvas id="mycanvas" moz-opaque></canvas>

If your canvas is not transparent or you don’t have to show anything behind it, just set this attribute and it will automatically improve rendering times.

CSS Transform Scaling

Scaling canvas is important when you don’t know the resolution of the target device. It would look weird if the game would take only one third of the screen, or worse when you’ll see just half of it. This method is not for every type of game – if you want to preserve a pixel art you have to manage the scale differently, but for most games it should work. Here’s the code responsible for scaling:

    var scaleX = canvas.width / window.innerWidth;
    var scaleY = canvas.height / window.innerHeight;
    var scaleToFit = Math.min(scaleX, scaleY);
    stage.style.transformOrigin = "0 0";
    stage.style.transform = "scale(" + scaleToFit + ")";

This will ensure that your game is filling the whole available space, but will preserve scale so the game won’t be distorted. On large devices the game will look a bit blurry though, so remember to watch out for this.

Nearest-neighbour Rendering

If you’re working on a pixel art game it’s very important to have crisp pixels instead of a blurry ones – turn off smoothing using JavaScript and you’re ready to go!

    var context = canvas.getContext(‘2d’);
    context.webkitImageSmoothingEnabled = false;
    context.mozImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;

Thanks to this you’ll have your pixels as sharp as possible, so the players can fully enjoy the pixel art in your game.

Other Techniques

There are many more techniques to explore like using offscreen canvas, whole-pixel rendering, multiple canvases for layers, and other cool tricks – check out this incredibly useful article on Mozilla Hacks written by Louis Stowasser and Harald Kirschner. Some of the techniques covered in that article are used in the ImpactJS game engine, so you don’t even have to implement it by yourself.

Prepare it for the Firefox Marketplace

There are two types of applications in the Marketplace – packaged and self hosted. The first type is just a .zip package containing all of the assets that will be uploaded to the Marketplace. The second type, self hosted, is a direct link to the game that you host on your own server. Packaged apps are allowed to get more access to the hardware, as they are delivered from a secure, Mozilla-known server, whereas self-hosted apps are easier to deploy and change.

Manifest File

For both types of apps the most important file is the
manifest.webapp containing all the needed information. A manifest file is just a simple JSON object from which all the data will be provided for the initial Firefox Marketplace settings:

{"name": "My App","description": "My description goes here","launch_path": "/","icons": {"128": "/img/icon-128.png"
        },"developer": {"name": "Your name or organization","url": "http://your-homepage-here.org"
        },"default_locale": "en"
}

You just have to provide very basic information like the name of the application, it’s decription, provide icons to be shown in the Marketplace, basic information about yourself like developer name, and developer url, and at the end the default locale (i.e. the application default language version). You can validate the manifest file to be sure it’s bug-free.

Permissions

The main difference between packaged and self hosted apps are the permissions – packaged apps can be privileged or certified. When you need access to the device’s hardware like the camera or the contacts list you have to list everything in the permissions section.

"permissions": {"contacts": {"description": "Required for autocompletion","access": "readcreate"
        },"alarms": {"description": "Required to schedule notifications"
        }
    }

Priviledged apps have access to Web APIs that have more permissions to access the hardware and user data on the device and are higher in the hierarchy than the usual web apps. There is also the highest rank called certified apps which have control over critical system functions like for example the default dialer – only Mozilla and partners provide those kind of apps.

Self Hosted Manifests

When you don’t need access to the WebAPIs on the device and want to have easy and instant updates you can decide to host the game yourself. There are some things that you have to keep in mind to deliver your game without any problems.

Cache all your files to allow users to play the game offline and to save on traffic on your server. Do not cache the manifest file though as this could lead to issues when you want to update the game. The manifest file must have an extension .webapp and have to be served from the same origin. The Content-Type have to be set to application/x-web-app-manifest+json, UTF-8 encoding is recommended.

When using Apache just drop that line in your .htaccess file:

AddType application/x-web-app-manifest+json .webapp

On Nginix you have to edit the mime.types file and add similar line:

types {
    // ...
    application/x-web-app-manifest+json webapp;
}

For testing your app you can also use GitHub Pages for hosting. Mozilla worked with GitHub to have all the settings in place, just remember to have your manifest file’s file name end in .webapp.

Uploading Your Game to the Marketplace

Time to add our game to the marketplace so people can find it easily on Firefox OS. We have our game ready to be submitted to the Firefox Marketplace – head over to the Developer Hub and login (or register) to get access. Look for the “Submit a new app” button – this will lead you to the submit form.

Firefox Marketplace - Submit Form

You’ll have various options to choose from – whether your app is free or paid, the game is hosted or packaged, you’ll also have a list of supported platforms. At the bottom there is a manifest validator that will ensure the manifest file is 100% correct. Another step is just filling the details like screenshots, other media, support information and app details. After that you’re done – the game was submitted!

All you have to do now is wait to have your game accepted – it should’t take longer than a few days. After that your game is published and available for everybody to see and play!

Firefox OS - Captain Rogers

Looking through the admin panel you’ll notice the statistics page where you’ll find all the info about installs. Other options include editing an already submitted game, managing team members, compatibility, payments, status and versions. Keep an eye on user reviews as they are a great source of feedback!

Lessons Learned

As you can see it’s very easy to adapt your game to Firefox OS and publish it in the Marketplace. The documentation is ready, all the needed information is there waiting for you. Is was a lot of fun working on Captain Rogers with Firefox OS in mind. I’m very curious how Enclave Games will be perceived in the Marketplace compared to other developers. If you have any opinions on the game itself (or want to test it on your device and give feedback), Firefox OS or the Marketplace feel free to discuss it in the comments!

Captain Rogers

Conclusion

You’re on the edge of the fast growing mobile market with huge potential where publishers are learning fast that HTML5 is the technology of the future. There are endless possibilities, you just have to grab the chance, use the opportunity and outrun the competition!

It’s unknown how it will work – I truly believe in the idea of having an HTML5 mobile operating system, but the future depends on the reaction of the market along with customers. One way or another we will have exciting times for HTML5 mobile development. Nothing here is wasted effort, as the games are running on all platforms and get special access on Firefox OS. Even if Firefox OS fails, you work is still compatible with the web rather than just one platform.

Create a Location-Aware Site with Sencha Touch – Displaying Locations

$
0
0

This tutorial will guide you through the development of a Location-based mobile website using the Google Place search engine and Sencha Touch 2.1 . This is the second in a two-part series, and today we’ll learn how to display map markers and location details.

This tutorial is the second and final part of our previous post Create a Location-Aware Site using Sencha Touch. In Part 1, we learned how to retrieve different local businesses like restaurant, hospitals, or theaters near our user’s current location. We did this using HTML5 geolocation and the Google Place Search API.

In this section, we’ll cover the following topics:

  • How to maintain browser history using Sencha router.
  • How to display multiple markers for each place in the Google map and, when selecting a marker, show an Info bubble with place information.
  • How to show complete details of each place, their reviews, and how to show the place images in a Pinterest style gallery.

We will resume where we left in the previous post.


1. Layout Change

In the first post, the app we developed was fairly simple by its navigation and a Sencha Navigation View was perfect for such a layout. However, while you find that you have to add more custom controls to your toolbars, using this type of view becomes difficult because Sencha maintains the Navigation bar on its own and showing/hiding items in it increases complexity.

So, as we proceed to add more functionality in the app, we need to make couple of changes in the structure. First, we will change the Main panel to a Card layout.

Main.js

/**
 * Main view - holder of all the views.
 * Card layout by default in order to support multiple views as items
 */
Ext.define('Locator.view.Main', {
  extend: 'Ext.Container',
  xtype: 'main',
  config: {
    cls: 'default-bg',
    layout: 'card',
    items: [{
      xtype: 'categories'
    }]
  }
});

Previously we were showing all the places as a list in PlaceList List view. Now, we want to add a Map option as well that will display all the place positions as markers. So, we change the second page to a Card layout which holds two panels – PlaceList and PlaceMap:

  • Places (Card layout)
  • PlaceList (Sencha List View)
  • PlaceMap (Sencha Map Container)

Places.js

Ext.define('Locator.view.Places',
 {
  extend: 'Ext.Container',
  xtype: 'places',
  config: {
    layout: 'card',
    items: [{
      xtype: 'placelist'
    }]
  }
});

PlaceList.js

The Places container has PlacesList panel as its children. Because we omitted the Navigation View, we have to add individual TitleBar to each child panel. Also, we add a map icon to the right of the toolbar; tapping this button, we show the Map panel with all the place markers.

Ext.define('Locator.view.PlaceList', {
  extend: 'Ext.List',
  xtype: 'placelist',
  requires: ['Ext.plugin.PullRefresh'],
  config: {
    cls: 'default-bg placelist',
    store: 'Places',
    emptyText: Lang.placeList.emptyText,
    plugins: [{
      xclass: 'Ext.plugin.PullRefresh',
      pullRefreshText: Lang.placeList.pullToRefresh
    }],
    itemTpl: Ext.create('Ext.XTemplate',
      document.getElementById('tpl_placelist_item').innerHTML, {
        getImage: function (data) {
          // If there is any image available for the place, show the first one in list item
          if (data.photos &amp;&amp; data.photos.length > 0) {
            return '<div class="photo"><img src="' + data.photos[0].url + '" /></div>';
          }
          // If there is no image available, then we will show the icon of the place
          // which we get from the place data itself
          return ['<div class="icon-wrapper">',
            '<div class="icon" style="-webkit-mask-image:url(' + data.icon + ');" ></div>',
            '</div>'].join('');
        },
        getRating: function (rating) {
          return Locator.util.Util.getRating(rating);
        }
      }),
    items: [{
      xtype: 'titlebar',
      docked: 'top',
      name: 'place_list_tbar',
      items: [{
        xtype: 'button',
        ui: 'back',
        name: 'back_to_home',
        text: 'Home'
      }, {
        xtype: 'button',
        align: 'right',
        iconCls: 'locate4',
        iconMask: true,
        name: 'show_map',
        ui: 'dark'
      }]
    }]
  }
});

The PlaceList looks like this:

Place List

Notice the map icon at top right corner. Tapping this, we create and activate the Map panel. Let’s create a PlaceMap view which will be a simple container with the map and a toolbar which has another button to come back to PlaceList.

PlaceMap.js

Ext.define('Locator.view.PlaceMap', {
  extend: 'Ext.Container',
  xtype: 'placemappanel',
  config: {
    layout: 'fit',
    items: [{
      xtype: 'map',
      name: 'place_map',
      mapOptions: {
        zoom: 15
      }
    }, {
      xtype: 'titlebar',
      docked: 'top',
      name: 'place_map_tbar',
      items: [{
        xtype: 'button',
        ui: 'back',
        name: 'back_to_home',
        text: 'Home'
      }, {
        xtype: 'button',
        align: 'right',
        iconCls: 'list',
        iconMask: true,
        name: 'show_place_list',
        ui: 'dark'
      }]
    }]
  }
});

Now, in the controller we will add three functions to create and activate these panels. I always prefer to have an individual navigation function for each page that separates the navigation from the functional part.

References in Controller/App.js

Let’s list down all the refswe need inside the controller.

refs: {
  // Containers
  categoriesList: 'categories',
  main: 'main',
  placesContainer: 'places',
  placeList: 'placelist',
  placeMapPanel: 'placemappanel',
  placeMap: 'map[name="place_map"]',
  // Buttons
  showMapBtn: 'button[name="show_map"]',
  showPlaceListBtn: 'button[name="show_place_list"]',
  backToHomeBtn: 'button[name="back_to_home"]',
  backToPlaceListBtn: 'button[name="back_to_placelist"]'
}

We will have a showHome method which we call while PlaceList and PlaceMap panel back buttons are pressed. It brings the user back to the home screen.

showHome()

/**
 * Show Home Panel
 **/
showHome: function () {
  var me = this;
  if (me.getMain().getActiveItem() !== me.getCategoriesList()) {
    me.util.showActiveItem(me.getMain(), me.getCategoriesList());
  }
}

While a category item is pressed, we create the Places container, set the title of each panel to the name of that category and activate the PlaceList panel within. The last part is necessary so that every time you land to the place list page from Home page.

showPlacesContainer()

/**
 * Show places container
 */
showPlacesContainer: function (type) {
  var me = this,
    name = me.util.toTitleCase(type.split('_').join(' '));
  if (!me.getPlacesContainer()) {
    this.getMain().add({
      xtype: 'places'
    });
  }
  // Set the placelist title to Category name
  Ext.each(me.getPlacesContainer().query('titlebar'), function (titleBar) {
    titleBar.setTitle(name);
  }, me);
  me.getPlacesContainer().setActiveItem(0);
  me.util.showActiveItem(me.getMain(), me.getPlacesContainer());
}

showPlaceList() / showPlacesMap()

We show/hide PlaceListand PlaceMapcontainers depending on what is pressed – the list icon or the map icon.

/**
 * Show place list
 */
showPlaceList: function () {
  var me = this;
  me.util.showActiveItem(me.getPlacesContainer(), me.getPlaceList(), {
    type: 'flip',
    reverse: true
  });
},
/**
 * Show places map
 */
showPlacesMap: function () {
  var me = this;
  if (!me.getPlaceMapPanel()) {
    me.getPlacesContainer().add({
      xtype: 'placemappanel'
    });
  }
  // Get the active category type and set it to title after changing to titlecase
  me.getPlaceMapPanel().down('titlebar').setTitle(me.util.toTitleCase(me.activeCategoryType.split('_').join(' ')));
  me.util.showActiveItem(me.getPlacesContainer(), me.getPlaceMapPanel(), {
    type: 'flip'
  });
}

this.activeCategoryType– this variable is set while the user selects a category. We will come in to that in a moment.  In the controller control, we add the button tap events:

showMapBtn: {
  tap: 'showPlacesMap'
},
showPlaceListBtn: {
  tap: 'showPlaceList'
}

You should now be able to test the app in browser and see how the layout change is working. We are done with the views related to multiple places. We will look into the placing the map markers soon but before that I want to cover how we can maintain browser history in these single page websites.


2. Maintain Browser History

While creating a single page website, it becomes absolutely necessary to maintain the browser history. Users navigates back and forth in a website and many times network issues or unresponsiveness forces the user to reload a page again. In these scenarios, if history is not maintained, refreshing a page will eventually bring the user back to the first page which becomes really annoying in certain cases.

In this section we will see how we can use Sencha’s routing functionality to find a solution to this problem. Always remember to incorporate history maintenance from the start of your application. Otherwise, it will be a tedious job to implement this functionality while your app is already half done.

We will add two routes for the Categories and Places pages respectively.

routes: {
  '': 'showHome',
  'categories/:type': 'getPlaces'
}

Sencha Controller has a method named redirectTo which accepts a relative url and calls the function matched to that. We will use an empty string to go to the first page (i.e the Categories list) and a specific category type to go to the Place list page.

So, while we want to come back to the Categories list from the Places container, instead of calling the showHome() function, we just redirect to the specific route and it will automatically call the required method.

backToHomeBtn: {
  tap: function () {
    this.redirectTo('');
  }
}

You may question: what difference is there between these two while both can be achieved in a single line of code? Basically, there is no difference but while you are going to maintain all the navigation via routing, I prefer to follow a single trend throughout. In addition, you have to maintain the window hash yourself if you call the function directly.

We saw the loadPlaces() method in part 1 of this tutorial which sends an Ajax request to retrieve the places data. This time we are going to add a bit there for saving the current category type in the controller instance which will be needed in future. Also, we want to disable the map button until all the places are loaded. We want to display the marker as soon as the map is rendered and for that, the places should be loaded first.

launch() / getPlaces()

launch: function () {
  var me = this;
  me.getApplication().on({
    categorychange: {
      fn: function (newCategory) {
        me.activeCategoryType = newCategory;
      }
    },
    placechange: {
      fn: function (newPlaceReference) {
        me.activePlaceReference = newPlaceReference;
      }
    }
  });
},
/**
 * Retrieve all the places for a particlur category
 */
getPlaces: function (type) {
  var me = this;
  // Show the place list page
  me.showPlacesContainer(type);
  // Disable the show map button until the list gets loaded
  me.getShowMapBtn().disable();
  // Keep a reference of the active category type in this controller
  me.getApplication().fireEvent('categorychange', type);
  var store = Ext.getStore('Places'),
    loadPlaces = function () {
      me.util.showLoading(me.getPlaceList(), true);
      // Set parameters to load placelist for this 'type'
      store.getProxy().setExtraParams({
        location: me.util.userLocation,
        action: me.util.api.nearBySearch,
        radius: me.util.defaultSearchRadius,
        sensor: false,
        key: me.util.API_KEY,
        types: type
      });
      // Fetch the places
      store.load(function () {
        me.util.showLoading(me.getPlaceList(), false);
        // Enable show map button
        me.getShowMapBtn().enable();
      });
    }
    // If user's location is already not set, fetch it.
    // Else load the places for the saved user's location
  if (!me.util.userLocation) {
    Ext.device.Geolocation.getCurrentPosition({
      success: function (position) {
        me.util.userLocation = position.coords.latitude + ',' + position.coords.longitude;
        me.util.userLocation = me.util.defaultLocation;
        loadPlaces();
      },
      failure: function () {
        me.util.showMsg(Lang.locationRetrievalError);
      }
    });
  } else {
    // Clean the store if there is any previous data
    store.removeAll();
    loadPlaces();
  }
}

In launch(), we add an event “categorychange” to the Application object and fire it once we get a new category type. It sets a Controller variable activeCategoryType to that type. Now the question is, why do we set the variable via an event than assigning it directly? It is because we may want to set the type from other views or controllers. Doing it this way also increases the feasibility a lot.

Hence, for the category list item tap event also, we are not going to call the getPlaces() function directly. Rather, we will use the redirectTo method to load places for that particular category type. Let’s add that in Controller’s control:

categoriesList: {
  itemtap: function (list, index, target, record) {
    this.redirectTo('categories/' + record.get('type'));
  }
}

So, while you select a category type, say “art_gallery”, your browser url changes to “/locator/locator/#categories/art_gallery” and a PlaceList panel will open with all the Art Galleries. Now, if you reload the page, it will again open the same PlaceList page – not the first page.


3. Display Places on the Map

We are done with all the views needed to show the places. We will create and show the PlaceMap container every time the user taps the Map button. We do not want to render the map unless the user wants to see it.

Also, instead of removing the markers one by one and adding others, I prefer to remove the map panel completely once the user comes back to the category listing page. So, let’s add this part for the home button tap:

backToHomeBtn: {
  tap: function () {
    this.redirectTo('');
    // Destroy the mappanel completely so that we do not need to
    // save and remove existing markers
    if (this.getPlaceMapPanel()) {
      this.getPlacesContainer().remove(this.getPlaceMapPanel());
    }
  }
},
placeMap: {
  // Create the markers in maprender event
  maprender: 'showPlacesMarkers'
}

We create the markers in the “maprender” event.

showPlaceMarkers()

/**
 * Create markers for user's location and all the
 * places. On clicking a marker, show Infobubble with details
 */
showPlacesMarkers: function (extMap, gMap)
{
  var me = this,
    location, data, marker,
    userLocation = me.util.userLocation.split(','),
    currentPosition = new google.maps.LatLng(userLocation[0], userLocation[1]),
    image = new google.maps.MarkerImage('resources/images/marker.png',
      new google.maps.Size(32, 32),
      new google.maps.Point(0, 0)
    ),
    // Create an InfoBubble instance
    ib = new InfoBubble({
      shadowStyle: 1,
      padding: 0,
      backgroundColor: 'rgb(0,0,0)',
      borderRadius: 4,
      arrowSize: 15,
      borderWidth: 1,
      borderColor: '#000',
      disableAutoPan: true,
      hideCloseButton: true,
      arrowPosition: 30,
      backgroundClassName: 'infobubble',
      arrowStyle: 2
    }),
    /*
     * Showing InfoBubble
     **/
    setupInfoBubble = function (data, _marker) {
      google.maps.event.addListener(_marker, 'mousedown', function (event) {
        // Close existing info bubble
        if (ib) {
          ib.close();
        }
        // Kepp an instance of the active place's id in
        // the infobubble instance for accessing it later
        ib.placeReference = data.reference;
        // Set teh content of infobubble
        ib.setContent([
          '<div class="infobubble-content">',
          data.name,
          '</div>'
        ].join(''));
        ib.open(gMap, this);
      });
    };
  /**
   * Tap on InfoBubble handled here
   */
  google.maps.event.addDomListener(ib.bubble_, 'click', function () {
    if (me.activeCategoryType) {
      me.redirectTo('categories/' + me.activeCategoryType + '/' + ib.placeReference);
    }
  });
  // For all the places create separate markers
  me.getPlaceList().getStore().each(function (record) {
    data = record.getData(),
    location = data.geometry.location,
    marker = new google.maps.Marker({
      position: new google.maps.LatLng(location.lat, location.lng),
      map: gMap,
      icon: image
    });
    setupInfoBubble(data, marker);
  }, me);
  // Create a different marker for user's current location
  new google.maps.Marker({
    position: currentPosition,
    map: gMap,
    icon: new google.maps.MarkerImage('resources/images/currentlocation.png',
      new google.maps.Size(32, 32),
      new google.maps.Point(0, 0)
    )
  });
  // Center the map at user's location. A delay is given because from
  // second time onward it doesn't center the map at user's position
  Ext.defer(function () {
    gMap.setCenter(currentPosition);
  }, 100);
}

Multiple aspects of functionality are handled in this function:

  • Create markers and show
  • Show user’s current location
  • Show info bubble while tapped on a marker
  • On tapping an info bubble, go to the Place Details page

Create Markers and Show Them

Showing markers is pretty easy. Just instantiate a marker passing the position and google map instance. You can also use a different image in the marker like what we did here. For each record in the Places store we create a marker and on mouse down (which actually works as tap event) we call the setupInfoBubble() method passing the data and the marker instance.

Show the User’s Current Location

We retrieve the user’s current location in the getPlaces() method and save it in the Util singleton class. We create a different marker for that position.

Show Infobubble While Tapped on a Marker

Google map’s utility library has a great component named Infobubble for showing custom info windows. You may get a detailed discussion on the implementation at iPhone like infobubble with Sencha Touch. Mostly, we create a new instance of InfoBubble with requires new config options. Because only one infobubble is shown at a certain point of time, we just replace the content of infobubble every time it is opened. We keep a reference to the place’s “reference” property which will be required to go to the PlaceDetails page.

Go to the Place Details page

/**
 * Tap on InfoBubble handled here
 */
google.maps.event.addDomListener(ib.bubble_, 'click', function () {
  if (me.activeCategoryType) {
    me.redirectTo('categories/' + me.activeCategoryType + '/' + ib.placeReference);
  }
});

On tapping the infobubble, we redirect the browser to a url like categories/:type/:reference. We will handle this part in another controller dedicated to place details functionality.

Places Map Panel

4. Place Details

The Place Details page opens up while the user selects a place either from list or from map. I categorized the details in 3 parts – Info, Image Gallery and Reviews. A TabPanel is most suited for such a layout. Let’s see how the Views can be structured:

Views

  • details.Main
  • details.Info
  • details.Gallery
  • details.GalleryCarousel
  • details.Review

Controller

  • PlaceDetails

We create a another subset of namespace and put all these files inside the “details” directory of the “view” folder. Also, we create a separate controller that maintains only place details related functions. Remember to add all these views and controller in the app.js file.

View/Details.Main

The main panel is a simple tab panel with three child panels and a titlebar.

Ext.define('Locator.view.details.Main', {
  extend: 'Ext.tab.Panel',
  xtype: 'detailspanel',
  config: {
    cls: 'details-tabpanel default-bg',
    tabBar: {
      docked: 'bottom'
    },
    items: [{
      xtype: 'info'
    }, {
      xtype: 'gallery'
    }, {
      xtype: 'review'
    }, {
      xtype: 'titlebar',
      docked: 'top',
      title: 'Details',
      items: [{
        text: 'Places',
        ui: 'back',
        name: 'back_to_placelist'
      }]
    }]
  }
});

Place Info

In the info page, we will show the basic information about the place, its website, phone, Google+ profile, and a map with only the place’s marker.

View/Details.Info

Ext.define('Locator.view.details.Info', {
  extend: 'Ext.Container',
  xtype: 'info',
  config: {
    cls: 'transparent details-info',
    iconCls: 'info',
    title: Lang.info,
    scrollable: true,
    tpl: Ext.create('Ext.XTemplate',
      document.getElementById('tpl_place_details_info').innerHTML, {
        getRating: function (rating) {
          return Locator.util.Util.getRating(rating);
        }
      })
  }
});

Info Template

<!-- Place Details Info Page --><script type="text/template" id="tpl_place_details_info"><div class="block content"><div class="container"><div class="name">{name}</div><div class="address">{formatted_address}</div>
      {rating:this.getRating}</div><div class="actions"><div class="website"><a href="{website}" target="_blank"><div class="icon"></div><div class="text">Website</div></a></div><div class="phone"><a href="tel:{phone}"><div class="icon"></div><div class="text">Call</div></a></div><div class="profile"><a href="{url}" target="_blank"><div class="icon"></div><div class="text">Profile</div></a></div></div></div><div class="block map"></div></script>

The DIV element with the “map” css class is used later to render the Google Map. I used different styling in all the details pages – I am not including all those CSS details in this tutorial content to keep it tidy. You will get all the CSS properly structured in the project files.

The info view is created. Now, we have to load the place details and apply its data in an Info page template. Let’s define the PlaceDetails controller.

Controller/PlaceDetails

Ext.define('Locator.controller.PlaceDetails', {
    extend: 'Ext.app.Controller',
    util: Locator.util.Util,
    config: {
      routes: {
        'categories/:type/:reference': 'showDetails'
      },
      refs: {
        main: 'main',
        placeDetailsPanel: 'detailspanel',
        placeDetailsInfo: 'info',
        placeDetailsGallery: 'gallery',
        placeDetailsReview: 'review'
      },
      control: {}
    }
});

We will need a showDetails method which will create and open the details.Main tab panel.  While selecting a particular place, we have both its category type and place reference. We need to store both these values – the place reference will be used to get further details of the place and the category type will be needed if user directly opens up a place details page and then try to go back to the places list page.

showDetails()

/**
 * Maintain details panel routes and show the details panel
 */
showDetails: function (categoryType, placeReference) {
  var me = this;
  if (!me.getPlaceDetailsPanel()) {
    me.getMain().add({
      xtype: 'detailspanel'
    });
  }
  me.util.showActiveItem(me.getMain(), me.getPlaceDetailsPanel());
  // Load place data
  me.loadPlaceDetails(placeReference);
  // Fire the category change and place change events
  // so that the category id and place reference can be kept
  // which is needed if users press back button
  me.getApplication().fireEvent('categorychange', categoryType);
  me.getApplication().fireEvent('placechange', placeReference);
}

We fire both the “categorychange” and “placechange” event on the Application object to save the type and the reference. Also, we call the loadPlaceDetails() method passing the places response to retrieve the complete information of that place.

The PHP function is pretty simple as we have used earlier:

getPlaceDetails()

/**
  * Get place details along with all the photo urls.
  * @return String
  */
public static function getPlaceDetails() {
  try {
    $data = json_decode(self::sendCurlRequest());
    $item = $data->result;
    if (isset($item->photos)) {
      for ($i = 0; $i < count($item->photos); $i++) {
        $item->photos[$i]->url = BASE_API_URL . "photo?photoreference=" . $item->photos[$i]->photo_reference
                . "&amp;sensor=false"
                . "&amp;maxheight=" . IMAGE_MAX_HEIGHT
                . "&amp;maxwidth=" . IMAGE_MAX_WIDTH
                . "&amp;key=" . $_REQUEST["key"];
      }
    }
    return json_encode($data);
  } catch (Exception $e) {
    print "Error at getPlaceDetails : " . $e->getMessage();
  }
}

The images for a place do not come automatically – rather, all the images are to be requested separately with their reference, api key and dimension. Complete details on it can be found here. We store all the photo urls in a “photos” property of the Place data.

loadPlaceDetails()

/**
 * Load the complete details of the place
 * and apply the details to all the panels' template in the tabpanel
 */
loadPlaceDetails: function (placeReference) {
  var me = this;
  me.util.showLoading(me.getPlaceDetailsPanel(), true);
  Ext.Ajax.request({
    url: me.util.api.baseUrl,
    method: 'GET',
    params: {
      action: me.util.api.details,
      reference: placeReference,
      key: me.util.API_KEY,
      sensor: true
    },
    success: function (response) {
      me.util.showLoading(me.getPlaceDetailsPanel(), false);
      var json = me.currentLocation = Ext.decode(response.responseText);
      // Apply the data in panel templates
      me.getPlaceDetailsInfo().setData(json.result);
      me.getPlaceDetailsGallery().setData(json.result);
      me.getPlaceDetailsReview().setData(json.result);
      // CShow the location of the place as a marker
      me.showPlaceLocation();
    },
    failure: function () {
      me.util.showMsg(Lang.serverConnectionError);
    }
  });
}

Once we get the place details object, we apply the result to all the child container of the Tab Panel because all of them are utilizing Sencha’s template fnctionality. Also, we call showPlaceLocation() method which creates a Google map, renders it in Info panel and display a marker for the place inside the map.

showPlaceLocation()

/**
 * Create the map and show a marker for that position in the map
 */
showPlaceLocation: function () {
  var me = this,
    showMarker = function () {
      var location = me.currentLocation.result.geometry.location,
        latLng = new google.maps.LatLng(location.lat, location.lng),
        image = new google.maps.MarkerImage('resources/images/marker.png',
          new google.maps.Size(32, 32),
          new google.maps.Point(0, 0)
        );
      // Create the marker for that place
      me.singleMapMarker = new google.maps.Marker({
        position: latLng,
        map: me.gmap,
        icon: image
      });
      // Set the marker to center. A timeout is needed in order to
      // bring the marker at center. Else, it will be shown at top-left corner
      Ext.defer(function () {
        me.gmap.setCenter(latLng);
      }, 100);
    };
  if (me.singleMap) {
    me.singleMap.destroy();
  }
  // Create a map and render it to certain element
  me.singleMap = Ext.create('Ext.Map', {
    renderTo: me.getPlaceDetailsInfo().element.down('.map'),
    height: 140,
    mapOptions: {
      zoom: 15
    },
    listeners: {
      maprender: function (mapCmp, gMap) {
        me.gmap = gMap;
        showMarker();
      }
    }
  });
}

So, we are done with the basic info page and this is how it looks:

Place Details: Info

Place Image Gallery

We will create a Pinterest style image gallery for the Place images and show the full view of the images in a carousel. I have developed a component for the same and wrote a detailed blog post on it which you can find here Mosaic image gallery with Sencha Touch. The same components are used here – just the data properties are different.

Place Details: Gallery

Here is how the gallery looks like. -webkit-column property of CSS3 is used here to get a mosaic like layout for the images.

View/Details.Gallery

Ext.define('Locator.view.details.Gallery', {
  extend: 'Ext.Container',
  xtype: 'gallery',
  config: {
    title: Lang.gallery,
    iconCls: 'photos2',
    cls: 'transparent gallery',
    scrollable: true,
    // Template to show the thumbnail images
    tpl: Ext.create('Ext.XTemplate',
      '<tpl if="this.isEmpty(values)">',
      '<div class="empty-text empty-gallery">', Lang.placeDetails.emptyGallery, '</div>',
      '</tpl>',
      '<div class="gallery body" id="photos">',
      '<tpl for="photos">',
      '<img src="{url}" class="thumbnail" />',
      '</tpl>',
      '</div>', {
        isEmpty: function (result) {
          if (!result.photos || result.photos.length === 0) {
            return true;
          }
          return false;
        }
      })
  },
  initialize: function () {
    var me = this;
    // Add tap event on the images to open the carousel
    me.element.on('tap', function (e, el) {
      me.showGalleryCarousel(el);
    }, me, {
      delegate: 'img.thumbnail'
    });
    me.callParent(arguments);
  },
  /**
   * Show the gallery carousel with all the images
   */
  showGalleryCarousel: function (clickedImage) {
    var me = this,
      clickedImgIndex = 0;
    // Query all the images and save in an array
    me.images = me.element.query('img.thumbnail');
    // Create the Gallery Carousel
    var galleryCarousel = Ext.Viewport.add({
      xtype: 'gallerycarousel',
      images: me.images
    });
    // On clicking close icon, hide the carousel
    // and destroy it after a certain perdiod
    galleryCarousel.element.on('tap', function (e, el) {
      galleryCarousel.hide(true);
      Ext.defer(function () {
        Ext.Viewport.remove(galleryCarousel);
      }, 300);
    }, this, {
      delegate: 'div[data-action="close_carousel"]'
    });
    // Get the image index which is clicked
    while ((clickedImage = clickedImage.previousSibling) != null) {
      clickedImgIndex++;
    }
    // Set the clicked image container as the active item of the carousel
    galleryCarousel.setActiveItem(clickedImgIndex);
    // Show the carousel
    galleryCarousel.show();
  }
});

View/Details.GalleryCarousel

Ext.define('Locator.view.details.GalleryCarousel', {
  extend: 'Ext.carousel.Carousel',
  xtype: 'gallerycarousel',
  config: {
    fullscreen: true,
    modal: true,
    images: [],
    html: '<div class="close-gallery" data-action="close_carousel"></div>',
    cls: 'gallery-carousel',
    showAnimation: 'popIn',
    hideAnimation: 'popOut',
    indicator: false,
    listeners: {
      initialize: 'changeImageCount',
      // Call image count checker for each image change
      activeitemchange: 'changeImageCount'
    }
  },
  initialize: function () {
    var me = this,
      images = me.getImages();
    // Create a bottom bar which will show the image count
    me.bottomBar = Ext.create('Ext.TitleBar', {
      xtype: 'titlebar',
      name: 'info_bar',
      title: '',
      docked: 'bottom',
      cls: 'gallery-bottombar',
      items: [{
        xtype: 'button',
        align: 'left',
        iconCls: 'nav gallery-left',
        ui: 'plain',
        handler: function () {
          me.previous();
        }
      }, {
        xtype: 'button',
        align: 'right',
        iconCls: 'nav gallery-right',
        ui: 'plain',
        handler: function () {
          me.next();
        }
      }]
    });
    // Add the images as separate containers in the carousel
    for (var i = 0; i < images.length; i++) {
      me.add({
        xtype: 'container',
        html: '<img class="gallery-item" src="' + images[i].src + '" />',
        index: i + 1
      });
    }
    me.add(me.bottomBar);
    me.callParent(arguments);
  },
  /**
   * Change image count at bottom bar
   */
  changeImageCount: function () {
    var me = this;
    me.bottomBar.setTitle((me.getActiveIndex() + 1) + ' of ' + me.getImages().length);
  }
});

Place Reviews

Place reviews is a simple list of the reviews – which I utilized with a plain Sencha container instead of a list view. Adding some custom styling becomes easy here and also makes it look cool. We used the same rating system to show individual ratings.

Place Details: Reviews

The profile images of the users are their Google+ user profile image. We use a set of custom template functions to edit the content on the fly.

Reviews Template

<!-- Place Details Review Page --><script type="text/template" id="tpl_place_details_reviews"><div class="details-item body"><tpl if="this.isEmpty(values)"><div class="empty-text"> No review available for this place. </div></tpl><tpl for="reviews"><div class="review"><a href="{author_url}" target="_blank"><img
             class="profile-img"
             src="{author_url:this.getUserImage}"<!-- Show a default user icon if there is no user image available -->
             onerror="Locator.util.Util.onBrokenProfileImage(this)"/></a><div class="heading">
           By <a href="{author_url}" target="_system">{author_name:this.toTitleCase}</a><span> {time:this.getDate}</span></div><div class="details"><div class="rating"><tpl for="aspects"><div class="aspect"><div class="type">{type:this.toTitleCase}</div><div class="stars">{rating:this.getStars}</div></div></tpl></div><div class="text">{[this.applyExpandable(values)]}</div><!-- A hidden element which holds the complete review text --><div class="full-review">{text}</div></div></div></tpl></div></script>

view/details.Review

Ext.define('Locator.view.details.Review', {
  extend: 'Ext.Container',
  xtype: 'review',
  config: {
    cls: 'transparent',
    title: Lang.reviews,
    iconCls: 'chat2',
    scrollable: true,
    tpl: Ext.create('Ext.XTemplate',
      document.getElementById('tpl_place_details_reviews').innerHTML, {
        toTitleCase: function (str) {
          return Locator.util.Util.toTitleCase(str);
        },
        getDate: function (timestamp) {
          return Locator.util.Util.prettyDate(timestamp * 1000);
        },
        getUserImage: function (authorUrl) {
          if (authorUrl) {
            var arr = authorUrl.split('/');
            return 'https://plus.google.com/s2/photos/profile/' + arr[arr.length - 1] + '?sz=50';
          }
          return Locator.util.Util.defaultUserImage;
        },
        getStars: function (rating) {
          return Locator.util.Util.getRating(rating, 3, true);
        },
        isEmpty: function (result) {
          if (!result.reviews || result.reviews.length === 0) {
            return true;
          }
          return false;
        },
        applyExpandable: function (data) {
          var text = data.text;
          if (text.length > 120) {
            text = Ext.String.ellipsis(text, 120) +
              ' <span data-action="more" class="resize-action">more</span>';
          }
          return text;
        }
      })
  }
});

From the template and XTemplate functions, we notice a number of new things:

Default User Profile Image

We add an onerror event on the IMG tag. This event gets fired if the src attribute is empty or null. We create a function onBrokenProfileImage() in our Util file and call it if there is any error and set the src attribute to a default user image.

onBrokenProfileImage: function (image) {
  image.onerror = "";
  image.src = Locator.util.Util.defaultUserImage;
  return true;
}

Use Pretty Date

Instead of just showing the date, we prefer a Twitter style date like “2 hours ago”, “3 mins ago” etc. We add a prettyDate() method to Util class which converts the date to pretty date.

/**
 * Give the date a format like 2 hours ago, 5 mins ago
 */
prettyDate: (function () {
  var ints = {
    second: 1,
    minute: 60,
    hour: 3600,
    day: 86400,
    week: 604800,
    month: 2592000,
    year: 31536000
  };
  return function (time) {
    time = +new Date(time);
    var gap = ((+new Date()) - time) / 1000,
      amount, measure;
    for (var i in ints) {
      if (gap > ints[i]) {
        measure = i;
      }
    }
    amount = gap / ints[measure];
    amount = gap > ints.day ? (Math.round(amount * 100) / 100) : Math.round(amount);
    amount = Math.ceil(amount);
    amount += ' ' + measure + (amount > 1 ? 's' : '') + ' ago';
    return amount;
  };
})()

I got this function somewhere in the web while browsing. It works like a charm.

Expand/Collapse Reviews

Most of the user reviews are large chunk of texts and showing full text in a list is not very mobile friendly. So, we add an option to show/hide a full review if it has more than 120 characters. We add a XTemplate method applyExpandable() which checks the length of the text and add a “more” link to the end of the text after ellipsis.

applyExpandable()

applyExpandable: function (data) {
  var text = data.text;
  if (text.length > 120) {
    text = Ext.String.ellipsis(text, 120) +
      ' <span data-action="more" class="resize-action">more</span>';
  }
  return text;
}

We handle the user actions on “more” link in the PlaceDetails controller. Lets add a method which takes care of the same.

handleReviewExpansion()

/**
 * Handle text expansion of long reviews.
 */
handleReviewExpansion: function (panel) {
  panel.element.on('singletap', function (e, el) {
    el = Ext.get(el),
    textEl = el.parent('.text');
    // If "more" is pressed, get the complete text from hidden element and a "less" button
    if (el.getAttribute('data-action') === 'more') {
      textEl.setHtml(textEl.next('.full-review').getHtml() +
      ' <span data-action="less" class="resize-action">less</span>');
    }
    // If "less" is pressed, ellipsis the text and show
    if (el.getAttribute('data-action') === 'less') {
      textEl.setHtml(Ext.String.ellipsis(textEl.getHtml(), 120) +
      ' <span data-action="more" class="resize-action">more</span>');
    }
  }, this, {
    delegate: '.resize-action'
  });
}

On singletap on the review panel’s element, we capture the event and if the text is already collapsed, expand it with a “less” link and vice versa with a “more” link. We use the reviewer profile link to wrap both the user image and name – so, clicking it will open user’s profile in Google+.


5. A New Theme

Probably you have already noticed that I changed the theme from my previous version of the app. It was because the background of the app is dark and a lighter toolbar color will make it look nice. Also, I used dark colored buttons in toolbar for a better contrast. Here is the SASS file I used:

app.scss

// Basic color definitions
$base-color: #00E7EB;
$base-gradient: 'matte';
$active-color: #00BABD;
// Toolbar styles
$toolbar-base-color: $base-color;
$toolbar-button-color: #111;
// Tabbar styles
$tabs-dark: #333;
$tabs-dark-active-color: $base-color;
// List styles
$list-header-bg-color : #ddd;
@import 'sencha-touch/default/all';
// Default styling with only those components which are required
@include sencha-panel;
@include sencha-buttons;
@include sencha-tabs;
@include sencha-toolbar;
@include sencha-indexbar;
@include sencha-list;
@include sencha-layout;
@include sencha-carousel;
@include sencha-loading-spinner;
@include sencha-list-pullrefresh;
// Icons
@include pictos-iconmask('locate4');
@include pictos-iconmask('list');
@include pictos-iconmask('photos2');
@include pictos-iconmask('chat2');
// Buttons
@include sencha-button-ui('back', #333, 'matte');
@include sencha-button-ui('dark', #333, 'matte');

This is it. We are done with all the pages for the application and now it is a full fledged mobile website. You can try to wrap it with Phonegap and it should work as a native app as well without any issue.


Conclusion

In this final post of the series we covered a number of interesting topics which include:

  • Sencha routing
  • Display map with markers
  • iPhone like Infobubble
  • Mosaic image gallery similar to Pinterest layout

Along with these, we learnt how with CSS we can make an app look beautiful and professional. Do notice how we structured the application at start, used lazy rendering of components whenever required, destroyed a component while its not in use and commented our code thoroughly. These things improves your app’s performance and coding standards a lot. Happy coding!


iOS Quick Tip: 5 Tips to Increase App Performance

$
0
0

Even though the iPhone 5 and the iPad 4 ship with a powerful A6(X) processor and a lot more RAM than the original iPhone, this doesn’t mean that iOS applications are by definition fast and performant. In this quick tip, I will give you five tips to improve application performance.


1. Caching

If an iOS application fetches a remote resource, it is important that it doesn’t fetch that same resources every time it needs to access it. If you plan to develop a Twitter client for iOS, for example, then the client shouldn’t be downloading the same avatar every time it needs to display it. Caching the avatar will significantly improve application performance.

What is caching? The basic idea of caching is simple. When the application fetches a remote resource for the first time, it stores a copy of that resource on disk for later use. If that same resource is needed later, the application can load it from disk instead of fetching it remotely. The added benefit is that the resource is also available when the device is not connected to the web. The complex aspect of caching is how to store the resource, when to store it, and, most importantly, when to refresh it.

Olivier Poitrey has developed a small library named SDWebImage that is aimed at caching images. It provides a category on UIImageView much like AFNetworking does. The key difference is that SDWebImage caches the images it downloads. Images are downloaded in the background and each images is decompressed, resulting in faster load times. The library leverages Grand Central Dispatch to keep the main thread responsive. If your application works with remote images, then you should take a look at this gem.


2. Do Not Block the Main Thread

One of the most important lessons to learn when developing for the iOS platform is to never block the main thread. If you block the main thread with a long running operation, such as downloading images or other resources, your application will become unresponsive as long as the operation isn’t completed. Make it a habit to move long running tasks to a background thread. Even if you only need a handful of bytes, if the device’s connection to the web is slow, the operation will still block the main thread.

Writing safe multithreaded code has become a lot easier with the introduction of Grand Central Dispatch. Take a look at the following code snippets. The first snippet downloads data on the main thread, while the second snippet leverages Grand Central Dispatch to perform the same task on a background queue.

NSData *data = [NSData dataWithContentsOfURL:URL];
UIImage *image = [UIImage imageWithData:data];
[imageView setImage:image];
dispatch_queue_t queue = dispatch_queue_create("downloadAsset",NULL);
dispatch_async(queue, ^{
    NSData *data = [NSData dataWithContentsOfURL:URL];
    dispatch_async(dispatch_get_main_queue(), ^{
        UIImage *image = [UIImage imageWithData:data];
        [imageView setImage:image];
    });
});

3. Lazy Loading

Being lazy isn’t always bad especially if you are a programmer. Lazy loading is a well known concept in software development. It simply means that you postpone the instantiation of an object until you need the object. This is advantageous for several reasons. Depending on the object and the cost for instantiation, it can dramatically improve application performance and memory usage. Lazy loading objects isn’t a difficult concept. The simplest implementation of this pattern is overriding the getter of a property as shown below.

- (MyClass *)myObject {
    if (!_myObject) {
        _myObject = [[MyClass alloc] init];
    }
    return _myObject;
}

The lazy loading pattern can be applied in many areas of software development. For example, if part of your application’s user interface is not shown by default, it may be advantageous to postpone its instantiation until that part is about to be shown.

Table and collection views use a combination of caching and lazy loading to optimize performance. If the next cell or item is about to be displayed on screen, the table or collection view looks for a cell or item it can reuse (caching). Only if no reusable cell or item is available will the table or collection view (or its data source) instantiate a new cell or item (lazy loading).


4. Measure Performance

If you notice that your application is slow at times and you want to find out what is causing it, then you may need to profile your application with a tool such as Instruments. Colin Ruffenach wrote a nice tutorial about time profiling with Instruments on Mobiletuts+.

If Instruments doesn’t give you a clear answer, you may be interested in MGBenchmark, a benchmarking library developed and maintained by Mattes Groeger. This compact library lets you measure how fast your code executes and it helps you to track down the bottlenecks in your code base. It actually complements Instruments quite well so you shouldn’t use one or the other. Mattes Groeger’s library comes with an extensive feature set and is remarkably powerful. I highly recommend it as an alternative or complement to Instruments.


5. Stress Testing

If you’re developing an application that works with lots of data, then it is important to test it with…well…lots of data! During the development of an application, it isn’t always easy or possible to imagine how your users are going to use your application, but it is good to put it through its paces before releasing it. Your application may perform admirably during development and perform terribly in situations where it is flooded with data. You probably won’t be able to predict, let alone test, all the circumstances in which your application will be used, but you can try to mimic common situations by creating data sets to test with.

I often create a small script or class that populates the application with test data to measure how well it performs with a lot of data. Ideally, you want to populate the application with more test data than an average user will have – to the extent that you can predict this during development. When I was developing Pixelsync, an iPad application that interfaces with Aperture and iPhoto, I created an Aperture library with close to 500,000 images. If Pixelsync performed well with a photo library of that size, I could be fairly confident that the average user wouldn’t run into performance issues.

If you really are a lazy developer or you just don’t have enough time, then you can also be more creative by building a robot to do the job for you.

Conclusion

Application performance remains an essential aspect of software development despite the fact that the new generation of devices are much more powerful and capable. Blocking the main thread, for example, will always result in a bad user experience no matter how capable the device is.

iOS Quick Tip: 7 Tips to Speed Up Your Development

$
0
0

Most developers are always looking for ways to improve or automate their workflow being it with a simple script to automate a common task or by learning every possible keyboard shortcut of their favorite text editor. In this article, I will show you seven tips that will speed up and streamline your workflow in Xcode.


1. Keyboard Shortcuts

Virtually every Xcode command has a keyboard shortcut that you can learn and even customize. Investing the time to learn keyboard shortcuts will increase your efficiency tremendously. If you are like me, and a lot of other developers, you prefer to keep your hands on the keyboard when you’re writing code and that is exactly what most key bindings are for.

If you don’t like the default keyboard shortcuts, then you’ll be happy to learn that you can customize Xcode’s default keyboard shortcuts in the Preferences window under the Key Bindings tab.

iOS Quick Tip: 5 Tips to Speed Up Your Development - Managing Key Bindings in Xcode's Preferences Window

If you don’t want to scroll through the long list of keyboard shortcuts to learn the most important ones, then I recommend browsing through this question on Stack Overflow. It lists the most important key bindings in Xcode and also includes a number of other neat tips and tricks.


2. Cocoapods

Cocoapods, a great project started by Eloy Durán, has gained significant traction in the Cocoa community. What is Cocoapods? Cocoapods is a tool for managing dependencies in Xcode projects. Due to Cocoapods’ popularity, hundreds of third party libraries have been updated to support Cocoapods. Even though Cocoapods is distributed as a Ruby gem, you don’t need to understand Ruby to benefit from Cocoapods.

If you are tired of manually managing third party libraries in your iOS or OS X projects, or you’re looking for a solution that makes updating third party libraries easier, then Cocoapods is the best solution available. If you want to learn about Cocoapods, then I recommend reading a post about Cocoapods that I wrote earlier this year. It will get you up and running in minutes.


3. Code Snippets

Chances are that you use a text or code snippet manager, such as TextExpander or CodeBox. I use TextExpander all the time and it has saved me countless keystrokes over the years. However, Xcode also has a snippet manager. It lives in Xcode’s right sidebar next to the Object Library.

Why should you use Xcode’s snippet manager instead of the alternatives I mentioned earlier? In Xcode, each snippet has a number of additional attributes that really make snippets powerful and flexible. Each snippet has a Platform and Language attribute as well as a completion scope. A snippet’s completion scope is especially useful and integrates neatly with Xcode’s editor. Did I mention that each snippet can have multiple placeholders?

iOS Quick Tip: 5 Tips to Speed Up Your Development - Creating and Managing Code Snippets is Very Easy in Xcode

4. Learn Git

If you’ve just started to learn how to program and the concept source control is new to you, then I strongly recommend that you take some time to learn the basics of Git. Git is a distributed version control and source code management system. Euh … what? In short, Git helps you manage your source code easily and efficiently. Combine Git with GitHub or BitBucket and you are starting to look like a professional programmer. The added benefit is that Xcode integrates nicely with Git and this integration will only improve over time.

Once you’ve learned the basics of Git, it is recommended to include a proper .gitignore file in your Xcode project. A good place to start is this question on Stack Overflow.

Even though Git is commonly used through the command line, there are a number of applications that provide a GUI (Graphical User Interface). My favorite is Tower, developed by Fournova, which recently reached version 1.5. Atlassian‘s SourceTree is another great and free alternative.


5. Xcode Behaviors

Xcode is an incredibly powerful editor and it only gets better over the years. One of the aspects that I use frequently is Xcode’s Behaviors feature. By defining behaviors, you can tell Xcode what it should do when a particular event occurs, such as when a crash occurs while debugging or when you’ve successfully archived an application.

You can configure these behaviors in Xcode’s Preferences Window. If you don’t like it when Xcode shows the console when output is generated, you can disable this behavior in the Behaviors panel. Do you want Xcode to show the Issues Navigator when one or more tests fail? This is all possible with Xcode Behaviors.

iOS Quick Tip: 5 Tips to Speed Up Your Development - Managing Behaviors in Xcode's Preferences Window

Xcode predefines a number of common behaviors for you, such as Build, Testing, and Running. However, you can also define custom behaviors and assign a keyboard shortcut to it. This is a convenient way to set up Xcode the way you want with a single key binding.


6. Tabs and Windows

I really like the unified look of Xcode 4 and the integration of Interface Builder. However, it is sometimes useful to have more than one window at hand, for example, when you are working in the console and the editor at the same time or you use multiple monitors. In Xcode 4, it is still possible to work with multiple windows. You can create a new window using the keyboard shortcut Shift + Cmd + T or use the menu, File > New > Window.

Even more useful are tabs. Almost every code editor has tabs these days and Xcode is no exception. I rarely find myself in an Xcode window that has no, or one, tab. What few people know is that Xcode tabs can be named. You can even tie named tabs and Xcode behaviors together, which is especially useful during debugging.


7. Documentation

Even if you’ve been developing Cocoa applications for years, the documentation should never be more than a click away. There are a number of ways to quickly access Xcode’s documentation browser. The solution I use most often is holding the option key and clicking a method in Xcode’s code editor. This presents you with a summary of the particular method. If you want to read more or open the documentation browser, you can click one of the links in the pop up window.

iOS Quick Tip: 5 Tips to Speed Up Your Development - The Documentation is Your Friend

You can achieve the same by opening Xcode’s right sidebar and open the Quick Help Inspector tab. The help inspector updates as the cursor moves. As with the pop up window, clicking on one of the links in the help inspector takes you to the respective section in the documentation in the documentation browser.

iOS Quick Tip: 5 Tips to Speed Up Your Development - The Quick Help Inspector

One of the downsides of Xcode’s documentation browser is that it can be slow at times. For this reason, I frequently use Dash, a dedicated documentation and snippet manager. Dash is very fast and provides support for more than eighty languages, libraries, frameworks, and APIs. It is well worth checking out.

Conclusion

There are a lot more tips and tricks that can help you during your development, but these are the ones that I use most frequently. Are there any tips that you use all the time and save you a lot of time? Feel free to share them in the comments below.

Using Depth of Field in Your Mobile Application

$
0
0

If you use Corona SDK, you know that it’s a powerful and easy to use tool for creating mobile games. However, the platform is limited to just two dimensions, although they are coming out with built-in ways to simulate 3-D effects. As a game developer, you can somewhat overcome this limitation by using what’s known as the depth of field concept.

Although I’ll be referring to Corona SDK in my example, the depth of field theory can be applied to any development platform and to almost any graphic object.


What is Depth of Field?

If you’ve dabbled in photography, you already know what depth of field is. For those of us that don’t know, I’ll try to explain. Depth of field is the blurry effect that you see in photographs caused by the focus on a particular object over others. Depth of field is based on two concepts, perception of focus and emulating distance.

In the first concept, the perception of focus will be used on two objects or two planes to create an environment with depth. One object will be the focal point and the other objects or planes will be out of focus. The out of focus objects will almost always appear blurry to help control the viewer’s focus.

The second concept is emulating distance. When you look at scenery, the closet objects to you will appear larger and objects further away will appear smaller. Depth of field uses this concept to reinforce the illusion of depth in the scene. By making objects in a 2D environment larger or smaller, the viewer’s mind will be tricked into assuming there’s depth in the scene.

If you’ve played video games before or even looked at some popular photographs, you’ve already seen the depth of field concept in use, but you may not have recognized the effect. If you run a quick Google Image search for depth of field, you can see hundreds of examples for this style of photography. The photos that you will see range from landscapes to wedding photos. In each depth of field photograph, you instantly know what the photographer wants the viewer to look at.

By using depth of field, you can control the focus of the scene and create depth by focusing in on the main subject while keeping other elements out of focus. Although I enjoy developing with Corona SDK, this effect is a simple but powerful way to overcome the 2D limitations of the Corona SDK platform.


Taking a Closer Look

In the following examples, I’ll be explaining how depth of field can be used in mobile games. Feel free to use the depth of field concept in any of your mobile applications with the SDK of your choice.

Now that we know what depth of field is, let’s look at an example of a scene that does not use the depth of field concept.


In this scene, both birds are in focus and it appears like a flat screen to the viewer. Although this scene may be good enough for some games, we can use the depth of field concept to make the scene more interesting to the player and really bring it to life. In the next scene, you’ll see a basic example of the depth of field concept in use.


By using the depth of field concept, this scene has simulated distance by keeping the bird on the left in focus and leaving the bird on the right out of focus. Making the out of focus bird smaller also reinforces the perception of distance.

We can also reverse the scene to make the focal point appear further away. Making the larger objects blurred and keeping the focal point in focus accomplish this reversal. Let’s take a look at an example of this scene reversal.


In the reversal scene, we’ve made the smaller bird the focal point and we’ve tricked the viewer into assuming the smaller bird is further away by blurring the closest objects.


Creating the image

By now, you may be curious about how these graphics were created. If you have Photoshop or a similar graphic editing tool, you can use a Gaussian Blur to create depth of field scenes. I’m sure any type of blur will work, but I’ve had the most success with the Gaussian Blur. Let’s take a look at how to create a blurred and a non-blurred graphic for our app.

Step 1

With Photoshop open, create a new document that’s 150 pixels by 150 pixels. The default setting for the other options will work for this tutorial.


Step 2

Next, click on the custom shapes tool and select Bird 2 from the shape dropdown menu. In this tutorial, I’m using the shape of a bird. Any shape will work.


Step 3

Now that we’ve selected a shape, let’s change the foreground color to a nice blue color by clicking on the Set Foreground Color option.


Set the foreground color to #1194f3.

Step 4

With the shape tool, create a bird shape inside the document and try to make the bird fill up most of the document.


Step 5

Let’s add a simple border to our shape to give it some definition.


Add a 2px black border on the outside of our shape.

Step 6

Before we add the Gaussian Blur to our shape, let’s go to File > Save for Web > Devices to save the shape as a PNG file to our project folder. The unblurred shape that we are saving will be used as the focal point in our mobile application.


Step 7

After we’ve saved the non-blurred shape, we can now add the Gaussian Blur. With most shapes, you can just add a Gaussian Blur without having to complete any extra steps. In our case, we will have to use the Flatten Image option in Photoshop because we’re using a border. If we don’t flatten the image, the blur will produce some unexpected results.

Right-click on the shape layer and click on Flatten Image. After the image is flattened, add the Gaussian Blur by going to Filter > Blur > Gaussian Blur.


Flatten Image, available when right-clicking the shape layer

Gaussian Blur with Radius at 2.2, feel free to adjust the radius for different blur effects.

Step 8

With our Gaussian Blur applied to our shape, we can now save the shape to our project folder using the same instructions in Step 6.

After you’ve completed these eight steps, you should have two birds in your project folder, a clear and crisp image of a bird and a blurry bird. You can now incorporate these images into your next mobile application.


A screenshot of the birds in a game built with Corona SDK.

In this tutorial, I hope you’ve learned how to use the depth of field concept to bring your games to life. Although I’ve used a bird in my tutorial, you can use almost any object to create a scene with depth using the depth of field concept. You can apply depth of field to the background on your next 2D side scroller or apply the concept to certain elements of your next horror game. Thanks for reading!

The Best Place for Mobile Developers to Get Design Work Done

$
0
0

Envato (the people behind Mobiletuts+) have recently launched a new service, currently in beta, called Microlancer. It’s a place where people can sell their design services, or get design work done for a fixed price.

Making great apps takes years to master, leaving little time left over to get really good at design. And yet, app developers regularly need to get design work done for things like:

Most of the time, the existing solution is less than ideal:

  • Do it yourself – not being a professional designer or artist, the results aren’t always great
  • Ask a friend – as we’ve all experienced at one time or another, working with friends can be a bad idea, especially when you have a firm deadline that needs to be met
  • Work with a designer you found – sometimes this works out great, but other times it can be expensive and frustrating with results that don’t match your vision

We created Microlancer because we think there’s a much better way to get this kind of design work done.


How Microlancer Helps

Microlancer brings together hundreds of skilled and affordable designers whose services can be browsed in a shop-like format, each service offered for a fixed price. You can view examples of service providers’ previous work, and browse services by the category most relevant to you (for example, Mobile App Icon Design).

The turnaround time and number of changes/revisions included in the price are stated upfront for each service. The designer must stick to these promises, or you’ll be eligible for a full refund.

Though Microlancer jobs generally go smoothly, a dispute resolution process is always available. In these cases, a trained staff member will step in to fairly resolve any disagreements that arise. We want unwinnable battles with contractors to be a thing of the past.

Microlancer aims to make it fun and easy to work with affordable, talented designers and artists on small projects.


It’s Easy to Find What You’re Looking For

Looking for the perfect freelancer to do exactly the work you need done can be a time-consuming challenge. We often end up settling for freelancers who aren’t quite the perfect fit, simply because we don’t have time to look any further.

Microlancer uses search, descriptively named categories and visual previews of work to make it extremely simple to find exactly what you’re looking for.


What’s Next?

Microlancer is currently in beta and, to start with, is focusing exclusively on design services. Next, we’ll be opening up HTML, CSS and WordPress services for both purchase and sale. If you’ve got design covered but need some simple, affordable help with your website, stay tuned for news on this. The best way to keep up to date with new features, categories and developments is to like Microlancer on Facebook or get updates on Twitter.

If you haven’t already, take 2 minutes to check out Microlancer, the newest service by Envato.

Android SDK: Create an Arithmetic Game – Setup and Interface Creation

$
0
0

The Android platform provides the ability to facilitate interaction, to carry out computation, and to save data. To explore some of these basic features with a practical focus, this series will create a simple arithmetic game for the Android platform. The series will involve creating user interface elements and layouts, four Activity classes, and one helper class. The gameplay logic will be relatively complex if you do not have much Java experience, but you will learn to use various Java control structures along the way!


Series Format

This series on Creating an Arithmetic Game will be released in three parts:


Game Overview

The following is a screenshot of the game I’ll teach you how to build:

Arithmetic Game

When the app launches, the user will be presented with a menu screen offering three options: Play, How to Play, and High Scores. On selecting the Play option, the user will be prompted to choose a level (i.e. easy, medium, or hard). The Play screen will display a calculator-style interface in which the user is presented with arithmetic questions that may include addition, subtraction, multiplication, or division. Their chosen level will determine the range of numerical operators used in the questions.

The user will be able to enter their answer and will receive feedback regarding whether or not it was correct, with a score indicator updating throughout. The user will simply be continuously presented with questions until they decide to quit. The score will be reset to zero if the user enters an incorrect answer, with high scores in the game being the number of questions answered correctly in a row.

The How to Play screen will include textual information about the game, with the High Scores screen listing the ten top scores along with their dates. As well as implementing the gameplay, the application code will need to automatically store any high scores reached by the user as they play the game. This will involve the Shared Preferences for the app. We will also be saving and restoring instance state data so that gameplay continues when the app is hidden or paused.


1. Create a New Android Project

Step 1

Open Eclipse and create a new project for your arithmetic game app. Enter a package and application name of your choice – we will be using a minimum SDK of 14 with a target of 17 for the code in this tutorial.

New Android Project

Let Eclipse create a blank Activity for you plus a main layout for it.

Arithmetic App Setup

Step 2

Open the Manifest file for your project and switch to the XML editing tab. Your main Activity should already be listed in the application element. After it, add three more Activity elements, before the closing application tag:

<activity android:name=".PlayGame"></activity><activity android:name=".HowToPlay"></activity><activity android:name=".HighScores"></activity>

Open your application’s “res/values” strings XML file to set the app name. Edit the automatically generated “app_name” string to suit a name of your choice, for example:

<string name="app_name">You Do The Math!</string>

2. Create Visual Elements

Step 1

The application is going to use three images we prepare outside of Eclipse. When the user answers a question, they will be presented with a tick or a cross image indicating whether they answered correctly or incorrectly. You can use the following images or create your own if you wish. The images are white since the background is going to be dark. Add them to your app’s drawable folder(s):

Tick
Cross

Step 2

A couple of the app screens are going to use a header image depicting the arithmetic operators which is purely for decoration – copy it to your drawables or use your own:

Operators

These images are also included in the download folder for this tutorial. If you are targeting more than one device screen density with multiple drawable folders, remember to add image files for each target. Otherwise, you can simply use a single folder named “drawable” to hold the images, but Eclipse does not create this folder by default.

Step 3

The game interface is going to use three backgrounds we will define in XML as part of the project. In your app’s drawables folder, create a new file named “app_back.xml”, entering the following code:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"><gradient
	android:startColor="#ff006666"
	android:endColor="#ff003333"
	android:angle="90"
/></shape>

This gradient will be displayed in the screen backgrounds – we will add references to it and the other drawables when we define the application layouts. You can of course change any of the colors if you like. Next create another new drawables files named “num_back.xml” with the following content:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"><gradient
	android:startColor="#ff003333"
	android:endColor="#ff009966"
	android:angle="90"
/><corners android:radius="20dp" /><stroke
	android:color="#ff003333"
	android:width="2dp"
/></shape>

This background will be used for the numerical buttons during gameplay. This time we use rounded corners and a border together with the gradient fill to define a button shape. Finally, add another drawables file, naming it “enter_back.xml” and adding the following content:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"><gradient
	android:startColor="#ff669999"
	android:endColor="#ff99ffcc"
	android:angle="90"
/><corners android:radius="20dp" /><stroke
	android:color="#ff003333"
	android:width="2dp"
/></shape>

This will be used for the enter button, with similar content as the previous drawable but slightly different colors.


3. Create the Main Activity Layout

Step 1

Open the main layout file Eclipse created when you started the project. This will be the layout users see on first launching the app and will present the three options for playing a game, reading how to play or viewing the high scores. Replace the content of the file with the following Scroll View and Relative Layout:

<ScrollView 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"
	android:background="@drawable/app_back"
	android:padding="10dp" ><RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:gravity="center" ></RelativeLayout></ScrollView>

Notice that we refer to one of the drawable background files we created and center the content of the screen.

Step 2

Now let’s add the content of the main Activity to its layout. Inside the Relative Layout, first add the image containing the operator symbols:

<ImageView
	android:id="@+id/operators"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_centerHorizontal="true"
	android:src="@drawable/operators"
	android:padding="5dp"
	android:contentDescription="operators"/>

We use an ID so that other items in the layout can be laid out relative to this one. We also refer to the image file we added to the drawables earlier. Eclipse will prompt you to store the content description attribute in the strings resource file, which is generally better for performance but in this case we will not be using the string anywhere else in the application so you can leave it hard-coded in the layout file.

Step 3

After the image, add a Text View in which the app name appears:

<TextView
	android:id="@+id/intro"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/operators"
	android:layout_centerHorizontal="true"
	android:padding="20dp"
 	android:text="@string/app_name"
	android:textColor="#ffffffff"
 	android:textSize="20sp"
	android:textStyle="bold" />

We position this view relative to the image we already added, using an ID attribute again for subsequent items to position themselves relative to this one. We use the “app_name” string we altered in the strings values file earlier.

Step 4

Now let’s add buttons for launching the three options. First the button to play a game:

<Button
	android:id="@+id/play_btn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/intro"
	android:layout_centerHorizontal="true"
	android:layout_margin="5dp"
	android:background="@drawable/enter_back"
	android:padding="20dp"
	android:text="Play"
	android:textColor="#ff333333"
	android:textSize="20sp"
	android:textStyle="bold" />

This time we will use the ID attribute for positioning other items and for identifying the button in Java. Notice that we use one of the backgrounds we created as a drawable file. Next add the How to Play button:

<Button
	android:id="@+id/help_btn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/play_btn"
	android:layout_centerHorizontal="true"
	android:layout_margin="5dp"
	android:background="@drawable/enter_back"
	android:padding="20dp"
	android:text="How to Play"
	android:textColor="#ff333333"
	android:textSize="20sp"
	android:textStyle="bold" />

Finally add the High Scores button:

<Button
	android:id="@+id/high_btn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/help_btn"
	android:layout_centerHorizontal="true"
	android:layout_margin="5dp"
	android:background="@drawable/enter_back"
	android:padding="20dp"
	android:text="High Scores"
	android:textColor="#ff333333"
	android:textSize="20sp"
	android:textStyle="bold" />

We will handle clicks on these buttons in the main Activity class later. That’s the main layout file complete.

Main Activity Screen

4. Create the Gameplay Activity Layout

Step 1

Let’s now turn to the gameplay Activity layout, which will be the most complex one. In your app’s layout folder, create a new file, naming it “activity_playgame.xml” and entering the following outline:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="@drawable/app_back"
	android:orientation="vertical" ></LinearLayout>

We use the background drawable again. The gameplay Activity screen will use nested Linear Layouts, which suits the calculator style content we will be placing in it. As you can see from the preview image above, the screen will display an area for the tick or cross image feedback together with the score at the top, then an area for the question, answer and clear button under that. Finally, running to the bottom of the screen will be the number pad with digits 0 to 9 plus the enter key.

Step 2

Inside the Linear Layout you added, add another one:

<LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal"></LinearLayout>

Inside this Linear Layout, place an Image View for the tick/cross image and a Text View for the score:

<ImageView
	android:id="@+id/response"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center|left"
	android:layout_weight="1"
	android:contentDescription="result"
	android:src="@drawable/tick" /><TextView
	android:id="@+id/score"
	android:layout_width="wrap_content"
  	android:layout_height="match_parent"
	android:layout_weight="1"
	android:gravity="center|right"
	android:paddingRight="20dp"
	android:text="Score: 0"
	android:textColor="#ffffffff"
	android:textSize="20sp"
	android:textStyle="bold" />

We start the Image View with the tick image displayed so that you can see how the layout comes together – in reality we will set either the tick or cross to display during gameplay. Similarly, we start the score Text View with a score of zero and will continually set it from Java as the gameplay progresses.

Step 3

Still inside the main Linear Layout in the file but after the nested one with the image and score text in it, add another Linear Layout:

<LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal" ></LinearLayout>

Inside this Linear Layout, add Text Views for the question and answer, then the clear button:

<TextView
	android:id="@+id/question"
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_marginBottom="2dp"
	android:layout_marginLeft="2dp"
	android:layout_marginTop="2dp"
	android:layout_weight="1"
	android:background="#ffffffff"
	android:gravity="center|right"
	android:padding="5dp"
	android:text="0 + 0"
	android:textColor="#ff333333"
	android:textSize="30sp" /><TextView
	android:id="@+id/answer"
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_marginBottom="2dp"
 	android:layout_marginRight="2dp"
	android:layout_marginTop="2dp"
	android:layout_weight="1"
	android:background="#ffffffff"
	android:gravity="center|left"
	android:padding="5dp"
	android:text="= ?"
	android:textColor="#ff333333"
	android:textSize="30sp" /><Button
	android:id="@+id/clear"
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_margin="1dp"
	android:layout_weight="1"
	android:background="@drawable/enter_back"
	android:gravity="center"
	android:padding="5dp"
	android:text="C"
	android:textColor="#ff333333"
	android:textSize="30sp"
	android:textStyle="bold" />

We will use the ID attributes to refer to all of these in Java during gameplay. We use one of the background drawables for the “C” button. As before, we set dummy initial text to display in the question and answer views.

Step 4

Next we are going to add the number pad area, which will involve four more horizontal Linear Layouts added after the last one but still inside the outer one:

<LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal" ><Button
		android:id="@+id/btn7"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="7"
		android:text="7"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn8"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="8"
		android:text="8"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn9"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="9"
		android:text="9"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /></LinearLayout><LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal" ><Button
		android:id="@+id/btn4"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="4"
		android:text="4"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn5"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="5"
		android:text="5"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn6"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="6"
		android:text="6"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /></LinearLayout><LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal" ><Button
		android:id="@+id/btn1"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="1"
		android:text="1"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn2"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="2"
		android:text="2"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/btn3"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="3"
		android:text="3"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /></LinearLayout><LinearLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_weight="1"
	android:orientation="horizontal" ><Button
		android:id="@+id/btn0"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="1"
		android:background="@drawable/num_back"
		android:gravity="center"
		android:padding="5dp"
		android:tag="0"
		android:text="0"
		android:textColor="#ffcccccc"
		android:textSize="30sp"
		android:textStyle="bold" /><Button
		android:id="@+id/enter"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_margin="1dp"
		android:layout_weight="2"
		android:background="@drawable/enter_back"
		android:gravity="center"
		android:padding="5dp"
		android:text="Enter"
		android:textColor="#ff333333"
		android:textSize="30sp"
		android:textStyle="bold" /></LinearLayout>

Notice that each numerical button also has a tag attribute representing its number – we will use this in Java to implement gameplay. That’s the gameplay layout complete, if you are in any doubt about the overall structure, check yours against the one in the download folder.

Gameplay Activity Screen

5. Create the How to Play Layout

Step 1

Create a new file in your layout folder, naming it “activity_how.xml” and entering the following outline:

<ScrollView 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"
	android:background="@drawable/app_back"
	android:padding="10dp" ><RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:gravity="center" ></RelativeLayout></ScrollView>

We use a containing Scroll View to ensure that the content will scroll if necessary. Inside the Relative Layout, add the operators image, a Text View header and a Text View for the How to Play information:

<ImageView
	android:id="@+id/operators"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_centerHorizontal="true"
	android:contentDescription="operators"
	android:src="@drawable/operators" /><TextView
	android:id="@+id/intro"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/operators"
	android:layout_centerHorizontal="true"
	android:padding="20dp"
	android:text="@string/app_name"
	android:textColor="#ffffffff"
	android:textSize="20sp"
	android:textStyle="bold" /><TextView
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:layout_below="@id/intro"
	android:layout_centerHorizontal="true"
	android:padding="10dp"
	android:text="@string/help_info"
	android:textColor="#ffffffff"
	android:textSize="16sp" />

That’s the How to Play layout complete. In your “res/values” strings XML file, add the information string we refer to here:

<string name="help_info">You Do The Math! is an arithmetic game.\n\nChoose Easy, Medium or Hard level to begin.\n\nAnswer the question by typing on the number pad displayed and pressing enter. Press the C button to clear an existing answer. Each question may be an addition, subtraction, multiplication or division.\n\nYour ten best scores will automatically be saved into the High Scores.</string>

You can of course alter the information if you wish.

How to Play Activity Screen

6. Create the High Scores Layout

Step 1

The only layout left to define is the one for the High Scores screen. Add another new file to your layout folder, this time naming it “activity_high.xml” and with the following initial content:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="fill_parent"
	android:background="@drawable/app_back"
	android:padding="10dp" ><RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:gravity="center" ></RelativeLayout></ScrollView>

Add the image and a couple of heading Text Views inside the Relative Layout:

<ImageView
	android:id="@+id/operators"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_centerHorizontal="true"
	android:contentDescription="operators"
	android:src="@drawable/operators" /><TextView
	android:id="@+id/intro"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/operators"
	android:layout_centerHorizontal="true"
	android:padding="20dp"
	android:text="@string/app_name"
	android:textColor="#ffffffff"
	android:textSize="26sp"
	android:textStyle="bold" /><TextView
	android:id="@+id/high_head"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_below="@id/intro"
	android:layout_centerHorizontal="true"
	android:padding="20dp"
	android:text="HIGH SCORES"
	android:textColor="#ffffffff"
	android:textSize="24sp"
	android:textStyle="bold" />

Finally, still inside the Relative Layout, add a Text View in which we will display the high scores:

<TextView
	android:id="@+id/high_scores_list"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:layout_below="@id/high_head"
	android:layout_centerHorizontal="true"
	android:gravity="center"
	android:padding="10dp"
	android:textColor="#ffffffff"
	android:textSize="22sp"
	android:textStyle="bold" />

We will populate the high score text from Java when the screen is launched, so at the moment your layout will not show much. That’s all the app layouts complete – you can see their initial appearance in the Graphical Layout tab in Eclipse.


7. Create Activity Classes

Step 1

When you created the project Eclipse should have created a blank main Activity for you. Now create the other Activities for the app. Start with the gameplay Activity – select your package and right-click or choose “File” then “New” and “Class.” Enter “PlayGame” as the class name and choose the Android Activity class as the superclass:

New Activity

Do the same for the other two Activities, naming the How to Play Activity “HowToPlay” and the High Scores Activity “HighScores” to match what you listed in the Manifest file.


8. Setup Main Activity Class

Step 1

The last thing we will do in this tutorial is get the main Activity class started. Open your main Activity Java file. Eclipse should have already added the onCreate method in which the main layout is set:

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

Add the following import statements to your class:

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

Add the following instance variables before your onCreate method:

private Button playBtn, helpBtn, highBtn;

We will use these to refer to the three buttons.

Step 2

Retrieve references to the button items after the existing code inside onCreate:

playBtn = (Button)findViewById(R.id.play_btn);
helpBtn = (Button)findViewById(R.id.help_btn);
highBtn = (Button)findViewById(R.id.high_btn);

Set the click listeners for these buttons next:

playBtn.setOnClickListener(this);
helpBtn.setOnClickListener(this);
highBtn.setOnClickListener(this);

Extend the opening line of the class declaration to implement click listening as follows:

public class MainActivity extends Activity implements OnClickListener

Step 3

Now let’s add the onClick method for responding to user clicks, after the onCreate method:

@Override
public void onClick(View view) {
//respond to clicks
}

Inside this method, find out which button was clicked and create a code block to respond to each:

if(view.getId()==R.id.play_btn){
	//play button
}
else if(view.getId()==R.id.help_btn){
	//how to play button
}
else if(view.getId()==R.id.high_btn){
	//high scores button
}

We will implement these responses in the next part of the series.


Conclusion

That is the first part of the series complete. In this tutorial we have set the application Activities and layouts up ready for building the gameplay functionality. In the second part of the series we will code all of the application gameplay logic. In the final part we will implement the High Scores and How to Play screens as well as saving high scores during gameplay and saving the app instance state data for continuity.

iOS Quick Tip: From Novice to Expert

$
0
0

Even though it’s possible to learn the essentials of iOS Development in a weekend, it will take much longer to master the craft. The question then is how do you transition from a novice to an expert? In this quick tip, I will provide you with a breadcrumb trail that may help you on your way to becoming a great iOS developer.


1. Practice, Practice, Practice

There are no shortcuts. This is something that I’d like to emphasize before continuing, because it is important to get rid of any illusions that might be stuck in your head. You don’t become a skilled developer if you only program on Sunday afternoons between 4PM and 5PM…if the sun isn’t shining and there’s nothing on television. Don’t get me wrong, it may be fun to do so, but it won’t bring you much closer to your goal of becoming an expert developer. Apart from a few exceptions, most people need practice -and lots of it. If you aren’t prepared to put in the hours, then it’s better to revisit your goals and ambitions. Become great at something you love and the time spent practicing will be its own reward.


2. Learn From Others

One of the best strategies to improve your skills and to adopt best practices is to learn from other people’s code. This not only means browsing Stack Overflow, but also, and more importantly, libraries or code snippets that are open sourced by fellow developers.

Whenever you dive into a library, such as AFNetworking or Magical Record, it is key to not be overwhelmed by the code you read. Chances are that you don’t understand every line of code in these libraries, but that’s not really the point. The point is to look at the source code from a higher level and learn as much as possible, such as naming conventions, best practices, design patterns, etc.

In addition to learning from other people’s code, it is a good learning experience to create your own libraries. Some time ago, I was developing an application that uses Core Data as the data layer. Instead of using Magical Record, I decided to create my own library by exploring Magical Record and recreating the pieces of functionality that I needed. Not only did this result in a lean, agile library, it also taught me a lot about the inner workings of Magical Record.


3. Don’t Copy and Paste

This brings me to another key aspect of learning the right way: don’t mindlessly copy and paste code. We all use code snippets that we find on places like Stack Overflow or Apple’s Developer Forums, but it’s important to not mindlessly copy and paste the code that you find on the web. By copying code that you find, you don’t learn a thing. The greater danger is that you don’t know what you’ve just added to your code base. This might result in unexpected behavior and it will make it very difficult to debug your code later.

It may be tempting from time to time to quickly use a code snippet that seemingly solves the problem that you are working on, but I strongly urge against this practice. Read the code, understand what you are adding into the code base, and, possibly, customize the solution to your needs.

It goes without saying that this doesn’t apply to libraries or frameworks that are actively maintained. If you had to go through Magical Record before you’d be able to use it in your project…I’m sure you understand the difference. Use your common sense.


4. Patterns

Cocoa and Objective-C are in many ways very different from other programming languages and environments. This means that they have their own patterns and best practices. I’m sure that you’re already familiar with a few common patterns, such as delegation and notifications. However, there are many more patterns that can help you during your development, such as the singleton, observer, and command patterns. The Cocoa Fundamentals Guide gives you a nice overview of the most common patterns in Cocoa.


5. Know Your Tools

Becoming a great developer isn’t only about understanding the language and the frameworks. It is just as much about working efficiently with the tools that you use day in and day out. For iOS development, this means Xcode and possibly other tools, such as PonyDebugger and Charles.

If you’d like to learn a few extra tricks, you may be interested in a previous quick tip that I wrote about this topic.


6. Stay Up To Date

Even if you can’t attend Apple’s yearly developer conference, WWDC, it is a good idea to browse the numerous session videos and watch the ones that spark your interest. The presentations are usually given by the engineers that work on the technologies covered in the session, which gives you detailed information and instructions about how to use them. It is also a great way to quickly get up to speed with those technologies.

There are many excellent developers that regularly write about their craft, such as Matt Gemmell, Aaron Hillegass, and Mike Ash. You can find a more extensive list in a previous post I’ve written for Mobiletuts+.


Bonus: Learn Other Languages

I have noticed that my overall understanding of software development has improved significantly by learning new languages or working with new frameworks. The advantage of this approach is that you don’t limit your view of what is possible to the language that you are most familiar with.

I recently dipped my toes in Ember.js and learned that the creators, Yehuda Katz and Tom Dale took inspiration from Cocoa. The implementation of the MVC (Model-View-Controller) pattern of Ember.js is a bit unconventional for a JavaScript framework, but it is not that surprising if you are familiar with Cocoa.

There is no “best” language to write software in as they all have their pros and cons. The nice thing, however, is that they are all a little (or a lot) different and it’s those differences that makes learning new languages interesting and eye opening. Ruby, for example, was a real eye opener for me in terms of writing DRY (Don’t Repeat Yourself), readable, and clean code.


Conclusion

If you don’t want to put in the hours to become a better programmer, then you may want to reconsider why you wanted to become a programmer in the first place. However, if you get excited about a new library or tool that can help you in your development, then you probably won’t have a problem improving your skills over time. You really have to love what you do to become good at it and I think this is especially true for programming. No matter what people – or ads – tell you, you won’t become an expert developer overnight, but I promise you that your skills will improve if you keep learning and beating on your craft.

Wanted: Awesome Writers

$
0
0

Mobiletuts+ is currently seeking to hire writers to cover mobile web development (e.g. JavaScript, HTML, CSS, etc.), iOS SDK development, Android SDK development, Mobile UI/UX, and more! If you’re a talented mobile developer with tips or techniques to share, we want to hear from you!


What We’re Looking For

  • Rock solid knowledge: Mobiletuts+ strives to be a highly respected source of information. Our authors must have excellent knowledge about their topic and be willing to perform thorough research when necessary.
  • Superb writing skills: You must be comfortable with the English language. We can proof your content, but we can’t rewrite everything for you. To put it simply, if you struggle when deciding whether to write its or it’s, this might not be the gig for you.

What’s the Pay?

  • $200 – $250 USD per full-length tutorial (depending on length/quality).
  • $50 – $150 USD per quick-tip (depending on length/quality).
  • Negotiable rates for well-known authors. Get in touch!

The exact rate is dependent on your industry experience, communication skills, and tutorial idea.


Excellent! Where Do I Sign Up?

We’ll need the following information from you:

  • Name.
  • A brief paragraph about your background and why you’re a good fit.
  • A single link to an article that you’ve written, helped create, or like.
  • Two ideas for tutorials that you’d like to write for us.

Optional: E-mails with a tutorial attached will get more attention from us since this means that you’re serious about the gig.


Please e-mail the above information to:

contact e-mail

We get a fair number of single line e-mails. Don’t be that person. E-mails without the required information will be discarded.

We hope to hear from you soon!


iOS Succinctly – Introduction

$
0
0

Cell phone applications are one of the fastest-growing segments of the technology industry, and the iPhone and iPad have been the leaders of this mobile revolution. Developing applications for these platforms opens the door to millions of mobile users. Unfortunately, the many hidden technologies can be overwhelming for newcomers to iOS, and the 1,500+ official help documents available from the iOS Developer Library don’t really provide an approachable introduction to the platform. The goal of iOS Succinctly is to provide a simple, understandable overview of the iOS landscape.


iOS and the iOS SDK

iOS is the operating system that runs the iPhone and iPad. It takes care of the low-level system tasks like managing memory, opening and closing applications, and rendering pixels to the screen. On top of this core operating system rests a collection of frameworks, which are C and Objective-C libraries, providing reusable solutions to common programming problems. For example, the UIKit Framework defines classes for buttons, text fields, and several other user interface components. Instead of implementing your own buttons from the ground up, you can leverage the existing UIButton class.

Together, the core operating system and these higher-level frameworks make up the iOS software development kit (SDK). The goal of the iOS SDK is to help you focus on developing what your application does instead of getting bogged down by how it does it. The SDK is divided into layers based on what level of abstraction they provide.

As a developer, you’ll rarely interact directly with the Core OS layer. Most of the time, you’ll be working with the frameworks in the Cocoa Touch, Media, or Core Services layers and let them handle the low-level operating system tasks for you.


About iOS Succinctly

iOS Succinctly is the second half of a two-part series on iPhone and iPad app development. The first book, Objective-C Succinctly, covered the Objective-C language and the core data structures used by virtually all applications. With this in mind, iOS Succinctly assumes that you’re already comfortable with Objective-C and have at least a basic familiarity with the Xcode integrated development environment (IDE).

This book begins by exploring the basic design patterns behind iOS development. We’ll learn how to create a user interface using a very simple, one-scene application. Then, we’ll expand this knowledge to a more complicated multi-scene application. By this point, you should have a solid grasp of the iOS workflow. The remaining chapters look at common development tasks like accessing files, localizing assets for different audiences, and playing sounds.

This lesson represents a chapter from iOS Succinctly, a free eBook from the team at Syncfusion.

iOS Succinctly – Hello, iOS!

$
0
0

In this article, we’ll introduce the three main design patterns underlying all iOS app development: model-view-controller, delegate objects, and target-action.

The model-view-controller pattern is used to separate the user interface from its underlying data and logic. The delegate object pattern makes it easy to react to important events by abstracting the handling code into a separate object. Finally, the target-action pattern encapsulates a behavior, which provides a very flexible method of performing tasks based on user input.

We’ll discuss these patterns in detail as we’re building up a simple example application. This should also give us some experience with basic user interface components like buttons, labels, and text fields. By the end of this chapter, you should be able to configure basic layouts and capture user input on your own.


Creating a New Project

First, we need to create a new Xcode project. Open Xcode and navigate to File > New > Project, or press Cmd+Shift+N to open the template selection screen. In this chapter, we’ll be creating the simplest possible program: a Single View Application. Select the template, and then click next.

tutorial_image
Figure 2: Selecting the Single View Application template

Use HelloWorld as the Product Name, anything you like as Organization Name, and edu.self as the Company Identifier. Make sure that Devices is set to iPhone and that the Use Storyboards and Use Automatic Reference Counting options are selected.

tutorial_image
Figure 3: Configuration for our HelloWorld app

After choosing a location to save the file, you’ll have your very first iOS app to experiment with.


Compiling the App

As we saw with the command-line application in Objective-C Succinctly, you can compile the project by clicking the Run button in the upper-left corner of Xcode or using the Cmd+R keyboard shortcut. But, unlike Objective-C Succinctly, our application is a graphical program that is destined for an iPhone. Instead of simply compiling the code and executing it, Xcode launches it using the iOS Simulator application. This allows us to see what our app will look like on the iPhone without having to upload it to an actual device every time we make the slightest change. The template we used is a blank project, so you’ll just see a white screen when you run it.

tutorial_image
Figure 4: Running the HelloWorld project in the iOS Simulator

While we can’t really tell with our current app, the simulator is a quite detailed replica of the actual iPhone environment. You can click the home button, which will display all the apps that we’ve launched in the simulator, along with a few built-in ones. As we’ll see in a moment, this lets us test the various states of our application.


App Structure Overview

Before we start writing any code, let’s take a brief tour of the files provided by the template. This section introduces the most important aspects of our HelloWorld project.

main.m

As with any Objective-C program, an application starts in the main() function of main.m. The main.m file for our HelloWorld project can be found in the Supporting Files folder in Xcode’s Project Navigator panel. The default code provided by your template should look like the following.

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc,
                                 argv,
                                 nil,
                                 NSStringFromClass([AppDelegate class]));
    }
}

This launches your application by calling the UIApplicationMain() function, and passing [AppDelegate class] as the last argument tells the application to transfer control over to our custom AppDelegate class. We’ll discuss this more in the next section.

For most applications, you’ll never have to change the default main.m. Any custom setup can be deferred to the AppDelegate or ViewController classes.

AppDelegate.h and AppDelegate.m

The iOS architecture relies heavily on the delegate design pattern. This pattern lets an object transfer control over some of its tasks to another object. For example, every iOS application is internally represented as a UIApplication object, but developers rarely create a UIApplication instance directly. Instead, the UIApplicationMain() function in main.m creates one for you and points it to a delegate object, which then serves as the root of the application. In the instance of our HelloWorld project, an instance of the custom AppDelegate class acts as the delegate object.

This creates a convenient separation of concerns: the UIApplication object deals with the nitty-gritty details that happen behind the scenes, and it simply informs our custom AppDelegate class when important things happen. This gives you as a developer the opportunity to react to important events in the app’s life cycle without worrying about how those events are detected or processed. The relationship between the built-in UIApplication instance and our AppDelegate class can be visualized as follows.

tutorial_image
Figure 5: Using AppDelegate as the delegate object for UIApplication

Recall from Objective-C Succinctly that a protocol declares an arbitrary group of methods or properties that any class can implement. Since a delegate is designed to take control over an arbitrary set of tasks, this makes protocols the logical choice for representing delegates. The UIApplicationDelegate protocol declares the methods that a delegate for UIApplication should define, and we can see that our AppDelegate class adopts it in AppDelegate.h.

@interface AppDelegate : UIResponder <UIApplicationDelegate>

This is what formally turns our AppDelegate class into the delegate for the main UIApplication instance. If you open AppDelegate.m, you’ll also see implementation stubs for the following methods:

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationDidEnterBackground:(UIApplication *)application;
- (void)applicationWillEnterForeground:(UIApplication *)application;
- (void)applicationDidBecomeActive:(UIApplication *)application;
- (void)applicationWillTerminate:(UIApplication *)application;

These methods are called by UIApplication when certain events occur internally. For example, the application:didFinishLaunchingWithOptions: method is called immediately after the application launches. Let’s take a look at how this works by adding an NSLog() call to some of these methods.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"Application has been launched");
    return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"Entering background");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"Entering foreground");
}

Now, when you compile the project and run it in the iOS Simulator, you should see the Application has been launched as soon as it opens. You can click the simulator’s home button to move the application to the background, and click the application icon on the home screen to move it back to the foreground. Internally, clicking the home button makes the UIApplication instance call applicationDidEnterBackground::

tutorial_image
Figure 6: Moving the HelloWorld application to the background

This should display the following messages in Xcode’s output panel.

tutorial_image
Figure 7: Xcode output after clicking the home button in the iOS Simulator

These NSLog() messages show us the basic mechanics behind an application delegate, but in the real world, you would write custom setup and cleanup code to these methods. For example, if you were creating a 3-D application with OpenGL, you would need to stop rendering content and free up any associated resources in the applicationDidEnterBackground: method. This makes sure that your application isn’t hogging memory after the user closes it.

To summarize, our AppDelegate class serves as the practical entry point into our application. Its job is to define what happens when an application opens, closes, or goes into a number of other states. It accomplishes this by acting as a delegate for the UIApplication instance, which is the internal representation of the entire application.

ViewController.h and ViewController.m

Outside of the application delegate, iOS applications follow a model-view-controller (MVC) design pattern. The model encapsulates the application data, the view is the graphical representation of that data, and the controller manages the model/view components and processes user input.

tutorial_image
Figure 8: The model-view-controller pattern used by iOS applications

Model data is typically represented as files, objects from the CoreData framework, or custom objects. The application we’re building in this chapter doesn’t need a dedicated model component; we’ll be focusing on the view and controller aspects of the MVC pattern until the next chapter.

View components are represented by the UIView class. Its UIButton, UILabel, UITextField and other subclasses represent specific types of user interface components, and UIView itself can act as a generic container for all of these objects. This means that assembling a user interface is really just a matter of configuring UIView instances. For our example, the ViewController automatically creates a root UIView instance, so we don’t need to manually instantiate one.

And, as you probably could have guessed, the ViewController class is the custom controller for our project. Its job is to lay out all of the UI components, handle user input like button clicks, text field input, etc., and update the model data when necessary. You can think of it as a scene manager.

Controllers typically inherit from the UIViewController class, which provide the basic functionality required of any view controller. In our HelloWorld program, the storyboard (discussed in the next section) automatically instantiates the root ViewController class for us.

While the AppDelegate is the programmatic entry point into the application, our ViewController is the graphical root of the project. The viewDidLoad method in ViewController.m is called after the root UIView instance is loaded. This is where we can create new user interface components and add them to the scene (we’ll do this in a moment).

MainStoryboard.storyboard

The last file we need to take a look at is MainStoryboard.storyboard. This is a special type of file that stores the entire flow of your application and lets you edit it visually instead of programmatically. Selecting it in Xcode’s Project Navigator will open up the Interface Builder instead of the normal source code editor, which should look something like this:

tutorial_image
Figure 9: The Interface Builder of our HelloWorld project

The large white area is called a scene, and it represents a screen worth of content on the iPhone. This is what you’re seeing when you compile and run the empty template, and it’s where we can visually create a layout by dragging and dropping user interface components. The arrow pointing into the left of the scene tells us that this is the root scene for our app. Underneath it is the dock, which contains icons representing relevant classes and other entities. We’ll see why this is important once we start making connections between graphical components and our custom classes.

Before we start adding buttons and text fields, let’s take a moment to examine the left-most yellow icon in the dock. First, make sure the Utilities panel is open by toggling the right-most button in the View selection tab:

tutorial_image
Figure 10: Displaying the Utilities panel (highlighted in orange)

Then, click the yellow icon in the dock to select it:

tutorial_image
Figure 11: Selecting the View Controller icon

This icon represents the controller for the scene. For our project, this is an instance of the custom ViewController class. We can verify this by selecting the Identity inspector in the Utilities panel, which will display the class associated with the controller:

tutorial_image
Figure 12: The Identity inspector in the Utilities panel

That Class field creates a connection between the storyboard’s graphical interface and our source code. This is important to keep in mind when we start accessing user interface components from our classes.

It’s also worth taking a look at the Attributes inspector, which is the next tab over in the Utilities panel.

tutorial_image
Figure 13: The Attributes inspector for the controller

That Is Initial View Controller check box is what makes this the root scene. Every app needs to have exactly one root scene, otherwise iOS won’t know how to launch your application. If you clear the box, the arrow pointing into the scene will disappear, and you’ll get the following message when you try to compile the project.

tutorial_image
Figure 14: Error message from a missing root scene

Make sure Is Initial View Controller is selected before moving on.


Designing a User Interface

There are two ways to design the user interface of your application. You can either programmatically create graphical components by instantiating UIView and related classes in your source code, or you can visually design layouts by dragging components into the Interface Builder. This section takes a brief look at both methods.

Programmatic Layouts

We’ll start with the programmatic method, as it shows us what’s going on behind the scenes when we construct layouts using the Interface Builder. Remember that one of the main jobs of our ViewController is to manage UI components, so this is where we should create our layout. In ViewController.m, change the viewDidLoad method to the following.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [aButton setTitle:@"Say Hello" forState:UIControlStateNormal];
    aButton.frame = CGRectMake(100.0, 200.0, 120.0, 40.0);
    [[self view] addSubview:aButton];
} 

First, we create a UIButton object, which is the object-oriented representation of a button. Then, we define its label using the setTitle:forState: method. The UIControlStateNormal constant tells the button to use this value for its “up” state. All graphical components use the frame property to determine their position and location. It accepts a CGRect struct, which can be created using the CGRectMake() convenience function. The previous sample tells the button to position itself at (x=100, y=200) and to use a width of 120 pixels and a height of 40 pixels. The most important part is the [[self view] addSubview:aButton] line. This adds the new UIButton object to the root UIView instance, which is accessed via the view property of our ViewController.

After compiling the project, you should see the button in the middle of the iOS Simulator.

tutorial_image
Figure 15: Programmatically creating a UIButton

You can click the button to see the default states, but actually making it do anything will take a bit more work. We’ll learn how to do this in the Connecting Code with UI Components section.

Remember that UIButton is but one of many UIView subclasses that can be added to a scene. Fortunately, all the other user interface components can be managed using the same process: instantiate an object, configure it, and add it with the addSubview: method of the parent UIView.

Interface Builder Layouts

Creating components in the Interface Builder is a little bit more intuitive than the programmatic method, but it essentially does the same thing behind the scenes. All you need to do is drag a component from the Object Library onto a scene. The Object Library is located at the bottom of the Utilities panel, and it looks something like the following:

tutorial_image
Figure 16: The Object library

In the previous screenshot, we opted to display only the user interface controls by selecting Controls from the drop-down menu. These are the basic graphical components for requesting input from the user.

Let’s add another Button, along with a Label and a Text Field component by dragging the associated objects from the library onto the large white area representing the root scene. After they are on the stage, you can position them by dragging them around, and you can resize them by clicking the target component then dragging the white squares surrounding it. As you move the components around, you’ll notice dotted guidelines popping up to help you align elements and create consistent margins. Try to arrange your layout to look something like Figure 17. The Say Goodbye button should be centered on both the x-axis and the y-axis:

tutorial_image
Figure 17: Laying out the Button, Label, and Text Field components

To change the text in the button, simply double-click it and enter the desired title (in this case, Say Goodbye). The Interface Builder also provides several other tools for editing the appearance and behavior of a component. For instance, you can set the placeholder text of our text field in the Attribute panel. Try changing it to Your Name:

tutorial_image
Figure 18: Defining the placeholder text

This will display some instructional text when the field is empty, which is usually a good idea from a user experience standpoint. When you compile your app, it should look something like this (note that we’re still using the Say Hello button that we added in viewDidLoad).

tutorial_image
Figure 19: The HelloWorld App

If you click on the text field in the iOS Simulator, it will open the keyboard—just like you would expect from any iOS app. You’ll be able to enter text, but you won’t be able to dismiss the keyboard. We’ll fix this issue in the Delegates portion of the next section. Until then, don’t click the text field while testing the app.

As you might imagine, trying to lay out an interface using both the programmatic method and the Interface Builder can be a little confusing, so it’s usually best to stick to one or the other for real-world applications.

These four components are all that we’ll need for this project, but notice that we’ve only learned how to add components to a scene—they can’t do anything useful yet. The next step is to get these user interface components to communicate with our code.


Connecting Code with UI Components

This section discusses the three most important types of connections between your source code and your user interface components: actions, outlets, and delegates. An action is a method that should be called when a particular event happens (e.g., when a user taps a button). An outlet connects a source code variable to a graphical component in the Interface Builder. We’ve already worked with delegates in the AppDelegate class, but this design pattern is also prevalent in the graphical aspects of iOS. It lets you control the behavior of a component from an arbitrary object (e.g., our custom ViewController).

Just like adding components to a layout, connecting them to your custom classes can be done either programmatically or through the Interface Builder. We’ll introduce both methods in the Actions section that follows, but we’ll rely on the Interface Builder for outlets and delegates.

Actions

Many interface controls use the target-action design pattern to react to user input. The target is the object that knows how to perform the desired action, and the action is just a method name. Both the target and the action are stored in the UI component that needs to respond to user input, along with an event that should trigger the action. When the event occurs, the component calls the action method on the specified target.

Programmatic Actions

The UIControl class from which UIButton inherits defines an addTarget:action:forControlEvents: method that lets you attach a target-action pair to an event. For example, we can make our Say Hello button display a greeting when we tap it by changing the viewDidLoad method in ViewController.m to the following.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [aButton setTitle:@"Say Hello" forState:UIControlStateNormal];
    aButton.frame = CGRectMake(100.0, 200.0, 120.0, 40.0);
    [[self view] addSubview:aButton];
    // Configure an action.
    [aButton addTarget:self
                action:@selector(sayHello:)
      forControlEvents:UIControlEventTouchUpInside];
} 

This code tells the button to call the sayHello: method on self when the UIControlEventTouchUpInside event occurs. This event is triggered when the user releases a touch on the inside of the button. Other events are defined by the UIControlEvents enumeration contained in UIControl.

Of course, for the target-action pair in the previous code sample to work, we need to define the action method. The action should accept a single argument, which represents the user interface component that triggered the event. Add the following method to ViewController.m.

- (void)sayHello:(id)sender {
    NSLog(@"Hello, World!");
}

Now, when you compile the project and click the Say Hello button in the iOS Simulator, it should display Hello, World! in the Xcode output panel. If you needed to access the UIButton that triggered the event, you could do so through the sender argument. This could be useful, for example, when you want to disable the button after the user clicks it.

Interface Builder Actions

Configuring actions through the Interface Builder takes a couple more steps, but it’s more intuitive when working with components that haven’t been created programmatically. In this section, we’ll use the Interface Builder to create an action for the Say Goodbye button.
Actions need to be publicly declared, so our first task is to add the action method in ViewController.h.

- (IBAction)sayGoodbye:(id)sender;

Notice the IBAction return type. Technically, this is just a typedef for void; however, using it as a return type makes Xcode and the Interface Builder aware of the fact that this is meant to be an action—not just an ordinary method. This is reflected by the small circle that appears next to the method in the source code editor.

tutorial_image
Figure 20: Xcode recognizing a method as an action

Next, we need to implement the action method. In ViewController.m, add the following method.

- (IBAction)sayGoodbye:(id)sender {
    NSLog(@"See you later!");
} 

Instead of attaching it programmatically with addTarget:action:forControlEvents:, we’ll use the Interface Builder to connect this method to the Say Goodbye button. Select the MainStoryboard.storyboard file to open the Interface Builder, and select the yellow View Controller icon in the dock.

tutorial_image
Figure 21: Selecting the View Controller icon

Then, open the Connections inspector, which is the right-most tab in the Utilities panel.

tutorial_image
Figure 22: The Connections tab in the Utilities panel

This panel contains all of the relationships available to our ViewController class. Notice the sayGoodbye: method listed under Received Actions. This is only available because we used the IBAction return type in the method declaration.

To create a connection between the sayGoodbye: method and the Say Goodbye button, click the circle next to sayGoodbye: in the Connections panel, and then drag it to the button on the scene. You should see a blue line while you drag, as shown in the following figure.

tutorial_image
Figure 23: Connecting the sayGoodbye: method to a UIButton in Interface Builder

When you release over the button, a menu will pop up containing all of the available events that can trigger the action. It should look something like the following.

tutorial_image
Figure 24: Selecting an event for the action

Select Touch Up Inside, which is the equivalent of the UIControlEventTouchUpInside enumerator we used in the previous section. This creates a target-action connection between the ViewController and the UIButton instance. Essentially, this is the exact same addTarget:action:forControlEvents: call we used for the Say Hello button in the previous section, but we did it entirely through the Interface Builder. You should now be able to compile the project and click the Say Goodbye button to display the See You Later! message in the output panel.

Note that instead of being stored as source code, the connection we just created is recorded in the storyboard. Again, it can be confusing to maintain actions in both source code and the storyboard, so it’s usually best to stick to one method or the other in real-world applications. Typically, if you’re creating your layout in the Interface Builder, you’ll want to create your connections there as well.

Outlets

An outlet is a simpler type of connection that links a source code variable with a user interface component. This is an important ability, as it lets you access and manipulate properties of the storyboard from custom classes. Outlets always originate from the custom class and are received by a UI component in the Interface Builder. For example, this section creates an outlet from a variable called messageLabel to a UILabel component in the storyboard. This can be visualized as follows:

tutorial_image
Figure 25: Creating an outlet from the ViewController class to a label component

To create an outlet, we first need to declare the property that will be associated with the UI component. Outlets are typically configured in a controller, so open ViewController.h and add the following property.

@property (weak, nonatomic) IBOutlet UILabel *messageLabel;

This looks like any other property declaration, except for the new IBOutlet qualifier. Just like IBAction, this designates the property as an outlet and makes it available through the Interface Builder, but doesn’t affect the variable itself. Once we set up the connection, we can use messageLabel as a direct reference to the UILabel instance that we added to the storyboard.

But before we do that, we need to synthesize the accessor methods in ViewController.m:

@synthesize messageLabel = _messageLabel;

Back in MainStoryboard.storyboard, select the yellow View Controller icon again and take a look at the Connections inspector.

tutorial_image
Figure 26: The Connections inspector after adding messageLabel to ViewController

Notice how the messageLabel property we just created appears in the Outlets listing. We can now connect it to a user interface component just like we did with the button action in the previous section. Click and drag from the circle next to messageLabel to the UILabel in the scene, like so:

tutorial_image
Figure 27: Linking the messageLabel with the UILabel instance

When you release the mouse, the outlet will be created, and you can use messageLabel to set the properties of the UILabel instance in the Interface Builder. As an example, try changing the text and color of the label in the sayHello: and sayGoodbye: methods of ViewController.m:

- (void)sayHello:(id)sender {
    _messageLabel.text = @"Hello, World!";
    _messageLabel.textColor = [UIColor colorWithRed:0.0
                                              green:0.3
                                               blue:1.0
                                              alpha:1.0];
}
- (IBAction)sayGoodbye:(id)sender {
    _messageLabel.text = @"See you later!";
    _messageLabel.textColor = [UIColor colorWithRed:1.0
                                              green:0.0
                                               blue:0.0
                                              alpha:1.0];
} 

Now, when you compile the project and click the buttons, they should display different messages in the label. As you can see, outlets are a necessary tool for updating the interface in reaction to user input or changes in the underlying data model.

Before we continue on to delegate connections, we need to set up another outlet for the UITextField that we added in the Interface Builder. This will be the exact same process as the UILabel. First, declare the property and synthesize its accessors:

// ViewController.h
@property (weak, nonatomic) IBOutlet UITextField *nameField;
// ViewController.m
@synthesize nameField = _nameField;

Then, open the Interface Builder, select the yellow View Controller icon in the dock, and make sure the Connections inspector is visible. To create the connection, click and drag from the circle next to nameField to the text field component in the scene. In the next section, this outlet will let us access the text entered by the user.

Delegates

The delegate design pattern serves the same purpose for UI components as it does for the AppDelegate discussed earlier: it allows a component to transfer some of its responsibilities to an arbitrary object. For our current example, we’re going to use the ViewController class as a delegate for the text field we added to the storyboard. As with the AppDelegate, this allows us to react to important text field events while hiding the complexities of its internal workings.

First, let’s turn the ViewController class into a formal delegate for the text field. Remember that the delegate design pattern is implemented through protocols, so all we have to do is tell ViewController.h to adopt the UITextFieldDelegate protocol, like so:

@interface ViewController : UIViewController <UITextFieldDelegate>

Next, we need to connect the text field and the ViewController class using the Interface Builder. This connection flows in the opposite direction as the outlets we created in the previous section, so instead of dragging from the ViewController to a UI component, we need to drag from the text field to the ViewController. In the Interface Builder, select the text field component and open the Connections panel. You should see a delegate field under the Outlets section:

tutorial_image
Figure 28: The beginning of the Connections panel for the Text Field component

To create the delegate connection, drag from the circle next to delegate to the yellow View Controller icon in the dock:

tutorial_image
Figure 29: Creating a delegate connection from the Text Field to the View Controller

Now, the ViewController can control the behavior of the text field by implementing the methods defined in UITextFieldDelegate. We’re interested in the textFieldShouldReturn: method, which gets called when the user clicks the Return button on the keyboard. In ViewController.m, implement the method as follows:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    self.name = textField.text;
    if (textField == self.nameField) {
        [textField resignFirstResponder];
    }
    return YES;
} 

This saves the value the user entered (textField.text) into the name property after they press the Return button. Then, we make the keyboard disappear by removing focus from the text field with the resignFirstResponder method. The textField == self.nameField conditional is a best practice to make sure that we’re working with the correct component (this isn’t actually necessary unless the ViewController is a delegate for multiple text fields). Note that we still have to declare that name field in ViewController.h:

@property (weak, nonatomic) NSString *name;

It’s always better to isolate the model data in dedicated properties in this fashion rather than rely directly on the values stored in UI components. This makes sure that they will be accessible even if the UI component has been removed or altered in the meantime. Our last step is to use this new name property to personalize the messages in sayHello: and sayGoodbye:. In ViewController.m, change these two methods to the following:

- (void)sayHello:(id)sender {
    if ([self.name length] == 0) {
        self.name = @"World";
    }
    _messageLabel.text = [NSString stringWithFormat:@"Hello, %@!"
                                                    self.name];
    _messageLabel.textColor = [UIColor colorWithRed:0.0
                                              green:0.3
                                               blue:1.0
                                              alpha:1.0];
}
- (IBAction)sayGoodbye:(id)sender {
    if ([self.name length] == 0) {
        self.name = @"World";
    }
    _messageLabel.text = [NSString stringWithFormat:@"See you later, %@!",
                                                    self.name];
    _messageLabel.textColor = [UIColor colorWithRed:1.0
                                              green:0.0
                                               blue:0.0
                                              alpha:1.0];
} 

You should now be able to compile the application, edit the text field component, dismiss the keyboard, and see the resulting value when you click the Say Hello and Say Goodbye buttons.

tutorial_image
Figure 30: Implementing the Text Field component

Summary

This chapter introduced the fundamentals of iOS development. We learned about the basic file structure of a project: the main file, the application delegate, the custom view controller, and the storyboard. We also designed a layout by programmatically adding components to the stage, as well as by visually editing components in the Interface Builder. And, to enable our code to communicate with buttons, labels, and text fields in the storyboard, we created action, outlet, and delegate connections using the Interface Builder.

That was a lot of work to create such a simple application, but we now have nearly all the skills we need to build real-world applications. Once we understand the basic workflow behind connecting code with user interface elements and capturing user input, all that’s left is exploring the capabilities of individual components/frameworks and making them all work together.

The next chapter will flesh out some of the topics we glossed over in the previous example by walking through a more complex application. We’ll learn about segues for transitioning between scenes, and we’ll also have the opportunity to discuss the model-view-controller pattern in more detail.

This lesson represents a chapter from iOS Succinctly, a free eBook from the team at Syncfusion.

iOS Succinctly – Multi-Scene Applications

$
0
0

The previous chapter introduced the basic workflow of iOS application development, but we worked within the confines of a single-view application. Most real-world applications, however, require multiple scenes to present data hierarchically. While there are many types of organizational patterns used for managing multi-scene apps, this chapter looks at one of the most common patterns: the master-detail application.

The minimal master-detail application consists of a “master” scene, which presents a list of data items to choose from, and a “detail” scene, which displays an item’s details when the user selects it from the master scene. Open the Mail app on your iPhone and you’ll find a good example of a master-detail application. The inbox lists your messages, making it the master scene, and when you select one of them, a detail scene is used to display the contents of the message, the sender, any attachments, etc.

For the most part, multi-scene applications use the same workflow discussed in the previous chapter. We’ll still create layouts by adding UI components through the Interface Builder and connect them to our code with actions, outlets, and delegates. However, having multiple scenes means that we’ll have multiple view controllers, so we’ll need to use the new UINavigationController class to organize their interaction. We’ll also learn how to configure scene transitions using segues.


Creating a Master-Detail Project

The example project for this chapter will be a simple contact list that lets users manage a list of their friends, along with their respective contact information. To create the example project, select File > New > Project and choose the Master-Detail Application. This will give us the opportunity to explore new navigation components and organizational structures, as well as how to handle transitions from one scene to another.

tutorial_image
Figure 31: Creating a Master-Detail Application template

Use FriendList for the Product Name field, whatever you like for the Organization Name, and edu.self for the Company Identifier. Like the previous app, make sure that iPhone is the selected Device and Use Storyboards and Use Automatic Reference Counting are selected:

tutorial_image
Figure 32: Configuring the project

You can save the project wherever you like.


Template Overview

We’ll be building on the template’s existing code, so let’s take a quick look at the default application. Click the Run button in the upper-left corner of Xcode or press Cmd+R to compile the application and launch it in the iOS Simulator. You should see an empty list entitled Master with an Edit button and an Add button (a plus sign) in the navigation bar. Clicking the Add button will insert a new item into the list, and selecting that item will transition to the detail scene. Both scenes are shown in the following figure.

tutorial_imagetutorial_image
Figure 33: The template’s default master and detail scenes

The default data items used by the template are dates, but we’re going to change the master scene to display a list of names and the detail scene to display their contact information.

We’ll be discussing the details behind each source file as we build up the example project, but it will help to have a basic overview of the default classes before we start editing them.

The Application Delegate

As in the previous chapter, the AppDelegate class lets you react to important events in the application’s life cycle. We don’t need any custom startup behavior for our friend list application, so we won’t be editing this class at all.

The View Controllers

Instead of a single ViewController, this template has two view controller classes: a MasterViewController and a DetailViewController. These manage the master scene and detail scene, and their viewDidLoad methods serve as the entry point into their respective scenes. The MasterViewController’s viewDidLoad method should look like the following:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.leftBarButtonItem = self.editButtonItem;
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                             target:self
                             action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
}

This creates the Edit and Add buttons that you see at the top of the master scene, and it sets the insertNewObject: method as the action for the latter. The insertNewObject: method adds a new NSDate instance to the private _objects variable, which is a mutable array containing the master list of data items, and all of the methods after the #pragma mark – Table View directive control how that list is displayed in the scene. The prepareForSegue:sender: method is called before transitioning to the detail scene, and it is where the necessary information is transferred from the master scene to the detail scene.

The DetailViewController class is a little bit simpler. It just declares a detailItem property to store the selected item and displays it through the detailDescriptionLabel outlet. We’re going to be changing this default implementation to display a person’s contact information.

The Storyboard

The storyboard is perhaps the most drastic change from the previous example. If you open MainStoryboard.storyboard, you should see the following:

tutorial_image
Figure 34: The template’s default storyboard

Instead of a single view controller, the Interface Builder now manages three controllers. This might seem odd considering our application only has two scenes, but both the MasterViewController and the DetailViewController are embedded in a UINavigationController instance. This navigation controller is why we see a navigation bar at the top of the app, and it’s what lets us navigate back and forth between the master and detail scenes. We’ll talk more about configuring navigation controllers throughout the chapter.

This template should also clarify why the MainStoryboard.storyboard file is called a “storyboard”— it visualizes not only the scenes themselves, but the flow between those scenes. As in the previous chapter, the arrow pointing into the navigation controller shows that it is the root controller. But, we also see another arrow from the navigation controller to the MasterViewController and from the MasterViewController to the DetailViewController. These arrows define the relationships and transitions between all of the view controllers in an application.


The Model Data

Unlike the previous chapter, this application will use a dedicated class to represent its model data. We’ll use the Person class to store the contact information of each friend. In Xcode, create a new file, select Objective-C class, and enter Person for the Class field, like so:

tutorial_image
Figure 35: Creating the Person class

Next, we need to declare a few properties to record the name, organization, and phone number of each contact. Open Person.h, and change it to the following:

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (copy, nonatomic) NSString *firstName;
@property (copy, nonatomic) NSString *lastName;
@property (copy, nonatomic) NSString *organization;
@property (copy, nonatomic) NSString *phoneNumber;
@end

Of course, we also need to synthesize these properties in Person.m.

#import "Person.h"
@implementation Person
@synthesize firstName = _firstName;
@synthesize lastName = _lastName;
@synthesize organization = _organization;
@synthesize phoneNumber = _phoneNumber;
@end

That’s all we need to represent the data behind our application. Instances of this class will be passed around between the MasterViewController and DetailViewController scenes, which will display them using various UI components.


The Master Scene

Next, we’ll configure the master scene to display a list of Person objects. Defining a scene’s behavior requires careful interaction between the underlying view controller’s source code and the visual representation in the Interface Builder. Before we do any coding, let’s take a closer look at the template’s master scene in the storyboard.

Relationships

In our storyboard, a relationship defines the connection between a navigation controller and another scene. The Interface Builder visualizes the relationship as an arrow from the navigation controller to the other scene with a link icon on top of it. Selecting this icon will highlight the navigation controller, as shown in the following screenshot:

tutorial_image
Figure 36: The relationship between the root navigation controller and the master view controller

The template set up this relationship for us, but it’s important to be able to configure it on your own. So, go ahead and delete the navigation controller by selecting it and pressing Delete. To re-create the relationship, select the yellow View Controller icon in the master view controller, and then navigate to Editor in Xcode’s menu bar and select Embed In > Navigation Controller. A new navigation controller should appear, and you should be back to where you started.

It’s important to understand that the relationship arrow does not signify a transition between the navigation controller and the master controller. Rather, embedding our master scene into a navigation controller in this fashion creates a view controller hierarchy. It says that the master scene belongs to the navigation controller. This makes it possible to switch between scenes using the navigation controller’s built-in transitions and navigation buttons. For example, the Master button that appears at the top of the detail scene is automatically added by the navigation controller:

tutorial_image
Figure 37: The built-in navigation button provided by the navigation controller

The built-in functionality for switching between scenes makes navigation controllers an easy way to configure the flow of complex applications. The next section discusses how to define transitions between a navigation controller’s scenes.

Segues

A segue represents a transition from one scene to another. Like relationships, it is visualized as an arrow from the source scene to the destination scene, but it uses a different icon. Notice that when you click the segue icon, only a single table cell is highlighted. This tells us that the segue is attached to individual table cells instead of the entire master scene.

tutorial_image
Figure 38: The push segue from the master scene to the detail scene

Again, our template created this segue for us, but it’s important to be able to create one from scratch. So, select the segue icon and press Delete to remove it from the storyboard. To re-create it, control-drag from the table cell to the detail scene.

tutorial_image
Figure 39: Control-dragging from the master’s table cell to the detail scene

This will open a menu prompting you for the Selection Segue/Accessory Action type. We want our segue to occur when the user selects the table cell, so choose push under the Selection Segue group.

tutorial_image
Figure 40: Selecting the type of segue to create

The parent UINavigationController manages its scenes through a navigation stack, and its pushViewController:animated: and popViewControllerAnimated: methods let you add or remove view controller instances from the stack. For example, pushing a detail view controller object onto the navigation stack is how you drill down to the detail scene, and clicking the Master button in the detail scene’s navigation bar pops it from the navigation stack. Selecting push from the menu in Figure 40 tells the segue to call the pushViewController:animated: method to transition from the master scene to the detail scene.

In addition to a type, each segue must also have a unique identifier so that it can be accessed from your source code. You can edit a segue’s ID by selecting the segue icon and opening the Attributes inspector panel. Our segue should have an identifier of showDetail, and you should also see the Push segue type in the Style field:

tutorial_image
Figure 41: The Attributes inspector for the master-detail segue

The other Style option is Modal, which presents another scene on top of an existing scene, completely independent of the parent navigation controller. You should leave this segue’s Style as Push (we’ll create a modal segue toward the end of this chapter).

Tables

One of the main differences between our master scene and the ViewController from the previous chapter is the fact that it inherits from UITableViewController instead of UIViewController. A table view controller manages a UITableView instance. Table views are composed of a single column of rows, possibly grouped into sections. This makes them well suited to presenting lists of data.

Since table views are graphical containers, it can be hard to select them in the scene editor. The easiest way to select it is from the document outline to the left of the scene editor. The document outline is a tree containing all the elements managed by the Interface Builder, and you should find a Table View item under the Master View Controller, as shown in the following figure.

tutorial_image
Figure 42: Selecting the UITableView instance from the document outline

When you select the table view, everything under the navigation bar in the master scene should be highlighted in the scene builder. This gives you the chance to edit the table view properties in the Attributes inspector. The most important option is the Content field, which determines how you will interact with the table from your code:

tutorial_image
Figure 43: The Attributes inspector for the master scene’s table view

If you set the Content field to Dynamic Prototypes, you can create new cells by duplicating a prototypical cell designed in the Interface Builder. Static cells, on the other hand, cannot be duplicated, resulting in a static table. This means that you should use Dynamic Prototypes when you want to insert or delete rows on the fly, and use Static Cells when your table always shows the same amount of information. Keep the master scene’s table dynamic. We’ll use a static table for the detail scene.

When you use prototype cells, you need to give each prototype a unique identifier so that it can be accessed from your source code (just like a segue ID). To edit a prototype cell’s ID, select the cell in either the scene editor or the interface builder and open the Attributes inspector. The identifier for that particular prototype can be set in the Identifier field, as shown in the following figure. Since we’re only going to have one prototypical cell in this application, you can leave the default Cell value, but for real applications you should give each prototype a descriptive identifier.

tutorial_image
Figure 44: The Attributes inspector for the prototype table cell

It’s also worth taking a look at the Connections inspector for the UITableView (not the prototype cell). You should see a dataSource and a delegate outlet, and both of them should specify the MasterViewController class for their destination.

tutorial_image
Figure 45: The outlet connections for the master scene’s table view

A table view’s data source is a special kind of delegate that provides the information for each row in the table. In addition to the raw data, a table view delegate is necessary to define the behavior of the table and the appearance of each row. As with application and text field delegates, these are implemented through protocols called UITableViewDataSource and UITableViewDelegate, respectively.

In this case, the MasterViewController class acts as both the data source and the delegate, which is why the master-detail template included methods like tableView:cellForRowAtIndexPath: and tableView:canEditRowAtIndexPath: in MasterViewController.m. In the next section, we’ll alter these methods to change the appearance of the friend list.

Coding the Master View Controller

Now that we have a better handle on what’s going on in the storyboard, we’re ready to start customizing our MasterViewController class. Right now, the master scene is displaying a list of NSDate objects, but we want to change those to Person objects. Of course, this means we’ll need access to the Person class, so import the header in MasterViewController.m:

#import "Person.h"

Remember that the viewDidLoad: method tells the master scene’s Add button to call the insertNewObject: method whenever the user taps it. Instead of adding a date object to the _objects array, we need insertNewObject: to add a Person object. Change it to the following:

- (void)insertNewObject:(id)sender {
    if (!_objects) {
        _objects = [[NSMutableArray alloc] init];
    }
    Person *friend = [[Person alloc] init];
    friend.firstName = @"<First Name>";
    friend.lastName = @"<Last Name>";
    friend.organization = @"<Organization>";
    friend.phoneNumber = @"<Phone Number>";
    [_objects insertObject:friend atIndex:0];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    [self.tableView insertRowsAtIndexPaths:@[indexPath]
                    withRowAnimation:UITableViewRowAnimationAutomatic];
}

This instantiates a new Person object and populates it with some dummy values, and then adds it to the front of the _objects array with insertObject:atIndex:. The NSIndexPath instance is a simple data object representing the index of a particular cell, and the insertRowsAtIndexPaths:withRowAnimation: adds a new cell at the specified location.
Notice that this last method doesn’t actually create the new cell—it just adds an item to the _objects array and tells the table that it should have one more row in it. This prompts the table to create a new cell, which is prepared by the tableView:cellForRowAtIndexPath: data source delegate method. It should look like the following:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView
                             dequeueReusableCellWithIdentifier:@"Cell"
                             forIndexPath:indexPath];
    Person *friend = _objects[indexPath.row];
    cell.textLabel.text = [NSString stringWithFormat:@"%@ %@",
                           friend.firstName, friend.lastName];
    return cell;
}

This method is called every time the table needs to render a given cell, and it should return a UITableViewCell object representing the corresponding row. First, we fetch a prototype cell using the identifier defined in the storyboard, and then we use the NSIndexPath instance to find the associated Person object. Finally, we display the person’s name through the textLabel property of the cell.

Now, you should be able to add, view, and delete Person objects from the master scene:

tutorial_image
Figure 46: Adding a Person object to the master scene

That covers the basic list functionality for the master scene, but we still have one more task before we move on to the detail scene. When a user selects one of the items in the master list, we need to pass that object to the detail scene.

Remember that the UINavigationController and the push segue handles the transition for us, but it gives us the opportunity to send data from the source view controller to the destination view controller by calling the prepareForSegue:sender: method right before it switches to the detail view. Change prepareForSegue:sender: in MasterViewController.m to the following (the only real change is to use a Person object instead of an NSDate instance):

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@"showDetail"]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        Person *friend = _objects[indexPath.row];
        [[segue destinationViewController] setDetailItem:friend];
    }
}

This method is how we pass data between the master scene and the detail scene. It is called for every segue associated with a particular controller, so our first step is to check the segue ID, which was defined in the Interface Builder. Then, we use the indexPathForSelectedRow method to get the index of the selected row (aren’t Objective-C naming conventions great), and we use that index to find the corresponding data item from the _objects array. Finally, we pass this object off to the detail scene by setting its detailItem property.

tutorial_image
Figure 47: Selecting a Person object from the master scene

Now when you select an item from the master list, you should see a Person object instead of an NSDate instance. The default detail scene uses the description method to convert the object to a string, which is why we see a memory address in Figure 47 instead of any meaningful information. We’ll change that in the next section.

To summarize our master scene: we have a relationship connection that embeds it in a UINavigationController instance, a segue defining the transition to the detail scene, a prototype cell that we use as a template for new table rows, an Add button that adds dummy instances to the master list of data items, and a prepareForSegue:sender: method that passes the selected item off to the detail scene.


The Detail Scene

Next, we need to configure the detail scene to display the selected friend. A single Person object always has the same amount of information (a name, an organization, and a phone number), so we’ll use three static cells to format the output instead of dynamic prototypes. Just like the master scene, we’re going to configure the Interface Builder first, and then code the functionality after we have the UI components laid out.

Switching to a Table View Controller

The master-detail template uses a plain ViewController for the detail scene, so our first task is to replace it with a UITableViewController. In the Interface Builder, select the detail scene and press Delete to remove it from the storyboard. Then, drag a Table View Controller object from the Objects Library onto the scene editor.

tutorial_image
Figure 48: The Table View Controller in the Objects Library

The segue was deleted along with the old detail scene, so the new table view isn’t a part of the navigation controller hierarchy yet. Re-create the segue by dragging from the master scene’s prototype cell to the new detail scene, and then select push to create a push segue. After that, be sure to change the ID of the segue back to showDetail.

tutorial_image
Figure 49: Re-creating the push segue from the master scene to the detail scene

This integrates the Table View Controller with the navigation hierarchy, and the Interface Builder reflects this by adding a navigation bar to the top of the detail scene. However, that navigation bar is now blank. Let’s fix that by double-clicking in the center of the empty navigation bar and entering Detail as the title of the scene, like so:

tutorial_image
Figure 50: Defining the title of the detail scene

We also need to connect the new scene to our DetailViewController class. Before changing the class in the interface builder, we need to make DetailViewController inherit from UITableViewController. Change the interface declaration in DetailViewController.h to the following:

@interface DetailViewController : UITableViewController

Then, open the storyboard again, select the yellow icon in the Table View Controller’s dock, open the Components inspector, and change the Class to DetailViewController.

tutorial_image
Figure 51: Setting the new Table View Controller’s class

Now we’re back to where we started, but we have a Table View Controller instead of a normal View Controller. Remember that we’re going to use a static table to lay out the selected Person object’s information. So, select the detail scene’s detail view from the document outline.

tutorial_image
Figure 52: Selecting the detail scene’s Table View

Then, change the Content field to Static Cells in the Attributes inspector. You can also change Separator to None and Selection to No Selection. This removes the line between the cells and prevents users from selecting them.

tutorial_image
Figure 53: Changing the Table View’s content from dynamic prototypes to static cells

You should now see three blank cells in the detail scene. Select all of them by holding Shift and clicking them, and then change their Style to Left Detail in the Attributes inspector. This adds a Title and a Detail label to each of the cells. Change the title labels to Name, Phone, and Organization so that your detail scene looks like the following:

tutorial_image
Figure 54: Configuring the title labels of the static cells

After we add a few properties to the DetailViewController class, we’ll turn the remaining detail labels into outlets and use them to display the selected Person’s information.

Coding the Detail View Controller

That’s about all we can do in the Interface Builder for now. Let’s add a few properties to DetailViewController so that we can access the detail labels that we just added. Change DetailViewController.h to the following:

#import <UIKit/UIKit.h>
@interface DetailViewController : UITableViewController
@property (strong, nonatomic) id detailItem;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *organizationLabel;
@property (weak, nonatomic) IBOutlet UILabel *phoneNumberLabel;
@end

Recall from the previous chapter that the IBOutlet modifier is what makes these properties available to the Interface Builder. Next, synthesize these properties in DetailViewController.m:

#import "DetailViewController.h"
#import "Person.h"
@implementation DetailViewController
@synthesize detailItem = _detailItem;
@synthesize nameLabel = _nameLabel;
@synthesize organizationLabel = _organizationLabel;
@synthesize phoneNumberLabel = _phoneNumberLabel;
Then, change the configureView method to set the value of the detail labels based on the Person object passed in from the master scene:
- (void)configureView {
    if (self.detailItem &&
        [self.detailItem isKindOfClass:[Person class]]) {
        NSString *name = [NSString stringWithFormat:@"%@ %@",
                          [self.detailItem firstName],
                          [self.detailItem lastName]];
        self.nameLabel.text = name;
        self.organizationLabel.text = [self.detailItem organization];
        self.phoneNumberLabel.text = [self.detailItem phoneNumber];
    }
}

Also notice that we use the isKindOfClass: method to ensure that the detail item is in fact a Person object. This is a best practice step when using dynamically typed variables like detailItem.

Outlet Connections

Our last step for the detail scene is to connect the nameLabel, organizationLabel, and phoneNumberLabel fields to their corresponding UILabel components in the storyboard. This can be accomplished by selecting the yellow icon in the detail scene’s dock and dragging from the circles in the Connections inspector to the label components in the scene editor. Be sure to drag each outlet to the corresponding labels.

tutorial_image
Figure 55: Connecting label components to the Detail View Controller

When you compile the app, you should be able to select items from the master list and view their details in the detail scene. Note that we can only display details; we can’t edit them yet.

tutorial_image
Figure 56: The completed detail scene

To summarize the changes to our detail scene: we replaced the default controller with a Table View Controller component, changed DetailViewController to inherit from UITableViewController, re-created the segue from the master scene to the detail scene, and declared several properties that served as outlets from the DetailViewController to UILabel instances. The goal of all of this was to display the properties of the Person instance that was selected in the master scene.


The Edit View Controller

Our final job for this chapter will be to add another scene that lets us edit the selected item. Instead of a push segue, we’re going to implement this new scene using a modal segue. A modal segue presents the destination scene “on top of” the existing scene, much like a pop-up window in a desktop computer. This does not affect the navigation hierarchy, so instead of a parent UINavigationController taking responsibility for navigating between the scenes, the modally-presented scene dismisses itself when necessary.

For our example, we’ll add a modal segue between our existing detail scene and a new edit scene, then we’ll use an unwind segue to get back to the original scene. This gives us a new tool for controlling the flow of our application, and it presents the opportunity to get a little bit more comfortable with navigation bars, too.

Creating the Edit Scene

Before we can create a modal segue, we need an edit scene to work with. This scene will work almost exactly like the detail scene, except it will have UITextField components instead of UILabels so that the user can edit each property. First, create a new class called EditViewController and use UITableViewController for the superclass:

tutorial_image
Figure 57: Creating the class for the Edit View Controller

Next, open the Interface Builder and drag another Table View Controller from the Object Library into the scene editor. Position it above the detail scene, like so:

tutorial_image
Figure 58: Adding a Table View Controller to the storyboard

This new controller needs to be connected to the EditViewController class that we just created, so select it in the Interface Editor, open the Identity inspector and change the Class field to EditViewController.

tutorial_image
Figure 59: Defining the class of the new table view controller

Navigating to the Edit Scene

Our edit scene will use a navigation bar to present Cancel and Save buttons. We could embed it in the root UINavigationController, but remember that we want to present it modally—not by pushing it onto the existing view controller stack. To give it its own navigation bar, all we need to do is embed it in its own navigation controller. Select the Edit View Controller in the Interface Builder and select Editor > Embed In > Navigation Controller from the Xcode menu.

tutorial_image
Figure 60: Embedding the edit scene in a new navigation controller

Whereas push segues let the containing navigation controller add navigation buttons for you, we need to add our own buttons for the modal segue. The UIKit Framework uses a special category of controls for use in navigation bars. We’re looking for a bar button item, which you can find in the Windows & Bars section of the Object Library.

tutorial_image
Figure 61: The Bar Button Item in the Object Library

Drag a Bar Button Item from the Object Library onto the right side of the detail scene’s navigation bar. It should snap into place and have a default value of Item, as shown in the following screenshot:

tutorial_image
Figure 62: Adding an edit button to the detail scene’s navigation bar

This button will launch the edit scene, so we should probably change the text to Edit. You could do this by manually changing the text in the scene editor, but the preferred way is to select one of the predefined button types from the Attributes inspector. Select the Bar Button Item and change its Identifier field from Custom to Edit.

tutorial_image
Figure 63: Changing the bar button to an edit button

These predefined types let you access the default system icons which provide a consistent user experience across applications. This isn’t a huge deal for the Add, Edit, Done, and other text-based buttons, but can make quite a difference for the iconic types like Compose:

tutorial_image
Figure 64: The Compose bar button item type

Next, we need to make our new edit button transition to the edit scene. This uses the same process as the push segue from the master table cell to the detail scene. Control-drag from the edit button to the new navigation controller, and select Modal for the Action Segue. You should see a new segue connection with a modal icon on it:

tutorial_image
Figure 65: Creating the modal segue

As with all segues, our new modal segue needs a unique identifier. Select the modal segue’s icon and enter editDetail in the Identifier field of the Attributes inspector.
You should now be able to compile the project (with a few warnings) and launch an empty edit scene by tapping the Edit button in the detail scene. Our next task will be to add some UI components to the edit scene, along with a Cancel and Save button.

Designing the Edit Scene

Next, we’re going to design the edit scene. It will look a lot like the detail scene, except it will have text fields instead of labels. Our first task is to add a title to the navigation bar. Double-click the center of the edit scene’s navigation bar and type Edit. The scene should look like the following afterwards:

tutorial_image
Figure 66: Adding a title to the edit scene

Next, we need to change the Table View from a dynamic table to a static one. Select the edit scene’s Table View object from the Document Outline, as shown in the following figure:

tutorial_image
Figure 67: Selecting the Table View object

Then, change the Content field of the Attributes inspector to Static Cells. Delete all but one of the static cells that appear in the scene editor. It’s also a good idea to change the Selection field to No Selection since we’re only using the table for layout purposes.

Now, we can’t use any of the default Style values for the cells since none of them use text fields. Instead, we’ll create the cell from scratch. First, drag a Label and a Text Field object onto the remaining cell and use the guidelines to make sure they are centered vertically. You should also resize both the label and the text field so that they look something like the following:

tutorial_image
Figure 68: Adding a label and text field to the edit scene

For the detail scene, we specified Left Detail for the cell Style. This automatically defined the style, font, and alignment of the components, but since we’re creating a custom cell, we need to do this ourselves. All of these settings can be defined in the Attributes inspector for the UILabel and UITextField objects. For the label, change the text to First Name, and then set the color to the same as the title labels in the detail scene. One way to do this is to open the Colors panel for the edit scene’s label, selecting the magnifying glass (which really acts more like a dropper), and selecting the color from the detail scene’s title label. The selected color should be the one in the following figure:

tutorial_image
Figure 69: The “dropper” tool in the Colors panel

Finally, change the font to System Bold with a size of 12 and change the alignment to Right. The final settings are shown in the following screenshot:

tutorial_image
Figure 70: Final attributes for the label

All you need to do for the text field is change the Capitalization to Words. To create the cells for the other fields, copy and paste the existing cell three times, and change their labels to Last Name, Phone, and Organization. This will give you the following table:

tutorial_image
Figure 71: The edit scene table cells with appropriate labels

You should also change the Keyboard field for the Phone text field to Number Pad to display a number pad instead of a QWERTY keyboard. That covers the edit scene’s table, but if you try to compile the project right now, you’ll notice that all of these cells disappear. This is because the EditViewController.m provided by the class template defines several data source methods that treat the table as a prototype cell. We’ll delete these in the next section.

But before we do that, let’s add two buttons to the navigation bar so that users can choose whether they want to cancel or save their edits. Drag two bar button items from the Object Library onto either side of the navigation bar. Change the left button’s Identifier field to Cancel and the right one to Save.

tutorial_image
Figure 72: The completed layout for the edit scene

Notice how the Save button is bright blue as per the iOS UX conventions. Again, these default Identifiers help ensure a consistent user interface across applications.

Coding the Edit View Controller

In this section, we’ll code the functionality behind the UI components we just added to the storyboard. The two main tasks are to prepare outlets for the text fields so that we can access them from the EditViewController class, and implement a text field delegate so users can dismiss the text field. This should all be a review from the previous chapter. First, let’s add a few properties to the header file:

// EditViewController.h
#import <UIKit/UIKit.h>
@interface EditViewController : UITableViewController
@property (strong, nonatomic) id detailItem;
@property (weak, nonatomic) IBOutlet UITextField *firstNameField;
@property (weak, nonatomic) IBOutlet UITextField *lastNameField;
@property (weak, nonatomic) IBOutlet UITextField *phoneNumberField;
@property (weak, nonatomic) IBOutlet UITextField *organizationField;
@end

The implementation looks a lot like DetailViewController.m. All it does is make sure that the text fields are updated when the detailItem property is changed:

// EditViewController.m
#import "EditViewController.h"
#import "Person.h"
@implementation EditViewController
@synthesize detailItem = _detailItem;
@synthesize firstNameField = _firstNameField;
@synthesize lastNameField = _lastNameField;
@synthesize phoneNumberField = _phoneNumberField;
@synthesize organizationField = _organizationField;
- (void)setDetailItem:(id)detailItem {
    if (_detailItem != detailItem) {
        _detailItem = detailItem;
        [self configureView];
    }
}
- (void)configureView {
    if (self.detailItem && [self.detailItem isKindOfClass:[Person class]]) {
        self.firstNameField.text = [self.detailItem firstName];
        self.lastNameField.text = [self.detailItem lastName];
        self.phoneNumberField.text = [self.detailItem phoneNumber];
        self.organizationField.text = [self.detailItem organization];
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self configureView];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
@end

Next, we need to prepare the text field delegate. In EditViewController.h, tell the class to adopt the UITextFieldDelegate protocol with the following line:

@interface EditViewController : UITableViewController <UITextFieldDelegate>
As in the previous chapter, we can dismiss the keyboard by implementing the textFieldShouldReturn: method. Add the following to EditViewController.m:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    if ((textField == self.firstNameField) ||
        (textField == self.lastNameField) ||
        (textField == self.phoneNumberField) ||
        (textField == self.organizationField)) {
        [textField resignFirstResponder];
    }
    return YES;
}

Recall that the prepareForSegue:sender: method is called on the source scene right before iOS switches to the destination scene. Just as we did in the master scene, we’ll use this to send the selected item to the edit scene. In DetailViewController.m, add the following method:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@"editDetail"]) {
        NSArray *navigationControllers = [[segue destinationViewController] viewControllers];
        EditViewController *editViewController = [navigationControllers objectAtIndex:0];
        [editViewController setDetailItem:self.detailItem];
    }
}

Remember that the edit scene is embedded in a navigation controller, so the modal segue points to the navigation controller, not the edit scene itself. This intervening navigation controller adds an extra step that we didn’t need to worry about in the master scene’s prepareForSegue:sender: method. To get the edit scene, we need to query the navigation controller’s viewControllers property, which is an array containing its navigation stack. Since the edit scene is the only child view controller, we can access it via the objectAtIndex:0 call. Once we have the EditViewController instance, we simply forward the selected item from the detail scene to the edit scene.

Outlet and Delegate Connections

Back in the storyboard, let’s connect the outlets and delegates that we just exposed. For the outlets, select the yellow icon in the edit scene’s dock, open the Connections inspector, and drag from the firstNameField, lastNameField, organizationField, and phoneNumberField circles to the corresponding text fields in the scene.

tutorial_image
Figure 73: Creating the outlet connections

To set the EditViewController as the delegate for the text fields, select each text field, open the Connections inspector, and drag from the delegate circle to the yellow icon in the dock, as shown in the following screenshot. Do this for each text field.

tutorial_image
Figure 74: Creating the delegate connections

When you compile the project, you should be able to launch the edit scene and see the text fields populated with the selected Person object’s properties. Hopefully by now, you’re relatively comfortable making these kinds of outlet and delegate connections on your own.

You can edit the values, but since we haven’t implemented the Cancel or Save buttons yet, you won’t be able to alter the underlying Person object or even navigate away from the edit scene.

Unwind Segues

Remember that Master button that automatically appeared in the detail scene’s navigation bar? The navigation controller for the master/detail scenes set up this “back” button for us, but since we’re using a modal segue, we need to manually dismiss the modally presented edit scene. We’ll use what’s called an unwind segue to return to the detail scene.

The main difference between an unwind segue and other segues is that the former uses an existing scene as the destination, whereas modal and push segues create a new instance of their destination scene. This is important to keep in mind if you’re doing a lot of transitioning back and forth.

The process of unwinding a scene is also a little bit different than initiating a push segue. It uses the target-action design pattern, which we discussed in the previous chapter. In addition to calling the prepareForSegue:sender: method on the source scene, an unwind segue calls an arbitrary method on the destination scene (DetailViewController). Let’s go ahead and declare a cancel and a save action in DetailViewController.h:

- (IBAction)save:(UIStoryboardSegue *)sender;
- (IBAction)cancel:(UIStoryboardSegue *)sender;

In a moment, we’re going to attach these methods to the Cancel and Save buttons in the edit scene. But first, we need to implement them. Add the following methods to DetailViewController.m:

- (IBAction)save:(UIStoryboardSegue *)segue {
    if ([[segue identifier] isEqualToString:@"saveInput"]) {
        EditViewController *editController = [segue sourceViewController];
        [self.detailItem setFirstName:editController.firstNameField.text];
        [self.detailItem setLastName:editController.lastNameField.text];
        [self.detailItem setPhoneNumber:editController.phoneNumberField.text];
        [self.detailItem setOrganization:editController.organizationField.text];
        [self configureView];
    }
}
- (IBAction)cancel:(UIStoryboardSegue *)segue {
    if ([[segue identifier] isEqualToString:@"cancelInput"]) {
        // Custom cancel handling can go here.
    }
}

These are pretty straightforward. The save: method updates the detailItem’s properties based on the text field values from the edit scene, and then updates its labels by calling configureView. The cancel: method simply ignores anything that happened in the edit scene.
Now, we can create an unwind segue to dismiss the edit scene and call the appropriate method. Configuring unwind segues is similar to creating push segues: you control-drag from the UI component that initiates the segue to the green Exit icon in the dock. This icon is dedicated solely to creating unwind segues.

tutorial_image
Figure 75: The exit icon in the dock (far right)

So, control-drag from the Save button in the edit scene to the Exit icon in the dock, as shown in the following figure:

tutorial_image
Figure 76: Creating the unwind segue for the Save button

A menu will pop up asking you to associate an action with the segue:

tutorial_image
Figure 77: Selecting the action for the unwind segue

Of course, you’ll want to choose save:. That’s all you need to do to create the unwind segue. After repeating the process for the Cancel button, you should see both unwind segues in the document outline:

tutorial_image
Figure 78: The unwind segues in the document outline

Unlike push and modal segues, unwind segues have no visual representation in the interface builder, so the document outline is the only way you can select them. Our last step will be to add unique identifiers to both of these segues via the Attributes inspector. Use cancelInput for the Cancel button and saveInput for the Save button (note that these are the identifiers we checked against in the cancel: and save: methods, respectively). Again, since our example app is so simple, adding segue identifiers is more of a best practice step than a necessity.

tutorial_image
Figure 79: Defining the unwind segue identifiers

You can think of an unwind segue as a combination of a transition and a button. The segue takes care of dismissing the scene (i.e. transitioning to the parent scene), but since it’s initiated by a button press, you can also attach a method to it using the target-action pattern.

Our edit scene is now complete, and you should be able to compile the project, enter values into the edit scene’s text fields, and choose to cancel or save your changes. Since the save: method calls configureView after saving the new values, the detail scene will update to reflect the edits. However, we never told the master scene to update itself, so your changes will not be reflected in the master list.

Updating the Master List

The final thing we have to go over in this chapter is updating the master scene’s table to reflect any changes in the underlying data. There are a number of ways to do this, but the easiest (though not necessarily the most efficient) is to reload the table each time the master scene is displayed.

UIViewController defines a method called viewWillAppear: and calls it right before the associated scene is displayed. This is different than viewDidLoad:, which gets the first time the view is displayed. Since the parent navigation controller displays the same instance of the master scene each time the user navigates to it, we need to use the viewWillAppear: method instead of viewDidAppear:. In MasterViewController.m, add the following method:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    UITableView *view = (UITableView *)self.view;
    [view reloadData];
}

First, we pass the method along to super, and then we fetch the controller’s root UIView instance through the view property. We can assume this is a UITableView because MasterViewController inherits from UITableViewController, but we still need to cast it to prevent the compiler from complaining. The UITableView’s reloadData method regenerates the table cells based on the underlying data set (the _objects array), and the master list should now reflect any changes you saved from the edit scene.

tutorial_image
Figure 80: The completed master scene

Summary

In this chapter, we learned how to manage multiple scenes within a single app. We experimented with UITableViewControllers, UINavigationControllers, and all sorts of segues. One of the most important concepts to take away from this chapter is how we transferred data between each scene: via the prepareForSegue:sender: method and the save: and cancel: methods for the unwind segue. Most applications are really just user-friendly editors for complex data structures, so understanding how that data is passed around goes a long way toward efficiently organizing iOS projects.

The previous two chapters covered everything you need to know to create simple user interfaces. For the rest of this series, we’ll explore other iOS frameworks for accessing media assets, localizing resources, and playing UI sound effects.

This lesson represents a chapter from iOS Succinctly, a free eBook from the team at Syncfusion.

iOS Succinctly – Asset Management

$
0
0

Now that we have a basic understanding of iOS scene management, the next big topic to tackle is how to manage the multimedia assets in an application. iOS apps store their assets using the same hierarchical file system as any other modern operating system. Text, image, audio, and video files are organized into folders and accessed using familiar file paths like Documents/SomePicture.png.

In this chapter, we’ll learn about the standard file structure for an app; how to add resources to a project; and how to locate, load, and save files. We’ll also talk about the required assets for all iOS apps.

Throughout the chapter, we’ll talk about files and folders, but keep in mind that the file system should be entirely hidden from iOS users. Instead of showing users the files and folders behind an application, iOS encourages developers to present the file system as user-oriented documents. For example, in a sketching app, drawings should be listed with semantic display names and organized into sketchbooks or a similar abstract organizational structure. You should never show the user file paths like sketchbook-1/your-drawing.svg.


Conceptual Overview

The Application Sandbox

The iOS file system was designed with security in mind. Instead of allowing an app to access a device’s entire file system, iOS gives each application its own separate file system (a sandbox). This means your application doesn’t have access to files generated by other apps. When you need to access information that is not owned by your app (e.g., the user’s contact list), you request it from a mediator (e.g., the Address Book Framework) instead of accessing the files directly.

A sandbox is like a mini file system dedicated solely to your app’s operation. All apps use a canonical file structure consisting of four top-level directories, each of which store a specific type of file.

  • AppName.app, the application bundle that contains your app’s executable and all of its required media assets. You can read from this folder, but you should never write to it. The next section discusses bundles in more detail.
  • Documents/, a folder for user-generated content and other critical data files that cannot be re-created by your app. The contents of this directory are available through iCloud.
  • Library/, a folder for application files that are not used by the user, but still need to persist between launches.
  • tmp/, a folder for temporary files used while your application is running. Files in this folder do not necessarily persist between application launches. iOS will automatically delete temporary files when necessary while your application isn’t running, but you should manually delete temporary files as soon as you’re done with them as a best practice.

When the user installs an app, a new sandbox containing all of these folders is created. After that, your application can dynamically create arbitrary subdirectories in any of these top-level folders. There are also a few pre-defined subdirectories, as described in the following list.

  • Library/Application Support/, a folder for support files that can be re-created if necessary. This includes downloaded and generated content. You should use the com.apple.MobileBackup extended attribute to prevent this folder from being backed up.
  • Library/Cache/, a folder for cache files. These files can be deleted without notice, so your app should be able to re-create them gracefully. This folder is also an appropriate place to store downloaded content.

It’s important to put files in the appropriate folder to make sure they are backed up properly without consuming an unnecessary amount of space on the user’s device. iTunes automatically backs up files in the Documents/ and Library/ folders (with the exception of Library/Cache/). Neither the application bundle nor the tmp/ folder should ever need to be backed up.

Bundles

An iOS application isn’t just an executable. It also contains media, data files, and possibly localized text for different regions. To simplify deployment, Xcode wraps the executable and all of its required files into a special kind of folder called an application bundle. Despite being a folder, an application bundle uses the .app extension. You can think of an application bundle as a ZIP file that runs an app when you open it.

Since your application bundle contains all of your media assets, you’ll need to interact with it while your program is running. The NSBundle class makes it easy to search your application bundle for specific files, which can then be loaded by other classes. For example, you can locate a particular image file using NSBundle, and then add it to a view using the UIImage class. We’ll do this in another section, “The Application Bundle.”


Creating the Example Application

This chapter uses a simple application to explore some of the fundamental methods for accessing files in iOS. First, open Xcode, create a new project, and select Single View Application for the template.

tutorial_image
Figure 81: Creating a new Single View Application

Use AssetManagement for the Product Name, edu.self for the Company Identifier, and make sure Use Storyboards and Use Automatic Reference Counting are selected.

tutorial_image
Figure 82: Configuring the new project

You can save the project wherever you like.


The File System

Before we go into an app’s multimedia assets, we’re going to look at the basic tools for accessing the file system. The upcoming sections discuss how to generate file paths, create plain text files, and load them back into the application.

Locating Standard Directories

One of the most common file system tasks is generating the path to a particular resource. But, before you can gain access to any given file, you need to find the path to the application sandbox or one of the top-level folders discussed previously. The easiest way to do this is via the global NSHomeDirectory() function, which returns the absolute path to the application sandbox. For example, try changing ViewController.m’s viewDidLoad method to the following:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *sandboxPath = NSHomeDirectory();
    NSLog(@"The app sandbox resides at: %@", sandboxPath);
}

When the app loads in the iOS Simulator, you should see something like the following in Xcode’s output panel.

/Users/ryan/Library/Application Support/iPhone Simulator/6.0/Applications/9E38D1C4-8B11-4599-88BE-CD9E36C21A41

This path represents the root of your application. If you navigate to this directory in a terminal, you’ll find the following four directories.

AssetManagement.app
Documents/
Library/
tmp/

Not surprisingly, this is the canonical file structure discussed previously. Of course, NSHomeDirectory() will return a different path when your application is running on an iOS device. The idea behind using NSHomeDirectory() instead of manually generating the path to your application is to make sure you always have the correct root path, regardless of where your application resides.

The related NSTemporaryDirectory() function returns the path to the tmp/ directory. For the other standard application folders, you’ll need to use the NSFileManager class.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSLog(@"The Library subfolder: %@", paths[0]);
    }
}

As you can see, NSFileManager is implemented as a singleton, and the shared instance should be accessed via the defaultManager class method. The NSSearchPathDirectory enum defines several constants that represent the standard locations used by both OS X and iOS applications. Some of these locations (e.g., NSDesktopDirectory) are not applicable in iOS apps, however, the URLsForDirectory:inDomains: method will still return the appropriate subfolder in the application sandbox. The constants for the directories we’ve discussed are listed as follows.

NSDocumentDirectory                /*  Documents/                    */
NSLibraryDirectory                 /*  Library/                      */
NSCachesDirectory                  /*  Library/Caches                */
NSApplicationSupportDirectory      /*  Library/Application Support/  */

The URLsForDirectory:inDomains: method returns an NSArray containing NSURL objects, which is an object-oriented representation of a file path.

Generating File Paths

Once you have the location of one of the standard directories, you can manually assemble the path to a specific file using the NSURL instance methods. Note that NSString also provides related utilities, but NSURL is the preferred way to represent file paths.

For example, the URLByAppendingPathComponent: method provides a straightforward way to generate the path to a specific file. The following snippet creates a path to a file called someData.txt in the application’s Library/ directory.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSURL *libraryPath = paths[0];
        NSURL *appDataPath = [libraryPath
                              URLByAppendingPathComponent:@"someData.txt"];
        NSLog(@"%@", appDataPath);
    }
}

A few of the other useful NSURL instance methods are described in the following list. Together, these provide the basic functionality for navigating a file hierarchy and manually determining file names and types.

  • URLByDeletingLastPathComponent returns a new NSURL representing the parent folder of the receiving path.
  • lastPathComponent returns the final component in the path as a string. This could be either a folder name or a file name, depending on the path.
  • pathExtension returns the file extension of the path as a string. If the path doesn’t contain a period, it returns an empty string, otherwise it returns the last group of characters that follow a period.
  • pathComponents decomposes the path into its component parts and returns them as an NSArray.

Saving and Loading Files

It’s important to understand that NSURL only describes the location of a resource. It does not represent the actual file or folder itself. To get at the file data, you need some way to interpret it. The NSData class provides a low-level API for reading in raw bytes, but most of the time you’ll want to use a higher-level interface for interpreting the contents of a file.

The iOS frameworks include many classes for saving and loading different types of files. For example, NSString can read and write text files, UIImage can display images inside of a view, and AVAudioPlayer can play music loaded from a file. We’ll look at UIImage once we get to the application bundle, but for now, let’s stick with basic text file manipulation.

To save a file with NSString, use the writeToURL:automatically:encoding:error: method. The first argument is an NSURL representing the file path, the second determines whether or not to save it to an auxiliary file first, and the third is one of the constants defined by the NSStringEncoding enum, and the error argument is a reference to an NSError instance that will record error details should the method fail. The following snippet demonstrates writeToURL:automatically:encoding:error: by creating a plain text file called someData.txt in the Library/ folder.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSURL *libraryPath = paths[0];
        NSURL *appDataPath = [libraryPath
                              URLByAppendingPathComponent:@"someData.txt"];
        NSString *someAppData = @"Hello, World! This is a file I created dynamically";
        NSError *error = nil;
        BOOL success = [someAppData writeToURL:appDataPath
                                     atomically:YES
                                       encoding:NSUnicodeStringEncoding
                                          error:&error];
        if (success) {
            NSLog(@"Wrote some data to %@", appDataPath);
        } else {
            NSLog(@"Could not write data to file. Error: %@", error);
        }
    }
}

When saving or loading text files, it’s imperative to specify the file encoding, otherwise your text data could be unexpectedly altered when writing or reading a file. A few of the common encoding constants are included here.

NSASCIIStringEncoding–7-bit ASCII encoding with 8-bit chars (ASCII values 0-127).
NSISOLatin1StringEncoding–8-bit ISO Latin 1 encoding.
NSUnicodeStringEncoding–Unicode encoding.

When in doubt, you’ll probably want to use NSUnicodeStringEncoding to make sure multi-byte characters are interpreted properly.

To load a text file, you can use the related stringWithContentsOfURL:encoding:error:. This works much the same as writeToURL:automatically:encoding:error:, but it’s implemented as a class method instead of an instance method. The following example loads the text file created by the previous snippet back into the app.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSURL *libraryPath = paths[0];
        NSURL *appDataPath = [libraryPath
                              URLByAppendingPathComponent:@"someData.txt"];
        NSError *error = nil;
        NSString *loadedText = [NSString
                                stringWithContentsOfURL:appDataPath
                                encoding:NSUnicodeStringEncoding
                                error:&error];
        if (loadedText != nil) {
            NSLog(@"Successfully loaded text: %@", loadedText);
        } else {
            NSLog(@"Could not load data from file. Error: %@", error);
        }
    }
}

In the real world, you’ll probably save and load data that was dynamically generated instead of hardcoded as a literal string. For instance, you might store template preferences or user information that needs to persist between application launches in a text file. It’s entirely possible to manually load and interpret this data from plain text, but keep in mind that there are several built-in tools for working and storing structured data or even whole Objective-C objects. For example, NSDictionary defines a method called dictionaryWithContentsOfURL: that loads an XML file containing key-value pairs.

Manipulating Directories

The NSString methods described previously combine the creation of a file and the writing of content into a single step, but to create directories, we need to return to the NSFileManager class. It defines several methods that let you alter the contents of a directory.

Creating Directories

The createDirectoryAtURL:withIntermediateDirectories:attributes:error: instance method creates a new directory at the specified path. The second argument is a Boolean value that determines whether or not intermediate directories should be created automatically, attributes lets you define file attributes for the new directory, and the final argument is a reference to an NSError instance that will contain the error details should the method fail.

For instance, if your application uses custom templates downloaded from a server, you might save them in Library/Templates/. To create this folder, you could use something like the following.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSURL *libraryPath = paths[0];
        NSURL *templatesPath = [libraryPath
                                URLByAppendingPathComponent:@"Templates"];
        NSError *error = nil;
        BOOL success = [sharedFM createDirectoryAtURL:templatesPath
                          withIntermediateDirectories:YES
                                           attributes:nil
                                                error:&error];
        if (success) {
            NSLog(@"Successfully created a directory at %@",
                  templatesPath);
        } else {
            NSLog(@"Could not create the directory. Error: %@", error);
        }
    }
}

Leaving the attributes argument as nil tells the method to use the default group, owner, and permissions for the current process.

Moving Files/Directories

The NSFileManager class can also be used to move or rename files and folders via its moveItemAtURL:toURL:error: instance method. It works as follows.

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSLibraryDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSURL *libraryPath = paths[0];
        NSURL *sourcePath = [libraryPath
                             URLByAppendingPathComponent:@"someData.txt"];
        NSURL *destinationPath = [libraryPath
                        URLByAppendingPathComponent:@"someOtherData.txt"];
        NSError *error = nil;
        BOOL success = [sharedFM moveItemAtURL:sourcePath
                                         toURL:destinationPath
                                         error:&error];
        if (success) {
            NSLog(@"Successfully moved %@ to %@",
                  sourcePath,
                  destinationPath);
        } else {
            NSLog(@"Could not move the file. Error: %@", error);
        }
    }
}

This renames the someData.txt file we created earlier to someOtherData.txt. If you need to copy files, you can use copyItemAtURL:toURL:error:, which works the same way, but leaves the source file untouched.

Removing Files/Directories

Finally, NSFileManager’s removeItemAtURL:error: method lets you delete files or folders. Simply pass the NSURL instance containing the path you want to remove, as follows.

[sharedFM removeItemAtURL:targetURL error:&error];

If the targetURL is a directory, its contents will be removed recursively.

Listing the Contents of a Directory

It’s also possible to list the files and subdirectories in a folder using NSFileManager’s enumeratorAtPath: method. This returns an NSDirectoryEnumerator object that you can use to iterate through each file or folder. For example, you can list the contents of the top-level Documents/ directory as follows:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    NSArray *paths = [sharedFM URLsForDirectory:NSDocumentDirectory
                                      inDomains:NSUserDomainMask];
    if ([paths count] > 0) {
        NSString *documentsPath = [paths[0] path];
        NSLog(@"%@", documentsPath);
        NSDirectoryEnumerator *enumerator = [sharedFM enumeratorAtPath:documentsPath];
        id object;
        while(object = [enumerator nextObject]) {
            NSLog(@"%@", object);
        }
    }
}

Note that this enumerator will iterate through all subdirectories. You can alter this behavior by calling the skipDescendents method on the NSDirectoryEnumerator instance, or using the more sophisticated enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: method of NSFileManager.


The Application Bundle

The file access methods discussed previously are typically only used to interact with files that are dynamically created at run time. The standard Library/, Documents/, and tmp/ folders are local to the device on which the application is installed, and they are initially empty. For assets that are an essential part of the application itself (as opposed to being created by the application), we need another tool—an application bundle.

The application bundle represents your entire project, which is composed of the iOS executable, along with all of the images, sounds, videos, and configuration files required by that executable. The application bundle is what actually installs when a user downloads your app, so—unlike the contents of the sandbox directories—you can assume that all of the supporting resources will be present regardless of the device on which your app resides.

Conceptually, a bundle is just a bunch of files, but interacting with it is a little bit different than using the file system methods from the previous section. Instead of manually generating file paths and saving or loading data with methods like writeToURL:automatically:encoding:error:, you use the NSBundle class as a high-level interface to your application’s media assets. This delegates some nitty-gritty details behind media assets to the underlying system.

In addition to custom media assets, the application bundle also contains several required resources, like the app icon that appears on the user’s home screen and important configuration files. We’ll talk about these in the “Required Resources” section.

Adding Assets to the Bundle

Remember that assets in the application bundle are static, so they will always be included at compile-time. To add a media asset to your project, simply drag the file(s) from the Finder into the Project Navigator panel in Xcode. We’re going to add a file called syncfusion-logo.jpg, which you can find in the resource package for this book, but you can use any image you like. In the next section, we’ll learn how to access the bundle to display this image in the view.

tutorial_image
Figure 83: Adding an image to the application bundle

After releasing the mouse, Xcode will present you with a dialog asking for configuration options. The Copy items into destination group’s folder check box should be selected. This tells Xcode to copy the assets into the project folder, which is typically desirable behavior. The Create groups for any added folders option uses the Xcode grouping mechanism. Add to targets is the most important configuration option. It defines which build targets the asset will be a compiled with. If AssetManagement wasn’t selected, it would be like we never added it to the project.

tutorial_image
Figure 84: Selecting configuration options for the new media assets

After clicking Finish, you should see your file in the Xcode Project Navigator:

tutorial_image
Figure 85: The media asset in the Project Navigator

Now that you have a custom resource in your application bundle, you can access it via NSBundle.

Accessing Bundled Resources

Instead of manually creating file paths with NSURL, you should always use the NSBundle class as the programmatic interface to your application bundle. It provides optimized search functionality and built-in internationalization capabilities, which we’ll talk about in the next chapter.

The mainBundle class method returns the NSBundle instance that represents your application bundle. Once you have that, you can locate resources using the pathForResource:ofType: instance method. iOS uses special file naming conventions that make it possible for NSBundle to return different files depending on how the resource is going to be used. Separating the file name from the extension allows pathForResource:ofType: to figure out which file to use automatically, and it allows NSBundle to automatically select localized files.

For example, the following code locates a JPEG called syncfusion-logo in the application bundle:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Find the resource.
    NSString *imagePath = [[NSBundle mainBundle]
                           pathForResource:@"syncfusion-logo"
                           ofType:@"jpg"];
    NSLog(@"Image path: %@", imagePath);
    // Load the resource.
    UIImage *imageData = [[UIImage alloc]
                          initWithContentsOfFile:imagePath];
    if (imageData != nil) {
        NSLog(@"Image size: %.0fx%.0f",
              imageData.size.width, imageData.size.height);
    } else {
        NSLog(@"Could not load the file");
    }
}

Like text files, locating an image and loading it are separate actions. The first NSLog() call should display something like /path/to/sandbox/AssetManagement.app/your-image.jpg in the Xcode Output Panel.

The UIImage class represents the contents of an image file, and it works with virtually any type of image format (JPEG, PNG, GIF, TIFF, BMP, and a few others). Once you’ve gotten the image location with NSBundle, you can pass it to the initWithContentsOfFile: method of UIImage. If the file loaded successfully, you’ll be able to access the image’s dimensions through the size property, which is a CGSize struct containing the width and height floating-point fields.

While UIImage does define a few methods for drawing the associated image to the screen (namely, drawAtPoint: and drawInRect:), it’s often easier to display it using the UIImageView class. Since it’s a subclass of UIView, it can be added to the existing view hierarchy using the addSubview: method common to all view instances. UIImageView also provides a convenient interface for controlling animation playback. To display UIImage in the root view object, change the viewDidLoad method of ViewController.m to the following.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Find the image.
    NSString *imagePath = [[NSBundle mainBundle]
                           pathForResource:@"syncfusion-logo"
                           ofType:@"jpg"];
    // Load the image.
    UIImage *imageData = [[UIImage alloc]
                          initWithContentsOfFile:imagePath];
    if (imageData != nil) {
        // Display the image.
        UIImageView *imageView = [[UIImageView alloc]
                                  initWithImage:imageData];
        [[self view] addSubview:imageView];
    } else {
        NSLog(@"Could not load the file");
    }
}

When you compile the project, you should see your image in the top-left corner of the screen. By default, the image will not be scaled. So, if your image is bigger than the screen, it will be cropped accordingly.

tutorial_image
Figure 86: A cropped UIImageView object

To change how your UIImageView scales its content, you should use the contentMode property of UIView. It takes a value of type UIViewContentMode, which is an enum defining the following behaviors.

  • UIViewContentModeScaleToFill, scale to fill the view’s dimensions, changing the image’s aspect ratio if necessary.
  • UIViewContentModeScaleAspectFit, scale to fit into the view’s dimensions, maintaining the image’s aspect ratio.
  • UIViewContentModeScaleAspectFill, scale to fill the view’s dimensions, maintaining the image’s aspect ratio. This may cause part of the image to be clipped.
  • UIViewContentModeCenter, use the original image’s size, but center it horizontally and vertically.
  • UIViewContentModeTop, use the original image’s size, but center it horizontally and align it to the top of the view.
  • UIViewContentModeBottom, use the original image’s size, but center it horizontally and align it to the bottom of the view.
  • UIViewContentModeLeft, use the original image’s size, but center it vertically and align it to the left of the view.
  • UIViewContentModeRight, use the original image’s size, but center it vertically and align it to the right of the view.
  • UIViewContentModeTopLeft, use the original image’s size, but align it to the top-left corner of the view.
  • UIViewContentModeTopRight, use the original image’s size, but align it to the top-right corner of the view.
  • UIViewContentModeBottomLeft, use the original image’s size, but align it to the bottom-left corner of the view.
  • UIViewContentModeBottomRight, use the original image’s size, but align it to the bottom-right corner of the view.

For example, if you want your image to shrink to fit into the width of the screen while maintaining its aspect ratio, you would use UIViewContentModeScaleAspectFit for the contentMode, and then change the width of the image view’s dimensions to match the width of the screen (available via the [UIScreen mainScreen] object). Add the following to the viewDidLoad method from the previous example after the [[self view] addSubview:imageView]; line.

CGRect screenBounds = [[UIScreen mainScreen] bounds];
imageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame = imageView.frame;
frame.size.width = screenBounds.size.width;
imageView.frame = frame;

The frame property of all UIView instances defines the position and the visible area of the view (i.e. its dimensions.). After changing the width of the frame, the UIViewContentModeScaleAspectFit behavior automatically calculated the height of the frame, resulting in the following image.

tutorial_image
Figure 87: Shrinking an image to fit the screen width

While this section extracted an image from the bundle, remember that it’s just as easy to access other types of media. In the previous section, we saw how text files can be loaded with NSString; this works the same way with bundles. A video works in a similar fashion to an image in that iOS provides a dedicated class (MPMoviePlayerController) for incorporating it into an existing view hierarchy. Audio files are slightly different, since playback is not necessarily linked to a dedicated view. We’ll discuss the audio capabilities of iOS later in this book. For now, let’s go back to the application bundle.

Required Resources

In addition to any custom media assets your app might need, there are also three files that are required to be in your application bundle. These are described as follows.

  • Information property list, the configuration file defining critical options like required device capabilities, supported orientations, etc.
  • App icon, the icon that appears on the user’s home screen. This is what the user taps to launch your application.
  • Launch image. After the user launches your application, this is the image that briefly appears while it’s loading.

Information Property List

A property list (also known as a “plist”) is a convenient format for storing structured data. It makes it easy to save arrays, dictionaries, dates, strings, and numbers into a persistent file and load it back into an application at run time. If you’ve ever worked with JSON data, it’s the same idea.

The information property list is a special kind of property list stored in a file called Info.plist in your application bundle. You can think of it as a file-based NSDictionary whose key-value pairs define the configuration options for your app. The Info.plist for our example app is generated from a file called AssetManagement-Info.plist, and you can edit it directly in Xcode by selecting it from the Supporting Files folder in the Project Navigator. When you open it, you should see something like the following.

tutorial_image
Figure 88: Opening the Info.plist file in Xcode

These are all of the configuration options provided by the template. The left-most column contains the option name, and the right-most one contains its value. Note that values can be Boolean, numbers, strings, arrays, or even entire dictionaries.

By default, keys are shown with human-readable titles, but it helps—especially when learning iOS for the first time—to see the raw keys that are referenced by the official documentation. To display the raw keys, Ctrl+click anywhere in the Info.plist editor and select Show Raw Keys/Values. These are the strings to use when you want to access configuration options programmatically (as discussed in the next section).

tutorial_image
Figure 89: Displaying raw keys

The left-most column should now show keys like CFBundleDevelopmentRegion, CFBundleDisplayName, and so on. The following keys are required by all Info.plist files.

  • UIRequiredDeviceCapabilities is an array containing the device requirements for your app. This is one way that Apple determines which users can view your application in the App Store. Possible values are listed in the UIRequiredDeviceCapabilities section of the iOS Keys Reference.
  • UISupportedInterfaceOrientations is an array defining the orientations your app supports. Acceptable values include UIInterfaceOrientationPortrait, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight, and UIInterfaceOrientationPortraitUpsideDown.
  • CFBundleIconFile is an array containing the file names of all your app icons. We’ll talk more about app icons in a moment.

The template provides defaults for the device requirements and the supported orientations, as shown in Figure 90.

tutorial_image
Figure 90: Default values for device requirements and supported orientations

Accessing Configuration Options

Most of the configuration options defined in Info.plist are used internally by iOS, but you may occasionally need to access them manually. You can get an NSDictionary representation of Info.plist through the infoDictionary method of NSBundle. For example, if you wanted to perform a custom launch behavior based on a key in Info.plist, you could do a quick check in the application:didFinishLaunchingWithOptions: method of AppDelegate.m, like so:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
    NSArray *supportedOrientations = [infoDict objectForKey:@"UISupportedInterfaceOrientations"];
    if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortraitUpsideDown"]) {
        NSLog(@"Do something special to enable an upside-down app");
    } else {
        NSLog(@"Can assume the app won't display upside-down");
    }
    return YES;
}

App Icon(s)

Your app bundle must include at least one icon to display on the home screen, but it’s also possible to specify multiple icons for different situations. For example, you might want to use a different design for the smaller icon that appears in search results, or use a high-resolution image for devices with Retina displays. Xcode takes care of all of this for you.

The CFBundleIconFiles key in Info.plist should be an array containing the file names of all your app icons. With the exception of the App Store icon, you can use any name you like for the files. Note that you do not need to specify the intended usage of each file—iOS will select the appropriate icon automatically based on the dimensions.

tutorial_image
Figure 91: Custom iPhone app icons with high-resolution versions for Retina displays

If your app supports devices with Retina displays, you should also include a high-resolution version of each icon and give it the same file name with @2x appended to it. For example, if your main icon file was called app-icon.png, the Retina display version should be called app-icon@2x.png.

The following table lists the standard iOS app icons, but be sure to visit the iOS Human Interface Guidelines for a more detailed discussion. This document also provides extensive guidelines for creating icon graphics. All icons should use the PNG format.

Icon TypePlatformRequiredStandard SizeRetina SizeDescription/
App Store IconiPhone and iPadYes512 × 5121024 × 1024The image presented to customers in iTunes. This file must be called iTunesArtwork or iTunesArtwork@2x (with no extension).
Main IconiPhoneYes57 × 57114 × 114The icon that appears on the iPhone home screen.
Main IconiPadYes72 × 72144 × 144The icon that appears on the iPad home screen.
Small IconiPhoneNo29 × 2958 × 58The icon displayed next to search results and in the Settings app for iPhone.
Small IconiPadNo50 × 50100 × 100The icon displayed next to search results and in the Settings app for iPad.

Next, you’re going to add an icon to the example application. In the resource package for this book, you’ll find four sample icons called app-icon.png, app-icon@2x.png, app-icon-small.png, and app-icon-small@2x.png. Drag all of these into the Project Navigator to add them to your application bundle, and then open AssetManagement-Info.plist and make sure you’re looking at raw keys or values. Add a new row and type CFBundleIconFiles for the key (not to be confused with CFBundleIcons or CFBundleIconFile). This will automatically add an empty item to the array, which you can view by clicking the triangle next to the CFBundleIconFiles item. Add all four of the icon files to the array, so it looks like the following. The order doesn’t matter.

tutorial_image
Figure 92: Adding icon files to Info.plist

Now, when you run your project, you should see a custom app icon in the home screen. You may need to restart the iOS Simulator for this to work.

tutorial_image
Figure 93: The custom app icon in the home screen

Try dragging the home screen to the right a few times until you reach the search screen. If you start typing “assetmanagement,” you should see the small version of the icon in the search results, as shown in Figure 94:

tutorial_image
Figure 94: The small app icon used in search results

And that’s all there is to customizing the icons for your iPhone application.

Launch Image(s)

The final required media asset for any iOS application is a launch image. A launch image is displayed immediately when the user opens your application. The idea is to give the user the impression that your app launched immediately, even though it may take a few seconds to load. Apple discourages developers from using launch images as an about page or a splash screen. Instead, it should be a skeleton of your app’s initial screen.

For example, consider the master-detail application we built in the previous chapter. The ideal launch image would simply be an empty master list:

tutorial_image
Figure 95: Appropriate launch image for a master-detail application

As you can see from previous chapters, a launch image is essentially a screenshot of your app’s initial screen, minus any dynamic data. This avoids any abrupt changes in the UI. Again, the idea is to downplay the application launch by making the transition as seamless as possible from app selection, to launch image, to the initial screen. The iOS Simulator has a convenient screen capture tool for creating launch images. Once your application is done, run it in the simulator and navigate to Edit > Copy Screen.

Like app icons, it’s possible to have multiple launch images depending on the device or screen resolution. Launch images use the same @2x affix for high-resolution images, but it’s also possible to target specific devices by appending a usage modifier immediately after the base name. For example, iPhone 5 has different screen dimensions than previous generations, and thus requires its own launch image. To tell iOS to use a particular file for iPhone 5 devices, you would append -568h to its base name, giving you something like launch-image-568h@2x.png (note that iPhone 5 has a Retina display, so the associated launch image will always have @2x in its filename).

The following table lists the dimension requirements for iOS launch images.

PlatformStandard SizeRetina Size
iPhone (up to 4th generation)320 × 480640 × 960iPhone (5th generation)640 × 1136640 × 1136iPad768 × 10041536 × 2008

Once you have your launch image, adding it to your application is very similar to configuring app icons. First, add the files to the top level of your application bundle, and then add the base name to the UILaunchImageFile key of your Info.plist. For example, if your launch images were called launch-image.png, launch-image@2x.png, and launch-image-568h@2x.png, you would use launch-image as the value for UILaunchImageFile.

If you don’t specify a UILaunchImageFile, iOS will use the files Default.png, Default@2x.png, and Default-568h@2x.png. These default launch images are provided by the Xcode templates.


Summary

This chapter covered many of the built-in asset management tools in iOS. We talked about an app’s sandbox file system and how to read and write dynamic files from various predefined directories. We also looked at the application bundle, which contains all of the resources that will be distributed with the app. In addition to any custom media assets, the application bundle must include an information property list, app icons, and launch images.

Bundles are a convenient way to distribute an application, but they also provide built-in internationalization capabilities. In the next chapter, we’ll learn how to show different files to different users based on their language settings. And, thanks to NSBundle, this will require very little additional code.

This lesson represents a chapter from iOS Succinctly, a free eBook from the team at Syncfusion.

iOS Succinctly – Localization

$
0
0

So far, all of our example projects have assumed that our apps were destined for English speakers, but many applications can benefit from being available to non-English-speaking audiences. The App Store takes care of presenting our app to the right audience, but it’s our job as developers to configure it in such a way that the appropriate resources are displayed to users from different regions. This process is called localization.

Fortunately, iOS makes it surprisingly easy to localize resources using bundles. The NSBundle class automatically selects the appropriate asset by taking into account the user’s preferred language. For example, if you’ve provided different versions of the same image for English speakers versus Spanish speakers, the pathForResource:ofType: method discussed in the previous chapter returns different file paths depending on the user’s settings. This is one of the primary reasons you shouldn’t directly access bundle resources using hardcoded paths.

The three aspects of an app that typically need to be localized are images, audio, or videos containing a specific language, hardcoded strings, and storyboards. In this chapter, we’ll take a brief look at localizing media resources and hardcoded strings using NSBundle’s built-in internationalization capabilities. Storyboard files can be localized using the same process.


Creating the Example Application

The example for this chapter is a simple application that displays different images or strings based on the user’s preferred language. Create a new Single View Application and call it “Internationalization.” As always, Use Storyboards, and Use Automatic Reference Counting should be selected.


Enabling Localization

The first step to making an application multilingual is to add the supported languages to the project. In the project navigator, select the project icon.

tutorial_image
Figure 96: Selecting the project in the Project Navigator

Then, select the Internationalization project in the left column (not to be confused with the Internationalization target). Make sure the Info tab is selected; you should see the following window:

tutorial_image
Figure 97: The Project Info window

To add support for another language, select the plus sign under the Localizations section. You can pick any language you like, but this book will use Spanish. Selecting a language will open a dialog asking which files should be localized. Clear the selection of MainStoryboard.storyboard, but leave InfoPlist.strings selected.

tutorial_image
Figure 98: Adding a Spanish localization

It’s now possible to add a Spanish version of each resource to the application bundle.


Localizing Images

Next, we’ll look at localizing media assets. In the resource package for this book, you’ll find a file called syncfusion-icon-en.png. Add this file to the application bundle by dragging it to the Project Navigator and rename it as syncfusion-icon.png. Then, display it in the view by changing the viewDidLoad method in ViewController.m to the following:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Find the image.
    NSString *imagePath = [[NSBundle mainBundle]
                           pathForResource:@"syncfusion-icon"
                           ofType:@"png"];
    NSLog(@"%@", imagePath);
    // Load the image.
    UIImage *imageData = [[UIImage alloc]
                          initWithContentsOfFile:imagePath];
    if (imageData != nil) {
        // Display the image.
        UIImageView *imageView = [[UIImageView alloc]
                                  initWithImage:imageData];
        CGRect screenBounds = [[UIScreen mainScreen] bounds];
        imageView.contentMode = UIViewContentModeCenter;
        CGRect frame = imageView.frame;
        frame.size.width = screenBounds.size.width;
        frame.size.height = screenBounds.size.height;
        imageView.frame = frame;
        [[self view] addSubview:imageView];
    } else {
        NSLog(@"Could not load the file");
    }
}

When you compile the project, you should see a small icon displayed in the middle of the screen:

tutorial_image
Figure 99: Programmatically adding an image to the view

You should also see the path Internationalization.app/syncfusion-icon.png in the Output Panel. Nothing new here, just an image at the top level of the application bundle, but this is going to change once we localize the image file.

To do this, select the image in the Project Navigator, open the Utilities panel, and click Make Localized under the Localization section.

tutorial_image
Figure 100: Creating a localized file

The next dialogue prompts you to choose a language. Select English and click Localize.

tutorial_image
Figure 101: Configuring the localization

This tells iOS that this version of syncfusion-icon.png is for English speakers. We’ll add a Spanish version in a moment, but first let’s look at what’s happening behind the scenes. To see your localizations in action, you’ll have to reset the iOS Simulator and do a clean build. To reset the simulator, navigate to iOS Simulator > Reset Content and Settings in the menu bar and select Reset in the resulting dialog.

tutorial_image
Figure 102: Resetting the iOS Simulator

Quit the simulator and go back to the Internationalization project in Xcode. To do a clean build, navigate to Product > Clean in the menu bar and compile the project again as you normally would. You should see a different file path in the Output Panel:

Internationalization.app/en.lproj/syncfusion-icon.png.

The new en.lproj/ subdirectory is the internal way of organizing language-specific files in iOS. All the resources localized in English will appear in this subdirectory, and all of the Spanish versions will appear in the es.lproj/ subdirectory. But again, we don’t actually have to know where the file resides; NSBundle’s pathForResource:ofType: method figures it out automatically.

So, our English version of the image is set up. Next, we need to configure the Spanish version. Select the English version of the file in the Project Navigator, and select the check box next to Spanish in the Localization section of the Utilities panel.

tutorial_image
Figure 103: Adding a Spanish version of the file

This copies the existing English-language version of syncfusion-icon.png into the es.lproj/ subdirectory. Back in the Project Navigator, you should be able to see this by expanding the syncfusion-icon.png file.

tutorial_image
Figure 104: Both versions of the image file in the Project Navigator

Of course, we need to replace the Spanish version with a completely different file. The easiest way to do this is by selecting the syncfusion-icon.png (Spanish) file and clicking the arrow icon next to the Full Path string in the Utilities panel.


Figure 105: The Utilities Panel for the Spanish image file

This displays the contents of the es.lproj/ folder in the Finder, which gives us the opportunity to replace the file manually. Delete the existing syncfusion-icon.png file and copy the syncfusion-icon-es.png file from the resource package into es.lproj/. Make sure to rename it as syncfusion-icon.png. It’s important for localized versions of the same file to have identical file names so NSBundle can find them. After replacing the file, you should see different images when you select the two localizations in Xcode.

That should be it for localizing our image file. To test it out, you can change the device language the same way you would change it in a real device—through the Settings app. Click the device’s home button in the simulator, click and drag the screen to the right, and launch the Settings application. Under General > International > Language, you can select the device language.

tutorial_image
Figure 106: Changing the device language in iOS Simulator

Choose Español, and re-open your application. You should see the Spanish version of syncfusion-icon.png. You might need to close the simulator and compile the program again. Note that the file path output by NSLog() now reads:

Internationalization.app/es.lproj/syncfusion-icon.png.
tutorial_image
Figure 107: Displaying the localized version of the image file

As you can see, it’s incredibly easy to localize files using NSBundle’s built-in functionality. The idea is to use NSBundle as an abstraction between your application code and the assets that they rely on. This isolates the localization process from the development process, making it very easy to outsource translations.

Localizing video and audio files uses the exact same process just discussed. However, preparing text for an international audience requires a little bit more work.


Localizing Text

When you’re dealing with a multilingual app, hardcoded strings must be abstracted into a bundle asset so that NSBundle can load the correct language at run time. iOS uses what’s called a strings file to store translations of all the string literals in your application. After creating this strings file, you can localize it using the same method discussed in the previous section.

Let’s change our viewDidLoad method to display a button and output a message when the user taps it.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [aButton setTitle:@"Say Hello" forState:UIControlStateNormal];
    aButton.frame = CGRectMake(100.0, 200.0, 120.0, 40.0);
    [[self view] addSubview:aButton];
    [aButton addTarget:self
                action:@selector(sayHello:)
      forControlEvents:UIControlEventTouchUpInside];
}
- (void)sayHello:(id)sender {
    NSLog(@"Hello, World!");
}

These methods have two string literals that we’ll have to move into a strings file. They are @”Say Hello” and @”Hello, World!”.

To create the strings file, create a new file and choose Resource > Strings File. Use Localizable.strings for the file name, which is the default string file that iOS looks for.

tutorial_image
Figure 108: Creating a strings file

The content of the strings file is a simple list of key or value pairs, formatted as follows.

"Button Title" = "Say Hello";"Greeting" = "Hello, World!";

The left side is the key that you’ll use to reference the translated string in your application code. The keys are arbitrary strings, but developers typically use either a semantic name describing how the string will be used, or the target phrase in their native language. In our strings file, we opted for the former. The values for each key follow an equal sign. Be sure to include a semicolon at the end of each line or terrible things will happen when you try to run your application.

As with media assets, you can access the contents of Localizable.strings via NSBundle. The localizedStringForKey:value:table: method returns the value of a key from a particular strings file. The value argument lets you specify a default return value if the key isn’t found, and the table argument determines which strings file to use. When you specify nil for table, the default Localizable.strings file is used.

Since accessing translated strings is such a common task, the Foundation Framework also provides a convenient NSLocalizedString() macro that you can use as a simple shortcut for localizedStringForKey:value:table:. It passes an empty string for the value argument and nil for the table argument. For most applications, NSLocalizedString() is all you really need to access localized text.

So, let’s change our button’s title configuration to use NSLocalizedString():

[aButton setTitle:NSLocalizedString(@"Button Title", nil)
         forState:UIControlStateNormal];
If you compile the project, the button should still read, “Say Hello”—but now it’s loaded from Localizable.strings. Let’s do the same for the sayHello method:
- (void)sayHello:(id)sender {
    NSLog(@"%@", NSLocalizedString(@"Greeting", nil));
}

Now that our strings are dynamically loaded instead of being hardcoded, it’s trivial to localize them. We’ll use the exact same process as with images. In the Project Navigator, select the Localizable.strings file, then click Make localized in the Utilities panel. Select English in the resulting dialog box to use this version of the file for English-speaking users.

To add a Spanish version, select Localizable.strings again and select the check box next to Spanish in the Localizations section.

tutorial_image
Figure 109: Adding a Spanish version of Localizable.strings

Just like syncfusion-icon.png, you should be able to expand the Localizable.strings file in the Project Navigator.

tutorial_image
Figure 110: Expanding the strings file to view its localizations

Finally, add some translations to the Spanish version of the file.

"Button Title" = "Dice Hola";"Greeting" = "Hola, Mundo!";

You can test it the same way we tested images. Navigate to Reset Content and Settings in the simulator, close the simulator, and do a clean build from Xcode. After changing the language to Español, your button should read “Dice Hola” instead of “Say Hello”, and clicking it should output “Hola, Mundo!”

tutorial_image
Figure 111: Changing the device language to Spanish

That’s all there is to localizing strings in an iOS application. Again, having all your translated text in a single file entirely abstracted from your application code makes it easy to outsource your localization efforts. This is a very good thing, as most developers don’t fluently speak all of the languages that they would like to translate their app into.


Localizing Info.plist

There is one important detail that hasn’t been addressed yet—localizing the app name. If you take a look at the home screen in the iOS Simulator, you’ll notice that the title under your app icon hasn’t been translated to Spanish. If you’ve already gone through the trouble of localizing the string inside your app, you might as well take the time to translate a little bit of metadata too.

An app’s display name is defined in the Info.plist under the CFBundleDisplayName key. Instead of forcing you to translate values in the main Internationalization-Info.plist file, iOS gives you a dedicated string file for overwriting certain configuration options with localized values. In the Supporting Files group of the Project Navigator, open the InfoPlist.strings file. This is just like the Localizable.strings file we created in the previous section, except it should only provide values for Info.plist keys. Add the following to your InfoPlist.strings file.

"CFBundleDisplayName" = "Hola, Mundo!";

Now, if you reset the simulator and do a clean build, you should see a Spanish title under your application icon.

tutorial_image
Figure 112: Localizing the app display name

Summary

In this chapter, we learned how to localize media assets, text, and metadata using NSBundle. By abstracting the resources that need to be localized into isolated files and referencing them indirectly via methods like pathForResource:ofType:, it’s possible to translate your application into another language without touching a single line of application code. This is a very powerful feature of iOS, especially considering the international prevalence of iPhone and iPad devices.

The final chapter of iOS Succinctly takes a brief look at the built-in audio support for iOS applications. As we touched on in previous chapters, audio files use the same bundle structure as images and strings files. However, instead of focusing on how to access those resources, we’ll discuss the higher-level tools for controlling audio playback.

This lesson represents a chapter from iOS Succinctly, a free eBook from the team at Syncfusion.

Viewing all 1836 articles
Browse latest View live