Appel d'une API REST Symfony 2 dans une application iOS avec Swift 1.2 et la librairie SwiftyJSON

On va créer une application iOS qui va parser les données d'un flux JSON d'une API REST crée avec Symonfy 2 et FOSRestBundle.

Publié le 23/06/2015

On reprend le tutoriel API REST avec FOSRestBundle Symfony 2 pour mise en place de l'API auquel on va faire quelques modifications.

L'environnement de développement pour l'application est Xcode 6.3.2.

L'objectif est de mettre en place d'une Class RestApiManager qui va exploiter la librairie SwiftyJSON. L'action réalisée par l'app est de charger l'utilisateur dont l'id est 1 dans une liste.

1 - Création du projet Xcode

Créer un nouveau projet, choisir le template iOS Application > Single View Application.

Configurer comme suit :

Product Name : FreshApiClient

Language : Swift

Devices : iPhone

2 - Ajout de la class SwiftyJSON.swift

Télécharger la librairie SwiftyJSON sur GitHub à l'adresse suivante :

https://github.com/lingoer/SwiftyJSON

3 - Création de la class RestApiManager

La classe RestApiManager va appeler l'API REST de notre back-end Symfony via la méthode makeHTTPGetRequest(). La méthode getRandomUser() ne sert pas ici à grand chose étant donné que dans le cadre de cet exemple on force la réponse sur l'utilisateur 1.

A la racine du projet créer un fichier Swift "RestApiManager.swift".

Ajouter le code suivant :


import Foundation

typealias ServiceResponse = (JSON, NSError?) -> Void

class RestApiManager: NSObject {
    static let sharedInstance = RestApiManager()
    
    let baseURL = "http://api-test.local/users/1"
    
    func getRandomUser(onCompletion: (JSON) -> Void) {
        let route = baseURL
        makeHTTPGetRequest(route, onCompletion: { json, err in
            onCompletion(json as JSON)
        })
    }
    
    func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
        let request = NSMutableURLRequest(URL: NSURL(string: path)!)
        
        let session = NSURLSession.sharedSession()
        
        let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
            let json:JSON = JSON(data: data)
            onCompletion(json, error)
        })
        task.resume()
    }
}

4 - Edition du ViewController

Notre ViewController est chargé lui de la logique et de l'affichage avec la méthode addDummyData() qui va parser le flux JSON et ajouter l'utilisateur à la vue. Les méthodes tableView() définissent la vue.

Editer le fichier ViewController.swift qui est situé à la racine du projet.

Ajouter le code suivant :


import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var tableView:UITableView?
    var items = NSMutableArray()
    
    override func viewWillAppear(animated: Bool) {
        let frame:CGRect = CGRect(x: 0, y: 100, width: self.view.frame.width, height: self.view.frame.height-100)
        self.tableView = UITableView(frame: frame)
        self.tableView?.dataSource = self
        self.tableView?.delegate = self
        self.view.addSubview(self.tableView!)
        
        let btn = UIButton(frame: CGRect(x: 0, y: 25, width: self.view.frame.width, height: 50))
        btn.backgroundColor = UIColor.orangeColor()
        btn.setTitle("Charger utilisateur 1", forState: UIControlState.Normal)
        btn.addTarget(self, action: "addDummyData", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(btn)
    }
    
    func addDummyData() {
        RestApiManager.sharedInstance.getRandomUser { json -> Void in
            let results = json["results"]
            for (index: String, subJson: JSON) in results {
                let user: AnyObject = subJson["user"].object
                self.items.addObject(user)
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    tableView?.reloadData()
                })
            }
        }
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell
        
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
        }
        
        let user:JSON = JSON(self.items[indexPath.row])
        
        let picURL = user["picture"].string
        let url = NSURL(string: picURL!)
        let data = NSData(contentsOfURL: url!)
        
        cell?.textLabel?.text = user["username"].string
        cell?.imageView?.image = UIImage(data: data!)
        
        return cell!
    }
    
    
}

5 - Modification du Backend Symfony 2

On va retourner la réponse dans un format différent pour coller au parsing de notre application iOS et on va rajouter une propriété picture à notre user

5.1 - Ajout de la propriété picture à l'entité User

Dans le fichier src/Fresh/ApiTestBundle/Entity/User.php ajouter les lignes suivantes :


/**
     * @var string
     * @ORM\Column(name="picture", type="string", length=255)
     */
    private $picture;

/**
     * @return string
     */
    public function getPicture()
    {
        return $this->picture;
    }

    /**
     * @param string $picture
     */
    public function setPicture($picture)
    {
        $this->picture = $picture;
    }

Mettre à jour la base de données :

php app/console doctrine:schema:update --force

Ajouter dans les fixtures pour chaque utilisateur, une entrée pour le nouveau champs picture :

nano src/Fresh/ApiTestBundle/DataFixtures/ORM/LoadUserData.php


<?php

namespace Fresh\ApiTestBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Fresh\ApiTestBundle\Entity\User;

class LoadUserData implements FixtureInterface
{
    /**
     * Load data fixtures with the passed EntityManager
     *
     *
     */
    public function load(ObjectManager $manager)
    {
        $toto = new User();
        $toto->setUsername('toto');
        $toto->setPassword('toto');
        $toto->setEmail('toto@toto.org');
	  $toto->setPicture('http://api-test.local/uploads/toto.png');

        $titi  = new User();
        $titi->setUsername('titi');
        $titi->setPassword('titi');
        $titi->setEmail('titi@titi.org');
	  $titi->setPicture('http://api-test.local/uploads/titi.png');

        $manager->persist($toto);
        $manager->persist($titi);

        $manager->flush();
    }

}

Ajout de fichiers titi.png et toto.png de 45px par 45px, dans le dossier /web/uploads/.

On flush le cache de l'application :

rm -rf app/cache/*

5.2 - Modification de la méthode getUser du UsersController

On met à jour la structure de la réponse JSON pour coller avec le format parsé par notre app iOS.

nano src/Fresh/ApiTestBundle/Controller/UsersController.php


/**
     * @param User $user
     * @return array
     * @View()
     * @ParamConverter("user", class="FreshApiTestBundle:User")
     */
    public function getUserAction(User $user)
    {
        $resultsArr = array('results' => array(
            array('user' => $user)
        ));

        return $resultsArr;
    }

5.3 - Modification du serializer

On ajoute le champ afin qu'il apparaisse dans le flux JSON.

nano src/Fresh/ApiTestBundle/Resources/config/serializer/Entity.User.yml


Fresh\ApiTestBundle\Entity\User:
    exclusion_policy: ALL
    properties:
        id:
            expose: true
        username:
            expose: true
        email:
            expose: true
        picture:
            expose: true

6 - Test de l'application iOS

On va pouvoir builder l'app dans Xcode et la tester dans iOS Simulator.

Cliquer sur "Charger utilisateur 1" et on verra l'image et le username s'ajouter dans la liste.

Ressources :