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

Creating a Mobile Event Calendar With DHTMLX

$
0
0

This tutorial describes how to build an HTML5-based mobile calendar to track conferences and events that run on iOS and Android phones using a mobile version of dhtmlxScheduler (open source, GPL). At the end, users will be able to add and edit events, select the conference location on Google Maps, and see the events in day, month, or list views.


Project Overview

The final demo includes the event calendar (we’ll also call it the scheduler) that displays events in three standard views: list, day, and month. While the end user adds, deletes, or edits events the calendar app saves the changes to the server and updates the database. The demo also automatically detects the locale in use and adapts the calendar interface accordingly. The end user can set the event location on Google Maps in two ways: typing in the address or pointing it on Google Maps.


1. Get Started

First, download the latest package of the mobile version of dhtmlxScheduler. It is based on the DHTMLX Touch framework and distributed under an open source license (GNU GPL).

The mobile DHTMLX scheduler has a so called multiview-based architecture. It displays a set of screens that switch between each other. Each screen is a singular view where you can add any content. By default, the scheduler contains the following views:

  • Event preview screen (displays a short info about the event)
  • Details form (used to edit the event details)
  • Start/end edit form (built-in datepickers to choose the start and end dates of the event)
  • Calendar views: List (Agenda), Day, and Month

The predefined views are customizable (you can find the default definitions in the documentation). Also, there is a possibility to add new custom views.


2. Rendering an Event Calendar on a Page

Step 1

We’ll start with the required code files to display the mobile scheduler on a page: dhxscheduler_mobile.js and dhxscheduler_mobile.css.

Step 2

Add the following code to your page:

    dhx.ready(function(){
            dhx.ui.fullScreen();
            dhx.ui({
                view: "scheduler",
                id: "scheduler"
            });
     });

Here is what these lines of code do:

  • calling the dhx.ready() function guarantees that the code placed inside is called after the page has been completely parsed, protecting it from potential errors. This is optional but I encourage you to use it.
  • dhx.fullScreen() activates the full-screen mode.
  • dhx.ui({}) is an object constructor for the scheduler (used in the DHTMLX Touch library).
  • view: "scheduler"– sets the component to render (‘scheduler’ is the hardcoded name of the mobile scheduler).
  • id is an optional parameter that sets the object id. We specified this parameter as we are going to refer to this object later.

After we’ve included the required files and added the code above, the calendar is rendered on a page:

Mobile Event Calendar - Month View

The scheduler has a neat and simple UI so you can use it as is. It’s flexible enough so you can configure its look and feel to match your design needs, if you want to.


3. Loading and Saving Data

Step 1

To load data to the calendar, we will use the load() method. As we’re going to load data from the server, we should set the only parameter (the path to the server script).

To provide the ability to save data back to the server, we need to define one more parameter in the object constructor – save. We also need to specify the same server-side script that we have specified in the load() method.

    dhx.ui({
                  view: "scheduler",
                id: "scheduler",
                save:"data/data.php"
    });
    $$("scheduler").load("data/data.php");

To refer to the scheduler through its id, we use the $$("scheduler") record.

To refer to the scheduler views (or views elements), you should write a complete dependency property inheritance chain: $$('scheduler"').$$('view_id').$$('viewElement_id')... You can check the elements ids in the related documentation.

Step 2

The data.php file contains the client-server communication logic to integrate with the server side. To define the logic, we will use a special helper dhtmlxConnector that implements all the routine (there are versions for Java, .NET, PHP, and ColdFusion, and it’s free to use with the dhtmlx UI widgets). You can get more details about the use of dhtmlxConnector here.

We will use the PHP version and create the following code in the data.php file:

<?php
         include ('../connectors/scheduler_connector.php');
         $res=mysql_connect("localhost","root","");
         mysql_select_db("schedulertest");
         $scheduler = new JSONSchedulerConnector($res);
         $scheduler--->render_table("events_map","event_id","start_date,end_date,event_name,details,event_location,lat,lng");
    ?>

The lines of the code do the following:

  • The first line includes the required code file.
  • The next two lines establish a connection to the server and open the specified SQL database.
  • The last two lines create a connector object ($scheduler) and configure data to retrieve.

In the attached demo package, you can find the dump file of the ‘schedulertest’ database.

As we complete this step we have a calendar populated with our demo data:

Mobile Event Calendar - Loaded with Data

4. Localization

Step 1

With this step we will make our calendar able to adapt to a particular language and region.

First, we need to specify a locale object (in the library it has the hardcoded name – ‘locales’) that will define all labels used in the calendar.

We will create an object in a separate file not to “overcrowd” the main .html file. The file will look like this:

    var locales  ={"de": {...},"en": {..},
        ...
    }

To see the full version of the ‘locales’ object, open the locales.js file included in the ‘codebase’ folder of the download package. In our demo, we have included the locales for just two languages (English and German) as an example. If needed, you can add the locale of any other language in this file.

Step 2

Then we include the locales.js file on the page:

<script charset="utf-8" type="text/javascript" src="../mobile_scheduler/codebase/locales.js"></script>

Next add the following code to the html file:

    //put this code at the very beginning of the page and not into the dhx.ready() function
    var locale = (navigator.language || navigator.systemLanguage || navigator.userLanguage ||'en').substr(0, 2).toLowerCase();
            if (!locales[locale])
                locale = 'en';
            scheduler.locale.labels=locales[locale];
            dhx.Date.Locale = locales[locale].calendar;

The lines of the code do the following:

  • The first line gets the language version of the browser.
  • The next few lines set the locale depending on the value returned by the first line.
  • scheduler.locale.labels sets the locale for common labels in the scheduler.
  • dhx.Date.Locale sets the locale for calendars used in the scheduler.

The calendar with a German locale looks like this:

Mobile Event Calendar - German Locale

5. Displaying an Event Location on Google Maps

Wouldn’t it be great if users could see the place where an event happens? Here is the list of steps required to provide such an opportunity in your app:

  • Create a separate view for Google Maps
  • Add a button, by clicking on it the user will open the Maps view
  • Add an ‘onclick’ handler function that will be responsible for displaying Google Maps with the appropriate event marker on it
  • Add the event location info into the event preview screen

Step 1

We start by creating a Maps view. For our first step we’ll include one more file on the page:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?libraries=places&amp;sensor=true"></script>

Then we need to add a new view that will display Google Maps. DHTMLX Touch library has the ‘googlemap’ component that makes integration with Google Maps pretty easy (related documentation).

Here is our Google Maps view:

scheduler.config.views.push({
            id:"locationView",
            rows:[
                {
                    view:"toolbar",
                    css:"dhx_topbar",
                    elements:[
                        {
                            view:'button',
                            inputWidth: 100,
                            css:"cancel",
                            label: scheduler.locale.labels.icon_back,
                            click: "$$('scheduler').$$('views').back()"
                        }
                    ]
                },
                {     view:"googlemap",
                    id:"mymap"
                }
            ]
        });

Some explanations about the above code:

  • scheduler.config.views.push command adds a new view to the existing scheduler multi-view collection.
  • rows:[{}] arranges elements vertically. Each object is a separate row. The view consists of a toolbar and Google Maps.
  • $$('scheduler').$$('views') refers to multi-view object. The back() method switches multi-view to the previously active view.

The Map view will look like this:

Mobile Event Calendar - Map View

Step 2

We now need to add a button on the toolbar. The most appropriate view is the one that displays details for the selected event.

//place this code after the 'localization' block (step 4)
scheduler.config.selected_toolbar = [
            {view:'button', inputWidth:scheduler.xy.icon_back, css:"cancel", id:"back",align:"left",label:scheduler.locale.labels.icon_back},
            {view:'button',  width:100, id:"location",align:"right", label:scheduler.locale.labels.label_location, click:"showLocation"}, //our new new button
            {view:'button',  width:70, id:"edit",align:"right",label:scheduler.locale.labels.icon_edit}
        ];

The code above is the default definition of the toolbar (you can find it in the library’s documentation) and our new button named as Location.

As we localize our app, all labels we add must be named in some way and added to the locales.js file. For example, now we’ll add a button with the name ‘Location’. So, in the locales.js file we add parameter label_location:"Location" and then set the button’s attribute label to scheduler.locale.labels.label_location value.

The click attribute sets the name of a handler function that will be invoked on button clicks.

This is how the screen with event details should look now that we added the Location button:

Mobile Event Calendar - Location Button

Step 3

Before going to the main code, let’s add to the page a variable named as ‘marker’ and assign it to the Google Maps marker instance. We define this variable as global because we need to have only one marker on the page (our events must have only one location).

//put this code at the very beginning of the page
 var marker = new google.maps.Marker({});

Executable function, or the ‘onClick’ handler, contains the following code:

function showLocation(){
            if (marker!=null){
                /*shows the view of multiview*/
                $$("scheduler").$$("locationView").show();
                /*necessary to resize google map*/
                $$("scheduler").$$("locationView").resize();
                /*event data*/
                var eventId = $$("scheduler").getCursor();
                var item = $$("scheduler").item(eventId);
                /*LatLng point*/
                var point = new google.maps.LatLng(item.lat,item.lng);
                var map = $$("scheduler").$$("mymap").map;
                map.setZoom(6);
                map.setCenter(point);
                google.maps.event.clearListeners(map, "click");
                marker.position= point;
                marker.map = map;
                marker.title = item.event_location;
                marker.setMap(map);
            }
        };

Again, let me explain what these lines of code do:

  • The first lines show the view created on the first sub-step and resizes it to fill the whole screen.
  • The next lines get the object of an event that the cursor is currently on. Note, the DHTMLX library uses a ‘cursor’ conception in its inner logic. So, to ensure the correct processing, you should operate with the ‘cursor’ while getting the currently selected item. In most cases, the getCursor() method returns the currently selected item. There is only one exception: when you delete an event, the scheduler removes the selection but keeps the cursor and it points to the non-existent event. Be careful with this!
  • The second line uses the Google Maps API to create a point based on the specified coordinates (the event coordinates that are stored in the database). Learn more about Google Mas API.
  • $$("scheduler").$$("mymap") refers to the ‘googlemap’ view. The “map” property returns the object of Google Maps.
  • The last lines zoom, center the map, and set the marker in the specified point.

Step 4

To add location information to the preview screen, we should redefine the default screen template. So that we do not have to rewrite the whole template (which is rather large), we’ll use a trick:

        var default_temp =  scheduler.templates.selected_event;
        scheduler.templates.selected_event = function(obj){
            var html = default_temp(obj);
    if (html!=""){
           html = html.replace(/<\/div>$/,"")
                 html += "</pre><div class="event_title">"+scheduler.locale.labels.label_location+"</div><pre>";
                 html += "</pre><div class="event_text">"+obj.event_location+"</div><pre>";
                    html += "";
            }
            return html;
       };

Here is what we have done with the above piece of code:

  • default_temp variable holds the default template of the screen.
  • ‘wrapper’ is a new div element to hold the location information.
  • 'event_text' is the predefined CSS class used in the default template, we use it to provide uniformity of the displayed event information.
  • scheduler.locale.labels.label_location is the label we added on the previous step (‘Location’, in the English locale).

Now the preview screen looks like this (with added location info):

Mobile Event Calendar - Event Preview Screen

6. Setting an Event Location on Google Maps

Now our app can display the location of an event. But what about editing the event location or setting the location for new events?

Now we need to allow the users to set/edit the event location and provide two different ways for typing the address in the text input and directly pointing on the map. This is what we need to do:

  • Add controls to the edit form
  • Provide handlers that process the incoming data

Step 1

We should add at least two controls to the event edit form: one is a text input to type the event address in and the second control can be a button, so that by clicking on it the user can open the map and set the point right on the map.

We will take the default edit form and then add the mentioned items (the list of available controls):

 scheduler.config.form = [
            {view:"text", label:scheduler.locale.labels.label_event, id:"text", name:'text'},
            {view:"text", label:scheduler.locale.labels.label_details, id:'details'},
            {view:"datetext", label:scheduler.locale.labels.label_start,    id:'start_date',name:'start_date', dateFormat:scheduler.config.form_date},
            {view:"datetext", label:scheduler.locale.labels.label_end,    id:'end_date',name:'end_date', dateFormat:scheduler.config.form_date},
            {view:"toggle", id:'allDay', label:"", options: [{value:"0",label:scheduler.locale.labels.label_time},{value:"1",label:scheduler.locale.labels.label_allday}], align: "right",value:"0"},
            //custom ‘location’ sections
            {view:"text", label:scheduler.locale.labels.label_location, id:"event_location"},
            {view:'button', id:"setLocation", label:scheduler.locale.labels.label_locate, click:"setLocation"},
            {view:"text", label:"Latitude", id:'lat', hidden:true},
            {view:"text", label:"Longitude", id:'lng', hidden:true}
];

We just added five new items to the event edit form:

  • A text field to manually type the address, an item with id:"event_location".
  • A button that users use to open Google Maps and set a point (id:"setLocation"). The item has the ‘click’ attribute that allows us to assign for it an ‘onclick’ event handler function (we named it as “setLocation”).
  • Two hidden fields (‘Latitude’ and ‘Longitude’) to store the point geographic coordinates. I should mention that the mobile scheduler automatically saves event data when the user clicks the ‘Save’ button. The scheduler takes data for the event from the inputs defined in the edit form. That’s why we added these fields but hid them, as they really don’t have any value for the end users and are needed just to visualize the stored content in DB locations on Google Maps.
  • A ‘notes’ field (id:"details"). It’s a fully optional field. We add it just to give users a possibility to add notes about upcoming events. The field has the related predefined parameter in the locale object.

So now we have an event add/edit form like this:

Mobile Event Calendar - Event Edit Form

Step 2

Before specifying the executable function for the input, we should define an event, firing which will invoke the function. The library allows us to use built-in events or any of native HTML events. We chosen the 'onFocusOut' event that occurs after an element loses focus.

To attach the event to the input, we will add the following command to the dhx.ready(function(){..} function:

dhx.event($$('scheduler').$$("event_location").$view, "focusout", setPlaceCoordinates);
  • dhx.event is a helper that attaches an event handler function for an HTML element.
  • $$('scheduler').$$("event_location") refers to the input. $view returns the view object.
  • setPlaceCoordinates() function will take an address typed by the user, detect its coordinates (to save in the DB), and display the address marker on the map.

The setPlaceCoordinates() function has the following implementation:

function setPlaceCoordinates(){
            if (marker!=null){
                var eventId = $$("scheduler").getCursor();
                var geocoder =  new google.maps.Geocoder();
                var address = $$('scheduler').$$("event_location").getValue();
                if (address !=""){
                     geocoder.geocode( { 'address': address}, function(results, status) {
                        if (status == google.maps.GeocoderStatus.OK) {
                            $$('scheduler').$$("lat").setValue(results[0].geometry.location.Xa);
                            $$('scheduler').$$("lng").setValue(results[0].geometry.location.Ya);
                        } else {
                            dhx.alert("Unfortunately,your location is not found.");
                            if ($$('scheduler').$$("lat")==""){
                                $$('scheduler').$$("lat").setValue(51.477840);
                                $$('scheduler').$$("lng").setValue(-0.001492);
                                $$('scheduler').$$("event_location").setValue("Blackheath Avenue London, Greenwich, Greater London SE10 8XJ, UK");
                            } else{
                                if (eventId!=null){
                                    var item = $$("scheduler").item(eventId);
                                    $$('scheduler').$$("event_location").setValue(item.event_location);
                                }
                            }
                        }
                     });
                }
            }
        };

Let’s consider the order in which the interpreter steps through the handler code:

  • Using the $$("scheduler").getCursor() command, the interpreter gets the object of the event that the edit form is open for.
  • Then, activates the geocoding service (that converts addresses, like “Berlin, Germany”, into geographic coordinates) and takes the typed address from the input (var address).

The root if-else conditional expression checks the value of the “Location” text field:

  • If the value is an empty string, searching is skipped.
  • If the user entered some value, this value is passed to the Google Maps service.
  • If some address is found, the interpreter writes its coordinates into the ‘Latitude’ and ‘Longitude’ hidden fields.
  • If the typed address doesn’t exist or the user closes the edit form before the service has finished the search, the app alerts the message informing about unsuccessful results and in the inputs keeps coordinates stored for the event in the database.
  • The if ($$('scheduler').$$("lat")==""){} else{} conditional expression is used to check whether the event in question is stored in the DB or if it’s a new event (because if the event is new, the interpreter couldn’t take its coordinates from db and an error will occur). In the case the event is new and search wasn’t complete, the app assigns to the event coordinates of the Greenwich Royal Observatory.

Step 3

The executable function, or an ‘onClick’ handler that occurs when the user clicks the ‘Locate on the map’ button, contains this code:

        function setLocation(){
            if (marker!=null){
                 /*shows the view of multiview*/
                $$("scheduler").$$("locationView").show();
                /*necessary to resize google map*/
                $$("scheduler").$$("locationView").resize();
                var point;
                var eventId = $$("scheduler").getCursor();
                 if (eventId!=null){
                     var item = $$("scheduler").item(eventId);
                    /*LatLng point*/
                     point = new google.maps.LatLng(item.lat,item.lng);
                    marker.title = item.event_location;
                 } else{
                    point = new google.maps.LatLng(51.477840, -0.001492); // the coordinates of the Greenwich Royal Observatory
                    marker.title = "Blackheath Avenue London, Greenwich, Greater London SE10 8XJ, UK";
            }
            var map = $$("scheduler").$$("mymap").map;
            map.setZoom(6);
            map.setCenter(point);
            marker.position= point;
            marker.map = map;
            marker.setMap(map);
            google.maps.event.addListener(map, "click", function (e) {
                    var request = {
                        location:e.latLng,
                        radius:'1'
                    };
                    service = new google.maps.places.PlacesService(map);
                    service.search(request, function(results, status){
                        if (status == google.maps.places.PlacesServiceStatus.OK) {
                            this.service.getDetails({ reference: results[0].reference }, function(details, status) {
                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $$('scheduler').$$("lat").setValue(details.geometry.location.Xa);
                                    $$('scheduler').$$("lng").setValue(details.geometry.location.Ya);
                                    marker.title = details.formatted_address;
                                    marker.position= e.latLng;
                                    marker.map = map;
                                    marker.setMap(map);
                                    $$('scheduler').$$("event_location").setValue(marker.title);
                                }
                            });
                        }
                    });
            });
}
    };

The lines of the code do the following:

  • The first lines show the view with Google Maps in the full screen mode and get the event object.
  • The first if-else conditional expression checks whether the user edits an existing event or if he creates a new one. This check is made in order to set the initial marker on the map.
  • If the user edits an existing event, the code generates a point with the coordinates from the DB.
  • If the user creates a new event, the code assigns the coordinates of the Greenwich Royal Observatory to it.
  • $$("scheduler").$$("mymap") refers to the ‘googlemap’ view. The map property returns the object of Google Maps.
  • The next lines zoom, center the map, and set the marker in the specified point.
  • google.maps.event.addListener() command attaches a function for handling clicks made by the user on the map. See the details of the used API in the Google Mas API Web Services.

One more point to mention: at this step we’ll add a function to handle map clicks. But in case the user only views the location of an event, he shouldn’t have a possibility to change it (step 5). So, along with adding the ‘click’ listener in the setLocation function, we will switch it off for the ‘preview’ mode (the showLocation function).

Add this command to the existing code of the showLocation() function:

function showLocation(){
         google.maps.event.clearListeners(map, "click");
    ...
};

That’s the end of this tutorial! Now you can download the final demo package to see how everything works and fits together in the event calendar we’ve built.


Conclusion

With the increasing use of mobile phones, there is no need to say how important it is to have a mobile version of a website or app. If you need an event calendar that can be viewed and edited online on phones, then the mobile version of dhtmlxScheduler can be a huge time saver because it offers a ready-to-use calendar UI and a set of basic features. The open-source GNU GPL license allows you to use the scheduler for free on websites and internal apps.


Something New and Exciting: Tuts+ Hub

$
0
0

We’re ready to announce something new and exciting — the Tuts+ Hub beta!

As you know, Tuts+ is all about helping people learn. With Hub, we’ve worked to deliver an experience that makes it easy for you to find exactly what you’d like to learn, with a delightful interface for reading.

What you see is a brand new layer that sits atop our current websites. All the articles and tutorials are still here (along with your comments), but we’ve designed a new way of presenting them and built new tools to help you navigate the library. Take a look now, and read on to find out more.


What’s New?

hub

Here are the basics of what’s new on the Hub:

  • A new content structure that emphasises disciplines over software-centric categories
  • Improved (pretty great) search
  • A more intuitive approach to browsing
  • Responsive design
  • Carefully formatted articles

We’re releasing Hub as a beta because it’s going to continue evolving. There are still some important features that we’re going to implement, and a few wrinkles that need ironing out. We’ve taken a lot of care to make Hub something that looks great and works well, upon which we can easily improve and iterate.

We want to tailor this site to you — our readers — so your feedback is incredibly valuable to us. If you notice anything that looks awry, or something that doesn’t behave the way you’d like, we’d be grateful if you took the time to fill out this feedback form.


The New Content Structure

If you’re used to the current Tuts+ blogs, you may be interested to know how we’ve reorganised our topics.

At present, the Tuts+ Network consists of thirteen sites. Though they all occupy different niches, some of them have similar kinds of content. By isolating the sites, it’s possible that we’re keeping you from finding the sort of things you want to learn, and we’re needlessly restricting ourselves to very specific niches.

We plan to expand the breadth of content that we provide, and our new topics are designed to cater to that expansion.

This is how the new topics on Tuts+ Hub map to the current sites:

Design & Illustration

  • Psdtuts+
  • Vectortuts+
  • Webdesigntuts+


Development

  • Nettuts+
  • Mobiletuts+
  • Webdesigntuts+
  • Wptuts+


Audio Production

  • Audiotuts+


Photography

  • Phototuts+


3D & Animation

  • Cgtuts+


Game Design

  • Gamedevtuts+

Video Production

  • Aetuts+

Arts & Crafts

  • Crafttuts+

Computing

  • Mactuts+

We’d Love Your Feedback

When you’ve had a chance to take a look, search around, and read a tutorial or two, we’d welcome any and all of your feedback. It’ll help us to create a product that our readers love to use!

Android SDK: Working with Google Maps – Map Setup

$
0
0

With the Google Maps Android API, you can build apps with localization features. In this series, we are creating an Android app in which the user’s location is presented on a map, as are nearby places of interest. In this part we will use the GoogleMap class to show the user location and also to control how the map presents itself to the user. We will also create custom map markers!

This is the second of four parts in a tutorial series on Using Google Maps and Google Places in Android apps:

Remember that the app we are creating in this series will not function on an Android emulator, only on an actual device!

App We Are Working Towards
This is a snapshot of the final app.

1. Create the User Interface Elements

Step 1

When we display the map, we will place Markers on top of it. These Markers will indicate the user location and nearby places of interest. When you add a Marker to a GoogleMap, you can configure various aspects of it, including the text that appears on it and the icon displayed. For this app, we will use the following collection of icons. Feel free to download and use them (you can also find them in the source code download for this tutorial):

User Location Marker
User location Marker icon
Blue Marker
Blue place marker icon
Red Marker
Ref place marker icon
Green Marker
Green place marker icon
Purple Marker
Purple place marker icon

The yellow icon indicates the user’s location. We will use the other colors to mark places of particular types in the area. When you fetch place data from Google Places API, each place is associated with one or more category types. For the purposes of this tutorial series, we are going to fetch places listed with the following types: food, bar, store, museum, and art gallery. We will use the red icon to indicate food, blue for drink, green for shopping, and purple for all other places returned. You will of course be able to alter the types of places your app returns if you wish.

Place your Marker icons in the drawables folder(s) for your app.

Step 2

Now we can bring these into the Activity code so that we can refer to them as part of the user interface. In your app’s main Activity add the following instance variables, which we will use to store the drawable resource ID values for each Marker icon:

private int userIcon, foodIcon, drinkIcon, shopIcon, otherIcon;

Inside the onCreate method after the existing code, initialize these variables:

userIcon = R.drawable.yellow_point;
foodIcon = R.drawable.red_point;
drinkIcon = R.drawable.blue_point;
shopIcon = R.drawable.green_point;
otherIcon = R.drawable.purple_point;

We will refer to these when we add the Markers to the map.


2. Create a GoogleMap Object

Step 1

For the code we’ll use in the rest of this tutorial, you need to add the following import statements to your Activity class:

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import android.location.Location;
import android.location.LocationManager;
import android.content.Context;

We’ve placed a map in the app’s main Activity by including a Map Fragment in the layout file, but at the moment we have no control over it in the Java code. Let’s get a reference to it as a GoogleMap object now. In your Activity class add an instance variable for it:

private GoogleMap theMap;

In the onCreate method after the existing code, add a conditional to check whether the map has been instantiated (in case we already have it when onCreate executes after the first run):

if(theMap==null){
    //map not instantiated yet
}

The remainder of the onCreate processing will be placed in this conditional block. Inside it, first try to get the map from the Fragment Manager:

theMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.the_map)).getMap();

We’ll pass the ID we gave the Map Fragment in the layout XML, cast it to a MapFragment object and attempt to retrieve the GoogleMap object from it. If the Google Play services resources are not available on the user device, this will return null, so add another conditional before proceeding:

if(theMap != null){
    //ok - proceed
}

Now we know the app will not attempt to carry out map processing if the required packages are not installed on the user device.

Step 2

Now we can carry out manipulations on the map using the GoogleMap class. Let’s start by setting the map type. We can choose from satellite, terrain, normal, and hybrid, which correspond to the options you see in the Google Maps app itself. Let’s go for hybrid:

theMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);

Here is an overview of the map types:

Hybrid
Hybrid
Normal
Normal
Satellite
Satellite
Terrain
Terrain

At this stage you can experiment by running the app with the different map types set. As an advanced project, you could add a user interface control to let the user choose between the types.


3. Display the User Location

Step 1

The app will display the user’s last recorded location. Since it carries out this process more than once, when the app first runs and then when the user’s location changes, we will place the code in a helper method. Add the following method outline after your onCreate method:

private void updatePlaces(){
//update location
}

Up at the top of the class, add the following variable to represent a Location Manager instance:

private LocationManager locMan;

Back in the updatePlaces method, attempt to retrieve this:

locMan = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

Now we can use this to retrieve the user’s last recorded location:

Location lastLoc = locMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

We will need the longitude and latitude to mark this location on the map, so retrieve them now as double variables:

double lat = lastLoc.getLatitude();
double lng = lastLoc.getLongitude();

We can wrap these in a LatLng object to pass to the map Marker object we will create soon:

LatLng lastLatLng = new LatLng(lat, lng);

Step 2

Now we have the user’s last known longitude and latitude, so we can use this information to mark the location on the map. To do this we will use a Marker object. Since the user location is going to be continually updated, let’s add an instance variable at the top of the class declaration to represent its Marker:

private Marker userMarker;

Back inside updatePlaces, let’s first check whether the Marker has already been instantiated, in which case we can remove it:

if(userMarker!=null) userMarker.remove();

This will mean that after the first time the Marker is added to the map, when the user’s location is updated, the app will remove the previous Marker so that it can be replaced with one representing the updated location. Now let’s instantiate the Marker. You can pass various settings to the Marker class, including the location details and the display details, such as icon and text labeling. Create the Marker and add it to the map:

userMarker = theMap.addMarker(new MarkerOptions()
	.position(lastLatLng)
	.title("You are here")
	.icon(BitmapDescriptorFactory.fromResource(userIcon))
	.snippet("Your last recorded location"));

Take a moment to look over this code. First we instruct the map object to add a new Marker, instantiating the new Marker object at the same time. We pass the latitude and longitude position, a text string for the title, the user location icon we created and set as a instance variable, then a text string snippet which will appear when the user taps the Marker.

Step 3

Now that we have the user’s position marked, let’s animate the map camera to zoom in on the location. This time we’ll use the GoogleMap object, calling the animateCamera method:

theMap.animateCamera(CameraUpdateFactory.newLatLng(lastLatLng), 3000, null);

We pass a camera update comprising the user location, a duration value, and null for the optional callback method parameter.

Now we can call the new helper method, which we will add more code to later. Back in your onCreate method, after setting the map type:

updatePlaces();

You can run your app now to zoom in on the last recorded location on your device. It’s also worth exploring the available methods for setting options with the GoogleMap object and for zooming to locations. You can also add overlays to the map with methods for various shapes and other visual elements.

User Location
User Location

Conclusion

In this tutorial, we explored how to manipulate the map object and created icons for our location Markers. We also retrieved and displayed the user location, zooming to it when the app runs. In the next two parts of the series, we will setup API access for Google Places and pass the user’s location to retrieve nearby places of interest. We will place additional Markers on the map to display information about these places, updating as the user moves around. Although we have a specific set of functionalities in mind for this app, you may wish to explore the possibilities for the various mapping and location classes as we go along, as there are many features available to experiment with!

Corona SDK: Build a Frogger Inspired Game – Interface Creation

$
0
0

In this tutorial series, I’ll be showing you how to create a Frogger Inspired game with the Corona SDK. You’ll learn about moving objects across the screen and detect when they are colliding using the physics engine. The objective of the game is to move the Frog on the stage to its containers without being hit by an obstacle. Read on!

1. Application Overview

App Overview

Using pre made graphics we will code an entertaining game using Lua and the Corona SDK API’s.

The player will be able to use the on-screen pad to move the frog and get it to the other side, you can modify the parameters in the code to customize the game.

2. Target Device

Target Device

The first thing we have to do is select the platform we want to run our app within, this way we’ll be able to choose the size for the images we will use.

The iOS platform has these characteristics:

  • iPad 1/2/Mini: 1024x768px, 132 ppi
  • iPad Retina: 2048×1536, 264 ppi
  • iPhone/iPod Touch: 320x480px, 163 ppi
  • iPhone/iPod Retina: 960x640px, 326 ppi
  • iPhone 5/iPod Touch: 1136×640, 326 ppi

Because Android is an open platform, there are many different devices and resolutions. A few of the more common screen characteristics are:

  • Asus Nexus 7 Tablet: 800x1280px, 216 ppi
  • Motorola Droid X: 854x480px, 228 ppi
  • Samsung Galaxy SIII: 720x1280px, 306 ppi

In this tutorial, we’ll be focusing on the iOS platform with the graphic design, specifically developing for distribution to an iPhone/iPod touch, but the code presented here should apply to Android development with the Corona SDK as well.


3. Interface

Interface

A simple and friendly interface will be used. This involves multiple shapes, buttons, bitmaps and more.

The interface graphic resources necessary for this tutorial can be found in the attached download.

Frogger Sprite Pack.


4. Export Graphics

Export Graphics

Depending on the device you have selected, you may need to export the graphics in the recommended PPI. You can do this in your favorite image editor.

I used the Adjust Size… function in the Preview app on Mac OS X.

Remember to give the images a descriptive name and save them in your project folder.


5. App Configuration

An external file will be used to make the application go fullscreen across devices, the config.lua file. This file shows the original screen size and the method used to scale that content in case the app is run in a different screen resolution.

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox"
    },
}

6. Main.lua

Let’s write the application!

Open your prefered Lua editor (any Text Editor will work, but you won’t have syntax highlighting) and prepare to write your awesome app. Remember to save the file as main.lua in your project folder.


7. Code Structure

We’ll structure our code as if it were a Class. If you know ActionScript or Java, you should find the structure familiar.

Necessary Classes
Variables and Constants
Declare Functions
    contructor (Main function)
    class methods (other functions)
call Main function

8. Hide Status Bar

display.setStatusBar(display.HiddenStatusBar)

This code hides the status bar. The status bar is the bar on top of the device screen that shows the time, signal, and other indicators.


9. Import Physics

We’ll use the Physics library to handle collisions. Use this code to import it:

-- Physics
local physics = require('physics')
physics.start()
physics.setGravity(0, 0)

10. Background

Background

A simple graphic is used as the background for the application interface, the next line of code stores it.

-- Graphics
-- [Background]
local bg = display.newImage('bg.png')

11. Title View

Title View

This is the Title View, it will be the first interactive screen to appear in our game, these variables store its components.

-- [Title View]
local title
local playBtn
local creditsBtn
local titleView

12. Credits View

Credits View

This view will show the credits and copyright of the game, this variable will be used to store it.

-- [CreditsView]
local creditsView

13. Game Background

Game Background

This image will replace our previous background. This will be the game background.

-- Game Background
local gameBg

14. Frog

Instructions

The frog you will be able to control.

-- Frog
local frog

15. Pad

Place Holders

This is the on-screen pad used to move the frog around the level.

-- [Pad]
local up
local left
local down
local right

16. Cars

Shapes

The obstacles the player needs to avoid to cross to the other side.


17. Alert

Alert

This is the alert that will be displayed when you win the game. It will complete the level and end the game.

  -- Alert
  local alertView

18. Sounds

Sounds

We’ll use Sound Effects to enhance the feeling of the game, the sounds used in this app were generated by AS3SFXR.

-- Sounds
local moveSnd = audio.loadSound('move.mp3')
local loseSnd = audio.loadSound('lose.mp3')
local goalSnd = audio.loadSound('goal.mp3')

19. Variables

This are the variables we’ll use. Read the comments in the code to learn more about them.

-- Variables
local lastY --Used to reposition the titleView after credits tween
local obstacles --Obstacles group
local counter = 0 --Counts the frogs that had crossed the street

20. Declare Functions

Declare all functions as local at the start.

-- Functions
local Main = {}
local startButtonListeners = {}
local showCredits = {}
local hideCredits = {}
local showGameView = {}
local gameListeners = {}
local addObstacle = {}
local movePlayer = {}
local update = {}
local onCollision = {}
local alert = {}

21. Constructor

Next we’ll create the function that will initialize all the game logic:

function Main()
	-- code...
end

22. Add Title View

Now we place the TitleView in the stage and call a function that will add the tap listeners to the buttons.

function Main()
	title = display.newImage('title.png', 64, 130)
	playBtn = display.newImage('playBtn.png', 134, 245)
	creditsBtn = display.newImage('creditsBtn.png', 114, 305)
	titleView = display.newGroup(bg, title, playBtn, creditsBtn)
	startButtonListeners('add')
end

Next Time…


In this part of the series you’ve learned the interface and the basic setup of the game. In the next and final part of the series, we’ll handle the level creation, collision detection, and the final steps to take prior to release like app testing, creating a start screen, adding an icon and, finally, building the app. Stay tuned for the final part!

Creating a Game with Bonjour – Client and Server Setup

$
0
0

In the first part of this series, I wrote about the basics of networking. In this article, we will start working with Bonjour and the CocoaAsyncSocket library by creating the foundation of our game.


Overview

In this article, we zoom in on establishing a connection between two devices running an instance of the game we are about to create. Even though this may sound trivial, there are quite a few components involved to make this work. Before we get our hands dirty, let me walk you through the process step by step.

In the previous article, I wrote about the client-server model. Before we create the game itself, we need to implement the client-server model in the application. Two devices each running a different instance of the game won’t magically find each other on the network and start a game. One instance needs to act as the server and make it known to other instances on the network that they can join.

A common approach to solve this problem is by allowing players to host or join a game. The device of a player hosting a game acts as a server, while the device of a player joining a game acts as a client connecting to a server. The server provides a service and it can announce this on the network using Bonjour. The device of the player looking for a session to join searches the network for services also using Bonjour. When a player joins a game, the service is resolved and the client device attempts to establish a connection between the two devices so that the game can start.

If you are confused by the client-server model, then I recommend revisiting the first part of this series in which the client-server model is described in more detail.

Bonjour

What role does Bonjour play in this setup? Bonjour is in charge of publishing and discovering services on the network. Bonjour is also used for the discovery of printers or other devices connected to the network. It is important to remember that Bonjour is not responsible for establishing a connection between the server and the client. We make use of the CocoaAsyncSocket library to accomplish this task.

CocoaAsyncSocket

The CocoaAsyncSocket library comes into play when we have to deal with sockets, ports, and connections. It also helps us with sending data from one end of the connection to the other end, in both directions. Even though Bonjour is not responsible for establishing a connection between two processes, it does provide us with information that we need to establish the connection. As I mentioned earlier in this series, Bonjour and the CocoaAsyncSocket are a powerful combination as you will see in this article.

Testing

Testing is a key aspect of software development especially when networking is involved. To test the networking component of our application, you will need to run two instances of it. You could do this by running one instance on the iOS Simulator and a second instance on a physical device. One instance will serve as the server by hosting a game, whereas the other instance will serve as the client by searching the network for games to join.


1. Project Setup

Open Xcode and create a new project based on the Single View Application template (figure 1). Name the project Four in a Row, set Devices to iPhone, and double-check that ARC (Automatic Reference Counting) is enabled for the project (figure 2). We won’t be using storyboards in this tutorial.

Creating a Game with Bonjour - Client and Server - Project Setup
Project Setup
Creating a Game with Bonjour - Client and Server - Project Setup
Project Setup

2. Adding CocoaAsyncSocket

Adding the CocoaAsyncSocket library is easy if you choose to use CocoaPods as I explained in a prior post. However, even without CocoaPods, adding the CocoaAsyncSocket library to your project isn’t rocket science.

Step 1

Download the latest version of the library from GitHub and expand the archive. Locate a folder named GCD and drag both GCDAsyncSocket.h and GCDAsyncSocket.m into your Xcode project. Make sure to copy the files to the Xcode project and add them to the Four in a Row target (figure 3).

Creating a Game with Bonjour - Client and Server - Adding the CocoaAsyncSocket Library
Adding the CocoaAsyncSocket Library

Step 2

The CocoaAsyncSocket library depends on the CFNetwork and Security frameworks, which means that we need to link our project against both frameworks. Open your project in the Project Navigator on the left, select the Four in a Row target from the list of targets, choose the Build Phases tab at the top, and open the Link Binary with Libraries drawer. Click the plus button and link your Xcode project against the CFNetwork and Security frameworks (figure 4).

Creating a Game with Bonjour - Client and Server - Linking the Project against the CFNetwork and Security Frameworks
Linking the Project against the CFNetwork and Security Frameworks

Step 3

Before continuing, add an import statement to the project’s precompiled header file to import the header file that we just added to our Xcode project. This makes sure that we can use the GCDAsyncSocket class throughout our project.

#import <Availability.h>
#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import "GCDAsyncSocket.h"
#endif

You may have noticed that we didn’t add GCDAsyncUdpSocket.h and GCDAsyncUdpSocket.m to our project. As the file names indiciate, these files declare and implement the GCDAsyncUdpSocket class, which is in charge of working with the UDP protocol. Even though we will only be working with the TCP protocol in this series, keep in mind that the CocoaAsyncSocket library also supports the UDP protocol.


3. Creating the Host Game View Controller

Step 1

Whenever a user launches our application, she has two options to choose from: (1) hosting a game, or (2) joining a game that is hosted by another player. The interface is pretty straightforward as you can imagine. Open MTViewController.xib, add two buttons to the view controller’s view, and give each button an appropriate title (figure 5).

Creating a Game with Bonjour - Client and Server - Creating the User Interface
Creating the User Interface

Step 2

Add two actions, hostGame: and joinGame:, to the view controller’s implementation file and connect each action with the appropriate button in MTViewController.xib.

- (IBAction)hostGame:(id)sender {
}
- (IBAction)joinGame:(id)sender {
}

Step 3

When the user taps the button titled Host a Game, it will present the user with a modal view. Under the hood, the application will publish a service using Bonjour and the CocoaAsyncSocket library. When another player joins the game, the user hosting the game will be notified and the game can start. Create a UIViewController subclass and name it MTHostGameViewController (figure 6). Tell Xcode to also create a XIB file for the new view controller class (figure 6).

Creating a Game with Bonjour - Client and Server - Creating the Host Game View Controller
Creating the Host Game View Controller

Step 4

Add an import statement for the new view controller to MTViewController.m and implement the hostGame: action as shown below. When the user taps the top button, an instance of the MTHostGameViewController class is created, set as the root view controller of a navigation controller, and presented modally.

#import "MTHostGameViewController.h"
- (IBAction)hostGame:(id)sender {
    // Initialize Host Game View Controller
    MTHostGameViewController *vc = [[MTHostGameViewController alloc] initWithNibName:@"MTHostGameViewController" bundle:[NSBundle mainBundle]];
    // Initialize Navigation Controller
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    // Present Navigation Controller
    [self presentViewController:nc animated:YES completion:nil];
}

Step 5

Open MTHostGameViewController.m and implement the viewDidLoad method as shown below. All we do is call setupView, a helper method. In the setupView method, we add a button to the navigation bar to let the user cancel hosting a game and dismiss the view controller. As the implementation of the cancel: action shows, cancelling the hosting of a game is something that we will implement later in this tutorial. Build and run the application for the first time to see if everything works as expected.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
}
- (void)setupView {
    // Create Cancel Button
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];
}
- (void)cancel:(id)sender {
    // Cancel Hosting Game
    // TODO
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

4. Publishing a Service

When the user opens the host game view controller, the view controller should automatically publish a service that other instances of the application on the network can resolve. The class that we will be using for this purpose is NSNetService. As the documentation of NSNetService states, an instance of the NSNetService class represents a network service. Remember that a service isn’t limited to a process or an application. Printers or other devices connected to the network can also make their services known by using Bonjour. It is this versatility that makes Bonjour great for consumers.

Instead of overwhelming you with more theory, let me show you what it takes to publish a service using Bonjour and the CocoaAsyncSocket library. Amend the viewDidLoad method as shown below.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
    // Start Broadcast
    [self startBroadcast];
}

Before we take a look at the startBroadcast method, we need to do some housekeeping. Add a class extension at the top of the host game view controller implementation file and declare two private properties, service, of type NSNetService, and socket, of type GCDAsyncSocket. In the class extension, we also conform the host game view controller to the NSNetServiceDelegate and GCDAsyncSocketDelegate protocols.

@interface MTHostGameViewController () <NSNetServiceDelegate, GCDAsyncSocketDelegate>
@property (strong, nonatomic) NSNetService *service;
@property (strong, nonatomic) GCDAsyncSocket *socket;
@end

The service property represents the network service that we will be publishing using Bonjour. The socket property is of type GCDAsyncSocket and provides an interface for interacting with the socket that we will be using to listen for incoming connections. With the class extension in place, let’s take a look at the implementation of the startBroadcast method.

We initialize an instance of the GCDAsyncSocket class and pass the view controller as the socket’s delegate. The second argument of the initializer is a GCD (Grand Central Dispatch) queue, the dispatch queue of the application’s main thread in this example. The CocoaAsyncSocket library has a queued architecture, which makes it extremely flexible and powerful. Even though the integration with GCD is an important addition to the CocoaAsyncSocket library, I won’t cover this integration in this series.

- (void)startBroadcast {
    // Initialize GCDAsyncSocket
    self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    // Start Listening for Incoming Connections
    NSError *error = nil;
    if ([self.socket acceptOnPort:0 error:&error]) {
        // Initialize Service
        self.service = [[NSNetService alloc] initWithDomain:@"local." type:@"_fourinarow._tcp." name:@"" port:[self.socket localPort]];
        // Configure Service
        [self.service setDelegate:self];
        // Publish Service
        [self.service publish];
    } else {
        NSLog(@"Unable to create socket. Error %@ with user info %@.", error, [error userInfo]);
    }
}

The second step is to tell the socket to accept incoming connections by sending it a message of acceptOnPort:error:. We pass 0 as the port number, which means that it is up to the operating system to supply us with a port (number) that is available. This is generally the safest solution as we don’t always know whether a particular port is in use or not. By letting the system choose a port on our behalf, we can be certain that the port (number) we get back is available. If the call is successful, that is, returning YES and not throwing an error, we can initialize the network service.

The order in which all this takes place is important. The network service that we initialize needs to know the port number on which to listen for incoming connections. To initialize the network service, we pass (1) a domain, which is always local. for the local domain, (2) the network service type, which is a string that uniquely identifies the network service (not the instance of our application), (3) the name by which the network service is identified on the network, and (4) the port on which the network service is published.

The type that we pass as the second argument of initWithDomain:type:name:port: needs to specify both the service type and the transport layer protocol (TCP in this example). The underscore prefixing the service type and the transport layer protocol is also important. The details can be found in the documentation.

With the network service ready to use, we set the delegate of the service and publish it. Publishing the service simply means advertising the network service on the network so that clients can discover it.

If we were to set all this up using the CFNetwork instead of the CocoaAsyncSocket library, we would have to write quite a bit of complex C code. As you can see, the CocoaAsyncSocket library makes this process a lot easier by providing a convenient Objective-C API.


5. Responding to Events

We don’t need to implement every method of the NSNetServiceDelegate protocol. The most important ones are listed below. In these delegate methods, we simply log a message to the console to make sure that we can keep track of what is happening.

- (void)netServiceDidPublish:(NSNetService *)service {
  NSLog(@"Bonjour Service Published: domain(%@) type(%@) name(%@) port(%i)", [service domain], [service type], [service name], (int)[service port]);
}
- (void)netService:(NSNetService *)service didNotPublish:(NSDictionary *)errorDict {
  NSLog(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@", [service domain], [service type], [service name], errorDict);
}

The GCDAsyncSocketDelegate protocol has quite a few delegate methods as you can see in the documentation. To not overwhelm you, we will implement one method at a time. The first method that is of interest to us is socket:didAcceptNewSocket:, which is invoked when the listening socket (the server socket) accepts a connection (a client connection). Because we only allow one connection at a time in our application, we discard the old (listening) socket and store a reference to the new socket in the socket property. To be able to use the incoming client connection, it is key to keep a reference to the new socket that is passed to us in this delegate method.

As the documentation states, the delegate and delegate queue of the new socket are the same as the delegate and delegate queue of the old socket. What many people often forget is that we need to tell the new socket to start reading data and set the timeout to -1 (no timeout). Behind the scenes, the CocoaAsyncSocket library creates a read and a write stream for us, but we should tell the socket to monitor the read stream for incoming data.

- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);
    // Socket
    [self setSocket:newSocket];
    // Read Data from Socket
    [newSocket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
}

The second delegate method that we can implement at this time is a method that is invoked when the connection is broken. All we do in this method is cleaning things up.

- (void)socketDidDisconnect:(GCDAsyncSocket *)socket withError:(NSError *)error {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.socket == socket) {
        [self.socket setDelegate:nil];
        [self setSocket:nil];
    }
}

Because networking can be a bit messy from time to time, you may have noticed that I have inserted a few log statements here and there. Logging is your best friend when creating applications that involve networking. Don’t hesitate to throw in a few log statements if you are not sure what is happening under the hood.

Build and run your application in the iOS Simulator (or on a physical device) and click the top button to host a game. If all went well, you should see a message in Xcode’s Console that indicates that the network service was successfully published. How easy was that? In the next step, we will create the second piece of the puzzle, discovering and connecting to a network service.

2013-04-10 11:44:03.286 Four in a Row[3771:c07] Bonjour Service Published: domain(local.) type(_fourinarow._tcp.) name(Puma) port(51803)

If you are running the application in the iOS Simulator and you have a firewall enabled on your Mac, then you should see a warning from the operating system asking for your permission to allow incoming connections for the application running in the iOS Simulator (figure 7). To make sure that everything works, it is important to accept incoming connections for our application.

Creating a Game with Bonjour - Client and Server - Telling the Firewall to Allow Incoming Connections
Telling the Firewall to Allow Incoming Connections

6. Discovering Services

For the second piece of the puzzle, we need to create another view controller class, a UITableViewController subclass to be precise (figure 8). Name the new class MTJoinGameViewController. There is no need to create a XIB file for the new class.

Creating a Game with Bonjour - Client and Server - Creating the Join Game View Controller
Creating the Join Game View Controller

The next steps are similar to what we did earlier. The implementation of the joinGame: action is almost identical to the hostGame: action. Don’t forget to add an import statement for the MTJoinGameViewController class.

#import "MTJoinGameViewController.h"
- (IBAction)joinGame:(id)sender {
    // Initialize Join Game View Controller
    MTJoinGameViewController *vc = [[MTJoinGameViewController alloc] initWithStyle:UITableViewStylePlain];
    // Initialize Navigation Controller
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    // Present Navigation Controller
    [self presentViewController:nc animated:YES completion:nil];
}

As we did in the MTHostGameViewController class, we invoke setupView in the view controller’s viewDidLoad method.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
}
- (void)setupView {
    // Create Cancel Button
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel:)];
}
- (void)cancel:(id)sender {
    // Stop Browsing Services
    [self stopBrowsing];
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

The only difference is that we stop browsing for services in the cancel: action prior to dismissing the view controller. We will implement the stopBrowsing methods in a moment.

Browsing Services

To browse for services on the local network, we use the NSNetServiceBrowser class. Before putting the NSNetServiceBrowser class to use, we need to create a few private properties. Add a class extension to the MTJoinGameViewController class and declare three properties as shown below. The first property, socket of type GCDAsyncSocket, will store a reference to the socket that will be created when a network service resolves successfully. The services property (NSMutableArray) will store all the services that the service browser discovers on the network. Every time the service browser finds a new service, it will notify us and we can add it to that mutable array. This array will also serve as the data source of the view controller’s table view. The third property, serviceBrowser, is of type NSNetServiceBrowser and will search the network for network services that are of interest to us. Also note that the MTJoinGameViewController conforms to three protocols. This will become clear when we implement the methods of each of these protocols.

@interface MTJoinGameViewController () <NSNetServiceDelegate, NSNetServiceBrowserDelegate, GCDAsyncSocketDelegate>
@property (strong, nonatomic) GCDAsyncSocket *socket;
@property (strong, nonatomic) NSMutableArray *services;
@property (strong, nonatomic) NSNetServiceBrowser *serviceBrowser;
@end

Amend the view controller’s viewDidLoad method as shown below. We invoke another helper method in which we initialize the service browser so that the application can begin browsing the network for services that we are interested in. Let’s take a look at the startBrowsing method.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
    // Start Browsing
    [self startBrowsing];
}

In the startBrowsing method, we prepare the table view’s data source, the mutable array that stores the services discovered on the network. We also initialize an instance of the NSNetServiceBrowser class, set its delegate, and tell it to start searching for services. It is key that the network service type that you pass as the first argument of searchForServicesOfType:inDomain: is identical to the type that we passed in the MTHostGameViewController class. It is a good idea to make this a constant to prevent any typos.

- (void)startBrowsing {
    if (self.services) {
        [self.services removeAllObjects];
    } else {
        self.services = [[NSMutableArray alloc] init];
    }
    // Initialize Service Browser
    self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
    // Configure Service Browser
    [self.serviceBrowser setDelegate:self];
    [self.serviceBrowser searchForServicesOfType:@"_fourinarow._tcp." inDomain:@"local."];
}

By invoking searchForServicesOfType:inDomain:, the service browser starts searching the network for services of the specified type. Every time the service browser finds a service of interest, it notifies its delegate by sending it a message of netServiceBrowser:didFindService:moreComing:. The network service (NSNetService) that is discovered is passed as the second parameter and we add that object to our array of services. The last parameter of this delegate method, moreComing, tells us if we can expect more services. This flag is useful if you don’t want to prematurely update your application’s user interface, such as updating a table view. If the moreComing flag is set to NO, we sort the array of services and update the table view.

- (void)netServiceBrowser:(NSNetServiceBrowser *)serviceBrowser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
    // Update Services
    [self.services addObject:service];
  if(!moreComing) {
        // Sort Services
        [self.services sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]];
        // Update Table View
        [self.tableView reloadData];
    }
}

It is also possible that a previously discovered service stopped for some reason and is no longer available. When this happens, the netServiceBrowser:didRemoveService:moreComing: delegate method is invoked. It works in much the same way as the previous delegate method. Instead of adding the network service that is passed as the second argument, we remove it from the array of services and update the table view accordingly.

- (void)netServiceBrowser:(NSNetServiceBrowser *)serviceBrowser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
    // Update Services
    [self.services removeObject:service];
    if(!moreComing) {
        // Update Table View
        [self.tableView reloadData];
    }
}

Two other delegate methods are of interest to us. When the service browser stops searching or is unable to start searching, the netServiceBrowserDidStopSearch: and netServiceBrowser:didNotSearch: delegate methods are invoked, respectively. All we do in these methods is clean up what we started in the stopBrowsing helper method as shown below.

- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)serviceBrowser {
    [self stopBrowsing];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aBrowser didNotSearch:(NSDictionary *)userInfo {
    [self stopBrowsing];
}

The implementation of stopBrowsing isn’t difficult. All we do is instruct the service browser to stop browsing and clean everything up.

- (void)stopBrowsing {
    if (self.serviceBrowser) {
        [self.serviceBrowser stop];
        [self.serviceBrowser setDelegate:nil];
        [self setServiceBrowser:nil];
    }
}

Before we continue our journey, let’s see all this in action. First, however, implement the table view data source protocol as shown below to make sure that the table view is populated with the services that the service browser finds on the network. As you can see in the tableView:cellForRowAtIndexPath: method, we display the name property of the network service, which is the name that we passed in the MTHostGameViewController when we initialized the network service. Because we didn’t pass a name, it automatically uses the name of the device.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.services ? 1 : 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.services count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ServiceCell];
    if (!cell) {
        // Initialize Table View Cell
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ServiceCell];
    }
    // Fetch Service
    NSNetService *service = [self.services objectAtIndex:[indexPath row]];
    // Configure Cell
    [cell.textLabel setText:[service name]];
    return cell;
}

Don’t forget to statically declare the cell reuse identifier that we use in the tableView:cellForRowAtIndexPath: table view data source method.

static NSString *ServiceCell = @"ServiceCell";

To test what we have built so far, you need to run two instances of the application. On one instance you tap the Host a Game button and on the other instance you tap the Join a Game button. On the latter instance, you should see a list with all the instances that are publishing the service that we are interested in (figure 9). Bonjour makes discovering network services very easy as you can see.

Creating a Game with Bonjour - Client and Server - Testing the Application
Testing the Application

7. Making the Connection

The last step of the process is making a connection between both devices when a player joins a service (game) by tapping a service in the list of discovered services. Let me show you how this works in practice.

When the user taps a row in the table view, we pull the corresponding network service from the array of services and attempt to resolve the service. This takes place in the tableView:didSelectRowAtIndexPath: table view delegate method. The resolveWithTimeout: method accepts one argument, the maximum number of seconds to resolve the service.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // Fetch Service
    NSNetService *service = [self.services objectAtIndex:[indexPath row]];
    // Resolve Service
    [service setDelegate:self];
    [service resolveWithTimeout:30.0];
}

One of two things will happen, succes or failure. If resolving the service is unsuccessful, the netService:didNotResolve: delegate method is invoked. All we do in this method is cleaning everything up.

- (void)netService:(NSNetService *)service didNotResolve:(NSDictionary *)errorDict {
    [service setDelegate:nil];
}

If the service resolves successfully, we try to create a connection by invoking, connectWithService: and passing the service as an argument. Remember that Bonjour is not responsible for creating the connection. Bonjour only provides us with the information necessary to establish a connection. Resolving a service is not the same as creating the connection. What needs to be done to establish a connection can be found in the implementation of connectWithService:.

- (void)netServiceDidResolveAddress:(NSNetService *)service {
    // Connect With Service
    if ([self connectWithService:service]) {
        NSLog(@"Did Connect with Service: domain(%@) type(%@) name(%@) port(%i)", [service domain], [service type], [service name], (int)[service port]);
    } else {
        NSLog(@"Unable to Connect with Service: domain(%@) type(%@) name(%@) port(%i)", [service domain], [service type], [service name], (int)[service port]);
    }
}

We first create a helper variable, _isConnected, and a mutable copy of the addresses of the network service. You might be surprised that a service can have multiple addresses, but this can indeed be the case in some situations. Each element of the array of addresses is an NSData instance containing a sockaddr structure. If the socket property has not yet been initialized (no connection is active), we create a new socket just like we did in the MTHostGameViewcontroller class. We then iterate over the array of addresses and try to connect to one of the addresses in the array of addresses. If this is successful, we set _isConnected to YES. This means that the connection was successfully established.

- (BOOL)connectWithService:(NSNetService *)service {
    BOOL _isConnected = NO;
    // Copy Service Addresses
    NSArray *addresses = [[service addresses] mutableCopy];
    if (!self.socket || ![self.socket isConnected]) {
        // Initialize Socket
        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        // Connect
        while (!_isConnected && [addresses count]) {
            NSData *address = [addresses objectAtIndex:0];
            NSError *error = nil;
            if ([self.socket connectToAddress:address error:&error]) {
                _isConnected = YES;
            } else if (error) {
                NSLog(@"Unable to connect to address. Error %@ with user info %@.", error, [error userInfo]);
            }
        }
    } else {
        _isConnected = [self.socket isConnected];
    }
    return _isConnected;
}

When the socket did successfully establish a connection, the socket:didConnectToHost: delegate method of the GCDAsyncSocketDelegate protocol is invoked. Apart from logging some information to the console, we tell the socket to start monitoring the read stream for incoming data. I will cover reading and writing in more detail in the next article of this series.

- (void)socket:(GCDAsyncSocket *)socket didConnectToHost:(NSString *)host port:(UInt16)port {
	NSLog(@"Socket Did Connect to Host: %@ Port: %hu", host, port);
    // Start Reading
    [socket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
}

This is also a good time to implement another method of the GCDAsyncSocketDelegate protocol. The socketDidDisconnect:withError: method is invoked when the connection is broken. All we do is logging some information about he socket to the console and clean everything up.

- (void)socketDidDisconnect:(GCDAsyncSocket *)socket withError:(NSError *)error {
	NSLog(@"Socket Did Disconnect with Error %@ with User Info %@.", error, [error userInfo]);
    [socket setDelegate:nil];
    [self setSocket:nil];
}

Run two instances of the application to make sure that everything works. It is important that on the device hosting the game the socket:didAcceptNewSocket: delegate method is invoked and on the device joining a game the socket:didConnectToHost: delegate method is invoked. If both delegate methods are called, then we know that we have successfully created a connection between both devices.


Loose Ends

There are still a few loose ends that we need to take care of, but that is something that we will do in the next instalment of this series. I hope you agree that getting up and running with Bonjour and the CocoaAsyncSocket library isn’t that complicated. I do want to emphasize that the CocoaAsyncSocket library makes working with sockets and streams incredibly easy by providing an Objective-C API and making sure that we don’t have to use the CFNetwork framework ourselves.


Conclusion

In this tutorial, we have laid the foundation of our game in terms of networking. Of course, we still have a lot of ground to cover as we haven’t even started implementing the game itself. That is something we do in the next instalment of this series. Stay tuned.

Understanding Mobile as a Lean-back Medium

$
0
0

A common misunderstanding occurs when designers look at a mobile device as an extension of their laptop, or, even worse, consider a mobile device as simply a smaller version of a traditional laptop or desktop computer. There is a distinct difference between how we use a traditional computer and a mobile device. Traditional computers represent a lean-forward medium, while mobile devices are essentially a lean-back medium. In this article, I’ll show you the difference!


Introduction

mobile lean-back

Let us start with taking a look how you use mobile devices such as tablets and smartphones. Often these are used in quite comfortable situations, such as your couch or while you’re waiting in line for something. Computers are used in a different fashion. They are more commonly used in work situations rather than for simply relaxing or entertainment. Because of this natural (though not absolute) trend, entertainment is moving more and more to something consumed on a mobile device. This cultural shift also changes how we experience these media.


Lean-forward vs. Lean-back Media

The difference between a lean-forward and a lean-back medium is the difference in the user’s physical posture and device engagement.

What exactly is the difference, then? We approach a lean-forward media in a far more interactive way. Regular websites (not mobile ones) are a great example. We often are more engaged when we use a computer because it’s easier to participate in complex or in-depth interactions, such as those found in a discussion forum. Because it is easier to interact, there is likewise far more opportunity to engage with an online community.

A lean-back medium is a medium with a very low level of engagement or even no engagement at all. Television is the best example. While traditionally television has been a medium that lacks any interactivity at all, this has recently begun to change. For example, digital television offers more possibilities to interact. However, despite this limited interactivity, it is still classified as a lean-back medium because the typical interaction is so limited.


Lean-back and Mobile Design

In what way is this relevant for mobile design? Well, understanding that we use our mobile devices primarily as a lean-back medium is already a great start. You simply can’t expect users to perform difficult actions on mobile devices or expect they will engage in a high level of interactivity while using a mobile device, particularly if that device is a phone. Apps such as Flipboard and Tumblr are quite popular on mobile for a reason. While the majority of the content is created on a computer (quite obvious as it’s far easier to create content with a laptop/desktop) a lot of this content is consumed on mobile devices.


Examples of Lean-back Apps

mobile lean-back
YouTube

Any application used to watch videos online is the perfect example of a lean-back application. After all, the interactivity is incredible limited and, in general, there’s one-way communication.

mobile lean-back
Foursquare

Foursquare is another great example. Interactivity isn’t limited, but it is simple. A check-in or leaving a tip are simple actions, yet they are integrated in a more complex application. You can compare locations, see what you’re friends are up too and other great features. Foursquare did an amazing job at making interaction in an otherwise complex application simple.

mobile lean-back
Fruit Ninja

Many games are relaxing and lean-back. Angry Birds is a perfect example of how successful games usually have simple, limited, yet fun gestures. Fruit Ninja is another popular app which is a great example how limited interactivity can be very fun for your app’s audience.


Examples of Lean-forward Apps

Naturally, there are also lean-forward applications. Does this mean that they are bad applications? Of course not. It rather means that people will use them in different (and limited) situations, and that they require far more attention to use effectively.

mobile lean-forward
Jamie’s Recipes

Jamie’s Recipes or other cooking applications are a great example because people are required to pay attention while using your application. They are dependent on this app while cooking. Consequently, one wouldn’t use these kind of applications in a lean-back situation such as relaxing on the couch.

mobile lean-forward
Tapatalk

Many productivity apps are also lean-forward. Sometimes developers attempt to translate lean-forward features from the computer to mobile devices. The forum application Tapatalk is an example of how a discussion forum is brought to a mobile device. Even though the target audience is more limited when compared to desktop users, people sometimes still want to have a more extensive way of interactivity on their mobile device. Translating extensive interactivity to a mobile device is very challenging, but it’s not impossible.

mobile lean-forward
Screenshot of the Undercroft app

Not all games are lean-back. Some games such as Undercroft are more extensive and require thought and strategy. It’s more difficult to play in a hundred percent lean-back situation and often will have more complicated methods of interactivity: navigation, movement, and gestures. Such games are usually considered hybrids, as games are in general still lean-back, but some are quite complex and don’t fit the general features of a lean-back application.


Tips for Mobile Design

“Efficiency is intelligent laziness.” – David Dunham

Understanding that you need to develop for mobile devices with a passive public in mind helps you to make the right design choices. It’s pretty obvious that you need to keep the functionality of your application simple and accessible. Does this mean that it’s difficult to build interaction into your apps? It’s not necessarily more difficult, but it may require more thought. It’s important to understand that you need to keep interactions simple and accessible, for example a like button (Facebook), a share button (Tumblr), and so on. By providing interactivity in a simple way you will generate far more engagement. You can’t expect users to write down a comment of a couple sentences like they might on a regular computer. Here are some basic reminders for your next project:

  • Minimizing effort is they key to a better user experience on mobile devices.
  • Engagement should be simplistic and rewarding for the user.
  • Keep gestures limited and fun (e.g. Angry Birds, FruitNinja).
  • Create visual coherence to guide the user’s eyes and actions.
  • Make browsing through content easy and quick.
  • Decrease navigation gestures and anticipate user behavior.
  • Keep It Simple! If it isn’t needed for the task at hand, drop it from the design!

Conclusion

This article serves simply as food for thought. Of course, it’s not fair to say that all mobile users are going to use their device in a lean-back fashion, but the majority of users interacting with your apps will likely have very limited attention spans. Make life easier on them by anticipating this and saving time whenever possible. Simplicity will create a better user experience, and mobile is all about providing the correct experience for the needs of your users. Do you agree? Disagree? Leave a comment and let me know!

iOS SDK: Creating a Custom Text Input View

$
0
0

Developing an amazing application is not a simple job. Traditionally, one of the most difficult aspects of doing so has been creating rich, compelling interfaces. This tutorial will teach you how to build a custom text input view that will help make your own apps shine!


Tutorial Preview



Series Overview

This tutorial is the first in a collection on building custom interface elements. In this tutorial, we will cover customized text input views, and in the next two tutorials we will go over custom accordion menus and alert views. The series format is as follows:

In this tutorial we are going to create a simple list application, which will incorporate some basic features. In this example, our app will only be able to add and edit items using our brand new custom text input view. The final project of this tutorial is going to be the base for the next one, where we are going to create an accordion menu to provide the user with some options. In the third and last tutorial, we will use the final project of the second tutorial to create a custom alert view.


1. Create the Project

Begin by launching the Xcode and create a Single View Application:

Proceed and set a name for the project. I named mine CustomViewsDemo. Make sure to check on the Use Automatic Reference Counting option as well:

Finally, select a folder to save the project and click Create:


2. Setup the Interface

Step 1

Click on the ViewController.xib file to let the Interface Builder show up on the screen. First, turn off the Autolayout option (we do this to let this demo project work on iOS older than 6 – Autolayout is an iOS 6 feature):

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

Step 2

Now click on the Attributes Inspector and under the Simulated Metrics section set the Size value to None, so that it works on a 3.5″ screen as well.

Step 3

Add a UIToolBar into the view and place it at the bottom of the view. Do the following:

  • Add a Flexible Space Bar Button Item to the left of the default bar button item to force it to stay on the right side of the toolbar.
  • Set the title of the bar button item to Add item.
  • Set the Tint Color of the button to: (R: 51, G: 51, B: 51).
  • Set the Tint Color of the toolbar to (R: 0, G: 0, B: 51).

Step 4

Next, add a UITableView inside the remaining available space of the view. Set its style to Grouped and the separator to Single Line:

Also, set its background color to (R: 51, G: 51, B: 51). Your interface should now look like this:


3. IBOutlet Properties & IBAction Methods

Before going any further we need to connect the UITableView to an IBOutlet property and create an IBAction method for the toolbar’s bar button item (the “Add item” button”). In order to do this, while in Interface Builder, click on the middle button of the Editor section on the Xcode toolbar to reveal the Assistant Editor:

Step 1

To insert a new IBOutlet property, Control+Click (Right click) on the Table View > Click on the New Referencing Outlet > Drag and Drop into the Assistant Editor.

Next specify a name for the new property. I simply named it table.

Step 2

To create an IBAction for the bar button item, Control+Click (Right click) on the Bar Button item > Click on the Selector option in the Sent Actions section > Drag and Drop into the Assistant Editor:

Set a name for the method. I named mine addItem.


4. UITableView Methods

Step 1

In this step we need to implement the minimum required table view methods in order for our table to work correctly. But before we do that, we need to set the view controller as the delegate and datasource for the table view.

Click on the ViewController.h file. Form the @interface header as follows:

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

Then inside the ViewController.m file and in the viewDidLoad method add the following:

// Set the tableview's delegate and datasource.
[_table setDelegate:self];
[_table setDataSource:self];

Step 2

A NSMutableArray array will be the source of our list items (the table’s contents). First, however, we need to declare and initialize it. Inside the ViewController.m file, go to the top of it and inside the private part of the @interface add the next line:

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *sampleDataArray;
@end

Also inside the viewDidLoad:

// Initialize the sample data array.
_sampleDataArray = [[NSMutableArray alloc] init];

Step 3

Let’s now implement some methods of the table view. We’ll begin with the number of sections:

-(int)numberOfSectionsInTableView:(UITableView *)tableView{
    // Set the number of sections inside the tableview. We need only one section.
    return 1;
}

Next set the total number of rows. This number is equal to the total objects existing in the _sampleDataArray array.

-(int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // The number of rows is equal to the number of the sample data in our tableview.
    return [_sampleDataArray count];
}

Let’s set the height of each row:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    // Set the row height.
    return 45.0;
}

Now, let’s setup our cells. Inside the code you’ll find all the customization for the cells. There are comments explaining almost everything. You may change whatever you desire at your own will:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        // Let's set some custom cell options.
        // Set the cell's background.
        [cell setBackgroundColor:[UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1.0]];
        // Set the selection style.
        [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
        // Set the accessory type.
        [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
        // Set a font for the cell's textlabel.
        [[cell textLabel] setFont:[UIFont fontWithName:@"Georgia" size:15.0]];
    }
    [[cell textLabel] setText:[_sampleDataArray objectAtIndex:[indexPath row]]];
    return cell;
}

Finally, add the -(void)tableView:didSelectRowAtIndexPath: delegate method:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
}

Right now leave this method blank. We will visit it again later.

To summarize so far, we have created our project and setup the interface using the Interface Builder. We connected the necessary IBOutlet property and IBAction method and implemented the minimum required methods in order to make our table (our list) to properly work. Let’s progress to create the custom text input. We will get back to the ViewController in a little bit.


5. Custom Text Input View Controller

When developing projects, it is a nice habit to keep everything tidy and in order. For this reason, we will create a new group (a virtual folder) for the text input view controller.

Step 1

On the Project Navigator pane on the left side of the Xcode, Control+Click (Right click) on the CustomViewsDemo group > Select the New Group option from the popup menu:

Name the new group Custom Text Input View:

Step 2

Now we’re ready to add the new view controller. Control+Click (Right click) on the CustomViewsDemo group > Select the New File… option from the popup menu:

As the template for the new file select the Objective-C class and click Next.

Give the name CustomTextInputViewController in the class field and make sure that in the Subclass of field the UIViewController value is selected. Also, don’t forget to set the With XIB for user interface option checked:

Finally, click on the Create button. Make sure that the Custom Text Input View is the selected group, as shown in the following image:


6. Text Input View’s Interface

Step 1

Click on the CustomTextInputViewController.xib file to reveal the Interface Builder and setup the interface. Do the following:

  • Just as we did earlier, turn the Autolayout option off (Utilities Pane > File Inspector > Click on the Use Autolayout checkbox).
  • Click on the Attributes Inspector and under the Simulated Metrics section change the Size to None.

Step 2

Now it’s time to add the desired subviews into our view. Add the next subviews and set their properties as described:

  1. UILabel
    • Frame: X: 0.0, Y: 145.0, Width: 320.0, Height: 30
    • Font:: Georgia, 17.0
    • Color:: Black
    • Alignment:: Center
    • Background Color:(R: 204, G: 204, B: 204)
  2. UITextField
    • Frame: X: 0.0, Y: 180.0, Width: 320.0, Height: 30.0
    • Font: Georgia, 15.0
    • Color: Black
    • Border Style: Bezel
    • Clear button: Appears while editing
    • Capitalization:Sentences
    • Return Key: Done
    • Background Color: White

    Most of these settings will appear in the next image:

  3. UIToolBar
    • Frame: X: 0.0, Y: 225.0, Width: 320.0, Height: 44.0
    • Tint Color:(R: 204, G: 204, B: 204)

Note that the Y point is not important right now as it’s going to be set programmatically for every subview.

Step 3

Apart from the above, add the following UIBarButton items to the toolbar next to the default bar button item:

  • A flexible space bar button item
  • Another bar button item

Select both of the bar button items (not the flexible space item) and set their color to (R: 51, G: 51, B: 51). For the left bar button, set the title to Okay. For the right bar button, set the title to Cancel.

Finally, select all three subviews (UILabel, UITextField, and UIToolBar) and set the Autosizing values as shown below (Flexible Width, Flexible Right Margin, Flexible Left Margin, and Flexible Bottom Margin):


7. IBOutlets & IBActions

I previously showed you how to create IBOutlet properties and IBAction methods and how to connect them to subviews. Following the same method as described above, create and connect the next properties to the UILabel, UITextField, and UIToolbar respectively:

@property (weak, nonatomic) IBOutlet UILabel *lblTitle;
@property (weak, nonatomic) IBOutlet UITextField *txtText;
@property (weak, nonatomic) IBOutlet UIToolbar *toolbarIAV;

Then go to declare and connect the next IBAction methods to the Okay and Cancel buttons:

- (IBAction)acceptTextChanges:(id)sender;
- (IBAction)cancelTextChanges:(id)sender;

At this point our job with the Interface Builder is over. It’s time to code the behavior that we require from the text input view.


8. Coding

Step 1

First, we need to make some kind of setup in the code, prior to implementing any method. We will begin by setting the view controller as the delegate for the textfield. Click on the CustomTextInputViewController.h file and modify the @interface header according to this:

@interface CustomTextInputViewController : UIViewController

Next, go to the CustomTextInputViewController.m file and inside the viewDidLoad method set the delegate:

// Set the textfield delegate.
[_txtText setDelegate:self];

Also, inside the viewDidLoad set the UIToolbar (the toolbarIAV) as the Input Accessory View of the textfield:

// Set the textfield delegate.
[_txtText setDelegate:self];
// Set the input accessory view of the textfield.
[_txtText setInputAccessoryView:_toolbarIAV];

So far, so good. At the beginning of this tutorial, I mentioned that the view of the view controller will work as a cover with the goal to prevent users from tapping anything else existing behind our view. We also need it to be semi-transparent too, so that it looks good. Therefore, go and add the next line that configures our view:

// Set the background color of the view to a semi-transparent gray.
[self.view setBackgroundColor:[UIColor colorWithRed:0.66
                                                  green:0.66
                                                  blue:0.66
                                                  alpha:0.75]];

Now let’s think about it. As we said at the beginning, we need the textfield to appear just right above the input accessory view (the toolbar) and the title label above the textfield. To achieve this layout, we need to know the origin of the keyboard and the input accessory view, so we can adjust the textfield and the label accordingly. But how do we know when the keyboard appears and how do we get its size or its origin?

Well, the answer is simple. Each time the keyboard is about to appear, iOS sends a notification, named UIKeyboardWillShowNotification (actually, the iOS sends more notifications but we don’t care about them right now). We will add the view controller as an observer for this notification and when it arrives a predefined method (by us) is going to be called. With this in mind, add the next line, again into the viewDidLoad method:

// Add self as observer to the NSNotificationCenter so we know when the keyboard is about to be shown up.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowWithNotification:) name:UIKeyboardWillShowNotification object:nil];

Note that the keyboardWillShowWithNotification: is a method that we’ll implement in a while.

Another point that we should take into consideration is the status bar. The position where our subviews appear depend on the status bar and its height. Therefore, it would be a good idea if we had a variable that keeps the offset created by the status bar (its height) when it’s visible.

Go to the top of the file and inside the private part of the @interface declare a CGFloat variable that will represent the status bar offset. Along with that declare the keyboardWillShowWithNotification: I mentioned a little bit earlier:

@interface CustomTextInputViewController (){
    CGFloat statusBarOffset;
}
-(void)keyboardWillShowWithNotification:(NSNotification *)notification;
@end

I’ll mention ore about the status bar soon.

Step 2

Now we are ready to proceed to the implementation phase and we can create the necessary public methods that can be accessed by other classes. Go to the CustomTextInputViewController.h file and declare the following three methods:

-(void)showCustomTextInputViewInView:(UIView *)targetView
                            withText:(NSString *)text
                            andWithTitle:(NSString *)title;
-(void)closeTextInputView;
-(NSString *)getText;

It should be clear what each method will do.

Let’s begin with the showCustomTextInputViewInView:withText:andWithTitle method. Before we begin coding, there are a few points that I should mention. First of all, we need to check whether the status bar is visible or not and set the statusBarOffset value appropriately. We can easily find out if it’s visible and get its size. However, there is a small trap in case the status bar is visible and we have to deal with both portrait and landscape orientation. In portrait mode the status bar size is in the form Width x Height (e.g. 320.0 x 20.0). In landscape mode the size is in the form Height x Width (e.g. 20.0 x 480.0). The easiest way for us to determine the status bar height is to check the minimum value between the width and the height. With this method it is certain that we will get the correct value. Add these right after into the code snippet that follows.

Additionally you may remember we turned the Autolayout feature off, so we are responsible for presenting our views correctly in all orientations. This means that we have to check the orientation every time this method is called and set the view’s frame accordingly. It is important to note that when we are in landscape mode the width of the screen is the height for the view and the height of the screen is the width for the view. Also because the status bar is visible, a padding is created at the left side of the view in landscape mode, which needs to be eliminated. That’s partially why we declared the statusBarOffset variable earlier.

Finally, we want our view to be out of sight at the beginning and animations to smoothly slide into the screen.

But first let’s see everything in action. Open the CustomTextInputViewController.m and implement the method:

-(void)showCustomTextInputViewInView:(UIView *)targetView withText:(NSString *)text andWithTitle:(NSString *)title{
    if (![[UIApplication sharedApplication] isStatusBarHidden]) {
        CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
        if (statusBarSize.width < statusBarSize.height) {
            // If the width is smaller than the height then this is the value we need.
            statusBarOffset = statusBarSize.width;
        }
        else{
            // Otherwise the height is the desired value we want to keep.
            statusBarOffset = statusBarSize.height;
        }
    }
    else{
        // Otherwise set it to 0.0.
        statusBarOffset = 0.0;
    }
    // Before showing the self.view on-screen, we need to calculate the following
    // values, depending always on the orientation.
    CGFloat x, width, height;
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft ||
        [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight){
        // Landscape orientation.
        // Set the x point for the view. If we don't substract the statusBarOffset value then a
        // padding is going to exist at the left of the view when it will appear.
        x = targetView.frame.origin.x - statusBarOffset;
        // In landscape orientation, the width of the view equals to the height of the target view.
        width = targetView.frame.size.height;
        // The same with the height.
        height = targetView.frame.size.width;
    }
    else{
        // In portrait orientation everything is normal.
        x = targetView.frame.origin.x;
        width = targetView.frame.size.width;
        height = targetView.frame.size.height;
    }
    // Initially set the self.view off screen. That's why the y point equals to -height.
    [self.view setFrame:CGRectMake(x,
                                   -height,
                                   width,
                                   height)];
    // Add the view to the target view.
    [targetView addSubview:self.view];
    // Begin animating the appearance of the view.
    [UIView beginAnimations:@"" context:nil];
    [UIView setAnimationDuration:0.25];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    // We simply want the y point of the origin to become 0.0.
    [self.view setFrame:CGRectMake(self.view.frame.origin.x,
                                   0.0,
                                   self.view.frame.size.width,
                                   self.view.frame.size.height)];
    [UIView commitAnimations];
    // Set the textfield as the first responder to make the keyboard appear.
    [_txtText becomeFirstResponder];
    // Set the text to edit (if exists) to the textfield.
    [_txtText setText:text];
    // Set the label's text (the title above the textfield).
    [_lblTitle setText:title];
}

The comments inside the method explain everything. Note that inside it we call the becomeFirstResponder method of the _txtText textfield, so that the keyboard will appear too. We’ll see in a while how important this call is, as that’s the key point for our views to be positioned correctly.

Now, let’s implement the next method. The closing of the view is the opposite action from the showing.

-(void)closeTextInputView{
    // Resign the textfield from first responder to make the keyboard go away.
    [_txtText resignFirstResponder];
    // Animate the view closing. It's just the opposite from the showing animation.
    [UIView beginAnimations:@"" context:nil];
    [UIView setAnimationDuration:0.25];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [self.view setFrame:CGRectMake(self.view.frame.origin.x,
                                   -self.view.frame.size.height,
                                   self.view.frame.size.width,
                                   self.view.frame.size.height)];
    [UIView commitAnimations];
    // Also remove the view from the superview after a while (we must let the animation finish first).
    [self.view performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.25];
}

Note that we removed the view from the superview not at once but after the animation is over.

Finally, the third method:

-(NSString *)getText{
    // Return the textfield's text.
    return [_txtText text];
}

Step 3

This next step will show you how to handle the keyboard. Earlier inside the private part of the interface we declared this method:

-(void)showCustomTextInputViewInView:(UIView *)targetView withText:(NSString *)text andWithTitle:(NSString *)title;

This is the method that will be called every time the keyboard is about to appear and a UIKeyboardWillShowNotification notification will arrive. Now we can start working on it. The notification parameter contains a NSDictionary with information regarding the keyboard. A piece of that information is the keyboard’s size and origin. What we actually need here is the origin point because if we know the Y coordination of the keyboard (input accessory view included), we can properly layout the textfield and the label. Once more, in landscape mode it changes and the keyboard’s X coordination is the desired Y coordination for our textfield. Here is the code you’ll need:

-(void)keyboardWillShowWithNotification:(NSNotification *)notification{
    // Get the userInfo dictionary from the notification object.
    NSDictionary *info = [notification userInfo];
    // Get the keyboard origin.
    CGPoint keyboardOrigin = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin;
    CGFloat keyboardOriginY;
    if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft ||
        [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight){
        // In landscape orientation the x point represents the vertical axis.
        keyboardOriginY = keyboardOrigin.x;
    }
    else{
        keyboardOriginY = keyboardOrigin.y;
    }
    // Set the appropriate frame for the textfield. That is just right above the input accessory view.
    [_txtText setFrame:CGRectMake(_txtText.frame.origin.x,
                                  keyboardOriginY - _txtText.frame.size.height - statusBarOffset,
                                  _txtText.frame.size.width,
                                  _txtText.frame.size.height)];
    // Set the label's frame in turn.
    [_lblTitle setFrame:CGRectMake(0.0,
                                   _txtText.frame.origin.y - _lblTitle.frame.size.height,
                                   _lblTitle.frame.size.width,
                                   _lblTitle.frame.size.height)];
}

I am sure that you have noticed the subtraction of the statusBarOffset in the code above. If we don’t do that the textfield will hide under the input accessory view.

Step 4

At this phase we need to work on the protocol definition. Up to this point the view controller is almost ready, but not completely finished. What is missing here is the protocol definition which allows the delegate class that adopts it (in our case the ViewController) to implement the necessary methods required to handle the Okay and Cancel button taps.

Go to the CustomTextInputViewController.h file and define a protocol, along with the next delegate methods:

@protocol CustomTextInputViewControllerDelegate
-(void)shouldAcceptTextChanges;
-(void)shouldDismissTextChanges;
@end

Also, right after the @interface header, add the highlighted line:

@interface CustomTextInputViewController : UIViewController
@property (nonatomic, strong) id delegate;
...
...

According to what we have done, the delegate class should implement the shouldAcceptTextChanges method in order to handle the Okay button tapping and the shouldDismissTextChanges method in order to handle the Cancel button tapping.

But, what about the IBAction methods of this class? They have been left blank until now. Let’s now fill them in:

- (IBAction)acceptTextChanges:(id)sender {
    [self.delegate shouldAcceptTextChanges];
}
- (IBAction)cancelTextChanges:(id)sender {
    [self.delegate shouldDismissTextChanges];
}

As you can see, when the Okay button is tapped the shouldAcceptTextChanges delegate method is called, while when the Cancel button is tapped the shouldDismissTextChanges delegate method is called.

In order to be entirely correct, there is one more step to complete: make the Done button of the keyboard work. To do this, we just need to implement the -(BOOL)textFieldShouldReturn: delegate method:

-(BOOL)textFieldShouldReturn:(UITextField *)textField{
    // The tap on the Done button of the keyboard equals to the Okay button.
    [self.delegate shouldAcceptTextChanges];
    return YES;
}

Now our new custom view is ready. All we need to do is try it out.


9. Text Input View in Action

Step 1

Before we see our new custom text input view working, there are a few more steps we need to complete. First of all, we should declare the ViewController class as its delegate. Open the ViewController.h file, import the CustomTextInputViewController.h file and make our class its delegate at the interface header:

#import <UIKit/UIKit.h>
#import "CustomTextInputViewController.h"
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, CustomTextInputViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UITableView *table;
...
...

Now turn to the ViewController.m file and inside the private part of the @interface declare a CustomTextInputViewController object. I named it textInput:

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *sampleDataArray;
@property (nonatomic, strong) CustomTextInputViewController *textInput;
@end

Let’s initialize it inside the viewDidLoad method:

// Initialize the custom text input object.
_textInput = [[CustomTextInputViewController alloc] init];
[_textInput setDelegate:self];

Step 2

Go to the addItem IBAction method to make our text input view appear:

- (IBAction)addItem:(id)sender {
    [_textInput showCustomTextInputViewInView:self.view
                                    withText:@""
                                    andWithTitle:@"Add new item"];
}

Simple, right?

If you run the app now, you’ll see the text input view in action, but the Okay and Cancel buttons won’t work because we haven’t implemented the delegate methods yet. So, let’s do it now:

-(void)shouldAcceptTextChanges{
    // Add the new item to the sampleDataArray.
    [_sampleDataArray addObject:[_textInput getText]];
    // Reload the table using animation.
    [_table reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    // Close the text input.
    [_textInput closeTextInputView];
}
-(void)shouldDismissTextChanges{
    [_textInput closeTextInputView];
}

Run it and it will work. Any text you enter into the textfield will appear in the table. But we are not completely ready because we are unable to edit any items.

Step 3

It’s time to visit the table view’s delegate method -(void)tableView:didSelectRowAtIndexPath:. We want each row that is tapped to be edited in our custom text input view. But we also need to know when the user adds or edits a row. For that reason, we will use a flag that will indicate whether the user edits a row or not.

Go to the private part of the @interface and add the following variable:

@interface ViewController (){
    BOOL isEditingItem;
}
@property (nonatomic, strong) NSMutableArray *sampleDataArray;
@property (nonatomic, strong) CustomTextInputViewController *textInput;
@end

Don’t forget the curly brackets. Now into the viewDidLoad method set its value to NO.

// Set the initial value of the isEditingItem flag.
isEditingItem = NO;

Move to the table’s delegate method:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [_textInput showCustomTextInputViewInView:self.view
                                    withText:[_sampleDataArray objectAtIndex:[indexPath row]]
                                    andWithTitle:@"Edit item"];
    // Set the isEditingItem flag value to YES, indicating that
    // we are editing an item.
    isEditingItem = YES;
}

At this point, when you tap on existing rows on the table view, the text input view will appear to edit them. But no matter how hard you try, your changes will not be saved into the edited row, instead they will be saved as new rows. That means that we have to slightly modify the -(void)shouldAcceptTextChanges delegate method as such:

-(void)shouldAcceptTextChanges{
    // If the isEditingItem flag is set to NO, then a new item has been
    // added to the list. Otherwise an existing item has been edited.
    if (!isEditingItem) {
        // Add the new item to the sampleDataArray.
        [_sampleDataArray addObject:[_textInput getText]];
    }
    else{
        // Replace the selected item into the array with the updated value.
        NSUInteger index = [[_table indexPathForSelectedRow] row];
        [_sampleDataArray replaceObjectAtIndex:index withObject:[_textInput getText]];
        // Set the isEditingItem flag to NO.
        isEditingItem = NO;
    }
    // Reload the table using animation.
    [_table reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    // Close the text input.
    [_textInput closeTextInputView];
}

Remember that when editing a row’s content, the equivalent object inside the sample data array is replaced. Also, pay attention to the isEditingItem. It gets the NO value again.

Now everything is ready to work. Run the app and test it in both portrait and landscape orientation.


Conclusion

As you may have realized, sometimes it is worthwhile to create custom views that serve your needs better than the standard iOS SDK views. The custom text input view is a reusable component with little or no modification. It can be used in many projects, and we’ve done all of it using simple techniques and programming principles. This custom text input view, now at everyone’s disposal, can be a subject for further customization and improvements, and hopefully it will be useful for many programmers! In the next tutorial we’ll use this project and we will add a new feature, an accordion menu with options provided to the user. But for now enjoy the custom text input view!

Creating a Game with Bonjour – Sending Data

$
0
0

In the previous article, we laid the foundation of the network component of the game by enabling a user to host or join a game. At the end of the tutorial, we successfully established a connection between two devices running the application separately. In this tutorial, we will take a closer look at how we send data from one socket to another.


Introduction

As we saw in the previous tutorial, the CocoaAsyncSocket library makes working with sockets quite easy. However, there is more to the story than sending a simple string from one device to another, as we did in the previous tutorial. In the first article of this series, I wrote that the TCP protocol can manage a continuous stream of data in two directions. The problem, however, is that it is literally a continuous stream of data. The TCP protocol takes care of sending the data from one end of the connection to the other, but it is up to the receiver to make sense of what is being sent through that connection.

There are several solutions to this problem. The HTTP protocol, which is built on top of the TCP protocol, sends an HTTP header with every request and response. The HTTP header contains information about the request or response, which the receiver can use to make sense of the incoming stream of data. One key component of the HTTP header is the length of the body. If the receiver knows the length of the body of the request or response, it can extract the body from the incoming stream of data.

That’s great, but how does the receiver know how long the header is? Every HTTP header field ends with a CRLF (Carriage Return, Line Feed) and the HTTP header itself also ends with a CRLF. This means that the header of each HTTP request and response ends with two CRLFs. When the receiver reads the incoming data from the read stream, it only has to search for this pattern (two consecutive CRLFs) in the read stream. By doing so, the receiver can identify and extract the header of the HTTP request or response. With the header extracted, extracting the body of the HTTP request or response is pretty straightforward.

The strategy that we will be using differs from how the HTTP protocols operates. Every packet of data that we send through the connection is prefixed with a header that has a fixed length. The header is not as complex as an HTTP header. The header that we will be using contains one piece of information, the length of the body or packet that comes after the header. In other words, the header is nothing more than a number that informs the receiver of the length of the body. With that knowledge, the receiver can successfully extract the body or packet from the incoming stream of data. Even though this is a simple approach, it works surprisingly well as you will see in this tutorial.


1. Packets

It is important to understand that the above strategies are tailored to the TCP protocol and they only work because of how the TCP protocol operates. The TCP protocol does its very best to ensure that every packet reaches its destination in the order that it was sent; thus, the strategies that I have outlined work very well.

Step 1: Creating the Packet Class

Even though we can send any type of data through a TCP connection, it is recommended to provide a custom structure to hold the data we would like to send. We can accomplish this by creating a custom packet class. The advantage of this approach becomes evident once we start using the packet class. The idea is simple, though. The class is an Objective-C class that holds data; the body, if you will. It also includes some extra information about the packet, called the header. The main difference with the HTTP protocol is that the header and body are not strictly separated. The packet class will also need to conform to the NSCoding protocol, which means that instances of the class can be encoded and decoded. This is key if we want to send instances of the packet class through a TCP connection.

Create a new Objective-C class, make it a subclass of NSObject, and name it MTPacket (figure 1). For the game that we are building, the packet class can be fairly simple. The class has three properties, type, action, and data. The type property is used to identify the purpose of the packet while the action property contains the intention of the packet. The data property is used to store the actual contents or load of the packet. This will all become clearer once we start using the packet class in our game.

Creating a Game with Bonjour - Sending Data - Creating the Packet Class
Creating the Packet Class

Take a moment to inspect the interface of the MTPacket class shown below. As I mentioned, it is essential that instances of the class can be encoded and decoded by conforming to the NSCoding protocol. To conform to the NSCoding protocol, we only need to implement two (required) methods, encodeWithCoder: and initWithCoder:.

Another important detail is that the type and action properties are of type MTPacketType and MTPacketAction, respectively. You can find the type definitions at the top of MTPacket.h. If you are not familiar with typedef and enum, you can read more about it at Stack Overflow. It will make working with the MTPacket class a lot easier.

The class’ data property is of type id. This means that it can be any Objective-C object. The only requirement is that it conforms to the NSCoding protocol. Most members of the Foundation framework, such as NSArray, NSDictionary, and NSNumber, conform to the NSCoding protocol.

To make it easy to initialize instances of the MTPacket class, we declare a designated initializer that takes the packet’s data, type, and action as arguments.

#import <Foundation/Foundation.h>
extern NSString * const MTPacketKeyData;
extern NSString * const MTPacketKeyType;
extern NSString * const MTPacketKeyAction;
typedef enum {
    MTPacketTypeUnknown = -1
} MTPacketType;
typedef enum {
    MTPacketActionUnknown = -1
} MTPacketAction;
@interface MTPacket : NSObject
@property (strong, nonatomic) id data;
@property (assign, nonatomic) MTPacketType type;
@property (assign, nonatomic) MTPacketAction action;
#pragma mark -
#pragma mark Initialization
- (id)initWithData:(id)data type:(MTPacketType)type action:(MTPacketAction)action;
@end

The implementation of the MTPacket class shouldn’t be too difficult if you are familiar with the NSCoding protocol. As we saw earlier, the NSCoding protocol defines two methods and both are required. They are automatically invoked when an instance of the class is encoded (encodeWithCoder:) or decoded (initWithCoder:). In other words, you never have to invoke these methods yourself. We will see how this works a bit later in this article.

As you can see below, the implementation of the designated initializer, initWithData:type:action: couldn’t be easier. In the implementation file, it also becomes clear why we declared three string constants in the class’s interface. It is good practice to use constants for the keys you use in the NSCoding protocol. The primary reason isn’t performance, but typing errors. The keys that you pass when encoding the class’s properties need to be identical to the keys that are used when decoding instances of the class.

#import "MTPacket.h"
NSString * const MTPacketKeyData = @"data";
NSString * const MTPacketKeyType = @"type";
NSString * const MTPacketKeyAction = @"action";
@implementation MTPacket
#pragma mark -
#pragma mark Initialization
- (id)initWithData:(id)data type:(MTPacketType)type action:(MTPacketAction)action {
    self = [super init];
    if (self) {
        self.data = data;
        self.type = type;
        self.action = action;
    }
    return self;
}
#pragma mark -
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.data forKey:MTPacketKeyData];
    [coder encodeInteger:self.type forKey:MTPacketKeyType];
    [coder encodeInteger:self.action forKey:MTPacketKeyAction];
}
- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (self) {
        [self setData:[decoder decodeObjectForKey:MTPacketKeyData]];
        [self setType:[decoder decodeIntegerForKey:MTPacketKeyType]];
        [self setAction:[decoder decodeIntegerForKey:MTPacketKeyAction]];
    }
    return self;
}
@end

Step 2: Sending Data

Before we move on to the next piece of the puzzle, I want to make sure that the MTPacket class works as expected. What better way to test this than by sending a packet as soon as a connection is established? Once this works, we can start refactoring the network logic by putting it in a dedicated controller.

When a connection is established, the application instance hosting the game is notified of this by the invocation of the socket:didAcceptNewSocket: delegate method of the GCDAsyncSocketDelegate protocol. We implemented this method in the previous article. Take a look at its implementation below to refresh your memory. The last line of its implementation should now be clear. We tell the new socket to start reading data and we pass a tag, an integer, as the last parameter. We don’t set a timeout (-1) because we don’t know when we can expect the first packet to arrive.

What really interests us, however, is the first argument of readDataToLength:withTimeout:tag:. Why do we pass sizeof(uint64_t) as the first argument?

- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);
    // Socket
    [self setSocket:newSocket];
    // Read Data from Socket
    [newSocket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
}

The sizeof function returns the length in bytes of the function’s argument, uint64_t, which is defined in stdint.h (see below). As I explained earlier, the header that precedes every packet that we send has a fixed length (figure 2), which is very different from the header of an HTTP request or response. In our example, the header has only one purpose, telling the receiver the size of the packet that it precedes. In other words, by telling the socket to read incoming data the size of the header (sizeof(uint64_t)), we know that we will have read the complete header. By parsing the header once it’s been extracted from the incoming stream of data, the receiver knows the size of the body that follows the header.

typedef unsigned long long   uint64_t;
Creating a Game with Bonjour - Sending Data - Using a Header with a Fixed Length
Using a Header with a Fixed Length

Import the header file of the MTPacket class and amend the implementation of socket:didAcceptNewSocket: as shown below (MTHostGameViewController.m). After instructing the new socket to start monitoring the incoming stream of data, we create an instance of the MTPacket class, populate it with dummy data, and pass the packet to the sendPacket: method.

#import "MTPacket.h"
- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);
    // Socket
    [self setSocket:newSocket];
    // Read Data from Socket
    [newSocket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
    // Create Packet
    NSString *message = @"This is a proof of concept.";
    MTPacket *packet = [[MTPacket alloc] initWithData:message type:0 action:0];
    // Send Packet
    [self sendPacket:packet];
}

As I wrote earlier, we can only send binary data through a TCP connection. This means that we need to encode the MTPacket instance we created. Because the MTPacket class conforms to the NSCoding protocol, this isn’t a problem. Take a look at the sendPacket: method shown below. We create a NSMutableData instance and use it to initialize a keyed archiver. The NSKeyedArchiver class is a subclass of NSCoder and has the ability to encode objects conforming to the NSCoding protocol. With the keyed archiver at our disposal, we encode the packet.

We then create another NSMutableData instance, which will be the data object that we will pass to the socket a bit later. The data object, however, does not only hold the encoded MTPacket instance. It also needs to include the header that precedes the encoded packet. We store the length of the encoded packet in a variable named headerLength which is of type uint64_t. We then append the header to the NSMutableData buffer. Did you spot the & symbol preceding headerLength? The appendBytes:length: method expects a buffer of bytes, not the value of the headerLength value. Finally, we append the contents of packetData to the buffer. The buffer is then passed to writeData:withTimeout:tag:. The CocoaAsyncSocket library takes care of the nitty gritty details of sending the data.

- (void)sendPacket:(MTPacket *)packet {
    // Encode Packet Data
    NSMutableData *packetData = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:packetData];
    [archiver encodeObject:packet forKey:@"packet"];
    [archiver finishEncoding];
    // Initialize Buffer
    NSMutableData *buffer = [[NSMutableData alloc] init];
    // Fill Buffer
    uint64_t headerLength = [packetData length];
    [buffer appendBytes:&headerLength length:sizeof(uint64_t)];
    [buffer appendBytes:[packetData bytes] length:[packetData length]];
    // Write Buffer
    [self.socket writeData:buffer withTimeout:-1.0 tag:0];
}

Step 3: Receiving Data

To receive the packet we just sent, we need to modify the MTJoinGameViewController class. Remember that in the previous article, we implemented the socket:didConnectToHost:port: delegate method. This method is invoked when a connection is established after the client has joined a game. Take a look at its original implementation below. Just as we did in the MTHostGameViewController class, we tell the socket to start reading data without a timeout.

- (void)socket:(GCDAsyncSocket *)socket didConnectToHost:(NSString *)host port:(UInt16)port {
    NSLog(@"Socket Did Connect to Host: %@ Port: %hu", host, port);
    // Start Reading
    [socket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
}

When the socket has read the complete header preceding the packet data, it will invoke the socket:didReadData:withTag: delegate method. The tag that is passed is the same tag in the readDataToLength:withTimeout:tag: method. As you can see below, the implementation of the socket:didReadData:withTag: is surprisingly simple. If tag is equal to 0, we pass the data variable to parseHeader:, which returns the header, that is, the length of the packet that follows the header. We now know the size the encoded packet and we pass that information to readDataToLength:withTimeout:tag:. The timeout is set to 30 (seconds) and the last parameter, the tag, is set to 1.

- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)data withTag:(long)tag {
    if (tag == 0) {
        uint64_t bodyLength = [self parseHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1.0 tag:1];
    } else if (tag == 1) {
        [self parseBody:data];
        [socket readDataToLength:sizeof(uint64_t) withTimeout:30.0 tag:0];
    }
}

Before we look at the implementation of parseHeader:, let’s first continue our exploration of socket:didReadData:withTag:. If tag is equal to 1, we know that we have read the complete encoded packet. We parse the packet and repeat the cycle by telling the socket to watch out for the header of the next packet that arrives. It is important that we pass -1 for timeout (no timeout) as we don’t know when the next packet will arrive.

In the parseHeader: method, the memcpy function does all the heavy lifting for us. We copy the contents of data in the variable headerLength of type uint64_t. If you are not familiar with the memcpy function, you can read more about it here.

- (uint64_t)parseHeader:(NSData *)data {
    uint64_t headerLength = 0;
    memcpy(&headerLength, [data bytes], sizeof(uint64_t));
    return headerLength;
}

In parseBody:, we do the reverse of what we did in the sendPacket: method in the MTHostGameViewController class. We create an instance of NSKeyedUnarchiver, pass the data we read from the read stream, and create an instance of MTPacket by decoding the data using the keyed unarchiver. To prove that everything works as it should, we log the packet’s data, type, and action to the Xcode console. Don’t forget to import the header file of the MTPacket class.

#import "MTPacket.h"
- (void)parseBody:(NSData *)data {
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    MTPacket *packet = [unarchiver decodeObjectForKey:@"packet"];
    [unarchiver finishDecoding];
    NSLog(@"Packet Data > %@", packet.data);
    NSLog(@"Packet Type > %i", packet.type);
    NSLog(@"Packet Action > %i", packet.action);
}

Run two instances of the application. Host a game on one instance and join that game on the other instance. You should see the contents of the packet being logged to the Xcode console.

2013-04-16 10:11:39.738 Four in a Row[1295:c07] Did Connect with Service: domain(local.) type(_fourinarow._tcp.) name(Tiger) port(58243)
2013-04-16 10:11:41.033 Four in a Row[1295:c07] Socket Did Connect to Host: 193.145.15.148 Port: 58243
2013-04-16 10:11:41.042 Four in a Row[1295:c07] Packet Data > This is a proof of concept.
2013-04-16 10:11:41.043 Four in a Row[1295:c07] Packet Type > 0
2013-04-16 10:11:41.044 Four in a Row[1295:c07] Packet Action > 0

2. Refactoring

It isn’t convenient to put the networking logic in the MTHostGameViewController and MTJoinGameViewController classes. This will only give us problems down the road. It is more appropriate to use MTHostGameViewController and MTJoinGameViewController for establishing the connection and passing the connection – the socket – to a controller that is in charge of the control and flow of the game.

The more complex a problem is, the more solutions a problem has and those solutions are often very specific to the problem. In other words, the solution presented in this article is a viable option, but don’t consider it as the only solution. For one of my projects, Pixelstream, I have also been using Bonjour and the CocoaAsyncSocket library. My approach for that project, however, is very different than the one I present here. In Pixelstream, I need to be able to send packets from various places in the application and I have therefore chosen to use a single object that manages the connection. In combination with completion blocks and a packet queue, this solution works very well for Pixelstream. In this article, however, the setup is less complicated because the problem is fairly simple. Don’t overcomplicate things if you don’t have to.

The strategy that we will use is simple. Both the MTHostGameViewController and MTJoinGameViewController classes have a delegate that is notified when a new connection is established. The delegate will be our MTViewController instance. The latter will create a game controller, an instance of the MTGameController class, that manages the connection and the flow of the game. The MTGameController class will be in charge of the connection: sending and receiving packets as well as taking appropriate action based on the contents of the packets. If you were to work on a more complex game, then it would be good to separate network and game logic, but I don’t want to overcomplicate things too much in this example project. In this series, I want to make sure that you understand how the various pieces fit together so that you can adapt this strategy to whatever project you are working on.

Step 1: Creating Delegate Protocols

The delegate protocols that we need to create are not complex. Each protocol has two methods. Even though I am allergic to duplication, I think it is useful to create a separate delegate protocol for each class, the MTHostGameViewController and MTJoinGameViewController classes.

The declaration of the delegate protocol for the MTHostGameViewController class is shown below. If you have created custom protocols before, then you won’t find any surprises.

#import <UIKit/UIKit.h>
@class GCDAsyncSocket;
@protocol MTHostGameViewControllerDelegate;
@interface MTHostGameViewController : UIViewController
@property (weak, nonatomic) id delegate;
@end
@protocol MTHostGameViewControllerDelegate
- (void)controller:(MTHostGameViewController *)controller didHostGameOnSocket:(GCDAsyncSocket *)socket;
- (void)controllerDidCancelHosting:(MTHostGameViewController *)controller;
@end

The delegate protocol declared in the MTJoinGameViewController class is almost identical. The only differences are the method signatures of the delegate methods.

#import <UIKit/UIKit.h>
@class GCDAsyncSocket;
@protocol MTJoinGameViewControllerDelegate;
@interface MTJoinGameViewController : UITableViewController
@property (weak, nonatomic) id delegate;
@end
@protocol MTJoinGameViewControllerDelegate
- (void)controller:(MTJoinGameViewController *)controller didJoinGameOnSocket:(GCDAsyncSocket *)socket;
- (void)controllerDidCancelJoining:(MTJoinGameViewController *)controller;
@end

We also need to update the hostGame: and joinGame: actions in the MTViewController class. The only change we make is assigning the MTViewController instance as the delegate of the MTHostGameViewController and MTJoinGameViewController instances.

- (IBAction)hostGame:(id)sender {
    // Initialize Host Game View Controller
    MTHostGameViewController *vc = [[MTHostGameViewController alloc] initWithNibName:@"MTHostGameViewController" bundle:[NSBundle mainBundle]];
    // Configure Host Game View Controller
    [vc setDelegate:self];
    // Initialize Navigation Controller
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    // Present Navigation Controller
    [self presentViewController:nc animated:YES completion:nil];
}
- (IBAction)joinGame:(id)sender {
    // Initialize Join Game View Controller
    MTJoinGameViewController *vc = [[MTJoinGameViewController alloc] initWithStyle:UITableViewStylePlain];
    // Configure Join Game View Controller
    [vc setDelegate:self];
    // Initialize Navigation Controller
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    // Present Navigation Controller
    [self presentViewController:nc animated:YES completion:nil];
}

This also means that the MTViewController class needs to conform to the MTHostGameViewControllerDelegate and MTJoinGameViewControllerDelegate delegate protocols and implement the methods of each protocol. We will take a look at the implementation of these delegate methods in a few moments. First, I would like to continue refactoring the MTHostGameViewController and MTJoinGameViewController classes.

#import "MTViewController.h"
#import "MTHostGameViewController.h"
#import "MTJoinGameViewController.h"
@interface MTViewController () <MTHostGameViewControllerDelegate, MTJoinGameViewControllerDelegate>
@end

Step 2: Refactoring MTHostGameViewController

The first thing that we need to do is update the socket:didAcceptNewSocket: delegate method of the GCDAsyncSocket delegate protocol. The method becomes much simpler because the work is moved to the delegate. We also invoke endBroadcast, a helper method that we will implement in a moment. When a connection is established, we dismiss the host view controller and the game can start.

- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    NSLog(@"Accepted New Socket from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]);
    // Notify Delegate
    [self.delegate controller:self didHostGameOnSocket:newSocket];
    // End Broadcast
    [self endBroadcast];
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

In endBroadcast, we make sure that we clean everything up. This is also a good moment to update the cancel: action that we left unfinished in the previous article.

- (void)endBroadcast {
    if (self.socket) {
        [self.socket setDelegate:nil delegateQueue:NULL];
        [self setSocket:nil];
    }
    if (self.service) {
        [self.service setDelegate:nil];
        [self setService:nil];
    }
}

In the cancel: action, we notify the delegate by invoking the second delegate method and we also invoke endBroadcast as we did earlier.

- (void)cancel:(id)sender {
    // Cancel Hosting Game
    [self.delegate controllerDidCancelHosting:self];
    // End Broadcast
    [self endBroadcast];
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

Before continuing our refactoring spree, it is good practice to clean things up in the view controller’s dealloc method as shown below.

- (void)dealloc {
    if (_delegate) {
        _delegate = nil;
    }
    if (_socket) {
        [_socket setDelegate:nil delegateQueue:NULL];
        _socket = nil;
    }
}

Step 3: Refactoring MTJoinGameViewController

Similar to what we did in the socket:didAcceptNewSocket: method, we need to update the socket:didConnectToHost:port: method as shown below. We notify the delegate, stop browsing for services, and dismiss the view controller.

- (void)socket:(GCDAsyncSocket *)socket didConnectToHost:(NSString *)host port:(UInt16)port {
    NSLog(@"Socket Did Connect to Host: %@ Port: %hu", host, port);
    // Notify Delegate
    [self.delegate controller:self didJoinGameOnSocket:socket];
    // Stop Browsing
    [self stopBrowsing];
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

We also update the cancel: and dealloc methods as we did in the MTHostGameViewController class.

- (void)cancel:(id)sender {
    // Notify Delegate
    [self.delegate controllerDidCancelJoining:self];
    // Stop Browsing Services
    [self stopBrowsing];
    // Dismiss View Controller
    [self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc {
    if (_delegate) {
        _delegate = nil;
    }
    if (_socket) {
        [_socket setDelegate:nil delegateQueue:NULL];
        _socket = nil;
    }
}

To make sure that we didn’t break anything, implement the delegate methods of both protocols in the MTViewController class as shown below and run two instances of the application to test if we didn’t break anything. If all goes well, you should see the appropriate messages being logged to the Xcode console and the modal view controllers should automatically dismiss when a game is joined, that is, when a connection is established.

#pragma mark -
#pragma mark Host Game View Controller Methods
- (void)controller:(MTHostGameViewController *)controller didHostGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}
- (void)controllerDidCancelHosting:(MTHostGameViewController *)controller {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}
#pragma mark -
#pragma mark Join Game View Controller Methods
- (void)controller:(MTJoinGameViewController *)controller didJoinGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}
- (void)controllerDidCancelJoining:(MTJoinGameViewController *)controller {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

3. Implementing the Game Controller

Step 1: Creating the Game Controller Class

The MTViewController class will not be in charge of handling the connection and the game flow. A custom controller class, MTGameController will be in charge of this. One of the reasons for creating a separate controller class is that once the game has started, we won’t make a distinction between server and client. It is therefore appropriate to have a controller that is in charge of the connection and the game, but that doesn’t differentiate between the server and the client. Another reason is that the only responsibility of the MTHostGameViewController and MTJoinGameViewController classes is finding players on the local network and establishing a connection. They shouldn’t have any other responsibilities.

Create a new NSObject subclass and name it MTGameController (figure 3). The interface of the MTGameController class is pretty straightforward as you can see below. This will change once we start implementing the game logic, but this will do for now. The designated initializer takes one argument, the GCDAsyncSocket instance that it will be managing.

Creating a Game with Bonjour - Sending Data - Creating the Game Controller Class
Creating the Game Controller Class
#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@interface MTGameController : NSObject
#pragma mark -
#pragma mark Initialization
- (id)initWithSocket:(GCDAsyncSocket *)socket;
@end

Before we implement initWithSocket:, we need to create a private property for the socket. Create a class extension as shown below and declare a property of type GCDAsyncSocket named socket. I have also taken the liberty to import the header file of the MTPacket class and define TAG_HEAD and TAG_BODY to make it easier to work with tags in the GCDAsyncSocketDelegate delegate methods. Of course, the MTGameController class needs to conform to the GCDAsyncSocketDelegate delegate protocol to make everything work.

#import "MTGameController.h"
#import "MTPacket.h"
#define TAG_HEAD 0
#define TAG_BODY 1
@interface MTGameController ()
@property (strong, nonatomic) GCDAsyncSocket *socket;
@end

The implementation of initWithSocket: is shown below and shouldn’t be too surprising. We store a reference to the socket in the private property we just created, set the game controller as the socket’s delegate, and tell the socket to start reading incoming data, that is, intercept the first header that arrives.

#pragma mark -
#pragma mark Initialization
- (id)initWithSocket:(GCDAsyncSocket *)socket {
    self = [super init];
    if (self) {
        // Socket
        self.socket = socket;
        self.socket.delegate = self;
        // Start Reading Data
        [self.socket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:TAG_HEAD];
    }
    return self;
}

The remainder of the refactoring process isn’t complicated either because we already did most of the work in the MTHostGameViewController and MTJoinGameViewController classes. Let’s start by taking a look at the implementation of the GCDAsyncSocketDelegate delegate protocol. The implementation doesn’t differ from what we saw earlier in the MTHostGameViewController and MTJoinGameViewController classes.

- (void)socketDidDisconnect:(GCDAsyncSocket *)socket withError:(NSError *)error {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.socket == socket) {
        [self.socket setDelegate:nil];
        [self setSocket:nil];
    }
}
- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)data withTag:(long)tag {
    if (tag == 0) {
        uint64_t bodyLength = [self parseHeader:data];
        [socket readDataToLength:bodyLength withTimeout:-1.0 tag:1];
    } else if (tag == 1) {
        [self parseBody:data];
        [socket readDataToLength:sizeof(uint64_t) withTimeout:-1.0 tag:0];
    }
}

The implementation of sendPacket:, parseHeader:, and parseBody: aren’t any different either.

- (void)sendPacket:(MTPacket *)packet {
    // Encode Packet Data
    NSMutableData *packetData = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:packetData];
    [archiver encodeObject:packet forKey:@"packet"];
    [archiver finishEncoding];
    // Initialize Buffer
    NSMutableData *buffer = [[NSMutableData alloc] init];
    // Fill Buffer
    uint64_t headerLength = [packetData length];
    [buffer appendBytes:&headerLength length:sizeof(uint64_t)];
    [buffer appendBytes:[packetData bytes] length:[packetData length]];
    // Write Buffer
    [self.socket writeData:buffer withTimeout:-1.0 tag:0];
}
- (uint64_t)parseHeader:(NSData *)data {
    uint64_t headerLength = 0;
    memcpy(&headerLength, [data bytes], sizeof(uint64_t));
    return headerLength;
}
- (void)parseBody:(NSData *)data {
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    MTPacket *packet = [unarchiver decodeObjectForKey:@"packet"];
    [unarchiver finishDecoding];
    NSLog(@"Packet Data > %@", packet.data);
    NSLog(@"Packet Type > %i", packet.type);
    NSLog(@"Packet Action > %i", packet.action);
}

The parseBody: method will play an important role a bit later in the story, but this will do for now. Our goal at this point is to get everything working again after the refactoring process is complete.

Before we move on, it is important to implement the dealloc method of the MTGameController class as shown below. Whenever the game controller is deallocated, the instance needs to break the connection by calling disconnect on the GCDAsyncSocket instance.

- (void)dealloc {
    if (_socket) {
        [_socket setDelegate:nil delegateQueue:NULL];
        [_socket disconnect];
        _socket = nil;
    }
}

Step 2: Creating Another Delegate Protocol

The MTViewController class will manage the game controller and interact with it. The MTViewController will display the game and let the user interact with it. The MTGameController and MTViewController instances need to communicate with one another and we will use another delegate protocol for that purpose. The communication is asymmetric in that the view controller knows about the game controller, but the game controller doesn’t know about the view controller. We will expand the protocol as we go, but for now the view controller should only be notified when the connection is lost.

Revisit MTGameController.h and declare the delegate protocol as shown below. In addition, a public property is created for the game controller’s delegate.

#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@protocol MTGameControllerDelegate;
@interface MTGameController : NSObject
@property (weak, nonatomic) id delegate;
#pragma mark -
#pragma mark Initialization
- (id)initWithSocket:(GCDAsyncSocket *)socket;
@end
@protocol MTGameControllerDelegate
- (void)controllerDidDisconnect:(MTGameController *)controller;
@end

We can immediately put the delegate protocol to use by notifying the game controller’s delegate in one of the GCDAsyncSocketDelegate delegate methods, socketDidDisconnect:withError: to be precise.

- (void)socketDidDisconnect:(GCDAsyncSocket *)socket withError:(NSError *)error {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.socket == socket) {
        [self.socket setDelegate:nil];
        [self setSocket:nil];
    }
    // Notify Delegate
    [self.delegate controllerDidDisconnect:self];
}

Step 3: Updating the MTViewController Class

The final piece of the refactoring puzzle is putting the MTGameController to use. Create a private property in the MTViewController class, conform the MTViewController class to the MTGameControllerDelegate protocol, and import the header file of the MTGameController class.

#import "MTViewController.h"
#import "MTGameController.h"
#import "MTHostGameViewController.h"
#import "MTJoinGameViewController.h"
@interface MTViewController () <MTGameControllerDelegate, MTHostGameViewControllerDelegate, MTJoinGameViewControllerDelegate>
@property (strong, nonatomic) MTGameController *gameController;
@end

In controller:didHostGameOnSocket: and controller:didJoinGameOnSocket:, we invoke startGameWithSocket: and pass the socket of the new connection.

- (void)controller:(MTHostGameViewController *)controller didHostGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // Start Game with Socket
    [self startGameWithSocket:socket];
}
- (void)controller:(MTJoinGameViewController *)controller didJoinGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // Start Game with Socket
    [self startGameWithSocket:socket];
}

In the startGameWithSocket: helper method, we instantiate an instance of the MTGameController class by passing the socket and store a reference of the game controller in the view controller’s gameController property. The view controller also serves as the game controller’s delegate as we discussed earlier.

- (void)startGameWithSocket:(GCDAsyncSocket *)socket {
    // Initialize Game Controller
    self.gameController = [[MTGameController alloc] initWithSocket:socket];
    // Configure Game Controller
    [self.gameController setDelegate:self];
}

In the controllerDidDisconnect: delegate method of the MTGameControllerDelegate protocol we invoke the endGame helper method in which we clean the game controller up.

- (void)controllerDidDisconnect:(MTGameController *)controller {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // End Game
    [self endGame];
}
- (void)endGame {
    // Clean Up
    [self.gameController setDelegate:nil];
    [self setGameController:nil];
}

To make sure that everything works, we should test our setup. Let’s open the XIB file of the MTViewController and add another button in the top left titled Disconnect (figure 4). The user can tap this button when she wants to end or leave the game. We show this button only when a connection has been established. When a connection is active, we hide the buttons to host and join a game. Make the necessary changes in MTViewcontroller.xib (figure 4), create an outlet for each button in MTViewController.h, and connect the outlets in MTViewcontroller.xib.

#import <UIKit/UIKit.h>
@interface MTViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIButton *hostButton;
@property (weak, nonatomic) IBOutlet UIButton *joinButton;
@property (weak, nonatomic) IBOutlet UIButton *disconnectButton;
@end

Finally, create an action named disconnect: in MTViewController.m and connect it with the button title Disconnect.

- (IBAction)disconnect:(id)sender {
    [self endGame];
}
Creating a Game with Bonjour - Sending Data - Updating the User Interface
Updating the User Interface

In the setupGameWithSocket: method, we hide hostButton and joinButton, and we show disconnectButton. In the endGame method, we do the exact opposite to make sure that the user can host or join a game. We also need to hide the disconnectButton in the view controller’s viewDidLoad method.

- (void)startGameWithSocket:(GCDAsyncSocket *)socket {
    // Initialize Game Controller
    self.gameController = [[MTGameController alloc] initWithSocket:socket];
    // Configure Game Controller
    [self.gameController setDelegate:self];
    // Hide/Show Buttons
    [self.hostButton setHidden:YES];
    [self.joinButton setHidden:YES];
    [self.disconnectButton setHidden:NO];
}
- (void)endGame {
    // Clean Up
    [self.gameController setDelegate:nil];
    [self setGameController:nil];
    // Hide/Show Buttons
    [self.hostButton setHidden:NO];
    [self.joinButton setHidden:NO];
    [self.disconnectButton setHidden:YES];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Hide Disconnect Button
    [self.disconnectButton setHidden:YES];
}

To test if everything still works, we need to send a test packet as we did a bit earlier in this article. Declare a method named testConnection in MTGameController.h and implement it as shown below.

- (void)testConnection;
- (void)testConnection {
    // Create Packet
    NSString *message = @"This is a proof of concept.";
    MTPacket *packet = [[MTPacket alloc] initWithData:message type:0 action:0];
    // Send Packet
    [self sendPacket:packet];
}

The view controller should invoke this method whenever a new connection has been established. A good place to do this is in the controller:didHostGameOnSocket: delegate method after the game controller has been initialized.

- (void)controller:(MTHostGameViewController *)controller didHostGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // Start Game with Socket
    [self startGameWithSocket:socket];
    // Test Connection
    [self.gameController testConnection];
}

Run the application once more to verify that everything is still working after the refactoring process.


4. Cleaning Up

It is now time to clean up the MTHostGameViewController and MTJoinGameViewController classes by getting rid of any code that no longer belongs in these classes. For the MTHostGameViewController class, this means removing the sendPacket: method and for the MTJoinGameViewController class, this means removing the socket:didReadData:withTag: method of the CocoaAsyncSocketDelegate delegate protocol as well as the code>parseHeader: and parseBody: helper methods.


Summary

I can imagine that this article has left you a bit dazed or overwhelmed. There was a lot to take in and process. However, I want to emphasize that the complexity of this article was primarily due to how the application itself is structured and not so much how to work with Bonjour and the CocoaAsyncSocket library. It is often a real challenge to architect an application in such a way that you minimize dependencies and keep the application lean, performant, and modular. This is the main reason why we refactored our initial implementation of the network logic.

We now have a view controller that takes care of displaying the game to the user (MTViewController) and a controller (MTGameController) that handles the game and connection logic. As I mentioned earlier, it is possible to separate connection and game logic by creating a separate class for each of them, but for this simple application that isn’t necessary.


Conclusion

We’ve made significant progress with our game project, but there is one ingredient missing…the game! In the next installment of this series, we will create the game and leverage the foundation that we’ve created so far.


Android SDK: Working with Google Maps – Google Places Integration

$
0
0

Google Maps provides Android developers with localization tools that can be included within your own app interfaces. In this series, we are creating a basic Android application with Google Maps and Google Places integration. In this part, we will access the Google Places API to retrieve information about places of interest near the user’s current location. We will use the returned data to present markers for each place on the map in the final part of this tutorial series. We will also set the application to update the map markers when the user’s location changes.

This is the third of four parts in a tutorial series on Using Google Maps and Google Places in Android apps:

App We Are Working Towards
This is a snapshot of the final app.

1. Get Google Places API Access

Step 1

In the first part of this series we used the Google API Console to get an API key for the mapping tools. Now we need to do the same for the Google Places API. Sign into your Google Account and browse to the Console again. Select Services from the options in the left-hand column and scroll down to the entry for Places API. Click to turn Places on for your account.

Places API On

Remember you need to carry out this step for any Google API service you wish to access, as your keys will not work otherwise.

Step 2

Now we need to get the key to access the Places API. Select API Access from the left-hand column in your Google APIs Console. You will see the page we used in the first part of the series to retrieve an API key for the mapping package. Although we are going to use the Places API in an Android app, the type of access is actually the same as for browser apps. When we retrieve the Places data, we request it using a URL and retrieve the results in JSON format, as you would in a Web application. Copy the key listed in the Key for browser apps section and save it.

Places API Key

We’re finished with the Google APIs Console so feel free to log out of your account.


2. Build a Places Search Query

Step 1

Google Places API provides a range of information about places. For our app, we are going to use the Place Search request to return a list of places near the user’s current location. We need to build the necessary elements into a URL query string to execute the Place Search. This is the basic format of the URL:

https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters

The output can be JSON or XML; we will use JSON for this app. The URL must be appended with various required parameters, including your API key, the user’s location as longitude and latitude values, the radius to search within, and a boolean flag indicating whether the location was derived from a location sensor such as a GPS. There are various optional parameters you can add to your query; see the Place Search documentation for an overview. For the purpose of this app, we will add the optional types parameter to search for places of particular types.

This is an indicator of what the final query will look like:

https://maps.googleapis.com/maps/api/place/nearbysearch/json?
	location=55.864237,-4.251805999999988&radius=1000&sensor=true&types=food|bar|store|museum|art_gallery&key=your_api_key

We will build the longitude and latitude values dynamically (the hard-coded example values above are just for demonstration). The radius value is in meters, so feel free to alter it to suit the purpose of your app. You can also alter the place types if you wish; see the Supported Place Types overview for the available options. Make sure you separate the place types using the pipe “|” character. You should of course alter the key parameter value to reflect your own API key.

In your Activity class updatePlaces helper method, after the existing code in which we retrieved the user location and animated the map camera to it, create a string for the Place Search URL:

String placesSearchStr = "https://maps.googleapis.com/maps/api/place/nearbysearch/" +"json?location="+lat+","+lng+"&radius=1000&sensor=true" +"&types=food|bar|store|museum|art_gallery"+"&key=your_key_here";

Include the API key you copied from the Google APIs Console for browser apps. Note that we pass the latitude and longitude values we previously retrieved from the user’s location.


3. Create an AsyncTask to Fetch Place Data in the Background

Step 1

You will need to add the following import statements to your Activity class for the code we’ll use next:

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.os.AsyncTask;

Since we will fetch data over the Web, we will use an AsyncTask to carry out this process off the UI thread. This will allow us to handle the results within the UI thread, adding markers to the visible map when the app receives the Place Search data.

In your Activity class, create the AsyncTask class outline as follows:

private class GetPlaces extends AsyncTask<String, Void, String> {
//fetch and parse place data
}

The three types indicated represent the types of parameters, progress units, and the result of the background operation. We will pass the Place Search URL as a string, so this is the parameter. We won’t indicate progress for this app, so the second type is void. The result of the background operation in this case will be a JSON string representing the places, so the third type is a text string.

Step 2

When you use an AsyncTask, you indicate the processing you want to occur in the background within the doInBackground method. Once that has executed and retrieved a result, the onPostExecute method will then execute. For this app, we will fetch the Place Search results in doInBackground, then parse the results in onPostExecute, creating markers to display on the map at the same time.

Inside your AsyncTask class, add the doInBackground method:

@Override
protected String doInBackground(String... placesURL) {
	//fetch places
}

The parameter type is string, as indicated by the class outline above. Let’s use a String Builder to build the returned JSON text string inside this new method:

StringBuilder placesBuilder = new StringBuilder();

Although we will only pass a single Place Search query URL string, the doInBackground method expects to potentially receive an array of parameters, so add a loop to process this:

//process search parameter string(s)
for (String placeSearchURL : placesURL) {
//execute search
}

Inside the loop, create an HTTP Client object to execute the query URL:

HttpClient placesClient = new DefaultHttpClient();

As with any input/output process, we need to take care of potential errors, so add try and catch blocks next, still inside the loop:

try {
	//try to fetch the data
}
catch(Exception e){
	e.printStackTrace();
}

Inside the try block, create an HTTP Get object, passing the URL string:

HttpGet placesGet = new HttpGet(placeSearchURL);

Use the HTTP Client to execute this, retrieving an HTTP Response:

HttpResponse placesResponse = placesClient.execute(placesGet);

Let’s check that we have a positive response before we attempt to carry out any further processing. Retrieve the status line:

StatusLine placeSearchStatus = placesResponse.getStatusLine();

We can only continue if we have a 200 “OK” status code indicating that the request was successful, so add a conditional statement:

if (placeSearchStatus.getStatusCode() == 200) {
//we have an OK response
}

Inside the if block, retrieve the HTTP Entity from the response object:

HttpEntity placesEntity = placesResponse.getEntity();

Now we can start to retrieve the actual content of the response, which should be the JSON string. Start by creating an Input Stream:

InputStream placesContent = placesEntity.getContent();

Now create a reader for this stream:

InputStreamReader placesInput = new InputStreamReader(placesContent);

Let’s carry out our input stream processing using a Buffered Reader:

BufferedReader placesReader = new BufferedReader(placesInput);

Now we can use a loop to read the input one line at a time, appending each one to the String Builder as we go along:

String lineIn;
while ((lineIn = placesReader.readLine()) != null) {
	placesBuilder.append(lineIn);
}

This loop will continue to execute as long as there is still data to read in.

Finally, we can return the string that is handled by the String Builder. At the end of the doInBackground method after the for loop, return the JSON data:

return placesBuilder.toString();

Conclusion

The third part of this tutorial series is now complete. We set the app up to utilize Google Places API and created an inner class to fetch the place data in the background off the app’s main UI thread. We executed the Place Search query and retrieved the resulting JSON. When you run your app, it will not behave any differently than it did after the previous part of this series. This is because we have not yet completed or instantiated the AsyncTask class. We will do that in the final part, after we add the onPostExecute method to parse the JSON Place Search result and translate it into map markers. We will also extend the app to update the displayed markers as the user’s location changes.

Build an AudioPlayer with PhoneGap: Application Setup

$
0
0

You don’t need to use platform-specific technologies to develop powerful and engaging apps. This series will teach you how to create a hybrid app – specifically an audio player – using web technologies and frameworks like jQuery Mobile and Cordova (also known as PhoneGap). The first framework will be used to create the interface, while several of the APIs of the second, like Media and File, will be used to develop the business logic.


The Project Features

The app we’ll start developing in a few moments will be a basic audio player called Audero Audio Player. It will have minimal utility requirements. This app will search through the file system and collect any audio files, which the user can then listen to. The app will collect files with any of the following extensions: .mp3, .wav, .m4a. Keep in mind that the formats supported depend heavily upon the platform from which you run the application, so choose formats that work in many platforms.

Audero Audio Player also enables the user to update the list at any time to include other files that they may have downloaded after running the operation for the first time. The user can also remove any unwanted audio from the list by clicking the icon on the right side of the audio’s name. The list is ordered alphabetically with letter dividers to organize and group list items. The list has a search box to filter the files as well.

You can see the described page by looking at the screenshot below:

Audio files list

Once the user chooses a file, the player’s controller will then be shown. The controller shows the file name, location path, playing time and duration. It includes buttons for play/pause, stop, and a slider to move the audio back and forth.

You can see the look and feel of the player in the photo below:

The Application player interface

Technologies Involved

The following list will give you a detailed understanding of what we’ll be utilizing:

  • HTML: It will be used to create the markup of the pages. When appropriate, I will use some of the new HTML5 tags.
  • CSS: Most of the CSS enhancements will be done by jQuery Mobile, so I will only be using a few lines of custom CSS code. It’s mainly used for the player itself.
  • JavaScript: This is the language I will use to develop the business logic.
  • jQuery: I will use jQuery mainly to select elements and attach event handlers.
  • jQuery Mobile: It will be used to enhance the application interface and optimize it for mobile devices. Some of the widgets used include Listview, Dialog, and Slider. I will also put some buttons inside the header and footer of the pages to build a simple toolbar. The version used is 1.3.0.
  • Cordova (PhoneGap): Cordova will be used to wrap the files so you can compile them as if you built a native app. To build Audero Audio Player we’ll take advantage of several of the APIs offered by the framework, such as Storage, InAppBrowser, Notification, File and Media. The version used is 2.3.0.

In addition to the above list, I’ll use also these Cordova APIs:

  • File API: An API to read, write and navigate file system hierarchies, it provides a set of objects to work with files and directories. For example, we’ll use the  DirectoryReader object to navigate the file system, using its method readEntries(), and search the sounds. We’ll also use the LocalFileSystem object to obtain the root file systems of the device using its requestFileSystem() method. You can find more information in the File API official doc.
  • InAppBrowser API: It’s a web-browser shown in your app when you use the window.open call. It isn’t much more than that but, as you’ll discover later in this article, managing external links properly with this API is of vital importance. If you want to read more about the methods offered by this API, you can take a look at the InAppBrowser official doc.
  • Storage API: This API provides access to the device’s storage options. We’ll specifically be using the Cordova implementation of the Web Storage API and its methods to store and load the list of the audio files. In case you need an in-depth explanation, refer to the Local Storage API doc.
  • Media API: The Media object provides the ability to record and play back audio files on a device. This is a key API for our player and we’ll use almost all of its methods. For example, we’ll use the play() method to play the sounds, the stop() method to stop playing the audio, and the getDuration() method to retrieve the duration of the current processed file. More on this topic can be found in the Media API docs.
  • Notification API: It allows you to notify the user with visual and audible notifications. We’ll use its alert() method to notify the user in case of errors. For a more in-depth overview on this API, read the Notification API docs.

Before continuing our tutorial, I want to emphasize that Cordova uses a different JavaScript file for every operating system. Therefore, if you want to compile the application on your own, you will need to use a specific IDE-SDK bundle. For example, if you want to create the Android version of our player (thus creating the relative .apk), you should use Eclipse and the Android SDK. Using different bundles for each platform can be problematic, so Audero Audio Player will be developed with the assumption that the compilation will be done using the Adobe cloud service, called Adobe PhoneGap Build. This service will include the correct Cordova library for each platform at compilation time.


The Project’s Structure

The structure of the project will be quite straightforward. To start building the player, create a folder and rename it “audero-audio-player”. Now, create three folders inside this folder with the following names: css, images, and js. Once you’ve done this, download the jQuery (I’ll use version 1.8.3) and jQuery Mobile (version 1.3.0) libraries. Move the jQuery and the JavaScript file of jQuery Mobile inside the “js” folder, and then put the jQuery Mobile CSS file into the “css” folder. Finally, place the jQuery Mobile bundled images into the “images” folder. Because we’ve slightly changed the native structure of jQuery Mobile, we need to adjust the paths pointing to the images inside its CSS file. Specifically, we need to replace the “images/” part with this string “../images/”. If you’re using an automated feature of your chosen editor, check that it replaced the string five times.

Now that you have set up the framework, you can proceed to the next step. In the root of the project folder, we’ll create the HTML files, and place the default application’s icon, the Adobe PhoneGap Build configuration file, and the default splash screen. In this tutorial we’ll also create the following files:

  • index.html: This is the entry point of the application and where we will put the links to the libraries used, inside the <head> section.
  • files-list.html: This is the page where you’ll have a list of the sounds stored on your device, grouped as I explained back in the introduction of this article. As you’ll see later, you’ll also have a little toolbar that allows you to go back to the homepage (the  button in the upper-left corner) and to refresh the sounds list (the button in the upper-right corner).
  • player.html: This simply contains the markup of the audio player.
  • aurelio.html: This is the app credits page; it contains information about the app’s author.
  • style.css: This file contains the few custom lines of CSS used by the app, which are mainly used to style the player buttons.
  • jquery.mobile.config.js: This will contain our custom configuration for the jQuery Mobile framework.
  • appFile.js: This file contains the class called AppFile that we’ll use to manage the sounds. It acts as an interface for the files table of our database. This class also allows us to retrieve and edit audio information in addition to allowing us to add and delete files from the stored list.
  • player.js: The file including the class, called Player, that manages the player and resets the player interface.
  • utility.js: This is just a couple of utility functions used by our project, which I’ll explain further in the next article of the series.
  • application.js: This is like a glue that merges all of the pieces of our project together. This class is responsible for initializing the behavior of the pages described before attaching events using the classes seen thus far.
  • config.xml: This XML file will have the metadata of the application and will be used by the Adobe cloud service to store settings like the app version number and its description.

This is a lot of stuff to create, but I promise you’ll have a good time with this project. As a final note, in addition to the listed files, Audero Audio Player will include some additional images – like the play/pause and stop buttons – that will be placed in the “images” folder.


The Homepage

jQuery Mobile will load all of the pages using AJAX, so unless you build explicit custom configurations, all the files needed by Audero Audio Player, such as the CSS and JavaScript files, must be loaded by the entry point, index.html. This page, apart from being the application’s title and description, has a button to access the sounds list. It also includes a footer with a button to access author info.

The full source of the homepage is shown below:

<!DOCTYPE html><html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1"><title>Audero Audio Player</title><link rel="stylesheet" href="css/jquery.mobile-1.3.0.min.css" type="text/css" media="all" /><link rel="stylesheet" href="css/style.css" type="text/css" media="all" /><script src="js/jquery-1.8.3.min.js"></script><script src="js/jquery.mobile.config.js"></script><script src="js/jquery.mobile-1.3.0.min.js"></script><script src="cordova.js"></script><script src="js/appFile.js"></script><script src="js/application.js"></script><script src="js/utility.js"></script><script src="js/player.js"></script><script>
         $(document).on('pagebeforecreate orientationchange', Application.updateIcons);
         $(document).one('deviceready', Application.initApplication);</script></head><body><div id="home-page" data-role="page"><header data-role="header"><h1>Audero Audio Player</h1></header><div data-role="content"><p id="app-description">
               Audero Audio Player is a basic audio player that searches the file system of a device, collects the audio files found and then allows the user to listen them. This app also
               enables the user to update the list at any time to include files that might have been downloaded after
               running the operation for the first time. You can also remove any unwanted audio - for example, sounds used as notifications in other applications - by clicking an icon on the right side of the song name. The sound list is ordered alphabetically with letter
               dividers to organize and group list items, and it has a search box to filter files.
               You can see the described page by looking at the screenshot below.</p><a href="files-list.html" data-role="button">File List</a></div><footer data-role="footer"><h3 id="copyright-title">Created by Aurelio De Rosa</h3><a id="credits-button" href="aurelio.html" data-icon="info" data-iconpos="notext" class="ui-btn-right">Credits</a></footer></div></body></html>

As you’ll see in the next section, the buttons inside the <header> and the <footer> tags, use the attribute data-iconpos="notext". This attribute is very important for achieving the effect I want: a responsive layout. The cited attribute tells jQuery Mobile to hide the link text, which is very useful in saving space for smaller screens. In the next section, I will demonstrate how to attach a handler to the pagebeforecreate and the orientationchange events so that we can test for the screen size. If a large enough screen is found, the attribute will be removed and the text will be shown. In this case, “large enough” means the screen’s width is larger than 480 pixels.

Observant readers may also have noticed the two JavaScript lines just above the <body>. Disregard this for the moment; we’ll delve into their meaning later.

This screenshot will give you an example of what the page will look like:

Audero Audio Player homepage

The List Page

This small page, called files-list.html, has a couple of interesting widgets like the autodivider and the searchbox. Those widgets are created for you by jQuery Mobile simply by adding a couple of attributes to the <ul id="files-list"> tag: data-autodividers="true" and data-filter="true". You have already seen how this page appears in a previous section, and you should have noted that each list item has a big “X” on the right side. This is the button that allows the user to delete the file from the list and the database. These two items are a standard effect achieved using jQuery Mobile. You can read more about them by looking at the official documentation page.

The last piece of code worth discussing is the <div id="waiting-popup"> element. This is shown when the code digs into the operating system’s folders to search for sounds. Searching audio is quite a heavy operation and even my device (a Galaxy S3) suffered, blocking the entire software. A better solution for this problem would be to use multi-threading. This way, while the app searches for files, the user can still play around. Unfortunately, as you may know, JavaScript doesn’t have multi-threading capabilities, and there is nothing you can do to change that. Another solution might be showing the loader during this process, but this approach also failed; it completely froze. For this reason, I chose to block user interaction completely with a dialogue that can only be closed with programming.

Now that you are fully aware of this page’s key points, I can list the full source:

<!DOCTYPE html><html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1"></head><body><div id="files-list-page" data-role="page"><header data-role="header"><a href="#" data-icon="back" data-iconpos="notext" data-rel="back" title="Go back">Back</a><h1>Media List</h1><a href="#" id="update-button" data-icon="refresh" data-iconpos="notext">Update List</a></header><div data-role="content"><ul id="files-list" data-role="listview" data-autodividers="true" data-filter="true" data-split-icon="delete"></ul></div><footer data-role="footer"><h3 id="copyright-title">Created by Aurelio De Rosa</h3><a id="credits-button" href="aurelio.html" data-icon="info" data-iconpos="notext" class="ui-btn-right">Credits</a></footer><div id="waiting-popup" data-role="popup" data-position-to="window" data-dismissible="false"><header data-role="header"><h1>Updating</h1></header><div data-role="content"><p>Searching audio files, please wait...</p></div></div></div></body></html>

The Player Page

The player page (player.html) doesn’t have many exciting facts to point out. It’s a set of labels and links with events attached to manage the sound. The only element worth mentioning is the slider used to move the audio forward and backward, created using the jQuery Mobile slider widget.

You can see the code of the page below:

<!DOCTYPE html><html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1"></head><body><div id="player-page" data-role="page"><header data-role="header"><a href="#" data-icon="back" data-iconpos="notext" data-rel="back" title="Go back">Back</a><h1>Player</h1></header><div data-role="content"><div id="player"><div id="media-info"><p>You're listening to: <span id="media-name"></span></p><p>Located at: <span id="media-path"></span></p><div>
                     Played: <label id="media-played">-</label> of <label id="media-duration">-</label><input type="range" name="time-slider" id="time-slider" value="0" min="0" max="100" data-highlight="true" /></div></div><a href="#" id="player-play" title="Play / Pause"></a><a href="#" id="player-stop" title="Stop"></a></div></div><footer data-role="footer"><h3 id="copyright-title">Created by Aurelio De Rosa</h3><a id="credits-button" href="aurelio.html" data-icon="info" data-iconpos="notext" class="ui-btn-right">Credits</a></footer></div></body></html>

The Credits Page

The page aurelio.html is surely the least important one, and it’s also very simple. Nonetheless I want to mention two things. The first is the use of two new HTML5 tags, <figure> and <article>. The <figure> element represents a unit of content, with an optional caption that is self-contained. The caption described is provided using the element <figcaption>. The <article>  element represents a component of a page that consists of a self-contained composition in a document, page, application, or site.

The second is the use of the attribute target="_blank" which is applied to all of the contact listing links on the page. This attribute is very common. However, in our player, we’ll use it to attach a handler to all of the external links, as you’ll learn in the next part of this series.

The whole code of aurelio.html is shown below:

<!DOCTYPE html><html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1"><title>Aurelio De Rosa</title></head><body><div id="aurelio-page" data-role="page"><header data-role="header"><a href="#" data-icon="back" data-iconpos="notext" data-rel="back" title="Go back">Back</a><h1>Aurelio De Rosa</h1></header><div id="content" data-role="content"><div class="person"><figure class="photo"><img src="images/aurelio-de-rosa.png" alt="Photo of Aurelio De Rosa" /><figcaption>Aurelio De Rosa</figcaption></figure><article><p>
                     I am an Italian web and app developer with a Bachelor's degree in Computer Sciences and more than
                     5 years' programming experience using HTML5, CSS3, JavaScript and PHP. I mainly use the
                     LAMP stack and frameworks like jQuery, jQuery Mobile, Cordova (PhoneGap) and Zend Framework.
                     My interests include web security, web accessibility, SEO and WordPress.</p><p>
                     I'm currently self-employed, working with the cited technologies, and I'm also a contributor to the
                     SitePoint and Tuts+ network where I write articles pertaining to the topics I work with.</p></article><article><h2>Contacts</h2><ul data-role="listview" data-inset="true"><li><a href="http://www.audero.it" target="_blank"><img src="images/website-icon.png" alt="Website icon" />
                           Visit my website: www.audero.it</a></li><li><a href="mailto:aurelioderosa@gmail.com" target="_blank"><img src="images/email-icon.png" alt="Email icon" />
                           Drop me an e-mail: aurelioderosa@gmail.com</a></li><li><a href="mailto:a.derosa@audero.it" target="_blank"><img src="images/email-icon.png" alt="Email icon" />
                           Drop me an e-mail (alternative address): a.derosa@audero.it</a></li><li><a href="https://twitter.com/AurelioDeRosa" target="_blank"><img src="images/twitter-icon.png" alt="Twitter icon" />
                           Follow me on Twitter (@AurelioDeRosa)</a></li><li><a href="http://it.linkedin.com/in/aurelioderosa/en" target="_blank"><img src="images/linkedin-icon.png" alt="LinkedIn icon" />
                           View my profile on LinkedIn</a></li><li><a href="https://bitbucket.org/AurelioDeRosa" target="_blank"><img src="images/bitbucket-icon.png" alt="BitBucket icon" />
                           Visit my repository on BitBucket (AurelioDeRosa)</a></li></ul></article></div></div></div></body></html>

Next Part

In the second installment of this series, we’ll take an in-depth look at the app’s business logic by looking at the JavaScript files that power our player.

Corona SDK: Build a Frogger Inspired Game – Adding Interaction

$
0
0

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


Where We Left Off. . .

Please be sure to check part 1 of the series, Interface Creation, to fully understand and prepare for this tutorial.


1. Start Button Listeners

This function adds the necessary listeners to the TitleView buttons.

function startButtonListeners(action)
	if(action == 'add') then
		playBtn:addEventListener('tap', showGameView)
		creditsBtn:addEventListener('tap', showCredits)
	else
		playBtn:removeEventListener('tap', showGameView)
		creditsBtn:removeEventListener('tap', showCredits)
	end
end

2. Show Credits

The credits screen is shown when the user taps the About button; a tap listener is added to the credits view to remove it.

function showCredits:tap(e)
	playBtn.isVisible = false
	creditsBtn.isVisible = false
	creditsView = display.newImage('credits.png', 0, display.contentHeight)
	lastY = title.y
	transition.to(title, {time = 300, y = -20})
	transition.to(creditsView, {time = 300, y = 265, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})
end

3. Hide Credits

When the credits screen is tapped, it’ll be tweened out of the stage and removed.

function hideCredits:tap(e)
	transition.to(creditsView, {time = 300, y = display.contentHeight + 25, onComplete = function() creditsBtn.isVisible = true playBtn.isVisible = true creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
	transition.to(title, {time = 300, y = lastY});
end

4. Show Game View

When the Play button is tapped, the title view is tweened and removed, revealing the game view. There are many parts involved in this view so we’ll split them in the next steps.

function showGameView:tap(e)
	transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})

5. Game Background

This code places the background image in the stage.

-- Game Background
gameBg = display.newImage('gameBg.png')

6. Add Cars

The next lines of code handles the cars’ placement on the stage. Each line represents a car, its position, the graphic, the rotation, the direction and the name. We’ll create that function later in the tutorial.

-- Cars Part 1
obstacles = display.newGroup()
addObstacle(184, 353, 'car2', false, 'l', 'car')
addObstacle(184, 326, 'car', true, 'r', 'car')
addObstacle(124, 293, 'car2', false, 'l', 'car')
addObstacle(94, 386, 'car', true, 'r', 'car')
addObstacle(64, 326, 'car', true, 'r', 'car')
addObstacle(94, 293, 'car2', false, 'l', 'car')
addObstacle(34, 386, 'car', true, 'r', 'car')
addObstacle(4, 353, 'car2', false, 'l', 'car')
addObstacle(4, 293, 'car2', false, 'l', 'car')
addObstacle(274, 386, 'car', true, 'r', 'car')
addObstacle(234, 353, 'car2', false, 'l', 'car')
addObstacle(274, 326, 'car', true, 'r', 'car')
-- Cars Part 2
addObstacle(94, 226, 'car', true, 'r', 'car')
addObstacle(94, 197, 'car2', false, 'l', 'car')
addObstacle(94, 167, 'car', true, 'r', 'car')
addObstacle(94, 137, 'car2', false, 'l', 'car')
addObstacle(94, 107, 'car', true, 'r', 'car')
addObstacle(274, 197, 'car2', false, 'l', 'car')
addObstacle(94, 107, 'car', true, 'r', 'car')
addObstacle(34, 226, 'car', true, 'r', 'car')
addObstacle(34, 197, 'car2', false, 'l', 'car')
addObstacle(184, 167, 'car', true, 'r', 'car')
addObstacle(184, 137, 'car2', false, 'l', 'car')
addObstacle(4, 107, 'car', true, 'r', 'car')
addObstacle(274, 197, 'car2', false, 'l', 'car')
addObstacle(274, 107, 'car', true, 'r', 'car')

7. Add Pad

A pad is added to the stage in order to control the frog.

-- Pad
up = display.newImage('up.png', 33.5, 369.5)
left = display.newImage('left.png', 0, 402.5)
down = display.newImage('down.png', 33.5, 436.5)
right = display.newImage('right.png', 66.5, 402.5)
up.name = 'up'
down.name = 'down'
left.name = 'left'
right.name = 'right'

8. Frog

Next we add our player to the stage.

-- Frog
frog = display.newImage('frog.png', 148.5, 417.5)

9. Goals

Rectangles are drawn on the stage to represent the goals. These will be added later to the physics engine and hidden.

-- Goals
local g1 = display.newRect(68, 70, 15, 15)
g1.name = 'goal'
local g2 = display.newRect(153, 70, 15, 15)
g2.name = 'goal'
local g3 = display.newRect(238, 70, 15, 15)
g3.name = 'goal'

10. Physics

We will need physics to handle the collisions. This part of the code takes care of that.

	-- Physics
	physics.addBody(frog)
	frog.isSensor = true
	physics.addBody(g1, 'static')
	g1.isSensor = true
	g1.isVisible = false
	physics.addBody(g2, 'static')
	g2.isSensor = true
	g2.isVisible = false
	physics.addBody(g3, 'static')
	g3.isSensor = true
	g3.isVisible = false
	gameListeners('add')
end

11. Game Listeners

This function adds the necessary listeners to start the game logic.

function gameListeners(action)
	if(action == 'add') then
		Runtime:addEventListener('enterFrame', update)
		up:addEventListener('tap', movePlayer)
		left:addEventListener('tap', movePlayer)
		down:addEventListener('tap', movePlayer)
		right:addEventListener('tap', movePlayer)
		frog:addEventListener('collision', onCollision)
	else
		Runtime:removeEventListener('enterFrame', update)
		up:removeEventListener('tap', movePlayer)
		left:removeEventListener('tap', movePlayer)
		down:removeEventListener('tap', movePlayer)
		right:removeEventListener('tap', movePlayer)
		frog:removeEventListener('collision', onCollision)
	end
end

12. Add Obstacles Function

The following function creates an obstacle based on its parameters. It creates a physics object of the graphic and adds it to a group.

function addObstacle(X, Y, graphic, inverted, dir, name)
	local c = display.newImage(graphic .. '.png', X, Y)
	c.dir = dir
	c.name = name
	--Rotate graphic if going right
	if(inverted) then
		c.xScale = -1
	end
	-- Physics
	physics.addBody(c, 'static')
	c.isSensor = true
	obstacles:insert(c)
end

13. Move Player

Pressing the buttons in the pad will call this function. It will move the player thirty-one pixels in the pressed direction.

function movePlayer(e)
	audio.play(moveSnd)
	if(e.target.name == 'up') then
		frog.y = frog.y - 31
	elseif(e.target.name == 'left') then
		frog.x = frog.x - 31
	elseif(e.target.name == 'down') then
		frog.y = frog.y + 31
	elseif(e.target.name == 'right') then
		frog.x = frog.x + 31
	end
end

14. Update Function

The update function that runs every frame. It is in charge of moving the cars across the stage and restoring their position.

function update()
	-- Move Obstacles
	for i = 1, obstacles.numChildren do
		if(obstacles[i].dir == 'l') then
			obstacles[i].x = obstacles[i].x - 1
		else
			obstacles[i].x = obstacles[i].x + 1
		end
		-- Respawn obstacle when out of stage
		--Right
		if(obstacles[i].dir == 'r' and obstacles[i].x > display.contentWidth + (obstacles[i].width * 0.5)) then
			obstacles[i].x = -(obstacles[i].width * 0.5)
		end
		-- Respawn obstacle when out of stage
		--Left
		if(obstacles[i].dir == 'l' and obstacles[i].x < -(obstacles[i].width * 0.5)) then
			obstacles[i].x = display.contentWidth + (obstacles[i].width * 0.5)
		end
	end
end

15. Collisions

Finally, we check for collisions. If the frog is hit by a car, the graphics are removed and a You Lose message is shown. When the three frogs have reached their goal, a You Win message will appear, ending the game.

function onCollision(e)
	if(e.other.name == 'car') then
		display.remove(e.target)
		audio.play(loseSnd)
		alert('lose')
	elseif(e.other.name == 'goal') then
		display.remove(e.other)
		local f = display.newImage('frog.png', e.other.x - 12, e.other.y - 18)
		audio.play(goalSnd)
		timer.performWithDelay(10, function() frog.x = 160 frog.y = 426 end, 1)
		counter = counter + 1
	end
	--check if goals complete
	if(counter == 3) then
		alert()
	end
end

16. Alert

The alert function creates an alert view, animates it, and ends the game.

function alert(action)
	gameListeners('rmv')
	display.remove(obstacles)
	if(action == 'lose') then
		alertView = display.newImage('lose.png', 127.5, 345)
	else
		alertView = display.newImage('win.png', 132, 345)
	end
	transition.from(alertView, {time = 200, alpha = 0.1})
end

17. Call Main Function

In order to start the game, the Main function needs to be called. With the above code in place, we’ll do that here:

Main()

18. Loading Screen

Loading Screen

The Default.png file is an image that will be displayed while the iOS loads the basic data to show the Main Screen. Add this image to your project source folder; it will be automatically added by the Corona compliler.


19. Icon

Icon

Using the graphics you created before, you can now create a nice and good looking icon. The icon size for the non-retina iPhone icon is 57x57px, but the retina version is 114x114px, and the iTunes store requires a 512x512px version. I suggest creating the 512x512px version first and then scaling down for the other sizes. It doesn’t need to have rounded corners or transparent glare; iTunes and the iPhone will do that for you.


20. Testing in Simulator

Testing

It’s time to do the final test. Open the Corona Simulator, browse to your project folder, and then click open. If everything works as expected, you are ready for the final step!


21. Build

Build

In the Corona Simulator, go to File > Build and select your target device. Fill the required data and click Build. Wait a few seconds and your app will be ready for device testing and/or submission for distribution!


Conclusion

In this series, we’ve learned about moving an object using an on-screen pad and collision detection using the physics engine, skills that can be really useful in a wide number of games. Experiment with the final result and try to make your own custom version of the game! I hope you liked this tutorial series and found it helpful. Thank you for reading!

Creating a Game with Bonjour – Game Logic

$
0
0

In the previous articles, we predominantly focused on the network aspect of the game. In this final installment, it is time to zoom in on the game itself. We will implement the game and leverage the foundation we laid in the previous articles to create a multiplayer game.


Introduction

In this article, we will discuss two topics, (1) creating the game and (2) leveraging the foundation that we created in the previous articles. In the previous article, a bug has found its way into our project. I must admit that it took me quite some time to discover this nasty, little creature. Don’t worry, though. We will squash this bug as we go and I will show you exactly where it is causing havoc. Even though I could have updated the previous article to get rid of the bug, I prefer to show you how to find and fix the bug as it will help understand how the CocoaAsyncSocket library works. We have quite a bit of work ahead of us so let’s get started.


1. Updating the User Interface

Let me start this article by briefly talking about the game, Four in a Row. If you haven’t heard of Four in a Row, then I suggest you pay Wikipedia a visit. By the way, Four in a Row is known by many names, such as as Connect Four, Find Four, and Plot Four. The concept is simple. We have a board or grid with seven columns each containing six cells. The user can tap a column to add a disc to that column. Each time a player adds a disc to a column, we invoke a method to check if the player has won the game, that is, four discs in a row. Rows can be horizontal, vertical, or diagonal.

This implies that we need to keep track of quite a few variables. To keep track of the state of the game, we create a data structure, an array of arrays, mirroring the board or grid of cells. Each array in the array represents a column. Whenever a player adds a disc to a column, we update the data structure that backs the game and check whether the player has won the game.

I am not an expert in game development and the approach we use in this project is not the only solution to implement Four in a Row. It probably isn’t the most efficient implementation either. However, by using well known Objective-C patterns and sticking to Foundation classes, most of you should be able to keep pace without much difficulty.

While exploring Four in a Row, I stumbled upon a Stack Overflow answer that outlines an algorithm for Four in a Row using bitboards. This is a very efficient and fast solution so if you are serious about board games, such as tic-tac-toe or chess, then I recommend exploring bitboards in more detail.

As I said, we will be using an array of arrays as the data structure of the game. The board itself will be a simple view with 42 subviews or board cells. Each subview or board cell corresponds to a position in the data structure. Because we need an easy way to keep a reference to each board cell, we manage a second data structure, another array of arrays, to store a reference to each board cell. This makes it easy to update the board view, but it also has some other benefits that will become evident a bit later in this tutorial.

Step 1: Adding the Board View

Let’s start by creating the board view. Open MTViewController.xib, add a UIView instance to the view controller’s view and set its dimensions to 280 points by 240 points (figure 1). Modify the constraints of the view in such a way that the board view has a fixed width and height. The board view should also be horizontally and vertically centered in the view controller’s view. Autolayout makes this a breeze.

Creating a Game with Bonjour - Game Logic - Adding the Board View
Figure 1: Adding the Board View

Create an outlet in MTViewController.h for the board view and name it boardView. In Interface Builder, connect the outlet to the board view. We will add the board view’s subviews programmatically.

#import <UIKit/UIKit.h>
@interface MTViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIView *boardView;
@property (weak, nonatomic) IBOutlet UIButton *hostButton;
@property (weak, nonatomic) IBOutlet UIButton *joinButton;
@property (weak, nonatomic) IBOutlet UIButton *disconnectButton;
@end

Step 2: Adding a Replay Button

When the game ends, we want to give the player the opportunity to start a new game. Add a new button to the view controller’s view and give it a title of Replay (figure 2). Create an outlet, replayButton, for the button in MTViewController.h and an action named replay: in MTViewController.m. Connect the outlet and action to the replay button in Interface Builder (figure 2).

#import <UIKit/UIKit.h>
@interface MTViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIView *boardView;
@property (weak, nonatomic) IBOutlet UIButton *hostButton;
@property (weak, nonatomic) IBOutlet UIButton *joinButton;
@property (weak, nonatomic) IBOutlet UIButton *replayButton;
@property (weak, nonatomic) IBOutlet UIButton *disconnectButton;
@end
- (IBAction)replay:(id)sender {
}
Creating a Game with Bonjour - Game Logic - Add a Replay Button
Figure 2: Add a Replay Button

Step 3: Adding a State Label

The player of the game should be informed about the state of the game. Whose turn is it? Who has won the game? We add a label to the view controller’s view and update it whenever the game state changes. Revisit MTViewController.xib and add a label (UILabel) to the view controller’s view (figure 3). Create an outlet for the label in the view controller’s header file, name it gameStateLabel, and connect it to the label in Interface Builder (figure 3).

Creating a Game with Bonjour - Game Logic - Add a State Label
Figure 3: Add a State Label
#import <UIKit/UIKit.h>
@interface MTViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIView *boardView;
@property (weak, nonatomic) IBOutlet UIButton *hostButton;
@property (weak, nonatomic) IBOutlet UIButton *joinButton;
@property (weak, nonatomic) IBOutlet UIButton *replayButton;
@property (weak, nonatomic) IBOutlet UIButton *disconnectButton;
@property (weak, nonatomic) IBOutlet UILabel *gameStateLabel;
@end

2. Laying Out the Board

Step 1: Creating the Board Cell Class

As I mentioned earlier, the board view contains 42 subviews or board cells. We will create a UIView subclass to make each board cell a bit smarter and easier to use. Create a UIView subclass and name it MTBoardCell (figure 4). The MTBoardCell class has one property, cellType of type MTBoardCellType, which is declared at the top of the header file.

Creating a Game with Bonjour - Game Logic - Creating the Board Cell Class
Figure 4: Creating the Board Cell Class
#import <UIKit/UIKit.h>
typedef enum {
    MTBoardCellTypeEmpty = -1,
    MTBoardCellTypeMine,
    MTBoardCellTypeYours
} MTBoardCellType;
@interface MTBoardCell : UIView
@property (assign, nonatomic) MTBoardCellType cellType;
@end

In the designated initializer, we set cellType to MTBoardCellTypeEmpty to mark the board cell as empty. In the implementation file of the class, we also override the setter of cellType. In setCellType:, we update the view by invoking updateView, a helper method in which we update the view’s background color.

#import "MTBoardCell.h"
@implementation MTBoardCell
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Cell Type
        self.cellType = MTBoardCellTypeEmpty;
    }
    return self;
}
#pragma mark -
#pragma mark Setters & Getters
- (void)setCellType:(MTBoardCellType)cellType {
    if (_cellType != cellType) {
        _cellType = cellType;
        // Update View
        [self updateView];
    }
}
#pragma mark -
#pragma mark Helper Methods
- (void)updateView {
    // Background Color
    self.backgroundColor = (self.cellType == MTBoardCellTypeMine) ? [UIColor yellowColor] : (self.cellType == MTBoardCellTypeYours) ? [UIColor redColor] : [UIColor whiteColor];
}
@end

Step 2: Setting Up a Game

To set up a new game, we invoke the main view controller’s resetGame method. We will invoke resetGame in various places in our project. One of those places is the view controller’s viewDidLoad method. Because I prefer to keep the viewDidLoad method concise, I generally move the view’s setup logic to a separate setupView helper method which is invoked in viewDidLoad. In setupView, we also hide all the view’s subviews with the exception of the host and join button.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
}
- (void)setupView {
    // Reset Game
    [self resetGame];
    // Configure Subviews
    [self.boardView setHidden:YES];
    [self.replayButton setHidden:YES];
    [self.disconnectButton setHidden:YES];
    [self.gameStateLabel setHidden:YES];
}

Before we can implement resetGame, we need to create the data structure that stores the game state and the data structure that stores the references to the board cells of the board view. Add a class extension at the top of MTViewController.h and create two properties, board (NSArray) and matrix (NSMutableArray). We also import the header file of MTBoardCell and define to constants, kMTMatrixWidth and kMTMatrixHeight, that store the dimensions of the board.

#import "MTViewController.h"
#import "MTBoardCell.h"
#import "MTGameController.h"
#import "MTHostGameViewController.h"
#import "MTJoinGameViewController.h"
#define kMTMatrixWidth 7
#define kMTMatrixHeight 6
@interface MTViewController () <MTGameControllerDelegate, MTHostGameViewControllerDelegate, MTJoinGameViewControllerDelegate>
@property (strong, nonatomic) MTGameController *gameController;
@property (strong, nonatomic) NSArray *board;
@property (strong, nonatomic) NSMutableArray *matrix;
@end

The implementation of resetGame isn’t rocket science as you can see below. Because resetGame will also be invoked when a player taps the replay button, the implementation starts with hiding the replay button. We calculate the size of a board cell, create a mutable array for each column of the board, and add six board cells to each column. This array of arrays is stored in the class’s board property as an immutable array. The class’s matrix property is very similar. It also stores an array of arrays. The main differences are that (1) the columns contain no objects when the game is reset and (2) each column is an instance of NSMutableArray.

- (void)resetGame {
    // Hide Replay Button
    [self.replayButton setHidden:YES];
    // Helpers
    CGSize size = self.boardView.frame.size;
    CGFloat cellWidth = floorf(size.width / kMTMatrixWidth);
    CGFloat cellHeight = floorf(size.height / kMTMatrixHeight);
    NSMutableArray *buffer = [[NSMutableArray alloc] initWithCapacity:kMTMatrixWidth];
    for (int i = 0; i < kMTMatrixWidth; i++) {
        NSMutableArray *column = [[NSMutableArray alloc] initWithCapacity:kMTMatrixHeight];
        for (int j = 0; j < kMTMatrixHeight; j++) {
            CGRect frame = CGRectMake(i * cellWidth, (size.height - ((j + 1) * cellHeight)), cellWidth, cellHeight);
            MTBoardCell *cell = [[MTBoardCell alloc] initWithFrame:frame];
            [cell setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
            [self.boardView addSubview:cell];
            [column addObject:cell];
        }
        [buffer addObject:column];
    }
    // Initialize Board
    self.board = [[NSArray alloc] initWithArray:buffer];
    // Initialize Matrix
    self.matrix = [[NSMutableArray alloc] initWithCapacity:kMTMatrixWidth];
    for (int i = 0; i < kMTMatrixWidth; i++) {
        NSMutableArray *column = [[NSMutableArray alloc] initWithCapacity:kMTMatrixHeight];
        [self.matrix addObject:column];
    }
}

3. Adding Interaction

Step 1: Adding a Gesture Recognizer

Adding interaction to the game is as simple as adding a tap gesture recognizer to the board view in the view controller’s setupView method. Each time a player taps the board view, the addDiscToColumn: message is sent to our MTViewController instance.

- (void)setupView {
    // Reset Game
    [self resetGame];
    // Configure Subviews
    [self.boardView setHidden:YES];
    [self.replayButton setHidden:YES];
    [self.disconnectButton setHidden:YES];
    [self.gameStateLabel setHidden:YES];
    // Add Tap Gesture Recognizer
    UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(addDiscToColumn:)];
    [self.boardView addGestureRecognizer:tgr];
}

Before we implement addDiscToColumn:, we need to take a detour and talk about the game state. The MTViewController class needs to keep track of the state of the game. By game state, I don’t refer to the data structures (board and matrix) that we created earlier. I simply mean a property that keeps tracks of whose turn it is and whether a player has won the game. To make things easier, it is a good idea to declare a custom type for the game state. Because we will use this custom type in various places in our project, it is best to declare it in a separate file, MTConstants.h, and add an import statement for MTConstants.h to the project’s precompiled header file.

Create a new NSObject subclass named MTConstants (figure 5), delete the implementation file (MTConstants.m), and clear the contents of MTConstants.h. In MTConstants.h, we define MTGameState as shown below.

Creating a Game with Bonjour - Game Logic - Creating MTConstants.h
Figure 5: Creating MTConstants.h
typedef enum {
    MTGameStateUnknown = -1,
    MTGameStateMyTurn,
    MTGameStateYourTurn,
    MTGameStateIWin,
    MTGameStateYouWin
} MTGameState;

Add an import statement for MTConstants.h to the project’s precompiled header file so that its contents are available throughout the project.

#import <Availability.h>
#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import "GCDAsyncSocket.h"
    #import "MTConstants.h"
#endif

In MTConstants.h, we declare the various states of the game. In a more complex game, this might not be the best strategy or you may need to add additional states. For this project, this approach will suffice. Because Four in a Row is a turn based game, most of the game is spent in the MTGameStateMyTurn and MTGameStateYourTurn states, that is, it is either your turn or your opponent’s turn to add a disc to the board. The last two states are used when the game has ended with one of the players as the winner of the game.

With MTGameState defined in MTConstants.h, it is time to declare the gameState property in the MTViewController class extension that we created earlier. As you might have guessed, the gameState property is of type MTGameState.

#import "MTViewController.h"
#import "MTBoardCell.h"
#import "MTGameController.h"
#import "MTHostGameViewController.h"
#import "MTJoinGameViewController.h"
#define kMTMatrixWidth 7
#define kMTMatrixHeight 6
@interface MTViewController () <MTGameControllerDelegate, MTHostGameViewControllerDelegate, MTJoinGameViewControllerDelegate>
@property (assign, nonatomic) MTGameState gameState;
@property (strong, nonatomic) MTGameController *gameController;
@property (strong, nonatomic) NSArray *board;
@property (strong, nonatomic) NSMutableArray *matrix;
@end

It is time to implement the addDiscToColumn: method. The implementation of addDiscToColumn: shown below is incomplete as you can see by the comments in its implementation. We will complete its implementation as we go. The main element to focus on at this point is the method’s flow. We start by checking if the game has already been won by one of the players. If it has, then there is no need to add any more discs to the board. The second check we make is whether the player can add a disc, that is, is it the player’s turn to add a disc to the board. If this isn’t the case, then we show an alert view informing the player that it’s not their turn.

The interesting part of addDiscToColumn: is what happens if the game hasn’t ended and the player is allowed to add a disc to the board. We calculate which column the player has tapped by invoking columnForPoint: and pass the location in the board view that the player has tapped. The column variable is then passed as an argument to addDiscToColumn:withType:. The second parameter of this method is the cell type, which is MTBoardCellTypeMine in this case.

- (void)addDiscToColumn:(UITapGestureRecognizer *)tgr {
    if (self.gameState >= MTGameStateIWin) {
        // Notify Players
    } else if (self.gameState != MTGameStateMyTurn) {
        NSString *message = NSLocalizedString(@"It's not your turn.", nil);
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Warning" message:message delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil];
        [alertView show];
    } else {
        NSInteger column = [self columnForPoint:[tgr locationInView:tgr.view]];
        [self addDiscToColumn:column withType:MTBoardCellTypeMine];
        // Update Game State
        // Send Packet
        // Notify Players if Someone Has Won the Game
    }
}

The columnForPoint: method is nothing more than a simple calculation to infer the column based on the coordinates of point.

- (NSInteger)columnForPoint:(CGPoint)point {
    return floorf(point.x / floorf(self.boardView.frame.size.width / kMTMatrixWidth));
}

In addDiscToColumn:withType:, we update the game state by updating the view controller’s matrix property. We then fetch a reference to the corresponding board cell, stored in the view controller’s board property, and set its cell type to cellType. Because we overrode the setCellType: method in MTBoardCell, the board cell’s background color will be updated automatically.

- (void)addDiscToColumn:(NSInteger)column withType:(MTBoardCellType)cellType {
    // Update Matrix
    NSMutableArray *columnArray = [self.matrix objectAtIndex:column];
    [columnArray addObject:@(cellType)];
    // Update Cells
    MTBoardCell *cell = [[self.board objectAtIndex:column] objectAtIndex:([columnArray count] - 1)];
    [cell setCellType:cellType];
}

Before testing the game, we need to amend the startGameWithSocket: and endGame methods. In these methods, we update the view controller’s view based on the state of the game. Run two instances of the application and test the game in its current state.

- (void)startGameWithSocket:(GCDAsyncSocket *)socket {
    // Initialize Game Controller
    self.gameController = [[MTGameController alloc] initWithSocket:socket];
    // Configure Game Controller
    [self.gameController setDelegate:self];
    // Hide/Show Buttons
    [self.boardView setHidden:NO];
    [self.hostButton setHidden:YES];
    [self.joinButton setHidden:YES];
    [self.disconnectButton setHidden:NO];
    [self.gameStateLabel setHidden:NO];
}
- (void)endGame {
    // Clean Up
    [self.gameController setDelegate:nil];
    [self setGameController:nil];
    // Hide/Show Buttons
    [self.boardView setHidden:YES];
    [self.hostButton setHidden:NO];
    [self.joinButton setHidden:NO];
    [self.disconnectButton setHidden:YES];
    [self.gameStateLabel setHidden:YES];
}

4. Improving Interaction

At the moment, there are no limits to the number of discs a player can add and the actions of player A are not visible to player B, and vice versa. Let’s fix that.

Step 1: Limiting Interaction

To limit interaction, we need to update the view controller’s gameState property at the appropriate time. The interaction with the board is already limited by the value of gameState in addDiscToColumn:, but this isn’t very useful if we don’t update the gameState property.

First of all, we need to decide who’s turn it is when a new game starts. We could do something fancy like a coin toss, but let’s keep it simple and let the player hosting the game make the first move. This is easy enough. We simply update the gameState property in the controller:didHostGameOnSocket: and controller:didJoinGameOnSocket: delegate methods. The result is that only the player hosting the game can add a disc to the board.

- (void)controller:(MTHostGameViewController *)controller didHostGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // Update Game State
    [self setGameState:MTGameStateMyTurn];
    // Start Game with Socket
    [self startGameWithSocket:socket];
}
- (void)controller:(MTJoinGameViewController *)controller didJoinGameOnSocket:(GCDAsyncSocket *)socket {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    // Update Game State
    [self setGameState:MTGameStateYourTurn];
    // Start Game with Socket
    [self startGameWithSocket:socket];
}

The second change we need to make is update the game state whenever the player makes a valid move. We do this in addDiscToColumn: as shown below. Each time a player adds a disc to the board, the game state is set to MTGameStateYourTurn, which means that the player cannot add any more discs to the board as long as the game state isn’t updated. Before we continue, test the application one more time to see the result of our changes.

- (void)addDiscToColumn:(UITapGestureRecognizer *)tgr {
    if (self.gameState >= MTGameStateIWin) {
        // Notify Players
    } else if (self.gameState != MTGameStateMyTurn) {
        NSString *message = NSLocalizedString(@"It's not your turn.", nil);
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Warning" message:message delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil];
        [alertView show];
    } else {
        NSInteger column = [self columnForPoint:[tgr locationInView:tgr.view]];
        [self addDiscToColumn:column withType:MTBoardCellTypeMine];
        // Update Game State
        [self setGameState:MTGameStateYourTurn];
        // Send Packet
        // Notify Players if Someone Has Won the Game
    }
}

Step 2: Sending Updates

Even though we establish a connection when a new game is started, thus far, we haven’t done much with that connection. The class that is in charge of the connection is MTGameController, which we created in the previous article. Open MTGameController.h and declare an instance method named addDiscToColumn:. The view controller will invoke this method to inform the game controller that the other player needs to be updated about the changed game state. This is also a good moment to expand the MTGameControllerDelegate protocol. When the game controller receives an update, it needs to notify its delegate, the main view controller, about the update because the main view controller is in charge of updating the board view. Take a look at the updated header file of the MTGameController class.

#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@protocol MTGameControllerDelegate;
@interface MTGameController : NSObject
@property (weak, nonatomic) id<MTGameControllerDelegate> delegate;
#pragma mark -
#pragma mark Initialization
- (id)initWithSocket:(GCDAsyncSocket *)socket;
#pragma mark -
#pragma mark Public Instance Methods
- (void)addDiscToColumn:(NSInteger)column;
@end
@protocol MTGameControllerDelegate <NSObject>
- (void)controller:(MTGameController *)controller didAddDiscToColumn:(NSInteger)column;
- (void)controllerDidDisconnect:(MTGameController *)controller;
@end

The addDiscToColumn: method is very easy to implement thanks to the groundwork we did in the previous articles. I have updated the header file of the MTPacket class by adding MTPacketTypeDidAddDisc to the enumeration of packet types. Even though we declared the action property in the MTPacket class, we won’t be needing it in this project.

- (void)addDiscToColumn:(NSInteger)column {
    // Send Packet
    NSDictionary *load = @{ @"column" : @(column) };
    MTPacket *packet = [[MTPacket alloc] initWithData:load type:MTPacketTypeDidAddDisc action:0];
    [self sendPacket:packet];
}
typedef enum {
    MTPacketTypeUnknown = -1,
    MTPacketTypeDidAddDisc
} MTPacketType;

The parseBody: method also needs to be updated. In its current implementation, all we do is log the packet’s data to the console. In the updated implementation, we check the packet’s type and notify the delegate that the opponent added a disc to a column if the packet’s type is equal to MTPacketTypeDidAddDisc.

- (void)parseBody:(NSData *)data {
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    MTPacket *packet = [unarchiver decodeObjectForKey:@"packet"];
    [unarchiver finishDecoding];
    /*
    NSLog(@"Packet Data > %@", packet.data);
    NSLog(@"Packet Type > %i", packet.type);
    NSLog(@"Packet Action > %i", packet.action);
    */
    if ([packet type] == MTPacketTypeDidAddDisc) {
        NSNumber *column = [(NSDictionary *)[packet data] objectForKey:@"column"];
        if (column) {
            // Notify Delegate
            [self.delegate controller:self didAddDiscToColumn:[column integerValue]];
        }
    }
}

Implement the new delegate method of the MTGameControllerDelegate protocol in the MTViewController class as shown below. We invoke addDiscToColumn:withType: and pass the column and cell type (MTBoardCellTypeYours). The view controller’s gameState property is also updated to ensure that the player can add a new disc to the board.

- (void)controller:(MTGameController *)controller didAddDiscToColumn:(NSInteger)column {
    // Update Game
    [self addDiscToColumn:column withType:MTBoardCellTypeYours];
    // Update State
    [self setGameState:MTGameStateMyTurn];
}

Last but not least, we need to invoke the addDiscToColumn: method of the MTGameController class in the view controller’s addDiscToColumn: method. This is the last piece of the puzzle.

- (void)addDiscToColumn:(UITapGestureRecognizer *)tgr {
    if (self.gameState >= MTGameStateIWin) {
        // Notify Players
    } else if (self.gameState != MTGameStateMyTurn) {
        NSString *message = NSLocalizedString(@"It's not your turn.", nil);
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Warning" message:message delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil];
        [alertView show];
    } else {
        NSInteger column = [self columnForPoint:[tgr locationInView:tgr.view]];
        [self addDiscToColumn:column withType:MTBoardCellTypeMine];
        // Update Game State
        [self setGameState:MTGameStateYourTurn];
        // Send Packet
        [self.gameController addDiscToColumn:column];
        // Notify Players if Someone Has Won the Game
    }
}

Run two instances of the application and test the game one more time. Did you run into a problem? It is time to squash that bug that I told you about earlier in this article. The bug is located in the MTJoinGameViewController class. In the socket:didConnectToHost:port: method of the GCDAsyncSocketDelegate protocol, we notify the delegate of the MTJoinGameViewController class and pass it a reference to the socket. We stop browsing for new services and dismiss the join game view controller.

By dismissing the join game view controller, we implicitly get rid of the join game view controller as it is no longer needed. This means that the class’s dealloc method is invoked when the object is released. The current implementation of the dealloc method is shown below.

- (void)dealloc {
    if (_delegate) {
        _delegate = nil;
    }
    if (_socket) {
        [_socket setDelegate:nil delegateQueue:NULL];
        _socket = nil;
    }
}

In the dealloc method of the MTJoinGameViewController class, we clean everything up. However, because this socket is managed by the game controller, we shouldn’t set the delegate to nil and neither should we set the delegate queue to NULL. The game controller is instantiated before the dealloc method is invoked, which means that the delegate of the game controller’s socket is (re)set to nil when the join game view controller is deallocated. In other words, even though the game controller has a reference to the socket, the socket’s delegate is set to nil and this renders the socket unusable to us. The solution is as simple as removing the last few lines of the dealloc method in which we set the socket’s delegate to nil and the socket’s delegate queue to NULL. Run the application one more time to see if we have successfully fixed that nasty bug.

- (void)dealloc {
    if (_delegate) {
        _delegate = nil;
    }
}

5. Winning the Game

In its current state, it is not possible to win a game because we haven’t implemented an algorithm that checks if one of the players has four of its own discs in a row. I have created a hasPlayerOfTypeWon: method for this purpose. It takes one argument of type MTPlayerType and checks the board if the player of the passed type has won the game. The MTPlayerType type is defined in MTConstants.h. Even though we could pass 0 for player A and 1 for player B, our code becomes much more readable (and maintainable) by declaring a custom type.

typedef enum {
    MTPlayerTypeMe = 0,
    MTPlayerTypeYou
} MTPlayerType;

As you might expect, hasPlayerOfTypeWon: returns a boolean value. I won’t discuss its implementation in detail because it is quite lengthy and not that difficult. The gist of it is that we check all possible winning combinations. It searches for horizontal, vertical, and diagonal matches. This is certainly not the best way to check for matches, but it is a method that I am sure most of you can understand without much difficulty. At the end of the hasPlayerOfTypeWon: method, we also update the view controller’s gameState property if appropriate.

- (BOOL)hasPlayerOfTypeWon:(MTPlayerType)playerType {
    BOOL _hasWon = NO;
    NSInteger _counter = 0;
    MTBoardCellType targetType = playerType == MTPlayerTypeMe ? MTBoardCellTypeMine : MTBoardCellTypeYours;
    // Check Vertical Matches
    for (NSArray *line in self.board) {
        _counter = 0;
        for (MTBoardCell *cell in line) {
            _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
            _hasWon = (_counter > 3) ? YES : _hasWon;
            if (_hasWon) break;
        }
        if (_hasWon) break;
    }
    if (!_hasWon) {
        // Check Horizontal Matches
        for (int i = 0; i < kMTMatrixHeight; i++) {
            _counter = 0;
            for (int j = 0; j < kMTMatrixWidth; j++) {
                MTBoardCell *cell = [(NSArray *)[self.board objectAtIndex:j] objectAtIndex:i];
                _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
                _hasWon = (_counter > 3) ? YES : _hasWon;
                if (_hasWon) break;
            }
            if (_hasWon) break;
        }
    }
    if (!_hasWon) {
        // Check Diagonal Matches - First Pass
        for (int i = 0; i < kMTMatrixWidth; i++) {
            _counter = 0;
            // Forward
            for (int j = i, row = 0; j < kMTMatrixWidth && row < kMTMatrixHeight; j++, row++) {
                MTBoardCell *cell = [(NSArray *)[self.board objectAtIndex:j] objectAtIndex:row];
                _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
                _hasWon = (_counter > 3) ? YES : _hasWon;
                if (_hasWon) break;
            }
            if (_hasWon) break;
            _counter = 0;
            // Backward
            for (int j = i, row = 0; j >= 0 && row < kMTMatrixHeight; j--, row++) {
                MTBoardCell *cell = [(NSArray *)[self.board objectAtIndex:j] objectAtIndex:row];
                _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
                _hasWon = (_counter > 3) ? YES : _hasWon;
                if (_hasWon) break;
            }
            if (_hasWon) break;
        }
    }
    if (!_hasWon) {
        // Check Diagonal Matches - Second Pass
        for (int i = 0; i < kMTMatrixWidth; i++) {
            _counter = 0;
            // Forward
            for (int j = i, row = (kMTMatrixHeight - 1); j < kMTMatrixWidth && row >= 0; j++, row--) {
                MTBoardCell *cell = [(NSArray *)[self.board objectAtIndex:j] objectAtIndex:row];
                _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
                _hasWon = (_counter > 3) ? YES : _hasWon;
                if (_hasWon) break;
            }
            if (_hasWon) break;
            _counter = 0;
            // Backward
            for (int j = i, row = (kMTMatrixHeight - 1); j >= 0 && row >= 0; j--, row--) {
                MTBoardCell *cell = [(NSArray *)[self.board objectAtIndex:j] objectAtIndex:row];
                _counter = (cell.cellType == targetType) ? _counter + 1 : 0;
                _hasWon = (_counter > 3) ? YES : _hasWon;
                if (_hasWon) break;
            }
            if (_hasWon) break;
        }
    }
    // Update Game State
    if (_hasWon) {
        self.gameState = (playerType == MTPlayerTypeMe) ? MTGameStateIWin : MTGameStateYouWin;
    }
    return _hasWon;
}

The hasPlayerOfTypeWon: method is invoked in two places in the MTViewController class. The first place is in the addDiscToColumn: method. After the player has added a disc to the board, we check if the player has won the game by passing MTPlayerMe as the argument of hasPlayerOfTypeWon:.

- (void)addDiscToColumn:(UITapGestureRecognizer *)tgr {
    if (self.gameState >= MTGameStateIWin) {
        // Notify Players
        [self showWinner];
    } else if (self.gameState != MTGameStateMyTurn) {
        NSString *message = NSLocalizedString(@"It's not your turn.", nil);
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Warning" message:message delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil];
        [alertView show];
    } else {
        NSInteger column = [self columnForPoint:[tgr locationInView:tgr.view]];
        [self addDiscToColumn:column withType:MTBoardCellTypeMine];
        // Update Game State
        [self setGameState:MTGameStateYourTurn];
        // Send Packet
        [self.gameController addDiscToColumn:column];
        // Notify Players if Someone Has Won the Game
        if ([self hasPlayerOfTypeWon:MTPlayerTypeMe]) {
            // Show Winner
            [self showWinner];
        }
    }
}

If the player did win the game, we invoke showWinner, which we will implement shortly. Notice that we also invoke the showWinner method at the beginning of the addDiscToColumn: method if the user taps the board view when the game has already ended.

The hasPlayerOfTypeWon: method is also invoked in the controller:didAddDiscToColumn: method of the MTGameControllerDelegate protocol. Take a look at its updated implementation below. If the player’s opponent has won the game, we also invoke the showWinner method.

- (void)controller:(MTGameController *)controller didAddDiscToColumn:(NSInteger)column {
    // Update Game
    [self addDiscToColumn:column withType:MTBoardCellTypeYours];
    if ([self hasPlayerOfTypeWon:MTPlayerTypeYou]) {
        // Show Winner
        [self showWinner];
    } else {
        // Update State
        [self setGameState:MTGameStateMyTurn];
    }
}

In the showWinner method, we update the view by displaying the replay button and showing an alert view that tells the player about the winner of the game.

- (void)showWinner {
    if (self.gameState < MTGameStateIWin) return;
    // Show Replay Button
    [self.replayButton setHidden:NO];
    NSString *message = nil;
    if (self.gameState == MTGameStateIWin) {
        message = NSLocalizedString(@"You have won the game.", nil);
    } else if (self.gameState == MTGameStateYouWin) {
        message = NSLocalizedString(@"Your opponent has won the game.", nil);
    }
    // Show Alert View
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"We Have a Winner" message:message delegate:self cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil];
    [alertView show];
}

6. Filling the Gaps

There are two pieces of functionality that I’d like to add before wrapping up this project, (1) updating the game state label whenever the game state changes and (2) enabling the replay button. Both are easy to implement.

Step 1: Updating the Game State Label

To update the game state label, we need to update the view whenever the gameState property changes. We could use KVO (Key Value Observing) for this, but I prefer to simply override the setter of the gameState property. Whenever the value of _gameState changes, we invoke updateView, another helper method.

- (void)setGameState:(MTGameState)gameState {
    if (_gameState != gameState) {
        _gameState = gameState;
        // Update View
        [self updateView];
    }
}

The updateView method is, just like setupView, a helper method. In updateView, we update the text property of gameStateLabel.

- (void)updateView {
    // Update Game State Label
    switch (self.gameState) {
        case MTGameStateMyTurn: {
            self.gameStateLabel.text = NSLocalizedString(@"It is your turn.", nil);
            break;
        }
        case MTGameStateYourTurn: {
            self.gameStateLabel.text = NSLocalizedString(@"It is your opponent's turn.", nil);
            break;
        }
        case MTGameStateIWin: {
            self.gameStateLabel.text = NSLocalizedString(@"You have won.", nil);
            break;
        }
        case MTGameStateYouWin: {
            self.gameStateLabel.text = NSLocalizedString(@"Your opponent has won.", nil);
            break;
        }
        default: {
            self.gameStateLabel.text = nil;
            break;
        }
    }
}

Step 2: Enabling the Replay Button

To enable the replay button, we should start by implementing the replay: action. This action is invoked when the player taps the replay button, which appears when the game ends. We do three things in replay:, (1) invoke resetGame to reset the game, (2) update the game state to MTGameStateMyTurn, and send the game controller a message of startNewGame. This means that the player initiating the new game can make the first move.

- (IBAction)replay:(id)sender {
    // Reset Game
    [self resetGame];
    // Update Game State
    self.gameState = MTGameStateMyTurn;
    // Notify Opponent of New Game
    [self.gameController startNewGame];
}

We need to implement the startNewGame method on the MTGameController class and extend the MTGameControllerDelegate protocol. Open the header file of the MTGameController class and declare the startNewGame method and the new delegate method of the MTGameControllerDelegate protocol.

#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@protocol MTGameControllerDelegate;
@interface MTGameController : NSObject
@property (weak, nonatomic) id<MTGameControllerDelegate> delegate;
#pragma mark -
#pragma mark Initialization
- (id)initWithSocket:(GCDAsyncSocket *)socket;
#pragma mark -
#pragma mark Public Instance Methods
- (void)startNewGame;
- (void)addDiscToColumn:(NSInteger)column;
@end
@protocol MTGameControllerDelegate <NSObject>
- (void)controller:(MTGameController *)controller didAddDiscToColumn:(NSInteger)column;
- (void)controllerDidStartNewGame:(MTGameController *)controller;
- (void)controllerDidDisconnect:(MTGameController *)controller;
@end

Again, thanks to the foundation we laid in the previous article, the startNewGame method is short and simple. To make all this work, we need to revisit the MTPacket class and update the MTPacketType enumeration.

- (void)startNewGame {
    // Send Packet
    NSDictionary *load = nil;
    MTPacket *packet = [[MTPacket alloc] initWithData:load type:MTPacketTypeStartNewGame action:0];
    [self sendPacket:packet];
}
typedef enum {
    MTPacketTypeUnknown = -1,
    MTPacketTypeDidAddDisc,
    MTPacketTypeStartNewGame
} MTPacketType;

In the parseBody: method of the MTGameController class, we send the delegate a message of controllerDidStartNewGame: if the packet’s type is equal to MTPacketTypeStartNewGame.

- (void)parseBody:(NSData *)data {
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    MTPacket *packet = [unarchiver decodeObjectForKey:@"packet"];
    [unarchiver finishDecoding];
    /*
    NSLog(@"Packet Data > %@", packet.data);
    NSLog(@"Packet Type > %i", packet.type);
    NSLog(@"Packet Action > %i", packet.action);
    */
    if ([packet type] == MTPacketTypeDidAddDisc) {
        NSNumber *column = [(NSDictionary *)[packet data] objectForKey:@"column"];
        if (column) {
            // Notify Delegate
            [self.delegate controller:self didAddDiscToColumn:[column integerValue]];
        }
    } else if ([packet type] == MTPacketTypeStartNewGame) {
        // Notify Delegate
        [self.delegate controllerDidStartNewGame:self];
    }
}

The last bit of work that we need to do is implementing the controllerDidStartNewGame: delegate method in the MTViewController class. We invoke resetGame, as we did in the replay: action, and update the gameState property.

- (void)controllerDidStartNewGame:(MTGameController *)controller {
    // Reset Game
    [self resetGame];
    // Update Game State
    self.gameState = MTGameStateYourTurn;
}

Run two instances of the application and play the game with a friend to see if everything works as it should.


Conclusion

Even though we now have a playable game, I think you agree that it still needs some tweaking and polishing. The design is pretty basic and a few animations would be nice too. However, the goal of this project has been achieved, creating a multiplayer game using Bonjour and the CocoaAsyncSocket library. You should now have a basic understanding of Bonjour and the CocoaAsyncSocket library and know what each can do for you.

Create a Sound Based Memory Game – Tuts+ Premium

$
0
0

In this tutorial series, I’ll be showing you how to create a sound based memory game with the Corona SDK. You’ll learn about loading and playing sounds as well as storing information in Lua Tables. The objective of the game is to reproduce the level sound by tapping on the color squares. Read on!


Tutorial Preview


Get the Full Series!

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


Joining Tuts+ Premium. . .

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

Build an AudioPlayer with PhoneGap: Application Logic

$
0
0

This is the second part of the series about Audero Audio Player. In this article, we’re going to create the business logic of our player. I’ll also explain some of the Cordova APIs that were introduced in the previous article.


Creating the Player

In this section, I’ll show you the class called Player, which let us play, stop, rewind and fast-forward. The class relies heavily on the Media API; without its methods, our player will be completely useless. In addition to the Media API, this class takes advantage of the alert() method of the Notification API. The look of the alert varies among platforms. Most of the supported operating systems use a native dialog box but others, like Bada 2.X, use the classic browser’s alert() function, which is less customizable. The former method accepts up to four parameters:

  1. message: A string containing the message to show
  2. alertCallback: A callback to invoke when the alert dialog is dismissed
  3. title: The title of the dialog (the default value is “Alert”)
  4. buttonName: The button’s text included in the dialog (the default value is “OK”)

Bear in mind that Windows Phone 7 ignores the button name and always uses the default. Windows Phone 7 and 8 don’t have a built-in browser alert, so if you want to use alert('message');, you have to assign window.alert = navigator.notification.alert.

Now that I’ve explained the APIs used by Player, we can take a look at how it’s made. We have three properties:

  • media: the reference to the current sound object
  • mediaTimer: which will contain a unique interval ID created using the setInterval() function that we’ll pass to clearInterval() to stop the sound’s timer
  • isPlaying: a variable that specifies if the current sound is playing or not. In addition to the property, the class has several methods.

The initMedia() method initializes the media property with a Media object that represents the sound selected by the user. The latter is notified using the Notification API in case of error. The aim of the playPause, stop(), and seekPosition() methods should be obvious, so I’ll move on. The resetLayout() and changePlayButton() methods are very simple. They are used to reset or update the player’s layout according to the action performed by the user. The last remaining method is updateSliderPosition(),  which is similar to the time slider. The latter has zero (the beginning of the slider) as the default value for the current position, set using the value="0" attribute. This must be updated accordingly while the sound is playing to give to the user visual feedback concerning elapsed playing time.

We’ve uncovered all the details of this class, so here is the source code of the file:

var Player = {
   media: null,
   mediaTimer: null,
   isPlaying: false,
   initMedia: function(path) {
      Player.media = new Media(
         path,
         function() {
            console.log('Media file read succesfully');
            if (Player.media !== null)
               Player.media.release();
            Player.resetLayout();
         },
         function(error) {
            navigator.notification.alert(
               'Unable to read the media file.',
               function(){},
               'Error'
            );
            Player.changePlayButton('play');
            console.log('Unable to read the media file (Code): ' + error.code);
         }
      );
   },
   playPause: function(path) {
      if (Player.media === null)
         Player.initMedia(path);
      if (Player.isPlaying === false)
      {
         Player.media.play();
         Player.mediaTimer = setInterval(
            function() {
               Player.media.getCurrentPosition(
                  function(position) {
                     if (position > -1)
                     {
                        $('#media-played').text(Utility.formatTime(position));
                        Player.updateSliderPosition(position);
                     }
                  },
                  function(error) {
                     console.log('Unable to retrieve media position: ' + error.code);
                     $('#media-played').text(Utility.formatTime(0));
                  }
               );
            },
            1000
         );
         var counter = 0;
         var timerDuration = setInterval(
            function() {
               counter++;
               if (counter > 20)
                  clearInterval(timerDuration);
               var duration = Player.media.getDuration();
               if (duration > -1)
               {
                  clearInterval(timerDuration);
                  $('#media-duration').text(Utility.formatTime(duration));
                  $('#time-slider').attr('max', Math.round(duration));
                  $('#time-slider').slider('refresh');
               }
               else
                  $('#media-duration').text('Unknown');
            },
            100
         );
         Player.changePlayButton('pause');
      }
      else
      {
         Player.media.pause();
         clearInterval(Player.mediaTimer);
         Player.changePlayButton('play');
      }
      Player.isPlaying = !Player.isPlaying;
   },
   stop: function() {
      if (Player.media !== null)
      {
         Player.media.stop();
         Player.media.release();
      }
      clearInterval(Player.mediaTimer);
      Player.media = null;
      Player.isPlaying = false;
      Player.resetLayout();
   },
   resetLayout: function() {
      $('#media-played').text(Utility.formatTime(0));
      Player.changePlayButton('play');
      Player.updateSliderPosition(0);
   },
   updateSliderPosition: function(seconds) {
      var $slider = $('#time-slider');
      if (seconds < $slider.attr('min'))
         $slider.val($slider.attr('min'));
      else if (seconds > $slider.attr('max'))
         $slider.val($slider.attr('max'));
      else
         $slider.val(Math.round(seconds));
      $slider.slider('refresh');
   },
   seekPosition: function(seconds) {
      if (Player.media === null)
         return;
      Player.media.seekTo(seconds * 1000);
      Player.updateSliderPosition(seconds);
   },
   changePlayButton: function(imageName) {
      var background = $('#player-play')
      .css('background-image')
      .replace('url(', '')
      .replace(')', '');
      $('#player-play').css(
         'background-image',
         'url(' + background.replace(/images\/.*\.png$/, 'images/' + imageName + '.png') + ')'
      );
   }
};

Managing the Audio Files

This section illustrates the AppFile class that will be used to create, delete, and load the sounds using the Web Storage API. This API has two areas, Session and Local, but Cordova uses the latter. All the sounds are stored in an item titled “files” as you can see by looking at the _tableName properties.

Please note that this API is only able to store basic data. Therefore, to fit our need to store objects, we’ll use the JSON format. JavaScript has a class to deal with this format called JSON. It uses the methods parse() to parse a string and recreate the appropriate data, and stringify() to convert the object in a string. As a final note, I won’t be using the dot notation of the API because Windows Phone 7 doesn’t support it, so we’ll use the setItem() and getItem() methods to ensure compatibility for all devices.

Now that you have an overview of how we’ll store the data, let’s talk about the data that we need to save. The only information that we need for each found sound is the name (name property) and an absolute path (fullPath property). The AppFile class also has a “constant”, called EXTENSIONS, where we’ll set the extensions that will be tested against each file. If they match up, the file will be collected by the application. We have a method to add a file (addFile()), one method to delete a file (deleteFile()), one method that deletes the whole database (deleteFiles()), and, lastly, two methods that retrieve the file from the database: getAppFiles() to retrieve all the files, and getAppFile() to retrieve just one. The class also has four comparison methods, two static (compare() and compareIgnoreCase()) and two non-static (compareTo() and compareToIgnoreCase()). The last method is the one used to retrieve the index of a certain file, getIndex(). The AppFile class enables you to perform all the basic operations you may need.

The code that implements what we’ve discussed can be read here:

function AppFile(name, fullPath)
{
   var _db = window.localStorage;
   var _tableName = 'files';
   this.name = name;
   this.fullPath = fullPath;
   this.save = function(files)
   {
      _db.setItem(_tableName, JSON.stringify(files));
   }
   this.load = function()
   {
      return JSON.parse(_db.getItem(_tableName));
   }
}
AppFile.prototype.addFile = function()
{
   var index = AppFile.getIndex(this.fullPath);
   var files = AppFile.getAppFiles();
   if (index === false)
      files.push(this);
   else
      files[index] = this;
   this.save(files);
};
AppFile.prototype.deleteFile = function()
{
   var index = AppFile.getIndex(this.fullPath);
   var files = AppFile.getAppFiles();
   if (index !== false)
   {
      files.splice(index, 1);
      this.save(files);
   }
   return files;
};
AppFile.prototype.compareTo = function(other)
{
   return AppFile.compare(this, other);
};
AppFile.prototype.compareToIgnoreCase = function(other)
{
   return AppFile.compareIgnoreCase(this, other);
};
AppFile.EXTENSIONS = ['.mp3', '.wav', '.m4a'];
AppFile.compare = function(appFile, other)
{
   if (other == null)
      return 1;
   else if (appFile == null)
      return -1;
   return appFile.name.localeCompare(other.name);
};
AppFile.compareIgnoreCase = function(appFile, other)
{
   if (other == null)
      return 1;
   else if (appFile == null)
      return -1;
   return appFile.name.toUpperCase().localeCompare(other.name.toUpperCase());
};
AppFile.getAppFiles = function()
{
   var files = new AppFile().load();
   return (files === null) ? [] : files;
};
AppFile.getAppFile = function(path)
{
   var index = AppFile.getIndex(path);
   if (index === false)
      return null;
   else
   {
      var file = AppFile.getAppFiles()[index];
      return new AppFile(file.name, file.fullPath);
   }
};
AppFile.getIndex = function(path)
{
   var files = AppFile.getAppFiles();
   for(var i = 0; i < files.length; i++)
   {
      if (files[i].fullPath.toUpperCase() === path.toUpperCase())
         return i;
   }
   return false;
};
AppFile.deleteFiles = function()
{
   new AppFile().save([]);
};

The Utility Class

The utility.js file is very short and easy to understand. It only has two methods. One is used to convert milliseconds into a formatted string that will be shown in the player, while the other is a JavaScript implementation of the well known Java method endsWith.

Here is the source:

var Utility = {
   formatTime: function(milliseconds) {
      if (milliseconds <= 0)
         return '00:00';
      var seconds = Math.round(milliseconds);
      var minutes = Math.floor(seconds / 60);
      if (minutes < 10)
         minutes = '0' + minutes;
      seconds = seconds % 60;
      if (seconds < 10)
         seconds = '0' + seconds;
      return minutes + ':' + seconds;
   },
   endsWith: function(string, suffix) {
      return string.indexOf(suffix, string.length - suffix.length) !== -1;
   }
};

Putting it All Together

This section discusses the last JavaScript file of the project, application.js, which contains the Application class. Its aim is to attach events to the app page’s elements. Those events will take advantage of the classes we’ve seen thus far and enable the player to work properly.

The code of the illustrated function is listed below:

var Application = {
   initApplication: function() {
      $(document).on(
         'pageinit',
         '#files-list-page',
         function()
         {
            Application.initFilesListPage();
         }
      );
      $(document).on(
         'pageinit',
         '#aurelio-page',
         function()
         {
            Application.openLinksInApp();
         }
      );
      $(document).on(
         'pagechange',
         function(event, properties)
         {
            if (properties.absUrl === $.mobile.path.makeUrlAbsolute('player.html'))
            {
               Application.initPlayerPage(
                  JSON.parse(properties.options.data.file)
               );
            }
         }
      );
   },
   initFilesListPage: function() {
      $('#update-button').click(
         function()
         {
            $('#waiting-popup').popup('open');
            setTimeout(function(){
               Application.updateMediaList();
            }, 150);
         }
      );
      $(document).on('endupdate', function(){
         Application.createFilesList('files-list', AppFile.getAppFiles());
         $('#waiting-popup').popup('close');
      });
      Application.createFilesList('files-list', AppFile.getAppFiles());
   },
   initPlayerPage: function(file) {
      Player.stop();
      $('#media-name').text(file.name);
      $('#media-path').text(file.fullPath);
      $('#player-play').click(function() {
         Player.playPause(file.fullPath);
      });
      $('#player-stop').click(Player.stop);
      $('#time-slider').on('slidestop', function(event) {
         Player.seekPosition(event.target.value);
      });
   },
   updateIcons: function()
   {
      if ($(window).width() > 480)
      {
         $('a[data-icon], button[data-icon]').each(function() {
            $(this).removeAttr('data-iconpos');
         });
      }
      else
      {
         $('a[data-icon], button[data-icon]').each(function() {
            $(this).attr('data-iconpos', 'notext');
         });
      }
   },
   openLinksInApp: function()
   {
      $("a[target=\"_blank\"]").on('click', function(event) {
         event.preventDefault();
         window.open($(this).attr('href'), '_target');
      });
   },
   updateMediaList: function() {
      window.requestFileSystem(
         LocalFileSystem.PERSISTENT,
         0,
         function(fileSystem){
            var root = fileSystem.root;
            AppFile.deleteFiles();
            Application.collectMedia(root.fullPath, true);
         },
         function(error){
            console.log('File System Error: ' + error.code);
         }
      );
   },
   collectMedia: function(path, recursive, level) {
      if (level === undefined)
         level = 0;
      var directoryEntry = new DirectoryEntry('', path);
      if(!directoryEntry.isDirectory) {
         console.log('The provided path is not a directory');
         return;
      }
      var directoryReader = directoryEntry.createReader();
      directoryReader.readEntries(
         function (entries) {
            var appFile;
            var extension;
            for (var i = 0; i < entries.length; i++) {
               if (entries[i].name === '.')
                  continue;
               extension = entries[i].name.substr(entries[i].name.lastIndexOf('.'));
               if (entries[i].isDirectory === true && recursive === true)
                  Application.collectMedia(entries[i].fullPath, recursive, level + 1);
               else if (entries[i].isFile === true && $.inArray(extension, AppFile.EXTENSIONS) >= 0)
               {
                  appFile = new AppFile(entries[i].name, entries[i].fullPath);
                  appFile.addFile();
                  console.log('File saved: ' + entries[i].fullPath);
               }
            }
         },
         function(error) {
            console.log('Unable to read the directory. Errore: ' + error.code);
         }
      );
      if (level === 0)
         $(document).trigger('endupdate');
      console.log('Current path analized is: ' + path);
   },
   createFilesList: function(idElement, files)
   {
      $('#' + idElement).empty();
      if (files == null || files.length == 0)
      {
         $('#' + idElement).append('<p>No files to show. Would you consider a files update (top right button)?</p>');
         return;
      }
      function getPlayHandler(file) {
         return function playHandler() {
            $.mobile.changePage(
               'player.html',
               {
                  data: {
                     file: JSON.stringify(file)
                  }
               }
            );
         };
      }
      function getDeleteHandler(file) {
         return function deleteHandler() {
            var oldLenght = AppFile.getAppFiles().length;
            var $parentUl = $(this).closest('ul');
            file = new AppFile('', file.fullPath);
            file.deleteFile();
            if (oldLenght === AppFile.getAppFiles().length + 1)
            {
               $(this).closest('li').remove();
               $parentUl.listview('refresh');
            }
            else
            {
               console.log('Media not deleted. Something gone wrong.');
               navigator.notification.alert(
                  'Media not deleted. Something gone wrong so please try again.',
                  function(){},
                  'Error'
               );
            }
         };
      }
      var $listElement, $linkElement;
      files.sort(AppFile.compareIgnoreCase);
      for(var i = 0; i < files.length; i++)
      {
         $listElement = $('<li>');
         $linkElement = $('<a>');
         $linkElement
         .attr('href', '#')
         .text(files[i].name)
         .click(getPlayHandler(files[i]));
         // Append the link to the <li> element
         $listElement.append($linkElement);
         $linkElement = $('<a>');
         $linkElement
         .attr('href', '#')
         .text('Delete')
         .click(getDeleteHandler(files[i]));
         // Append the link to the <li> element
         $listElement.append($linkElement);
         // Append the <li> element to the <ul> element
         $('#' + idElement).append($listElement);
      }
      $('#' + idElement).listview('refresh');
   }
};

Managing External Links

In the previous part of this series, I mentioned that an interesting point of the credits page was the attribute target="_blank" applied to the links. This section will explain why the openLinksInApp() method of the Application class makes sense.

Once upon a time, Cordova used to open external links in the same Cordova WebView that was running the application. When a link was open and the user clicked the “back” button, the last displayed page was shown exactly as it was before the user left it. In the newer version, this has changed. Nowadays, the external links are opened, by default, using the Cordova WebView if the URL is in your app’s whitelist. URLs that aren’t on your whitelist are opened using the InAppBrowser API. If you don’t manage the links in the right way, or if the user taps a link that is shown in the InAppBrowser or the system and then chooses to go back, all the jQuery Mobile enhancements are lost. This behavior happens because the CSS and JavaScript files are loaded by the main page, and the following ones are loaded using AJAX. Before uncovering the solution, let’s take a look at what’s the InAppBrowser.

The InAppBrowser is a web-browser that is shown in your app when you use the window.open call.

This API has three methods:

  • addEventListener(): Allows you to listen for three events (loadstart, loadstop, and exit) and attach a function that runs as soon as those events are fired
  • removeEventListener(): Removes a previously-attached listener.
  • close(): Used to close the InAppBrowser window.

So, what’s the solution? The aim of the openLinksInApp() function, coupled with the whitelist specified in the configuration file, is to catch the clicks on all the external links recognized by using the target="_blank" attribute, and open them using the window.open() method. With this technique, we’ll avoid the problem described, and our player will continue to look and work as expected.


Next Part

In the third and last installment of this series, we’ll see the last remaining files so that you can complete the project and play around with it.

Android SDK: Working with Google Maps – Displaying Places of Interest

$
0
0

With Google Maps in your Android apps, you can provide users with localization functions, such as geographical information. Throughout this series we have been building an Android app in which the Google Maps Android API v2 combines with the Google Places API. So far we have displayed a map, in which the user can see their current location, and we have submitted a Google Places query to return data about nearby places of interest. This required setting up API access for both services. In the final part of the series, we will parse the Google Places JSON data and use it to show the user nearby places of interest. We will also make the app update the markers when the user location changes.

This is the last of four parts in a tutorial series on Using Google Maps and Google Places in Android apps:

App We Are Working Towards
This is a snapshot of the final app.

1. Process the Place Data

Step 1

You will need to add the following import statements to your Activity class for this tutorial:

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;

In the last tutorial we created an inner AsyncTask class to handle fetching the data from Google Places in the background. We added the doInBackground method to request and retrieve the data. Now we can implement the onPostExecute method to parse the JSON string returned from doInBackground, inside your AsyncTask class, after the doInBackground method:

protected void onPostExecute(String result) {
	//parse place data returned from Google Places
}

Step 2

Back in the second part of this series, we created a Marker object to indicate the user’s last recorded location on the map. We are also going to use Markers to show the nearby places of interest. We will use an array to store these Markers. At the top of your Activity class declaration, add the following instance variable:

private Marker[] placeMarkers;

By default, the Google Places API returns a maximum of 20 places, so let’s define this as a constant too:

private final int MAX_PLACES = 20;

When we create the Markers for each place, we will use MarkerOptions objects to configure the Marker details. Create another array instance variable for these:

private MarkerOptions[] places;

Now let’s instantiate the array. In your Activity onCreate method, after the line in which we set the map type, create an array of the maximum required size:

placeMarkers = new Marker[MAX_PLACES];

Now let’s turn to the onPostExecute method we created. First, loop through the Marker array, removing any existing Markers. This method will execute multiple times as the user changes location:

if(placeMarkers!=null){
	for(int pm=0; pm<placeMarkers.length; pm++){
		if(placeMarkers[pm]!=null)
			placeMarkers[pm].remove();
	}
}

When the app code first executes, new Markers will be created. However, when the user changes location, these methods will execute again to update the places displayed. For this reason the first thing we must do is remove any existing Markers from the map to prepare for creating a new batch.

Step 3

We will be using Java JSON resources to process the retrieved place data. Since these classes throw certain exceptions, we need to build in a level of error handling throughout this section. Start by adding try and catch blocks:

try {
	//parse JSON
}
catch (Exception e) {
	e.printStackTrace();
}

Inside the try block, create a new JSONObject and pass it to the result JSON string returned from doInBackground:

JSONObject resultObject = new JSONObject(result);

If you look at the Place Search page on the Google Places API documentation, you can see a sample of what the query actually returns in JSON. You will see that the places are contained within an array named “results”. Let’s first retrieve that array from the returned JSON object:

JSONArray placesArray = resultObject.getJSONArray("results");

You should refer to the sample JSON result as we complete each section of this process – keep the page open in a browser while you complete the remainder of the tutorial. Next let’s instantiate the MarkerOptions array we created with the length of the returned “results” array:

places = new MarkerOptions[placesArray.length()];

This should give us a MarkerOptions object for each place returned. Add a loop to iterate through the array of places:

//loop through places
for (int p=0; p<placesArray.length(); p++) {
	//parse each place
}

Step 4

Now we can parse the data for each place returned. Inside the for loop, we will build details to pass to the MarkerOptions object for the current place. This will include latitude and longitude, place name, type and vicinity, which is an excerpt of the address data for the place. We will retrieve all of this data from the Google Places JSON, passing it to the Marker for the place via its MarkerOptions object. If any of the values are missing in the returned JSON feed, we will simply not display a Marker for that place, in case of Exceptions. To keep track of this, add a boolean flag:

boolean missingValue=false;

Now add local variables for each aspect of the place we need to retrieve and pass to the Marker:

LatLng placeLL=null;
String placeName="";
String vicinity="";
int currIcon = otherIcon;

We create and initialize a LatLng object for the latitude and longitude, strings for the place name and vicinity and initially set the icon to use the default icon drawable we created. Now we need another try block, so that we can detect whether any values are in fact missing:

try{
	//attempt to retrieve place data values
}
catch(JSONException jse){
	missingValue=true;
	jse.printStackTrace();
}

We set the missing value flag to true for checking later. Inside this try block, we can now attempt to retrieve the required values from the place data. Start by initializing the boolean flag to false, assuming that there are no missing values until we discover otherwise:

missingValue=false;

Now get the current object from the place array:

JSONObject placeObject = placesArray.getJSONObject(p);

If you look back at the sample Place Search data, you will see that each place section includes a “geometry” section which in turn contains a “location” section. This is where the latitude and longitude data for the place is, so retrieve it now:

JSONObject loc = placeObject.getJSONObject("geometry").getJSONObject("location");

Attempt to read the latitude and longitude data from this, referring to the “lat” and “lng” values in the JSON:

placeLL = new LatLng(
	Double.valueOf(loc.getString("lat")),
	Double.valueOf(loc.getString("lng")));

Next get the “types” array you can see in the JSON sample:

JSONArray types = placeObject.getJSONArray("types");

Tip: We know this is an array as it appears in the JSON feed surrounded by the “[" and "]” characters. We treat any other nested sections as JSON objects rather than arrays.

Loop through the type array:

for(int t=0; t<types.length(); t++){
	//what type is it
}

Get the type string:

String thisType=types.get(t).toString();

We are going to use particular icons for certain place types (food, bar and store) so add a conditional:

if(thisType.contains("food")){
	currIcon = foodIcon;
	break;
}
else if(thisType.contains("bar")){
	currIcon = drinkIcon;
	break;
}
else if(thisType.contains("store")){
	currIcon = shopIcon;
	break;
}

The type list for a place may actually contain more than one of these places, but for convenience we will simply use the first one encountered. If the list of types for a place does not contain any of these, we will leave it displaying the default icon. Remember that we specified these types in the Place Search URL query string last time:

food|bar|store|museum|art_gallery

This means that the only place types using the default icon will be museums or art galleries, as these are the only other types we asked for.

After the loop through the type array, retrieve the vicinity data:

vicinity = placeObject.getString("vicinity");

Finally, retrieve the place name:

placeName = placeObject.getString("name");

Step 5

After the catch block in which you set the missingValue flag to true, check that value and set the place MarkerOptions object to null, so that we don’t attempt to instantiate any Marker objects with missing data:

if(missingValue)	places[p]=null;

Otherwise, we can create a MarkerOptions object at this position in the array:

else
	places[p]=new MarkerOptions()
	.position(placeLL)
	.title(placeName)
	.icon(BitmapDescriptorFactory.fromResource(currIcon))
	.snippet(vicinity);

Step 6

Now, at the end of onPostExecute after the outer try and catch blocks, loop through the array of MarkerOptions, instantiating a Marker for each, adding it to the map and storing a reference to it in the array we created:

if(places!=null && placeMarkers!=null){
	for(int p=0; p<places.length && p<placeMarkers.length; p++){
		//will be null if a value was missing
		if(places[p]!=null)
			placeMarkers[p]=theMap.addMarker(places[p]);
	}
}

Storing a reference to the Marker allows us to easily remove it when the places are updated, as we implemented at the beginning of the onPostExecute method. Notice that we include two conditional tests each time this loop iterates, in case the Place Search did not return the full 20 places. We also check in case the MarkerOptions is null, indicating that a value was missing.

Step 7

Finally, we can instantiate and execute our AsyncTask class. In your updatePlaces method, after the existing code in which we built the search query string, start this background processing to fetch the place data using that string:

new GetPlaces().execute(placesSearchStr);

You can run your app now to see it in action. It should display your last recorded location together with nearby places of interest. The colors you see on the Markers will depend on the places returned. Here is the app displaying a user location in Glasgow city center, UK:

App in Glasgow

Perhaps unsurprisingly a lot of the places listed in Glasgow are bars.

When the user taps a Marker, they will see the place name and snippet info:

Tapping Marker in Glasgow

2. Update With User Location Changes

Step 1

The app as it stands will execute once when it is launched. Let’s build in the functionality required to make it update to reflect changes in the user location, refreshing the nearby place Markers at the same time.

Alter the opening line of the Activity class declaration to make it implement the LocationListener interface so that we can detect changes in the user location:

public class MyMapActivity extends Activity implements LocationListener {

A Location Listener can respond to various changes, each of which uses a dedicated method. Inside the Activity class, implement these methods:

@Override
public void onLocationChanged(Location location) {
	Log.v("MyMapActivity", "location changed");
	updatePlaces();
}
@Override
public void onProviderDisabled(String provider){
	Log.v("MyMapActivity", "provider disabled");
}
@Override
public void onProviderEnabled(String provider) {
	Log.v("MyMapActivity", "provider enabled");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
	Log.v("MyMapActivity", "status changed");
}

The only one we are really interested in is the first, which indicates that the location has changed. In this case we call the updatePlaces method again. Otherwise we simply write out a Log message.

At the end of the updatePlaces method, add a request for the app to receive location updates:

locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this);

We use the Location Manager we created earlier in the series, requesting updates using the network provider, at delays of 30 seconds (indicated in milliseconds), with a minimum location change of 100 meters and the Activity class itself to receive the updates. You can, of course, alter some of the parameters to suit your own needs.

Tip: Although the requestLocationUpdates method specifies a minimum time and distance for updates, in reality it can cause the onLocationChanged method to execute much more often, which has serious performance implications. In any apps you plan on releasing to users, you should therefore limit the frequency at which your code responds to these location updates. The alternative requestSingleUpdate method used on a timed basis may be worth considering.

Step 2

Last but not least, we need to take care of what happens when the app pauses and resumes. Override the two methods as follows:

@Override
protected void onResume() {
	super.onResume();
	if(theMap!=null){
		locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this);
	}
}
@Override
protected void onPause() {
	super.onPause();
	if(theMap!=null){
		locMan.removeUpdates(this);
	}
}

We check for the GoogleMap object before attempting any processing, as in onCreate. If the app is pausing, we stop it from requesting location updates. If the app is resuming, we start requesting the updates again.

Tip: We’ve used the LocationManager.NETWORK_PROVIDER a few times in this series. If you are exploring localization functionality in your apps, check out the alternative getBestProvider method with which you can specify criteria for Android to choose a provider based on such factors as accuracy and speed.


Before We Finish

That pretty much completes the app! However, there are many aspects of the Google Maps Android API v2 that we have not even touched on. Once you have your app running you can experiment with features such as rotation and tilting. The updated maps service displays indoor and 3D maps in certain places. The following image shows the 3D facility with the app if the user location was in Venice, Italy:

3D Map

This has the map type set to normal – here is another view of Venice with the hybrid map type set:

Hybrid Map in Venice

Conclusion

In this tutorial series we have worked through the process of integrating both Google Maps and Google Places APIs in a single Android app. We handled API key access, setting up the development environment, workspace and application to use Google Play Services. We utilized location data, showing the user location together with nearby places of interest, and displaying the data with custom UI elements. Although what we have covered in this series is fairly extensive, it really is only the beginning when it comes to building localization features into Android apps. With the release of Version 2 of the Maps API, Android apps are set to take such functions to the next level.


Wanted: Producer for Tuts+ Premium

$
0
0

Tuts+ Premium is hiring for an expert in mobile development. We’re looking for a developer who’s passionate and experienced with mobile design and development to produce courses with us! You need to be experienced in mobile development platforms, be proactive and engaged with the industry, and have a passion for teaching. Sound like you? Read on!

We’re looking for a new Producer to join the Tuts+ Premium Content team. A Producer is responsible for recruiting and producing courses for Tuts+ Premium members and is a vital part of the content team. They should have professional experience in designing and developing for mobile (apps, websites, etc) and additional experience in screencasting or teaching.

A Producer role requires a commitment of 20 hours a week and ability to perform daily responsibilities, but you’ll have flexibility in scheduling your time. This role is a contract position and is paid monthly.

An ideal Producer on our team will have:

  • At least 3+ years professional experience in mobile development or design. This could include successfully developing iOS/Android apps, web apps, or other mobile interfaces. Should love every aspect of mobile development–from concept and design, to development and deployment, on up to iteration and strategy. Hands-on knowledge of backend development is required.
  • Experience screencasting or teaching in a on-line environment preferred.
  • Preferred experience working with WordPress, S3 and other content publishing tools.
  • Excellent written and verbal communication skills to recruit and work with other talented professionals. Good working relationships in the mobile dev industry.
  • Experience working with high performance remote teams using a variety of web-based management tools and technologies. (Such as email, Basecamp, Skype and other web-based tools.)
  • A knack for time management, reliable performance and managing multiple projects at once.
  • A passion for teaching and strong ideas about how to make Tuts+ Premium even better!

Apply!

Think you fit the bill? Then we want to hear from you! Send a us a short email to amanda@envato.com telling us how you’re the perfect fit for the Tuts+ Premium team. Make sure to include links or samples to back up your claims. A great application will be sure to mention:

  • Your professional experience in mobile development and design. Links to public apps or sites is highly preferred.
  • Previous samples of screencasting and teaching work. The more you’ve done, the better!
  • Your member name if you’re already active with the community.
  • Your available hours and local time zone.
  • Your top suggestion or idea for Tuts+ Premium courses. If you were brought on as a Producer, what would be the first course you’d want to make?

Make sure to get your email in by May 14, 2013. While every email will be considered we may not be able to respond to every application, but if you sound like a good fit you’ll hear from us soon. Join the team and help us make Tuts+ Premium even better!

How To Submit an iOS App to the App Store

$
0
0

You have worked weeks or months on your first iOS application and you are ready to submit your masterpiece to Apple’s App Store. How do you do this? Is your application ready for submission? I am sure that some of these questions have entered your mind at one point or another. Is submitting an application as simple as sending Apple your application’s binary? Not quite. With this tutorial, I will provide you with a detailed map to get your application submitted to Apple’s App Store.


Introduction

Even though the App Store review process is a black box for the most part, that doesn’t mean that you can’t prepare yourself and your application for Apple’s review process. Apple provides guidelines to help you stay within the sometimes invisible boundaries of what is and isn’t allowed in the App Store.

The first time you submit an application to the App Store is exciting and nerve-racking at the same time. Even for experienced iOS developers, submitting an application to the App Store is often a stressful undertaking because it is something that most developers don’t do on a daily basis.

Throughout this article, I am assuming that you are a registered iOS developer which means that you are enrolled in Apple’s iOS Developer Program and are allowed to submit applications for publication in the App Store. To submit an iOS application to the App Store, you need to be a registered iOS developer. Red flag? Don’t worry. You can enroll in Apple’s iOS Developer Program by visiting this link and clicking the Enroll Now button.

How To Submit an iOS App to the App Store - Enrolling in Apple's iOS Developer Program
Figure 1: Enrolling in Apple’s iOS Developer Program

1. Is your application ready?

Step 1: Testing

An application isn’t necessarily ready when you’ve written the last line of code or implemented the final feature of the application’s specification. Have you tested your application on one or more physical devices? Have you profiled your application for memory leaks and performance issues? Does your application crash from time to time? The family of iOS devices has grown substantially over the past years and it is important to test your application on as many iOS devices as you can lay your hands on. Common issues include not optimizing an application for the iPhone 5′s 4″ screen or the iPad Mini’s 7.9″ screen.

The iOS Simulator is a great tool, but it runs on your Mac, which has more memory and processing power than the phone in your pocket. I can assure you that the differences in performance between an old(er) iPhone 3GS and an iPhone 5 are like night and day. As an iOS developer, you should never get rid of an old iOS device as long as you build or maintain applications that can run on any of these older devices.

Apple’s Review Process isn’t airtight, but it is very capable of identifying problems that might affect your application’s user experience. If your application crashes from time to time or it becomes slow after ten minutes of use, then you have some work to do before submitting it to the App Store. Even if Apple’s review team doesn’t spot the problem, your users will. If the people using your application are not pleased, they will leave bad reviews on the App Store, which may harm sales or inhibit downloads.

Step 2: Rules and Guidelines

As I mentioned earlier, Apple provides developers with a number of documents that are a great help during the creation and development process of your application. The documents that you should be aware of are the iOS Human Interface Guidelines and the App Store Review Guidelines. Despite the availability of these documents, it seems that few developers take the time to browse them, let alone read them. It shouldn’t be a surprise that some applications are therefore rejected even though the reason for the rejection is clearly stated in these documents.

Even if you don’t intend to read the iOS Human Interface Guidelines or the App Store Review Guidelines, it is important to know about some of the rules that they talk about. Take a look at the short list below to get an idea of what your application should and shouldn’t do.

Your application …

  • doesn’t crash.
  • shouldn’t use private API’s.
  • shouldn’t replicate the functionality of native applications.
  • should use In App Purchase for in-app (financial) transactions.
  • shouldn’t use the camera or microphone without the user’s knowledge.
  • only uses artwork that you have the copyright of or you have permission to use.

Keep in mind that this is a tiny subset of the guidelines included in the aforementioned documents. The majority of the rules and guidelines are trivial, but some are not and you might even violate some of them inadvertently. Let me give you an example. Before Apple started using its own maps, the MapKit framework used Google’s maps. This was clear to the user because of the small Google logo in the bottom left corner of each map. However, if some part of your application’s user interface covered or obscured Google’s logo, your application would get rejected. This rule seems trivial, but it is a rule that is easily violated if you’re not careful. Even automated tests won’t cover you in this case.


2. Prerequisites

Before you can even start thinking about submitting your application to the App Store, you need to make sure that you have an App ID, a valid distribution certificate, and a valid provisioning profile. Let me show you what this entails.

Step 1: App ID

Every application needs an App ID or application identifier. There are two types of application identifiers, (1) an explicit App ID and (2) a wildcard App ID. A wildcard App ID can be used for building and installing multiple applications. Despite the convenience of a wildcard App ID, an explicit App ID is required if your application uses iCloud or makes use of other iOS features, such as Game Center, Apple Push Notifications, or In App Purchase.

If you’re not sure what App ID best fits your project, then I recommend reading Technical Note QA1713 for more information about this topic.

Step 2: Distribution Certificate

To submit an application to the App Store, you need to create an iOS provisioning profile for distribution. To create such a provisioning profile, you first need to create a distribution certificate. The process for creating a distribution certificate is very similar to creating a development certificate. If you have tested your application on a physical device, then you are probably already familiar with the creation of a development certificate.

If you need to refresh your memory, I suggest reading Apple’s detailed guide about signing certificates and provisioning profiles. The process is not difficult once you understand how the various pieces of the puzzle fit together.

Step 3: Provisioning Profile

Once you’ve created an App ID and a distribution certificate, you can create an iOS provisioning profile for distributing your application through the App Store. Keep in mind that you cannot use the same provisioning profile that you use for ad hoc distribution. You need to create a separate provisioning profile for App Store distribution. If you use a wildcard App ID for your project, then you can use the same provisioning profile for multiple applications.

Step 4: Build Settings

With the App ID, distribution certificate, and provisioning profile in place, it is time to configure your target’s build settings in Xcode. This means selecting the target from the list of targets in Xcode’s Project Navigator, opening the Build Settings tab at the top, and updating the settings in the Code Signing section to match the distribution provisioning profile you created earlier. Newly added provisioning profiles are sometimes not immediately visible in the Code Signing section of the build settings. Quitting and relaunching Xcode remedies this issue.

How To Submit an iOS App to the App Store - Configuring the Target's Build Settings
Figure 2: Configuring the Target’s Build Settings

Even though the code signing process is fairly simple once you understand it, it is something that trips up a lot of developers. I don’t know a single Cocoa developer who hasn’t run into code signing issues at some point in their career. Once you’ve taken this hurdle, the rest of the submission process is fairly easy.

Step 5: Deployment Target

It is useful to write a few words about your application’s deployment target. Each target in an Xcode project, has a deployment target, which indicates the minimum version of the operating system that the application can run on. It is up to you to set the deployment target, but keep in mind that modifying the deployment target is not something you can do without consequences once your application is in the App Store. If you increase the deployment target for an update of your application, then users who already purchased your application but don’t meet the new deployment target, cannot run the update. It gets really problematic when a user downloads an update through iTunes (not the device), replacing the previous version on their computer, and then discovers that the new update doesn’t run on their device.

I have two very simple tips with regards to your application’s deployment target. (1) Be very careful when you decide to increase the deployment target of an existing application. Mention this in the application’s release notes of the updates that precede the change and again in the update that uses the new deployment target. If your warn your customers well in advance, you have done all you can to prevent potential problems. (2) For new applications, I almost always set the deployment target to the last major release, iOS 6 at the time of writing. Because of the incredible adoption rate of new iOS releases, there is no harm in doing this. Some people think that they miss out on a large chunk of the market, but that is not true. Take the release of iOS 6 as an example. One month after the release of iOS 6, more than 60% of iOS devices had upgraded to the new version of iOS. Unfortunately, the same isn’t true for Android.


3. Assets

Step 1: Icons

You probably know that an application icon is a vital component of every iOS application, but you need to make sure that your application ships with the correct sizes of the artwork. Take a look at the list below for an overview.

  • iTunes Artwork: 1024px x 1024px (required)
  • iPad/iPad Mini: 72px x 72px and 114px x 114px (required)
  • iPhone/iPod Touch: 57px x 57px and 114px x 114px (required)
  • Search Icon: 29px x 29px and 58px x 58px (optional)
  • Settings Application: 50px x 50px and 100px x 100px (optional)

It goes without saying that you don’t need to include an application icon for the iPad/iPad Mini device family if your application only targets the iPhone/iPod Touch device family, and vice versa.

Step 2: Screenshots

Each application can have up to five screenshots and you must provide at least one. If you are developing a universal application, then you need to provide separate screenshots for iPhone/iPod Touch and iPad/iPad Mini. In addition, you can optionally include separate screenshots for the 3.5″ and the 4″ screen sizes of the iPhone/iPod Touch. This is quite a bit of work and you want to make sure that the screenshots show your application from its best side. Shiny Development sells a Mac application, Status Magic that helps you get the status bar in your screenshots right. Status Magic will save you quite a bit of time.

It is important to spend some time thinking about the screenshots. Your application’s screenshots are often the only thing that a customer can use to decide whether she purchases or downloads your application or not. What a lot of developers don’t know is that the screenshots don’t have to be actual screenshots. The hard rule is that the size of each screenshot needs to be that of the screen size of the target device. Many companies are creative with this rule. Take a look at the screenshots of Where’s My Water?, for example. By using this strategy, screenshots can be much more attractive and compelling.

Step 3: Metadata

Before you submit your application, it is a good idea to have your application’s metadata at hand. This includes (1) your application’s name, (2) the version number, (3) the primary (and an optional secondary) category, (4) a concise description, (5) keywords, and (6) a support URL. If you are submitting an update, then you can also provide information for the What’s new in this Version section.

Does your application require users to sign in? Then you also need to provide Apple with a test or demo account to make sure that the review team can immediately sign in and use your application without first having to sign up for an account.


4. Submission Preparation

The submission process has become much easier since the release of Xcode 4. You can now validate and submit an application using Xcode, for example. First, however, you need to create your application in iTunes Connect.

Visit iTunes Connect, sign in with your iOS developer account, and click Manage Your Apps on the right. Click the Add New App in the top left, select iOS App, and fill out the form.

How To Submit an iOS App to the App Store - Visit iTunes Connect to Get Started
Figure 3: Visit iTunes Connect to Get Started

Step 1: Basic Information

The App Name, which needs to be unique, is the name of your application as it will appear in the App Store. This can be different than the name that is displayed below your application icon on the home screen, but it is recommended to choose the same name. The SKU Number is a unique string that identifies your application. I usually use the application’s bundle identifier. The last piece of information is the Bundle ID of your application. This means selecting the (wildcard or explicit) App ID that you created earlier from the drop down menu.

How To Submit an iOS App to the App Store - Specifying Name, SKU Number, and Bundle ID
Figure 4: Specifying Name, SKU Number, and Bundle ID

Step 2: Price and Availability

In the next step, you specify your application’s price and availability. Apple works with price tiers so that you don’t have to specify a price for each country that Apple operates in. You can also specify in which stores your application should – or shouldn’t – be available. The information that you enter in this step can be modified once your application is live in the App Store. In other words, you can change the price and availability of an application without having to submit an update.

How To Submit an iOS App to the App Store - Specifying Price and Availability
Figure 5: Specifying Price and Availability

Step 3: Metadata

We’ve already covered the application’s metadata. The only aspect that I haven’t talked about yet is your application’s rating. Based on your application’s content and functionality, it is given a rating. This rating is not only useful for telling users about your application’s content and features, the rating is also used by the operating system for the parental controls features.

It is strongly recommended that you don’t try to outsmart the rating system. Apple is well aware of this and will reject your application if it doesn’t agree with the rating that you have set.

How To Submit an iOS App to the App Store - Entering Your Application's Metadata and Assigning a Rating
Figure 6: Entering Your Application’s Metadata and Assigning a Rating

Step 4: Ready to Upload Binary

Once your application’s metadata is submitted, you will be presented with a summary of your application. Under Versions, you should see the version that you submitted a moment ago. Click the View Details button and click the Ready to Upload Binary button in the top right. You are then asked one or more questions regarding your application and, if all went well, you should see a message telling you that you are now ready to upload your application binary. The status of your application has changed to Waiting for Upload.

How To Submit an iOS App to the App Store - Your Application's Summary
Figure 7: Your Application’s Summary

5. Uploading Binary

To submit your application, you need to create an archive of your application. You can only create an archive by building your application on a physical device. If you select the iOS Simulator in the active scheme, you will notice that the Archive option in Xcode’s Product menu is grayed out. Connect an iOS device to your Mac, select it in the active scheme, and select Archive from Xcode’s Product menu.

How To Submit an iOS App to the App Store - Archiving Your Application using Xcode
Figure 8: Archiving Your Application using Xcode

If all went well, you should now have an archive and Xcode’s Organizer should automatically open and show you the archive you just created. Select the archive from the list and click the Distribute… button on the right. From the options you are presented with, select Submit to the iOS App Store. After entering your iOS developer account credentials and selecting the Application and Code Signing Identity, the application binary is uploaded to Apple’s servers. During this process, your application is also validated. If an error occurs during the validation, the submission process will fail. The validation process is very useful as it will tell you if there is something wrong with your application binary that would otherwise result in a rejection by the App Store review team.

How To Submit an iOS App to the App Store - Archiving Your Application using Xcode
Figure 9: Archiving Your Application using Xcode
How To Submit an iOS App to the App Store - Submit Your Application to the iOS App Store
Figure 10: Submit Your Application to the iOS App Store
How To Submit an iOS App to the App Store - Enter Your iOS Developer Account Credentials
Figure 11: Enter Your iOS Developer Account Credentials
How To Submit an iOS App to the App Store - Select Application and Code Signing Identity
Figure 12: Select Application and Code Signing Identity
How To Submit an iOS App to the App Store - An Error is Shown if Validation Fails
Figure 13: An Error is Shown if Validation Fails

6. Waiting

If the submission process went without problems, your application’s status will change to Waiting for Review. It takes several days for Apple to review your application and the time it takes, tends to fluctuate over time. To get an idea of the average review times of iOS and Mac applications, I recommend visiting the website of Shiny Development (Dave Verwer). This will give you a good indication of how long the review process will take.


Conclusion

The submission process is quite lengthy for a new application, but submitting an update to the App Store is much less cumbersome. Keep in mind that the submission process is much more involving if your application is localized in various languages as your application’s metadata needs to be localized as well. However, localizing your application is well worth the effort as it often results in higher sales and positive customer feedback.

Try Tuts+ Premium, Get Cash Back!

$
0
0

Try Tuts+ Premium and get cash back on a monthly subscription.

At $19 a month, Tuts+ Premium is fantastic value. But it’s even better when we hand your first $19 right back to you! For a limited time, we’re offering $19 cash back to new Tuts+ Premium monthly subscribers when signing up via PayPal.

If you’ve been thinking about checking out our extensive library of courses, tutorials, eBooks and guides, there’s never been a better time to join up and dive in.

But this offer ends at noon on the 20th of May AEST, so act fast.

Become a Tuts+ Premium Member to take your creative and technical skills to a new level.


What can you learn on Tuts+ Premium? Glad you asked! Currently, more than 15,000 members are sharpening their skills in a wide range of areas including web design, web development, Photoshop, vectors, video effects, and many more.

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)

Join now and get instant access to your very own library of courses, tutorials, and eBooks, available whenever you need them. Become part of a community of over 15,000 members and start getting better at the skills you care about. Our dedicated team adds new content weekly, so there’s always something fresh to sink your teeth into.

Become a Tuts+ Premium Member to take your creative and technical skills to a new level.

Build an AudioPlayer with PhoneGap: Application Tuning

$
0
0

This is the third and final part of the series about Audero Audio Player. In this article, I will go over the remaining files so that you can finish the project and play around with it.


Series Overview


Style Tuning

jQuery Mobile does a lot of the work for you by enhancing pages’ elements for devices with smaller screens. However, there are some that I don’t care for, and we’ll adjust these in our style.css file. This is also used to style the player’s markup.

By default, even if you don’t have buttons within the header or the footer of a page, the framework still reserves some space on both side of the elements and truncates long titles. This behavior is applied to other elements as well. We can change it simply by applying the rule white-space: normal !important; to our targeted elements as .ui-title.

The source of the stylesheet is shown here:

.ui-header .ui-title,
.ui-footer .ui-title,
.ui-btn-inner *
{
  white-space: normal !important;
}
.photo
{
  text-align: center;
}
#player-play,
#player-stop
{
  display: inline-block;
  width: 48px;
  height: 48px;
}
#player-play
{
  background-image: url('../images/play.png');
}
#player-stop
{
  background-image: url('../images/stop.png');
}
#time-slider
{
  display: none;
}
div.ui-slider-track
{
  margin: 0;
  width: 100%;
}

jQuery Mobile Custom Configuration

jQuery Mobile has a default configuration that should be good enough for most projects with simple requirements. However, there will be times when you will want to modify or take control of some default behavior. You can achieve this by writing a configuration file. The file jquery.mobile.config.js is exactly where we’ll have the configuration. Please note that you must include the configuration file before the jQuery Mobile files. When jQuery Mobile starts, it fires the mobileinit event, which is the one you must bind to override the default settings.

We’ll make the change by assigning values to the properties of the $.mobile object. I won’t change a lot of properties. I’ll instead change the option to have the text shown on the page loader widget, and the theme.

The full source of the file is listed below:

$(document).on(
   'mobileinit',
   function()
   {
      // Page Loader Widget
      $.mobile.loader.prototype.options.text = 'Loading...';
      $.mobile.loader.prototype.options.textVisible = true;
      // Theme
      $.mobile.page.prototype.options.theme  = 'a';
      $.mobile.page.prototype.options.headerTheme = 'a';
      $.mobile.page.prototype.options.contentTheme = 'a';
      $.mobile.page.prototype.options.footerTheme = 'a';
      $.mobile.page.prototype.options.backBtnTheme = 'a';
   }
);

Build Configuration

The Adobe PhoneGap Build service gives you the ability to specify the metadata of an application, like author and description, by using a configuration file. This file is called config.xml. Explaining the format in depth is outside the scope of this series, but I’ll give you a brief overview. If you want to read more on this topic, take a look at the official documentation page.

The config.xml file follows the W3C widget specification and must stay inside the app’s root, at the same level of the index.html file. Its first line is the XML declaration, and the root of the document is a <widget> tag that has several possible attributes. The most important ones are id (the unique identifier for your project), and version (which specifies the version of the application). Inside the <widget> tag, you can write the metadata of your application. In our file we’ll use a lot of them, but the most important are the following:

  • name (required): The name of the application. It doesn’t have to be unique.
  • description (required): The description of the application. It’s particularly important because it will be shown in the app’s marketplace listing.
  • icon (optional): The icon to display on the devices that will install your app. If you do not specify it, the Cordova logo will be used.
  • splash (optional): This tag sets the splash screen of the application, which is the image shown during loading.
  • feature (optional): Specifies the features you want to use. For example, Android, before installing any app, shows the user the permissions it requires and, if the user agrees, it goes on.
  • preference (optional): A set of preferences you want to apply to your app. It’s a closed tag and you can have zero or more <preference> tags inside the file. It has two attributes, and both are required: name and value. There are a lot of preferences that you can define, but the most important one in my opinion is specifying the Cordova version used.

The <access> tag is also very important because, to cite the documentation, it provides your app with access to resources on other domains – in particular, it allows your app to load pages from external domains that can take over your entire webview. Recalling what we discussed in the section Managing External Links from the previous post, to open the external links in the Cordova WebView, we must add them to the app whitelist. Since our application won’t retrieve links from external and unsafe sources, we can shorten the process to allow for any external resource using the * special character. For example:

<access origin="*" />

I’ve pointed out the key points of the format, and now you can understand the source of the configuration file. The complete file is below:

<?xml version="1.0" encoding="UTF-8"?><widget xmlns     = "http://www.w3.org/ns/widgets"
        xmlns:gap = "http://phonegap.com/ns/1.0"
        id        = "com.audero.free.player.auderoaudioplayer"
        version   = "1.0.0"><name>Audero Audio Player</name><description>Audero Audio Player is a basic audio player that collects the audio files and then allows the user to listen to them. This app also enables you to update the list at any time to include other files that may have been downloaded after running the operation for the first time. You can also remove any unwanted audio from the list by clicking an icon on the right side of the song's name. The sound list is ordered alphabetically with letter dividers to organize and group the list items, and has a search box to filter the files.</description><author href="http://www.audero.it" email="aurelioderosa@gmail.com">Aurelio De Rosa</author><feature name="http://api.phonegap.com/1.0/network"/><feature name="http://api.phonegap.com/1.0/media"/><feature name="http://api.phonegap.com/1.0/file"/><feature name="http://api.phonegap.com/1.0/notification"/><preference name="phonegap-version" value="2.3.0" /><preference name="target-device" value="universal" /><access origin="*" /><!-- Icons --><icon src="icon.png" width="64" height="64" gap:role="default" /><icon src="images/icon-72x72.png" width="72" height="72" gap:platform="android" gap:density="hdpi" /><icon src="images/icon-96x96.png" width="96" height="96" gap:platform="android" gap:density="xhdpi" /><icon src="images/icon-72x72.png" width="72" height="72" gap:platform="ios" /><!-- Splash Screens --><gap:splash src="splash.png" /><gap:splash src="images/splash-160x220.png" gap:platform="android" gap:density="ldpi" /><gap:splash src="splash.png" gap:platform="android" gap:density="mdpi" /><gap:splash src="images/splash-450x650.png" gap:platform="android" gap:density="hdpi" /></widget>

Running the Application

In the last section, all the business logic, HTML and CSS files for the application were built, so now it’s time to set the entry functions for the application and play. The targeted function will be the initApplication() method of the Application class. It will run once Cordova is fully loaded, ensuring that you can safely call the Cordova APIs. To do this,  we’ll set initApplication() as a callback function for the deviceready event by adding the following code to the index.html file. You can see this by looking at the next snippet:

<script>
  $(document).on('pagebeforecreate orientationchange', Application.updateIcons);
  $(document).one('deviceready', Application.initApplication);</script>

Possible Improvements

You are now at the end of the project. That being said, every project has room for improvements and new feature, so before I conclude the series, I would like to suggest some of these to you.

The first feature that you can add is the internationalization (i18n) of the application. Our player doesn’t have much text, so translating it into other languages should be very easy. To translate the application, you can use the Globalization API, an API added to the core starting from version 2.2.0. In addition, a specific jQuery library like jquery-i18n-properties or jQuery-i18n would surely be useful for this feature.

Other minor suggestions are:

  • Allow the user to create playlist.
  • Create a “Play All” button to play all the songs in the list.
  • Create a ratings system for the audio so that the user can filter and order songs by rating.
  • Add a “Repeat” button so that the user can continue listening to the current song.

These suggestions are just some of the potential improvements you can make to the Audero Audio Player. Using the information from this tutorial and your own skills, you can do much, much more.


Conclusion

As you’ve seen throughout this series, you can build powerful and useful apps using web technologies and popular frameworks. Now it’s your turn to play around with this project. Try starting your own project to test what you learned in this series!

iOS SDK: Customizing Popovers

$
0
0

Popovers are a great way to display supplementary information in an iPad app. Inevitably, as with most iOS objects, a little customization goes a long way in creating a design that is unique and fits in with your own app. In this tutorial, we will build a basic popover, then explore customizations one at a time, giving you an easy-to-follow path to implement customizations in your own app.


1. Setting Up Your Project

Step 1

Launch Xcode and choose File > New > Project to create a new project. Select an iOS Single View Application and click Next.

Figure 1
Figure 1

Step 2

Fill in the text fields with your project name, organization name, and company identifier. Select iPad from the Devices drop down and make sure the box next to Use Automatic Reference Counting is checked. Leave the boxes for Use Storyboards and Include Unit Tests unchecked and click Next. Choose a location to save your file and click Create.

Figure 2
Figure 2

2. Adding a Navigation Controller

Step 1

Let’s use a navigation controller so that we can add a button to show the popover. Click on AppDelegate.m and find the application:didFinishLaunchingWithOptions: method. Add the following code to create a navigation controller and set it as the root view controller.

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = navController;

Step 2

Now we can add a plus button to the navigation bar. Click on ViewController.m and add the following code to the viewDidLoad method just below [super viewDidLoad];.

UIBarButtonItem *popoverButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                     target:self
                     action:@selector(showPopover:)];
self.navigationItem.rightBarButtonItem = popoverButton;

The UIBarButtonSystemItemAdd creates a plus button; we’ll add it to the right side of the navigation bar. Next we’ll implement the showPopover: method used as the selector.


3. Showing the Popover

Step 1

Before implementing the showPopover: method, let’s add a property for the popover controller. Click on the ViewController.h file and add the following property.

@property (nonatomic, strong) UIPopoverController *popController;

Step 2

Navigate back to the ViewController.m file and declare the showPopover: method in the class extension as demonstrated below.

@interface ViewController ()
- (void)showPopover:(id)sender;
@end

Step 3

Add the following code to define the method under @implementation.

- (void)showPopover:(id)sender
{
    if (self.popController.popoverVisible) {
        [self.popController dismissPopoverAnimated:YES];
        return;
    }
    UIViewController *contentViewController = [[UIViewController alloc] init];
    contentViewController.view.backgroundColor = [UIColor yellowColor];
    UIPopoverController *popController = [[UIPopoverController alloc] initWithContentViewController:contentViewController];
    popController.popoverContentSize = CGSizeMake(300.0f, 600.0f);
    self.popController = popController;
    [self.popController presentPopoverFromBarButtonItem:sender
                    permittedArrowDirections:UIPopoverArrowDirectionUp
                                    animated:YES];
}

First we check to see if the popover is already being shown on the screen. If it is visible, the popover is dismissed and the method returns. If the popover is not being shown on the screen, we create a view controller to be displayed in the popover. Then we create the popover controller and set its size. The last line of code tells the popover controller to present itself from the navigation bar button that was tapped – in this case the plus button -and allows the arrow direction to point up only. One benefit to using this method is that if the user taps another button on the navigation bar, the tap is passed through to the navigation bar.


4. Testing the Standard Popover

At this point, we have implemented a standard popover. Build and run your project and tap the plus button to see a basic popover appear. Let’s take a look at the basics of customizing its appearance.


5. Subclassing UIPopoverBackgroundView

Step 1

In order to customize the appearance of the popover, we’ll need to subclass UIPopoverBackgroundView. Click File > New > File, choose an iOS Cocoa Touch Objective-C Class, and click Next.

Figure 3
Figure 3

Step 2

Name the class PopoverBackgroundView and choose UIPopoverBackgroundView from the Subclass of drop down.

Figure 4
Figure 4

Step 3

There are two UIPopoverBackgroundView properties that need to be overridden. Add the following code to synthesize the arrow direction and arrow offset, overriding the setter and getter methods for these two properties.

@synthesize arrowDirection  = _arrowDirection;
@synthesize arrowOffset     = _arrowOffset;

Step 4

There are three class methods that need to be overridden. Let’s define some values to use with the methods.

#define kArrowBase 30.0f
#define kArrowHeight 20.0f
#define kBorderInset 8.0f

Step 5

Add the code below to override the arrowBase, arrowHeight and contentViewInsets methods.

+ (CGFloat)arrowBase
{
    return kArrowBase;
}
+ (CGFloat)arrowHeight
{
    return kArrowHeight;
}
+ (UIEdgeInsets)contentViewInsets
{
    return UIEdgeInsetsMake(kBorderInset, kBorderInset, kBorderInset, 		kBorderInset);
}

The arrowBase method determines the width of the arrow’s base, while the arrowHeight method determines the height of the arrow. The contentViewInsets method indicates how far from the edge of the background to display the content.

Step 6

Let’s add a background color so we can see the different pieces clearly. Add the following code inside the if statement in the initWithFrame: method.

self.backgroundColor = [UIColor grayColor];

6. Setting the Popover Background View Class Property

Before we can test the popover, we’ll need to import and set the popover controller’s popover background view class property. Click on the ViewController.m file and import the popover background view header file as demonstrated below.

#import "PopoverBackgroundView.h"

While we’re still in the ViewController.m file, add the following line of code just below where we created the UIPopoverController in the showPopover: method.

popController.popoverBackgroundViewClass = [PopoverBackgroundView class];

7. Testing the Popover Background View

Build and run the project and tap the plus button to see the popover. You can see that the standard popover has been replaced with the customizations we’ve added so far. The gray border around the popover shows the insets returned from the contentViewInsets method. You can adjust the insets as needed to achieve a desired look. We’ll draw an arrow later in the tutorial to display on the screen.


8. Setting the Shadows and Rounded Corners

The wantsDefaultContentAppearance method determines whether the default inset shadows and rounded corners are displayed in the popover. By returning NO, the popover background view will no longer show the default shadows and rounded corners, allowing you to implement your own. Add the following code to override the method.

+ (BOOL)wantsDefaultContentAppearance
{
    return NO;
}

Build and run the project and you will be able to see the difference.


9. Adding the Arrow

Step 1

We’ll need to create and manage the arrow ourselves; let’s declare a property for an image view that will display the arrow. Add the following code to the class extension.

@property (nonatomic, strong) UIImageView *arrowImageView; 

Now we can instantiate the image view. Replace the code inside the if statement in initWithFrame: with the following code.

self.backgroundColor = [UIColor clearColor];
UIImageView *arrowImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.arrowImageView = arrowImageView;
[self addSubview:self.arrowImageView];

Step 2

Let’s change the border inset by updating the the kBorderInset defined in PopoverBackgroundView.m with the following code.

#define kBorderInset 0.0f

Step 3

In order to draw the arrow, we’ll need to declare a method to perform the drawing. Add the following method declaration in the class extension in PopoverBackgroundView.m.

- (UIImage *)drawArrowImage:(CGSize)size;

Step 4

Now add the method definition under @implementation.

- (UIImage *)drawArrowImage:(CGSize)size
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    [[UIColor clearColor] setFill];
    CGContextFillRect(ctx, CGRectMake(0.0f, 0.0f, size.width, size.height));
    CGMutablePathRef arrowPath = CGPathCreateMutable();
    CGPathMoveToPoint(arrowPath, NULL, (size.width/2.0f), 0.0f);
    CGPathAddLineToPoint(arrowPath, NULL, size.width, size.height);
    CGPathAddLineToPoint(arrowPath, NULL, 0.0f, size.height);
    CGPathCloseSubpath(arrowPath);
    CGContextAddPath(ctx, arrowPath);
    CGPathRelease(arrowPath);
    UIColor *fillColor = [UIColor yellowColor];
    CGContextSetFillColorWithColor(ctx, fillColor.CGColor);
    CGContextDrawPath(ctx, kCGPathFill);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Instead of using an imported image, the code above programmatically creates the arrow.

Step 5

Each time the bounds of the popover background view subclass changes, the frame of the arrow needs to be recalculated. We can accomplish this by overriding layoutSubviews. Add the following code to PopoverBackgroundView.m.

- (void)layoutSubviews
{
    [super layoutSubviews];
	CGSize arrowSize = CGSizeMake([[self class] arrowBase], [[self class] arrowHeight]);
    self.arrowImageView.image = [self drawArrowImage:arrowSize];
    self.arrowImageView.frame = CGRectMake(((self.bounds.size.width - arrowSize.width) kBorderInset), 0.0f, arrowSize.width, arrowSize.height);
}

The frame of the arrow’s image view and the arrow’s image are calculated based on the bounds of the popover background view, the border’s insets, and the arrow’s base and height.


10. Testing the Popover

Build and run your project to see the customized popover. Even though the border inset is set to zero, the arrow is adjusted to line up with the inside edge of the right inset. By subtracting the border inset when determining the x coordinate for the image view frame for the arrow, we are able to align the image view appropriately.


Conclusion

This tutorial is designed to get you up and running with customizing a popover. There are many directions you can take the project from here; for example, adding another image view to display a custom border image. To do this, you would follow a similar pattern like we did when we created the image view for the arrow. Furthermore, you may want to customize the popover based on the arrow’s direction. In layoutSubviews, a series of if statements could help you test for the arrow’s direction so you could adjust the arrow accordingly. Leave a comment or question below if you have a specific direction you’d like to go with your customizations.

Viewing all 1836 articles
Browse latest View live