Sunday, October 27, 2013

Creating Netflix-like UI for iOS with Xamarin.iOS and Nuclios

A Xamarin.iOS version of the XCode-based sample Creating a Netflix style iOS Grid with C#, Xamarin.iOS and NucliOS by Stephen Zaharuk.


While checking out my usual late afternoon reading stream (using Flipboard), I found this great article on how to put together a very sexy UI utilizing Infragistics's iOS Controls; it instantly occurred to me that this would be a great idea for an article. It was almost muscle memory, because I found my hands spinning a browser window open to my favorite Search engine - Bing.com - and typing the keywords "Nuclios Monotouch Bindings". To my surprise the product already ships with Xamarin.iOS bindings out of the box.
Innovative Functionality Meets iOS Familiarity
Take advantage of a powerful API that is familiar to all iOS developers. You can use NucliOS controls in native iOS projects built with Objective C and Xcode. You can even build in C#; with Xamarin.ioS support, NucliOS includes final MonoTouch bindings for all of our iOS controls.
And so the journey begins. Steve has done a great job of explaining in detail the implementation, so I won't bore you with the details. Instead I will highlight what things to keep in mind when "translating" code developed with XCode and Obj-C to the stuff dreams are made of (C# and Xamarin.iOS).
// Obj-C Implementation of the model 

@interface NetflixData : NSObject
       @property (nonatomic, retain)NSString* category;
       @property (nonatomic, retain)NSMutableArray* media;
       @property (nonatomic, assign)CGFloat scrollPosition;
@end 
1-Remember to add the [Export()] Attribute to your properties in your NSObject derived classes. This will allow the under-laying native implementation to use the properties of your Data Model.
public class NetflixMedia: NSObject
    {
        [Export("ImageUrl")]
        public string ImageUrl {
            get;
            set;
        }

        [Export("Title")]
        public string Title {
            get;
            set;
        }

        [Export("Genre")]
        public string Genre {
            get;
            set;
        }
    }
2 - Use the WeakDelagate property WeakDataSource
 //NucliosViewController.cs  - Obj-C
 _gridView.WeakDataSource = _ds;
...
Instead of..
//NucliosViewController.m  - Obj-C
_gridView.dataSource = _ds;
...
3- When creating creating overloaded constructors, remember to extract any initialization code to a separate method, so you can call it (and don't try to code on an empty stomach).
public class MediaCell:IGGridViewCell
{
    public MediaCell (string identifier): base(identifier)
    {
        Init ();
    }
4- This is not a required thing, but If you find something that just doesn't make sense to "translate" and you can make it better, then do it. That's the case of the code used to consume iTunes Web services. I wasn't going to simply translate line by line.
//Obj-C +(NSMutableArray*)generateData { NSMutableDictionary* mediaLookup = [[NSMutableDictionary alloc]init]; NSMutableArray* netFlixData = [[NSMutableArray alloc]init];
NSString* rootUrl = @"https://itunes.apple.com/search?term=%@&media=movie&entity=movie&limit=600&attribute=releaseYearTerm";

NSArray* keys = @[@"2012", @"2013"];

for(NSString* key in keys)
{
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:rootUrl, key]];

    NSData* data = [NSData dataWithContentsOfURL:url];
    NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
    NSArray* results = [json valueForKey:@"results"];

    if(results != nil)
    {
        for(NSDictionary* mediaInfo in results)
        {
            NSString* genre = [NSString stringWithFormat:@"%@", [mediaInfo valueForKey:@"primaryGenreName"]];

            NSString* url = [mediaInfo valueForKey:@"artworkUrl100"];

            MediaItem* media = [[MediaItem alloc]init];
            media.title = [mediaInfo valueForKey:@"trackName"];
            media.imgUrl = [NSURL URLWithString:url];

            MediaData* nfd = [mediaLookup valueForKey:genre];
            if(nfd == nil)
            {
                nfd = [[MediaData alloc]init];
                nfd.genre = genre;
                nfd.media = [[NSMutableArray alloc]init];
                [mediaLookup setValue:nfd forKey:genre];
                [netFlixData addObject:nfd];
            }

            [nfd.media addObject:media];
        }

    }
}


return netFlixData;
//C#
async Task> GetData () { var movies = new List (); var url = "https://itunes.apple.com/search?term={0}&media=movie&entity=movie&limit=600&attribute=releaseYearTerm";
        var client = new HttpClient ();

        var response = await client.GetAsync (string.Format (url, "2013"));
        var stringData = await response.Content.ReadAsStringAsync ();
        var json = JsonObject.Parse (stringData);

        var results = json["results"];

        foreach(JsonValue movie in results)
        {
            movies.Add (new NetflixMedia(){
                Title = movie["trackName"],
                ImageUrl = movie["artworkUrl100"],
                Genre = movie["primaryGenreName"],
            });

        }
        return movies;
    }

Our UI

Netflix  UI


Netflix

Wednesday, May 15, 2013

Creando un UI como la de Netflix para iOS con C#, Xamarin iOS y NucliOS


Este articulo es una adaptación del articulo en inglés "Creating a Netflix style iOS Grid with C#, Xamarin.iOS and NucliOS".
Repositorio en Github.
Mientras me relajaba un poco, leyendo un poco de mi lista de lectura (via Flipboard), encontré este interesante articulo de como crear una Interfaz de Usuario utilizando la libreria INucliOS de Infragistics. Casi instantaneamente se me ocurrió que sería una buena idea para un articulo a mi blog. De pronto, casi como segunda naturaleza ya mis manos se desplazaban hacia el teclado, abrieron una ventana de IE 10 y buscaron -vía Bing - por los terminos de busqueda "NucliOS MonoTouch Bindings". Para mi sorpresa, el producto ya viene con los "bindings" para Xamarin.iOS en la descarga oficial. 
Es aquí cuando comienza la travesía. Como parte del trabajo de este articulo está basado en un articulo publicado por Stephen Zaharuk, bajo el titulo "Creating a Netflix style iOS Grid", no les aburriré con los detalles; en vez, les monstraté como tomar el original y crear un UI similar en Xamarin.iOS sin mucho esfuerzo. 
Aquí están las cosas que deben de tener en cuenta cuando "traducen"  código en Obj-C y XCode al glorioso C# y Xamarin.iOS. 

// Obj-C Implementation of the model 

@interface NetflixData : NSObject
       @property (nonatomic, retain)NSString* category;
       @property (nonatomic, retain)NSMutableArray* media;
       @property (nonatomic, assign)CGFloat scrollPosition;
@end 

1- Recuerda decorar con el atributo[Export()] a las propiedades de tus objectos que derivande NSObject. Esto le permitirá a la implementación nativa usar las propiedades de tu modelo de datos.

public class NetflixMedia: NSObject
    {
        [Export("ImageUrl")]
        public string ImageUrl {
            get;
            set;
        }

        [Export("Title")]
        public string Title {
            get;
            set;
        }

        [Export("Genre")]
        public string Genre {
            get;
            set;
        }
    }


2 - Usa la propiedad WeakDelagate que se llama WeakDataSource
 //NucliosViewController.cs  - Obj-C
 _gridView.WeakDataSource = _ds;
...
Instead of..
//NucliosViewController.m  - Obj-C
_gridView.dataSource = _ds;
...
3- Cuando crees "constructores sobrecargados", recuerda extraer el código de inicialización a un metodo aparte (eso es puro sentido común), y por último no trates de escribir código con el estomago vacío, que el "Malcomío no piensa".
public class MediaCell:IGGridViewCell
{
    public MediaCell (string identifier): base(identifier)
    {
        Init ();
    }
4- Y por último, si encuentras algo escrito de forma que no hace sentido reproducirlo tal cual, luce lo que sabes y re-escribelo, y utiliza lo que está de punta (Async) y echa vainas a tus colegas.
//Obj-C
+(NSMutableArray*)generateData
{
NSMutableDictionary* mediaLookup = [[NSMutableDictionary alloc]init];
NSMutableArray* netFlixData = [[NSMutableArray alloc]init];

NSString* rootUrl = @"https://itunes.apple.com/search?term=%@&media=movie&entity=movie&limit=600&attribute=releaseYearTerm";

NSArray* keys = @[@"2012", @"2013"];

for(NSString* key in keys)
{
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:rootUrl, key]];

    NSData* data = [NSData dataWithContentsOfURL:url];
    NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
    NSArray* results = [json valueForKey:@"results"];

    if(results != nil)
    {
        for(NSDictionary* mediaInfo in results)
        {
            NSString* genre = [NSString stringWithFormat:@"%@", [mediaInfo valueForKey:@"primaryGenreName"]];

            NSString* url = [mediaInfo valueForKey:@"artworkUrl100"];

            MediaItem* media = [[MediaItem alloc]init];
            media.title = [mediaInfo valueForKey:@"trackName"];
            media.imgUrl = [NSURL URLWithString:url];

            MediaData* nfd = [mediaLookup valueForKey:genre];
            if(nfd == nil)
            {
                nfd = [[MediaData alloc]init];
                nfd.genre = genre;
                nfd.media = [[NSMutableArray alloc]init];
                [mediaLookup setValue:nfd forKey:genre];
                [netFlixData addObject:nfd];
            }

            [nfd.media addObject:media];
        }

    }
}


return netFlixData;

//C#
async Task<IList<NetflixMedia>> GetData ()
    {
        var movies = new List<NetflixMedia> ();
        var url = "https://itunes.apple.com/search?term={0}&media=movie&entity=movie&limit=600&attribute=releaseYearTerm";

        var client = new HttpClient ();

        var response = await client.GetAsync (string.Format (url, "2013"));
        var stringData = await response.Content.ReadAsStringAsync ();
        var json = JsonObject.Parse (stringData);

        var results = json["results"];

        foreach(JsonValue movie in results)
        {
            movies.Add (new NetflixMedia(){
                Title = movie["trackName"],
                ImageUrl = movie["artworkUrl100"],
                Genre = movie["primaryGenreName"],
            });

        }
        return movies;
    }

El resultado final