Posts tagged with iOS

Avatar-eric-london
Created by Eric.London on 2013-01-25
Tags:
New Comment
 
Please note: the content on this page orginates from ericlondon.com.
In this tutorial, I'll show how I created an iOS app to make a JSON API request to a Rails app and show the content as images in the iPad simulator.

In my rails app model file, I defined an "as_json" method to format the JSON output:
# file: app/models/image.rb

def as_json(options={})
  {
    :thumb_path => self.upload.url(:thumb),
    :image_desc => self.description.blank? ? self.title : "#{self.title}\n#{self.description}",
    :large_path => self.upload.url(:large),
  }
end


And in my images controller, I setup my index method for JSON output:
# file: app/controllers/images_controller.rb

class ImagesController < ApplicationController

  def index
    // ... snip...
    respond_to do |format|
      format.html {  }
      format.json { render json: @images }
    end

  end

end


Onto Xcode. I created a new project:

File > New > Project
- iOS > Single View Application; Next

I entered the following project options:
- Product Name: Pictures
- Organization Name: self
- Company Identifier: com.self
- Class Prefix: Pictures
- Devices: iPad
- Use Storyboards: yes
- User Automatic Reference Counting: yes
- Include Unit Tests: (optional, not used in this tutorial)

Click Next; Choose Location; Click Create

For this project, I decided to use CocoaPods to manage my third party objc libraries. In this case, primarily AFNetworking.

In the terminal, go to dir that contains the new .xcodeproj
example: /Users/Eric/Documents/Objective-C/Pictures

Create new file: .rvmrc (if you're using RVM)

rvm use --create ruby-1.9.3@iOS.Pictures


Create new Gemfile with contents:

source 'https://rubygems.org'
gem 'cocoapods'


Execute bundle to fetch gems

bundle


Create new Podfile with contents:

platform :ios, '6.0'
pod 'FMDB'
pod 'AFNetworking'
xcodeproj 'Pictures.xcodeproj'


Execute pod install to get objc libraries and setup Xcode workspace

pod install


You should now have the following file structure:

$ ls -1
Gemfile
Gemfile.lock
Pictures
Pictures.xcodeproj
Pictures.xcworkspace
Podfile
Podfile.lock
Pods


From now on, open "Pictures.xcworkspace" instead of "Pictures.xcodeproj". You'll see the following structure:
pictures workspace

To revise the default storyboard and add a new collection view controller:
- click on MainStoryboard.storyboard
- click on Pictures View Controller, and press delete
pictures view controller
- drag a Collection View Controller onto the canvas

Edit PicturesViewController.h

// change:
@interface PicturesViewController : UIViewController
// to:
@interface PicturesViewController : UICollectionViewController


Click on MainStoryboard.storyboard. Click on Collection View Controller (in Collection View Controller Scene). In the Utilities region, click on Identity inspector, enter "PicturesViewController" for the custom class.
pictures view controller class

Click on Collection View Cell.
collection view cell
File >> New >> File… >> in iOS >> Cocoa Touch >> Objective-C class >> Next
- Class: PicturesCollectionViewCell
- Subclass of: UICollectionViewCell
- Next >> Create
The above commands will create the files: PicturesCollectionViewCell.h and PicturesCollectionViewCell.m

Click on PicturesViewController.m, add:

#import "PicturesCollectionViewCell.h"


Click on MainStoryboard.storyboard. Click on Collection View Cell in Pictures View Controller Scene. In the Utilities region, click on Identity inspector, enter "PicturesCollectionViewCell" for the custom class
pictures collection view cell

Click on Pictures Collection View Cell in Pictures View Controller Scene. In the Utilities region, click on Attributes inspector, under Collection Reusable View, enter "pictureCell" in the Identifier field.
picture cell

Drag a new Image View object inside the collection view cell.

Click on the button to show the Assistant editor. This will allow you to connect elements from the interface builder to code in your classes. In the Pictures View controller scene, click on Image View. In the code window to the right, you should see the PicturesViewController.h file. You'll want to change that to the PicturesCollectionViewCell.h header file. Clicking on the class name will allow you to change the active file.
imageview select file

Hold down the control key and drag UIImage view object to the PicturesCollectionViewCell.h header file; release inside the @interface and @end lines of code.
imageview drag

In the popup, enter:
- Connection: Outlet
- Name: pictureImageView
- Type: UIImageView
- Storage: Weak
Click Connect.

The above commands will add the following IBOutlet code:

@property (weak, nonatomic) IBOutlet UIImageView *pictureImageView;


Now it's time to add the guts of the view controller code. Click on PicturesViewController.m file.

TIP:
With the Utilities View open, click on the Quick Help tab. If you click on "PicturesViewController" in the @interface PicturesViewController () line of code, the quick help section will provide a reference link to the class that the PicturesViewController inherits from: UICollectionViewController. Click on "UICollectionViewController Class Reference" link. In the "Conforms to" section at the top, click on UICollectionViewDataSource. As noted in the documentation, "An object that adopts the UICollectionViewDataSource protocol is responsible for providing the data and views required by a collection view.", so let's add those instance methods (many are marked as required). You can copy and paste the instance method declarations directly into your code, or simply start to type the method name and use Xcode's autocomplete functionality.

Add (or update) the following methods to the PicturesViewController.m file, before the closing @end line:

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 20;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    PicturesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"pictureCell" forIndexPath:indexPath];
    
    return cell;
}


At this point, you should be able to build and run the program, but you'll just see a blank (black) screen. The following steps are adjustments I made to better fit my images which are 188px x 188px. Click back on the MainStoryboard.storyboard, and click on Pictures Collection View Cell. Resize the cell to 188px x 188px.
view cell resize

I ensured the Image View was the same dimensions.
image view resize

To see the image layout in the simulator, click on the Attributes inspector and set the background for the Image view to white. Click on Collection View and click on size inspector, set the min spacing to 0. The above adjustments yield the following layout if you build/run the project:
ipad white cells

Now, it's time to make an API call and populate the image cells asynchronously. I opened PicturesViewController.m and added the following import at the top of the file:


#import "AFJSONRequestOperation.h"


I then added the following code to the viewDidLoad method after the line: [super viewDidLoad];


- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
        
    // URL/Request
    NSString *imagesUrl = [NSString stringWithFormat:@"http://pics.ericlondon.com/images.json"];
    NSURL *url = [NSURL URLWithString:imagesUrl];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[url standardizedURL]];
    
    // request parameters
    [request setHTTPMethod:@"GET"];
    [request setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
    
    // AF json request
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
            
            [self.collectionView performBatchUpdates:^{
                
                NSArray *visibleCells = [self.collectionView visibleCells];
                
                for (NSInteger i=0; i <[visibleCells count]; i++) {
                    
                    PicturesCollectionViewCell *cell = visibleCells[i];
                    
                    NSString *thumbPath = [NSString stringWithFormat:@"http://pics.ericlondon.com%@", JSON[i][@"thumb_path"]];
                    NSURL *thumbPathURL = [NSURL URLWithString:thumbPath];
                    NSData *imageData = [NSData dataWithContentsOfURL:thumbPathURL];
                    UIImage *image = [[UIImage alloc] initWithData:imageData];
                    cell.pictureImageView.image = image;
                    
                }
                
            } completion:nil];            
            
        }
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
            NSLog(@"ERROR: %@", error);
        }
    ];
    
    [operation start];
    
}


The above code makes an asynchronous JSON GET request to the Rails API which returns a JSON array containing image paths. The success block uses an objc block to update the image cells..
ipad with images

Next steps: sell app in Apple AppStore :)

Source code on GitHub
Avatar-eric-london
Created by Eric.London on 2011-06-19
Tags:
New Comment
 
Please note: the content on this page orginates from ericlondon.com.
In this article, I'll share some code that I wrote that uses Appcelerator Titanium to create a simple iPhone application that communicates with a Drupal site via SOAP. From the iPhone side of things I used the Suds Javascript SOAP client, and from Drupal, I created a nusoap soap server instance.

Let's start with the SOAP server instance. I created a module called "nusoap", and copied the lib folder from nusoap into it. I then created a file in the module's directory called "soap-server.php" and added the following code. For the sake of this article, I did not include the rest of the Drupal module's code.

<?php
// $Id$

// define namespace
define('NUSOAP_NAME_SPACE', 'erl.dev');

// define path to nusoap library file
$nu_soap_path = 'lib/nusoap.php';

// ensure nu_soap library exsists
if (!file_exists($nu_soap_path)) {
  die('An error has occurred initializing the soap server.');
}

// include nu_soap library
require_once ($nu_soap_path);

// create new soap server instance
$soap_server = new nusoap_server();

// configure wsdl
$soap_server->configureWSDL(NUSOAP_NAME_SPACE, 'urn:'. NUSOAP_NAME_SPACE);

$soap_server->register(
  // method name
  'login_fetch_data',
  // input args
  array(
    'name' => 'xsd:string',
    'pass' => 'xsd:string',
  ),
  // output args
  array(
    'return' => 'xsd:string'
  ),
  // namespace
  'uri:'. NUSOAP_NAME_SPACE,
  // SOAPAction
  'uri:'. NUSOAP_NAME_SPACE .'#login_fetch_data',
  // style
  'rpc',
  // use
  'encoded'
);

// change directory to docroot
chdir($_SERVER['DOCUMENT_ROOT']);

// drupal bootstrap
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

// process raw post data
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$soap_server->service($HTTP_RAW_POST_DATA);

/**
 * Define soap methods
 */

function login_fetch_data($name, $pass) {
  
  $params = array(
    'name' => $name,
    'pass' => $pass,
  );
  
  $account = user_authenticate($params);
  
  // check for error
  if (!is_object($account)) {
    // handle authentication error here
    // return something
  }
    
  // return user uid, or some other piece of data
  return $account->uid;
  
}
?>


The above code registers one soap server method "login_fetch_data" which accepts the arguments (name and pass), loads a full Drupal bootstrap, authenticates the user, and returns back the user's uid. By browsing to the soap server file directly, I can see nusoap's WSDL definition: (example: http://drupal.vm/sites/all/modules/custom/nusoap/soap-server.php)

SOAP Server Methods

To test if the soap server method was working, I used the follow PHP code snippet (which I pulled out of a test page callback):

<?php

// define path to nu_soap library, relative to module directory
define('NUSOAP_PATH', drupal_get_path('module', 'nusoap') .'/lib/nusoap.php');

// define namespace
define('NUSOAP_NAME_SPACE', 'erl.dev');

// include nu_soap library
require_once(NUSOAP_PATH);

// define wsdl path
$wsdl_path = 'http://' . $_SERVER['HTTP_HOST'] . base_path() . drupal_get_path('module', 'nusoap') . '/soap-server.php?wsdl';

// create new soap client instance
$soap_client = new nusoap_client($wsdl_path, true);

// check for error
$error = $soap_client->getError();
if ($error) {
  // handle error
}
  
$args = array(
  'name' => 'Eric.London',
  'pass' => 'super secret password',
);

// call soap server method
$result = $soap_client->call('login_fetch_data', $args);

$output = "";
$output .= "<pre>";
$output .= "SENT: ";
$output .= print_r($args, true);
$output .= "RECEIVED: ";
$output .= print_r($result, true);
$output .= "</pre>";
    
return $output;

?>


I then created a new Titanium iPhone project. In the Resources directory, I created a directory called "soap-test-includes" to contain the suds.js library, and the soap.js file which uses this library. The suds.js file is available in the Kitchen Sink Titanium project, if you choose to import it.

Titanium folder structure

I added the following code to the app.js file:


// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

// create tab group
var tabGroup = Titanium.UI.createTabGroup();

//
// create base UI tab and root window
//
var win1 = Titanium.UI.createWindow({  
    title:'Soap Test',
    backgroundColor:'#fff',
    url: 'soap-test-includes/soap.js'
});
var tab1 = Titanium.UI.createTab({  
    icon:'KS_nav_views.png',
    title:'Soap Test',
    window:win1
});

//
//  add tabs
//
tabGroup.addTab(tab1);  
 
// open tab group
tabGroup.open();


And, I added the following code to the soap.js include file:



// include suds soap library
Titanium.include('suds.js');

var window = Ti.UI.currentWindow;
var label = Ti.UI.createLabel({
  top: 10,
  left: 10,
  width: 'auto',
  height: 'auto',
  text: 'Contacting Drupal soap server...\n'
});

window.add(label);

// define soap params
var url = "http://drupal.vm/sites/all/modules/custom/nusoap/soap-server.php";
var callparams = {
  name: 'Eric.London',
  pass: 'super secret password'
};

// create new suds soap client
var suds = new SudsClient({
  endpoint: url,
  targetNamespace: 'erl.dev'
});

try {
  
  // make soap server call
  suds.invoke('login_fetch_data', callparams, function(xmlDoc) {
  
    // fetch results from method
    results = xmlDoc.documentElement.getElementsByTagName('login_fetch_dataResponse');
       
    // check if results exits
    if (results && results.length>0) {
  
      // output results 
      label.text += "User uid: " + results.item(0).text;      	 

    }
    else {
      // handle error     	
    }

  });
} catch(e) {
  Ti.API.error('Error: ' + e);
}


When I build and run the project, the iOS Simulator opens and shows the results of the soap connection!

iOS soap connection

As you can see, Titanium allows you to write an iPhone application without touching the native Objective-C code. In this example, all the code was written in javascript, with a PHP backend. Much of the javascript code I used is based on code found in the Kitchen Sink project; I suggest you check it out :)