Mobile Zone is brought to you in partnership with:

Matt Raible has been building web applications for most of his adult life. He started tinkering with the web before Netscape 1.0 was even released. For the last 16 years, Matt has helped companies adopt open source technologies (Spring, Hibernate, Apache, Struts, Tapestry, Grails) and use them effectively. Matt has been a speaker at many conferences worldwide, including Devoxx, Jfokus, ÜberConf, No Fluff Just Stuff, and a host of others. Matt is a DZone MVB and is not an employee of DZone and has posted 149 posts at DZone. You can read more from them at their website. View Full User Profile

Developing an iOS Native App with Ionic

04.07.2014
| 6770 views |
  • submit to reddit

In my current project, I've been helping a client develop a native iOS app for their customers. It's written mostly in Objective-C and talks to a REST API. I talked about how we documented our REST API a couple days ago. We developed a prototype for this application back in December, using AngularJS and Bootstrap. Rather than using PhoneGap, we loaded our app in a UIWebView.

It all seemed to work well until we needed to read an activation code with the device's camera. Since we didn't know how to do OCR in JavaScript, we figured a mostly-native app was the way to go. We hired an outside company to do iOS development in January and they've been developing the app since the beginning of February. In the last couple weeks, we encountered some screens that seemed fitting for HTML5, so we turned back to our AngularJS prototype.

The prototype used Bootstrap heavily, but we quickly learned it didn't look like an iOS 7 app, which is what our UX Designer requested. A co-worker pointed out Ionic, developed by Drifty. It's basically Bootstrap for Native, so the apps you develop look and behave like a mobile application.

What is Ionic?
Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly interactive apps. Built with Sass and optimized for AngularJS.

I started developing with Ionic a few weeks ago. Using its CSS classes and AngularJS directives, I was able to create several new screens in a matter of days. Most of the time, I was learning new things: how to override its back button behavior (to launch back into the native app), how to configure routes with ui-router, and how to make the $ionicLoading service look native. Now that I know a lot of the basics, I feel like I can really crank out some code.

Tip: I learned how subviews work with ui-router thanks to a YouTube video of Tim Kindberg on Angular UI-Router. However, subviews never fully made sense until I saw Jared Bell's diagram.

To demonstrate how easy it is to use Ionic, I whipped up a quick example application. You can get the source on GitHub at https://github.com/mraible/boot-ionic. The app is a refactored version of Josh Long's x-auth-security that uses Ionic instead of raw AngularJS and Bootstrap. To keep things simple, I did not develop the native app that wraps the HTML.

Below are the steps I used to convert from AngularJS + Bootstrap to Ionic. If you want to convert a simple AngularJS app to use Ionic, hopefully this will help.

1. Download Ionic and add it to your project.

Ionic 1.0 Beta was released earlier this week. You can download it from here. Add its files to your project. In this example, I added them to src/main/resources/public. In my index.html, I removed Bootstrap's CSS and replaced it with Ionic's.

-    <link href="webjars/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
+    <link rel="stylesheet" href="css/ionic.css"/>
  </head>
-<body style="padding-top: 60px">
+<body>

Next, I replaced Angular, Bootstrap and jQuery's JavaScript references.

-    <script src="webjars/jquery/2.0.3/jquery.js"></script>
-    <script src="webjars/bootstrap/3.1.1/js/bootstrap.min.js"></script>
-    <script src="webjars/angularjs/1.2.13/angular.js"></script>
+    <script src="js/ionic.bundle.js"></script>
     <script src="webjars/angularjs/1.2.13/angular-resource.js"></script>
-    <script src="webjars/angularjs/1.2.13/angular-route.js"></script>
     <script src="webjars/angularjs/1.2.13/angular-cookies.js"></script>
What about WebJars?
You might ask - why not use WebJars? You can, once this pull request is accepted and an updated version is deployed to Maven central. Here's how the application would change.

2. Change from Angular's Router to ui-router.

Ionic uses ui-router for matching URLs and loading particular pages. The raw Angular routing looks pretty similar to how it does with ui-router, except it uses a $stateProvider service instead of $routeProvider. You'll notice I also added 'ionic' as a dependency.

-angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services'])
+angular.module('exampleApp', ['ionic', 'ngCookies', 'exampleApp.services'])
    .config(
-       [ '$routeProvider', '$locationProvider', '$httpProvider', function($routeProvider, $locationProvider, $httpProvider) {
+       [ '$stateProvider', '$urlRouterProvider', '$httpProvider', function($stateProvider, $urlRouterProvider, $httpProvider) {
 
-       $routeProvider.when('/create', { templateUrl: 'partials/create.html', controller: CreateController});
+           $stateProvider.state('create', {url: '/create', templateUrl: 'partials/create.html', controller: CreateController})
+               .state('edit', {url: '/edit/:id', templateUrl: 'partials/edit.html', controller: EditController})
+               .state('login', {url: '/login', templateUrl: 'partials/login.html', controller: LoginController})
+               .state('index', {url: '/index', templateUrl: 'partials/index.html', controller: IndexController});
 
-       $routeProvider.when('/edit/:id', { templateUrl: 'partials/edit.html', controller: EditController});
-       $routeProvider.when('/login', { templateUrl: 'partials/login.html', controller: LoginController});
-       $routeProvider.otherwise({templateUrl: 'partials/index.html', controller: IndexController});
-
-       $locationProvider.hashPrefix('!');
+       $urlRouterProvider.otherwise('/index');

3. Add Ionic elements to your index.html.

In contrast to Bootstrap's navbar, Ionic has header and footer elements. Rather than using a ng-view directive, you use an <ion-nav-view>. It's a pretty slick setup once you understand it, especially since they allow you to easily override back-button behavior and nav buttons.

-    <nav class="navbar navbar-fixed-top navbar-default" role="navigation">
-        <!-- lots of HTML here -->
-    </nav>
-
-    <div class="container">
-        <div class="alert alert-danger" ng-show="error">{{error}}</div>
-        <div ng-view></div>
-    </div>
+    <ion-nav-bar class="bar-positive nav-title-slide-ios7"></ion-nav-bar>
+    <ion-nav-view animation="slide-left-right">
+        <div class="alert alert-danger" ng-show="error">{{error}}</div>
+    </ion-nav-view>
+    <ion-footer-bar class="bar-dark" ng-show="user">
+        <button class="button button-assertive" ng-click="logout()">
+            Logout
+        </button>
+    </ion-footer-bar>

4. Change your templates to use <ion-view> and <ion-content>.

After routes are migrated and basic navigation is working, you'll need to modify your templates to use <ion-view> and <ion-content>. Here's a diff from the most complicated page in the app.

-<div style="float: right">
-   <a href="#!/create" class="btn btn-default" ng-show="hasRole('ROLE_ADMIN')">Create</a>
-</div>
-<div class="page-header">
-   <h3>News</h3>
-</div>
+<ion-view title="News">
+    <ion-content>
+        <ion-nav-buttons side="left">
+            <div class="buttons" ng-show="hasRole('ROLE_ADMIN')">
+                <button class="button button-icon icon ion-ios7-minus-outline"
+                        ng-click="data.showDelete = !data.showDelete"></button>
+            </div>
+        </ion-nav-buttons>
+        <ion-nav-buttons side="right">
+            <a href="#/create" class="button button-icon icon ion-ios7-plus-outline"
+               ng-show="hasRole('ROLE_ADMIN')"></a>
+        </ion-nav-buttons>
 
-<div ng-repeat="newsEntry in newsEntries">
-   <hr />
-   <div class="pull-right">
-       <a ng-click="deleteEntry(newsEntry)" class="btn btn-xs btn-default" ng-show="hasRole('ROLE_ADMIN')">Remove</a>
-       <a href="#!/edit/{{newsEntry.id}}" class="btn btn-xs btn-default" ng-show="hasRole('ROLE_ADMIN')">Edit</a>
-   </div>
-   <h4>{{newsEntry.date | date}}</h4>
-   <p>{{newsEntry.content}}</p>
-</div>
-<hr />
+        <ion-list show-delete="data.showDelete" on-delete="deleteEntry(item)"
+                  option-buttons="itemButtons" can-swipe="hasRole('ROLE_ADMIN')">
+            <ion-item ng-repeat="newsEntry in newsEntries" item="newsEntry">
+                <h4>{{newsEntry.date | date}}</h4>
+                <p>{{newsEntry.content}}</p>
+            </ion-item>
+        </ion-list>
+    </ion-content>
+</ion-view>

I did migrate to use an <ion-list> with delete/options buttons, so some additional JavaScript changes were needed.

-function IndexController($scope, NewsService) {
+function IndexController($scope, $state, NewsService) {
 
    $scope.newsEntries = NewsService.query();
 
+     $scope.data = {
+         showDelete: false
+     };
+
    $scope.deleteEntry = function(newsEntry) {
         newsEntry.$remove(function() {
              $scope.newsEntries = NewsService.query();
         });
    };
+
+     $scope.itemButtons = [{
+         text: 'Edit',
+         type: 'button-assertive',
+         onTap: function (item) {
+              $state.go('edit', {id: item.id});
+         }
+     }];
}
Published at DZone with permission of Matt Raible, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)