Level: Intermediate Noel Rappin (noelrappin@gmail.com), Vice President of Rails Development, Pathfinder Development
03 Jun 2008 The iPhone and iPod touch made Mobile Safari the most popular mobile browser in
the United States. Although Mobile Safari is more than adequate at rendering normal Web
pages, many Web developers created versions of applications aimed at the iPhone. This
"Developing
iPhone applications using Ruby on Rails and Eclipse" series shows how to use
Ruby On Rails on the server side to identify and serve custom content to Mobile Safari.
In the months since Apple released the iPhone and the iPod touch, Mobile Safari has
become the most commonly used mobile Web browser in the United States, and its market
share continues to increase. Because the iPhone's form factor and user interface (UI)
model are so different from other mobile browsers, many developers are choosing to
redesign their Web sites to support Mobile Safari's particular UI model.
The decision to create custom content for the iPhone is the middle ground between two
more extreme options. On the one extreme, you could do nothing. Mobile Safari's
tap-and-zoom interface is designed to allow users to easily browse Web sites even if
the sites were not designed for mobile devices. Apple takes this route on the theory
that iPhone users expect to access the full and complete Web. At the other extreme, you
could use the newly released iPhone software development kit (SDK) to place your
application on the iPhone natively. This gives you an immense amount of flexibility in
your UI, as well as access to iPhone features — such as the accelerometer or the camera
— that are impossible to use in a Web application. On the downside, the
overhead of creating a native SDK application is higher than creating a Web
application, and if you already have a Web application, creating a custom iPhone Web
version is the fastest way to get a clean iPhone UI into your users' hands.
This article shows how to build a Ruby on Rails application that dynamically recognizes
iPhone or iPod touch browsers (throughout this article, I refer to the iPhone — remember that everything here also applies to the iPod touch), while allowing Mobile
Safari users the option of seeing the full Web content if they want. The article also
focuses on the server-side structures needed to support serving separate content to
iPhone users and how to start to serve iPhone content. Part 2 of this "Developing
iPhone applications using Ruby on Rails and Eclipse" series focuses on how to give that content an iPhone look and feel.
Setting up your environment
This article uses Eclipse with the Aptana plug-ins for Ruby on Rails and iPhone
support. The Ruby on Rails plug-in provides Ruby- and Rails-specific syntax
highlighting, shortcuts, execution environments, etc. The iPhone plug-in provides a
preview environment for displaying your Web application in an iPhone-size viewport.
There are two options for acquiring the Eclipse/Aptana combination: You can add the
Aptana plug-ins into an existing Eclipse environment or you can download the Aptana
Studio, which is a derivative of Eclipse, and add the plug-ins from the startup screen
provided by Aptana. If you already have an Eclipse environment set up, do the typical
Eclipse plug-in search. Select Help > Software Updates > Find and Install
and add the plug-in URLs provided in the Resources section.
You need two Eclipse plug-ins to follow along. If you're using Eclipse for Rails
development, you probably already have the RadRails plug-in. You'll also be using the
iPhone development plug-in, which provides a mock iPhone screen for you to preview your
development in an iPhone-size viewport. Although this plug-in is designed for
previewing static HTML pages, it can also be configured to point to your Rails application. Figure 1 shows the plug-in in action.
Figure 1. The iPhone plug-in
The first thing you'll note about the plug-in display is that it's larger than an
actual iPhone. This is to maintain pixel-for-pixel compatibility — the plug-in
display has the same pixel dimensions as the iPhone display, but the iPhone has a much
greater pixel density. If you are on a Macintosh, you have two other options for an
iPhone simulator: iPhoney or the official iPhone simulator included in the iPhone SDK.
iPhoney is a dedicated application that provides a fake iPhone display similar to the
Aptana plug-in. The two displays differ in what they show when the site is larger than
the iPhone viewport. As you can see, the Aptana plug-in displays the content full size
and offers scroll bars. In iPhoney, the Web page is compressed to the size of the
viewport (more in keeping with the actual iPhone display of the page). Neither
application supports Mobile Safari's double-tap to zoom in and out, so there's no
complete substitute for testing on an actual iPhone.
If you have signed up and downloaded the official iPhone SDK, you can also use the
official iPhone simulator that is part of that package. It sends the correct user agent
and mimics all of Mobile Safari's behavior, including double-tap zoom. The only minor
disadvantages are that you have to be running Mac OS X V10.5 and because it's
simulating the entire iPhone operating system, you need to launch Mobile
Safari on startup. You also can't get an HTML source listing as you would be able to
from a desktop browser. If you can use it, it's the most faithful Mobile Safari simulator available.
Serving iPhone content
Suppose you are the proprietor of Soups OnLine, your online source for all things hot
and brothy. Your site looks great on a desktop, but you become aware that a growing
number of users need soup information on the road and are accessing the site via an
iPhone. Currently, your iPhone users see what is shown below.
Figure 2. Desktop view on iPhone
It's not horrible, and thanks to Mobile Safari's nice zoom UI, the user can navigate
the site. Still, it seems like the look could be cleaner and more directed to the needs of a mobile user.
I should mention here that Soups OnLine is the site created in my book Professional
Ruby On Rails. I use it here largely because it's a complete site that already
exists that I can mess around with and not step on somebody else's copyright. See Resources for code for the version of Soups OnLine
used in this article and the template the original version was based on.
To serve your mobile users, your Rails application needs to manage the
following:
- Detecting when a user accesses the site using an iPhone or an iPod touch.
- Allowing the user to freely switch between the mobile and regular versions of the site.
- Using a different layout for Mobile Safari users, including separate Cascading
Style Sheets (CSS) files and, possibly, JavaScript libraries.
- Serving different content to mobile users.
The code used to perform these tasks in this article can be used as-is. It has also
been gathered into a Rails plug-in called rails_iui, which you can add to your
project to get all the same functionality in a single package.
Detecting Mobile Safari users
To serve iPhone custom content, your Rails application needs to be able to recognize an
iPhone. From the server side, the primary means of identification is the user-agent
string sent by the browser to the server. The iPhone user-agent string looks something
like this (version numbers will change over time):
Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML,
like Gecko) Version/3.0 Mobile/1A543 Safari/419.3
|
The iPod touch user string differs in that the beginning of the parenthesized
expression is iPod, rather than iPhone. Apple, for what it's worth, recommends using the WebKit
build number (in this example AppleWebKit/420+) to determine
if new tags or CSS settings are available. (If you are testing client-side, rather than
server-side, Apple recommends not using the user-agent string at all but
testing for the existence of specific features.)
Inside Rails, you want to be able to identify an iPhone user agent in the
ApplicationController for eventual use in a
before filter. You can't
just search for "iPhone" in the string because you'll miss the iPod touch. At the
moment, it seems like the most future-proof way to identify iPhone users is by matching
"Mobile" and "Safari," like so:
def is_iphone_request?
request.user_agent =~ /(Mobile\/.+Safari)/
end
|
Now the goal is to integrate the iPhone into the Rails V2.0 respond_to framework, such that iPhone is automatically treated as a
pseudo-MIME type, and you can augment your controllers as shown below.
Listing 1. Sample controller method that handles an iPhone
def index
@recipes = Recipe.find_for_index(params[:format])
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @recipes }
format.iphone # index.iphone.erb
end
end
|
This takes a couple of steps. First, add the following line to the config/initializers/mime_types.rb file:
Mime::Type.register_alias "text/html", :iphone
|
Recent versions of Rails actually have this line as a comment in the file, so you can
just uncomment it. For older versions of Rails that do not have a config/initializers
directory, you can place this line in config/environment.rb. This creates a custom iphone MIME type for use within your
application. Externally, the iphone type is treated as text/html, but internally, you can respond to the two types differently.
Back in the ApplicationController, add another
private method as a before filter.
Listing 2. Adding iPhone format as a
before filter
before_filter :set_iphone_format
def set_iphone_format
if is_iphone_request?
request.format = :iphone
end
end
|
At this point, all requests from an iPhone or iPod touch will be flagged with a request
format of :iphone. Note that nothing in this code so far is
specific to the site example I'm using, so you should feel free to add this snippet to any site you are working on.
If you are using the rails_iui plug-in, all you need to do is insert the following line
in your ApplicationController or in any controller you
want to respond to iPhone requests:
acts_as_iphone_controller
|
As mentioned, the Aptana plug-in doesn't send the user-agent string listed above.
However, Rails' naming conventions make for an easy workaround. Using the iphone extension on any URL (as in http://localhost:3000/recipies.iphone) automatically sets the
request format to :iphone and causes the iPhone content to
be served. This enables you to test your iPhone code in the Aptana simulator. If you
are using the rails_iui plug-in, changing the above command to acts_as_iphone_controller(true) places the application in a test
mode where all requests are treated as iPhone requests, making testing in the simulator or other browser a breeze.
Viewing iPhone content during development
I've mentioned four ways to view your iPhone optimized Web site while in development:
- Aptana's iPhone Eclipse plug-in
- iPhoney
- iPhone SKD simulator
- iPhone or iPod touch
Aptana's iPhone Eclipse plug-in
The Aptana plug-in has a few advantages. It's a cross-platform tool, running anywhere
Eclipse runs. It allows you to see mobile and classic versions of your site side by
side, and it integrates nicely with other Eclipse development tools. The plug-in allows
you to view your application in multiple browsers at one time, which is helpful if
you're targeting mobile and traditional users. Also, if you are using client-side
JavaScript on the iPhone, Aptana has a nice console feature that allows you to log
events from a connected iPhone and even to send JavaScript commands to the phone from
your applications. Since the focus of this article is server-side development, I won't discuss those features here.
On the downside, the Aptana plug-in is kind of heavyweight if you aren't using Eclipse
as your other development environment, pointing it to start at specific pages is a
little awkward, and it doesn't quite mimic actual iPhone behavior on sites wider than
the iPhone display. Another negative is that the Aptana plug-in does not identify
itself using the iPhone or iPod touch user-agent string. As described, this means you
need a workaround to view the iPhone content on Aptana.
With the Aptana plug-in installed, start a new project of type iPhone project and give
the project a name. The Aptana plug-in defaults to testing the static index.html page in the project. That's not actually what you want
here. You want it to go to the index page of your application. In the Properties window
for the iPhone project:
- Go to the HTML Preview tab and click Override workspace settings.
- In the Preview Type panel, click Use absolute URL.
- Enter the URL for your locally running Rails server. (The Rails project does not
have to be running within Eclipse. It just has to be running at whatever URL you give the plug-in).
When you're done, the whole thing should look like Figure 3.
Figure 3. Properties of the iPhone project
iPhoney
Using iPhoney is another option for previewing iPhone content. It's lighter-weight,
allows you to enter arbitrary URLs in the address bar, and compresses wide pages
accurately. The downside is that it is a Macintosh-only application. If you do use
iPhoney, be sure to set the preferences in the iPhoney menu to use the iPhone user-agent
string, which is not the default behavior.
iPhone SDK simulator
The iPhone SDK simulator works similarly. Launch the program and click the
Safari button to enter the browser. You can enter Web sites directly in the
address bar with the keyboard, although if you simply must, the iPhone soft keyboard
will also show up. A nice feature of this simulator is that it supports both Mobile
Safari's bookmarks and the "place on home screen" functionality. On the downside, since
it's not really a Safari simulator, it doesn't have an easy way to show the HTML source
being rendered, which all the other simulators can do easily.
iPhone or iPod touch
Finally, you can just use an actual iPhone or iPod touch. Networking issues are your
main obstacles here. If you are in the common situation of developing on a server that
sits behind a router and has an IP address in one of the private ranges (10.*.*.*,
172.16-31.*.*, or 192.168.*.*), the iPhone will only be able to see your development
server if it is connecting to the Internet via a local Wi-Fi network your server is on.
(You'll also have an easier time if you use the IP address directly.) If you can't set
that situation up (for example, if there's no available Wi-Fi network with permission
to reach your server), you'll need to deploy your application to a publicly available staging server to test it.
Try viewing your site
No matter which option you use to view the iPhone, try and hit a page on your site. In
this example, the page has been set to http://localhost:3000/recipes. (That should be recipes.iphone on the Aptana browser, or test mode if using the
rails_iui plug-in.) If you made the code changes described earlier, you see a blank
screen. This is expected behavior. (You can verify this by viewing the site in the
desktop browser of your choice — it'll work fine.) The before filter has
identified Mobile Safari, changed the response format, and is now attempting to serve
the response in line with Rails conventions. In this case, the Rails convention is to
try and find a respond_to block for iphone. If one is found, Rails looks for the layout.iphone.erb file
for the layout and then fills the interior of the page with the contents of app/views/recipes/index.iphone. Since none of that stuff exists yet,
Rails serves a blank page. Specifically, it serves a blank page because you haven't yet
told it that the recipe controller's index method should
respond to iphone-formatted requests.
 |
Creating an iPhone layout
When you know that your users are browsing your site with iPhones, you can make a
number of strong assumptions about their environments. At the moment, the Mobile Safari
ecosystem has only two devices with identically sized screens and browser software. The
visible viewing area at the top of a page on an iPhone is 320 pixels wide and 356
pixels high. The URL bar at the top of the page is an additional 60 pixels you
will get as your user scrolls down. Mobile Safari pages go right up to the edge of the screen on both sides with no margin.
The default behavior of Mobile Safari is to assume that the Web site is 980 pixels wide
and shrink it by about one-third to fit in the viewable area of the iPhone. This works
fine for many sites, but not if your site is unusually narrow and not if you are trying
to actually make your site fit the exact dimensions of the iPhone. Luckily, Mobile
Safari has a mechanism for you to specify exactly what width you want used to display your site.
You can use a special meta-tag to specify the properties of
the Mobile Safari viewport. Listing 3 shows what the tag looks like placed in the new
app/views/layouts/recipes.iphone.erb layout file.
Listing 3. Header file with Mobile Safari viewport tag
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Recipes: <%= controller.action_name %></title>
<meta name = "viewport"
content = "width = device-width, user-scalable = no">
<%= stylesheet_link_tag 'iphone' %>
</head>
<body>
<h1 id="header"><%= "Soups OnLine" %></h1>
<%= yield %>
</body>
</html>
|
The viewport meta-tag sets two properties of the viewport.
The first, width = device-width, tells Mobile Safari that
you want your site to be rendered using the current width of the device. The value can
also be set to any constant value between 200 and 10,000. The second property, user-scalable = no, shuts off the Mobile Safari double-tap zoom
behavior on the grounds that you're setting the site up to fit in the
iPhone view screen. In addition to these properties, you can also set the height of your page. If you want finer-grain control of user scaling
behavior, you can set an initial-scale, a minimum-scale, and a maximum-scale. All
three use 1.0 as the default and can range between 0.0 and 10.0.
The other iPhone-specific line in this partial layout file is the CSS stylesheet tag,
which specifies a new iPhone-specific CSS file. You may see some references that
recommend using conditional CSS for iPhone content. You can do that, but I find the
syntax kind of opaque. Since you know server-side what browser you're rendering to,
there's really no need — you can specify the exact file you want for this browser.
Listing 4 is the CSS file I created to handle the initial header for the iPhone version of Soups OnLine.
Listing 4. CSS file for Mobile Safari
h1, h2, h3 {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
font-weight: bold;
font-size: 175%;
margin: 0;
}
body {
margin: 0;
padding: 0;
}
#header {
width: 320px;
height: 40px;
margin: 0 auto;
background: url(/images/img02_iphone.gif) no-repeat;
}
|
There are only a few differences between this file and the matching entries in the
original CSS file. The font size of the h1 element is
smaller (actually, I hit upon the size I wanted through a little trial and error). The
h1 tag is now bold and has an explicit margin setting of
zero. The bold helps it stand out a bit more, and the zero margin brings it tight
against the upper left of the viewport. The dimensions of the #header ID class are now in line with the iPhone viewport, and I
manually changed the background image to fit in the space.
You also need to create the app/views/recipes/index.iphone.erb file, but for the
moment, you can leave it blank. With those changes in place, the iPhone version of the
site is under way. The header looks like Figure 4.
Figure 4. Soups OnLine header
On most of the simulators, you'll notice that rotating the viewport causes the image to
be scaled out to match the new 480-pixel width of the device. This is because you
specified that the device width should be used as the scaling dimension.
Opting in and out
The last thing to add is a mechanism for Mobile Safari users to opt out of the mobile
view and then opt back in. This allows a mobile user a chance to see the full
interface, which likely has more features (though it may be harder to navigate), then
switch back to the iPhone interface as desired. You don't want to set a similar link
for desktop users to opt in to the iPhone interface because in Part
2, you have Mobile Safari-specific CSS and JavaScript code there and the site won't work.
The mechanism is simple: You add links in the footer of the iPhone interface that set a
cookie specifying the user browser-type preference. Then you allow that preference to
override the user-agent string when determining what format to send out. First, add the
link. Change the body tag in the
app/views/layouts/recipes.iphone.erb file.
Listing 5. Layout body with opt-out link
<body>
<h1 id="header"><%= "Soups OnLine" %></h1>
<%= yield %>
<br/>
<%= link_to "Switch To Desktop View",
{:controller => "browsers", :action => :desktop},
:class => "mobile_link" %>
</body>
|
The link has a new CSS class, defined in the iPhone CSS file.
Listing 6. CSS code for opt-out link
.mobile_link {
font-size: 14px;
font-weight: bold;
font-family: Helvetica;
color: #00f;
text-decoration: none;
text-align: center;
display: block;
width: 320px;
}
|
Helvetica is the font of choice for iPhone system links. The font size is a little
smaller than recommended for body text, but this isn't supposed to be body text. The
other items will center the link in the viewport. The result looks like Figure 5.
Figure 5. Switch to desktop link
The next step is to set the cookie. As you can see from the link definition in Listing
5, I created a new BrowsersController controller class to
manage this code. Listing 7 provides the code for setting the cookie.
Listing 7. BrowsersController code for opt-in
and opt-out
class BrowsersController < ApplicationController
def desktop
cookies["browser"] = "desktop"
redirect_to recipes_path
end
def mobile
cookies["browser"] = "mobile"
redirect_to recipes_path
end
end
|
You can see that it's quite simple. It just sets the cookie and redirects back to the
page being used as the index page. After that, you need to change the set_iphone_format method
to take the browser preference into account.
Listing 8. Setting iPhone format with opt-out
def set_iphone_format
if is_iphone_request? or request.format.to_sym == :iphone
request.format = if cookies["browser"] == "desktop"
then :html
else :iphone
end
end
end
|
There are actually two changes here. The initial if
statement adds the clause request.format.to_sym == :iphone.
This allows URLs that are iPhone requests via the .iphone
extension to also have the opportunity to have their format overridden by the cookie.
This is primarily there to allow this code to be tested on the Aptana simulator. After
that, the request.format is set based on whether the user
cookie has the value desktop, as set in the BrowserController method.
The only remaining task is to add the opt-out link to the desktop view. You only want
iPhone users to see this, so start by adding the following line to the ApplicationController:
helper_method :is_iphone_request?
|
This line makes the is_iphone_request? method a helper
method that is callable from any view. Specifically, you can use it to add the content
of Listing 9 to the end of the original desktop layout in the app/views/layouts/recipes.html.erb file.
Listing 9. Conditionally placing tag if iPhone is present
<% if is_iphone_request? %>
<%= link_to "Switch To Mobile Safari View",
{:controller => "browsers", :action => :mobile},
:class => "big_link" %>
<% end %>
|
This addition sets the analogous link to the desktop browser. It also adds a CSS class
to the desktop CSS file (in this case, public/scaffold.css).
Listing 10. CSS listing for desktop opt-in link
.big_link {
font-size: 40px;
font-weight: bold;
font-family: Helvetica;
color: #00f;
text-decoration: none;
text-align: center;
display: block;
width: 980px;
}
|
The CSS class adds a nice big hard-to-miss link at the bottom of the desktop pane. It
has to be really big because Mobile Safari is going to shrink it down to fit, and you
still want users to be able to see and click on it even if they don't zoom the display.
You can make it big and attention-getting because desktop users won't see this link. In
an iPhone display, the link looks like Figure 6.
Figure 6. Switch back to mobile link
The link sets the user cookie to mobile and redirects back
to the index page, where the link is interpreted as being for a mobile browser again.
Summary and look ahead
This article focused on the structure needed to support separating content for iPhone
and iPod touch users. It covered how to manage the viewport to display Mobile Safari
content at the right size and scale, and started to discuss what to place in the basic layout of your mobile site.
Part
2 covers how to display your content in a Mobile Safari browser. It also looks at
the UI guidelines for managing iPhone content and explores how to make those guidelines
come to life in your own Rails application.
Resources Learn
Get products and technologies
-
Visit the Aptana to download Aptana Studio, as well
as the RadRails and iPhone plug-ins.
-
Go to the Aptana Update Site to obtain the Aptana RadRails and iPhone plug-ins.
-
Explore iPhoney.
-
Check out the code for the version of Soups Online used in this article.
-
The design and CSS of the original Soups OnLine site were adapted from a template at FreeWebTemplates.com.
-
Check out the rails_iui plug-in.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
About the author  | |  | Noel Rappin is the vice president of the Rails practice at Pathfinder
Development (http://www.pathf.com), and has a decade of experience
with web application development. He has a doctorate from the Georgia
Institute of Technology, where he studied how to teach Object-Oriented
design concepts. He is the author of Professional Ruby on
Rails, and the co-author of wxPython in Action and
Jython Essentials. Read more at
http://www.pathf.com/blogs and http://10printhello.blogspot.com. |
Rate this page
|