Skip to main content

skip to main content

developerWorks  >  Open source | Web development  >

Developing iPhone applications using Ruby on Rails and Eclipse, Part 1: Serving content for iPhones

Detecting Mobile Safari from a Ruby on Rails application

developerWorks
Document options

Document options requiring JavaScript are not displayed


My developerWorks needs you!

Connect to your technical community


Rate this page

Help us improve this content


Level: Intermediate

Noel Rappin, 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 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.



Back to top


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



Back to top


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.



Back to top


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:

  1. Go to the HTML Preview tab and click Override workspace settings.
  2. In the Preview Type panel, click Use absolute URL.
  3. 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
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.



Back to top


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



Back to top


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



Back to top


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


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top