Sinds de introductie van de iPhone in 2007 door Apple, staat de smart phone definitief op de kaart, vooral in de consumentenmarkt. De BlackBerry van RIM was al een veelgebruikte zakelijke smart phone, maar Apple wist het grote publiek te bereiken met een stijlvolle, gebruikersvriendelijke, maar vooral coole smart phone. Het heeft even geduurd voordat er daadwerkelijk ook apps voor konden worden ontwikkeld, want de App Store die de iPhone groot heeft gemaakt, werd pas bij de tweede editie geintroduceerd.
Android volgde, en met Windows Phone 7 van Microsoft erbij hebben we nu meerdere grote mobiele platformen die strijden om de gunst van de consument. Deze concurrentie is goed voor de consument en uitstekend voor innovatie.
iOS is inmiddels toe aan zijn vijfde major release. Het wordt zichtbaar dat alle leveranciers features van elkaar aan het overnemen zijn en verder proberen te perfectioneren. Het gaat er niet meer zozeer om welke features je hebt, maar vooral hoe ze zijn geimplementeerd. Elk OS heeft zijn eigen stijl, zodat de keuze voor de consument alleen maar groter wordt en mensen de telefoon kunnen kiezen die past bij hun smaak en life style.
In de paar jaar dat iOS bestaat heeft Apple een stevig ecosysteem opgebouwd voor ontwikkelaars om apps te bouwen en te distribueren. De AppStore was een gouden greep, inmiddels gevolgd door alle concurrenten.
De Apple tools
Apple voert een developer programma waarbinnen je gratis kunt beschikken over developer tools. Deze bestaan uit XCode, de IDE waarin al van oudsher Mac OS applicaties konden worden ontwikkeld, Interface Builder, een grafische ontwerptool voor gebruikersinterfaces en de iPhone/iPad emulator. Voor $99 per jaar ben je officieel geregistreerd als Apple Developer, zodat je ook apps kunt publiceren in de AppStore. Maar om alleen te ontwikkelen en te oefenen hoef je nog niets te betalen (XCode 4 kost dan $4.99 om te downloaden).
Met iedere release van iOS verschijnt een nieuwe versie van de iOS SDK. Daarin zitten alle libraries en hulpmiddelen om iOS apps te ontwikkelen in XCode. Standaard doe je dat in Objective-C. Objective-C is een object georienteerde versie van ANSI C en inmiddels al behoorlijk wat jaartjes oud. Voor mensen met een C# of Java achtergrond kan deze taal er wat vreemd uitzien vanwege de blokhaken en afwijkende notatie van method calls (“messages” in de Objective-C wereld). Omdat de taal niet veel meer is dan ANSI C met een object georienteerd laagje erop, wordt de taal ook wel ervaren als “primitief” of spartaans. Dit uit zich vooral in het feit dat je bijvoorbeeld zelf memory management moet regelen, nog met pointers werkt, enzovoort.
Met Objective-C programmeer je tegen de libraries aan van het Cocoa Touch framework. Cocoa Touch biedt alle API’s die toegang bieden tot het iOS device. Je kunt dit vergelijken met de .NET base class libraries. Het bevat onder andere libraries als UIKit voor user interface, CoreData voor omgang met data, CoreLocation voor toegang tot GPS, enzovoort.
Hoe primitief we Objective-C als .NET developers ook ervaren, kennelijk is het toch een redelijk productieve taal, getuige het grote aantal apps in de AppStore. De vele fraaie voorbeelden van mooie, gelikte UI’s bewijzen ook de kracht van het Cocoa Touch framework.
Waarom C# op iOS?
Hoewel ik in mijn carriere vrijwel uitsluitend heb gewerkt met Microsoft technologie, was ik al snel verkocht toen ik de iPhone voor het eerst zag. Ik ben sindsdien fanatiek iPhone gebruiker en het ontwikkelen van apps voor dit prachtige device stond altijd op mijn lijstje om eens te proberen. Objective-C weerhield mij hier een beetje van. Hoewel ook ik mijn portie pointers en memory management heb gehad, liggen mijn C/C++ dagen al een tijdje achter mij. Ik was al verwend door de productiviteit van C# en .NET en de kracht van bijvoorbeeld de Garbage Collector, taalconstructies als LINQ, en frameworks als ADO.NET en WCF.
Zo dacht ook Miguel de Icaza, grondlegger van onder andere Mono. Mono is begonnen als de open source “Linux versie van .NET”, maar kan inmiddels worden beschouwd als de cross platform uitvoering van het .NET platform. Mono bestaat al heel wat jaartjes, en bevat inmiddels vele belangrijke features van .NET, zodat veel C# code hergebruikt kan worden op andere platforms dan Windows. De Icaza heeft Mono als basis gepakt en verenigd met Cocoa Touch. Het resultaat daarvan heet MonoTouch.
MonoTouch bestaat uit een SDK met libraries en tools waarmee in C# applicaties kunnen worden gebouwd tegen het Cocoa Touch framework. Daarvoor zijn Mono wrappers gemaakt. Nagenoeg alle classes in Cocoa Touch zijn beschikbaar gemaakt als C# classes. Daarnaast kan gebruik worden gemaakt van vele classes die in de .NET BCL beschikbaar zijn. De code wordt vervolgens gecompileerd naar een native app, waarbij een minimale MonoTouch “runtime” wordt meeverpakt voor o.a. de Garbage Collector.
Figuur 1: Van C# naar native app met MonoTouch
Apple verbiedt developers om in hun apps dynamisch code te genereren of te downloaden, wat betekent dat Just-In-Time compilatie van IL niet mogelijk is. Ook zaken als Reflection.Emit zijn niet mogelijk. MonoTouch apps worden daarom “Ahead-Of-Time” (AOT) gecompileerd. Daarbij kan de MonoTouch tool heel selectief Mono libraries meenemen of weglaten, om zo de omvang van de uiteindelijke app bundle te beperken. Figuur 1 toont hoe dit in zijn werk gaat. Een gemiddelde MonoTouch app is ongeveer 4 MB in omvang.
Met MonoTouch maakten De Icaza en zijn team het mogelijk om C# skills van Microsoft developers toe te passen voor iOS. De taal C# en alle krachtige features als LINQ, WCF en ADO.NET zijn beschikbaar om apps te bouwen.
Daarnaast is de filosofie van MonoTouch ook dat met een goede applicatie-architectuur een grote mate van hergebruik van code kan worden bereikt. Business logica, het ontsluiten van services, rekenlogica: deze code kun je hergebruiken in bijvoorbeeld een Windows Phone 7 app, of… op Android. De Icaza heeft hetzelfde trucje namelijk ook herhaald voor Android. Elders in dit magazine staat een artikel over Mono for Android van de hand van mijn collega Willem Meints. Ik raad je aan om ook zijn artikel te lezen om de kracht van deze tools te ontdekken.
Wat zit er in MonoTouch?
MonoTouch bevat alle taalfeatures van C# 4.0 (dus ook dynamics en Parallel Framework!). De Mono library is verder qua features vergelijkbaar met Silverlight 3+, exclusief de UI technologie (XAML).
Het idee achter MonoTouch is dat je beschikt over alle Mono libraries waarmee je het niet-zichtbare deel van de applicatie bouwt. Dus: business logica, data-afhandeling, in feite de onderdelen die je ook zou willen delen met andere platforms. Voor het zichtbare gedeelte, de UI, gebruik je wrappers voor de Cocoa Touch API. Door Cocoa Touch te gebruiken sluit je naadloos aan op het UI framework van iOS, zodat de look en feel van de app helemaal “native” aanvoelt. Ook het bouwen van de UI is daarmee volledig in lijn met Apple’s tools. Voor het definieren van schermen hergebruikt MonoTouch dan ook gewoon Apple’s eigen Interface Builder.
Deze filosofie spreekt mij zeer aan, omdat je op deze manier apps bouwt die helemaal passen in het OS, en ook de mogelijkheden van de UI volledig benutten. Vergelijk dit met cross platform tools als PhoneGap, die UI’s opleveren die op geen van de platforms echt native aanvoelen. Windows Phone 7 en iOS zijn zo onderscheidend qua stijl en user experience dat het onmogelijk is om een app te maken die op beide platforms de UI guidelines volgt.
Deze aanpak betekent wel dat je als C# developer kennis op moet doen van Cocoa Touch. De Apple documentatie van het framework is zeer uitgebreid en begrijpelijk. Het loont de moeite om die als referentiemateriaal te gebruiken bij het ontwikkelen van apps. Daarnaast is de community rondom MonoTouch enorm enthousiast en actief, zodat op internet veel blog posts, tutorials en voorbeelden te vinden zijn waarin Cocoa Touch patterns zijn uitgewerkt met MonoTouch.
Cocoa Touch
Apple maakt in Cocoa Touch veel gebruik van patterns en conventies. De meest prominente die je ook in MonoTouch zult tegenkomen is Model-View-Controller (MVC). Deze applicatie-architectuur bepaalt grotendeels welke classes je zult implementeren bij het bouwen van je app. Hieronder volgt een hele korte samenvatting van dit pattern om je te helpen begrijpen welke classes in de voorbeeldapplicatie de revue passeren:
· Het Model bevat de definitie van je data: de entiteiten en hun onderlinge relatie. Runtime bevindt zich hier ook je data, en zorg je voor persistent storage. Voor data-afhandeling kun je in Cocoa Touch gebruik maken van Core Data, maar in MonoTouch zal je vaker Collections en persistence classes zoals ADO.NET gebruiken vanwege de portabiliteit van je code.
· De View is het zichtbare deel van de applicatie. Het presenteert de data en invoerelementen aan de gebruiker en ontvangt invoer van de gebruiker via de iOS touch interface. Views leiden altijd af van de UIView base class.
· De Controller ontvangt de commando’s vanuit de view en voert logica uit die de data manipuleert en de View bijwerkt. In iOS wordt hiervoor de term ViewController gebruikt. ViewControllers leiden af van de UIViewController base class.
UIKit bevat diverse “voorgekookte” view en viewcontroller classes waarmee je snel applicaties in elkaar kunt zetten. Zo is er een UITableView om lijsten weer te geven, Veel schermen in iOS zijn feitelijk UITableViews vanwege de veelzijdigheid. De profielpagina in de Twitter applicatie is hier een voorbeeld van. Zie figuur 2.
Figuur 2: Enkele basis Cocoa Touch UI elementen gecombineerd op een scherm
In het schermvoorbeeld zie je ook de toepassing van twee veelgebruikte navigatie controllers. Het zwarte keuzemenu onderin is de UITabBarController. Per tab kun je een andere UIView tonen. De UINavigationController is ook een veel gebruikte base class voor navigatie. Deze class zorgt ervoor dat je UIViews achter elkaar kunt doorlopen, waarbij een navigatiestack wordt opgebouwd. Je kunt een UINavigationController herkennen aan de navigatieknoppen boven in beeld, waarmee je teruggaat in de navigatie, zoals in figuur 2. De UINavigationController handelt de navigatie af en zorgt daarbij ook voor de coole animaties bij schermovergangen.
Een app bouwen met MonoTouch
Laten we eens een app gaan bouwen. Om aan de slag te kunnen met MonoTouch heb je een aantal zaken nodig, namelijk:
· Een Mac met Mac OSX
· De iOS SDK 3.x inclusief iOS emulator en XCode 3.x (gratis).
· Het Mono framework; versie 2.10 is de meest recente, deze is ook gratis.
· De MonoDevelop IDE; een gratis, open source variant op Visual Studio, die o.a. werkt op Mac OSX en waarmee je met Mono kunt ontwikkelen.
· MonoTouch; een commercieel product van Novell. Je kunt met de gratis trial versie prima ontwikkelen en testen op de emulator. Als je op je device wilt deployen, of in de app store wilt publiceren, dan moet je de licentie aanschaffen.
Om te laten zien hoe krachtig het gebruik van Mono is, gebruik ik in de voorbeeld app een OData service. Het ontsluiten van web services in Objective-C, zowel SOAP als REST, kan vrij arbeidsintensief zijn. Er zijn wel open source libraries beschikbaar die het parsen van XML voor je afhandelen, maar het aanroepen van services blijft allemaal vrij ambachtelijk.
Als je Visual Studio en .NET gewend bent, dan wordt dat altijd netjes voor je geregeld met een service proxy… Je doet “Add service reference”, en voila, al je entities en service proxy logica wordt voor je uitgegenereerd. Dit is ook allemaal mogelijk in MonoDevelop. Sterker nog, je kunt een WCF of OData proxy die is gegenereerd voor een Windows Phone 7 app 1-op-1 hergebruiken in MonoTouch.
Om dit aan te tonen heb ik een business logic layer op basis van de publieke eBay OData service gemaakt in Visual Studio 2010. De proxy is als volgt gegenereerd op de command line:
DataSvcUtil.exe /out:"EBay.cs" /uri:"http://ebayodata.cloudapp.net"
Sinds Microsoft de .NET OData Client open source heeft gemaakt, is het ook onderdeel van Mono. Ik kan daarom de gegenereerde file “EBay.cs” overnemen in MonoTouch, een referentie toevoegen naar “System.Data.Services.Client” en vervolgens compileren.
De code voor de business logic layer die deze proxy gebruikt staat in Listing 1. Let op de LINQ-to-DataServices statements, en het gebruik van de gegenereerde entity classes. De OData Client vertaalt deze naar OData REST queries. Probeer dat maar eens in Objective-C!
using System;
using System.Linq;
using System.Collections.Generic;
using eBay.Model.Entities;
namespace EBayViewer
{
public class BusinessLogicLayer
{
private const string EBAY_URI =
"http://ebayodata.cloudapp.net/";
/// <summary>
/// Geeft alle categorieen met "Best offer"
/// </summary>
public static IEnumerable<Category> GetCategories()
{
var context = new EBayData(new Uri(EBAY_URI));
var categories = from c in context.Categories
where c.BestOfferEnabled == true
orderby c.Name
select c;
// Beperk de lijst omwille van snelheid...
return categories.Take(20).ToList();
}
/// <summary>
/// Geeft alle items in een specifieke categorie.
/// </summary>
/// <param name="categoryId">
/// Categorie-ID om op te filteren.
/// </param>
public static IEnumerable<Item> GetItemsByCategory
(string categoryId)
{
var context = new EBayData(new Uri(EBAY_URI));
var items = from i in context.Items
where i.PrimaryCategoryId == categoryId
orderby i.Title
select i;
return items.Take(20).ToList();
}
}
}
Listing 1: Implementatie van de business logic layer
Deze BusinessLogicLayer is volledig herbruikbaar in Windows Phone 7 of Mono for Android. Hoe complexer de logica in deze applicatielaag wordt, hoe waardevoller het wordt om deze code te hergebruiken.
Om de data te tonen aan de gebruiker heb ik een iPhone View with Controller aangemaakt in MonoDevelop, genaamd CategoriesViewController. Je krijgt drie bestanden cadeau: CategoriesViewController.xib, CategoriesViewController.xib.designer.cs en CategoriesViewController.xib.cs. XIB staat voor XML Interface Builder file. Dit is het bestandsformaat waarin Apple’s Interface Builder viewdefinities opslaat. Het .xib bestand bevat dus de view. Het designer.cs bestand is vergelijkbaar met wat je in .NET gewend bent: dit is de gegenereerde code behind, waarin de bindings naar de controls terecht komen. Dit is ene de helft van de ViewController. We zullen na het toevoegen van enkele controls zien welke daarin terecht komt.
Het laatste bestand, CategoriesViewController.xib.cs, is de andere helft van de ViewController waarin je de implementatie levert. MonoTouch maakt hierbij slim gebruik van C# features: de designer.cs en implementatie bevatten allebei een partial class declaratie, die samen de ViewController vormen. Op deze manier voelt het ontwikkelen met iOS helemaal als “thuis” voor de .NET developer.
Interface Builder is de grafische editor voor user interfaces. Deze tool moet je even leren kennen, maar zodra je de slag te pakken hebt, lijkt het erg op wat je van de XAML of WinForms UI editor gewend bent. We maken de view waarop we de eBay categorieen gaan tonen. Door op het .xib bestand te dubbelklikken in MonoDevelop, start automatisch Interface Builder op. Zie figuur 3.
In Interface Builder kun je items vanuit de Libary (toolbox) op het scherm slepen. Je bouwt hiermee een control-hierarchie op. Naast de view zelf vind je hierin twee onderdelen: de First Responder en de File’s Owner.
De First Responder is de class die als eerste in aanmerking komt voor het afhandelen van UI events. Zodra de gebruiker bijvoorbeeld een tekstvak (UITextField) selecteert, komt het toetsenbord in beeld en wordt het toetsenbord de First Responder. De File’s Owner is, zoals de naam al zegt, de class die “eigenaar” is van de view. In dit geval is dit de CategoriesViewController die we net hebben aangemaakt.
Wanneer je in Visual Studio een control op een scherm sleept, dan wordt automatisch een property toegevoegd in de code behind van je scherm. Hiervoor moet je in Interface Builder een stapje extra zetten. De connectie tussen view elementen en de ViewController noemen we een “outlet”. Via een outlet kan de controller communiceren met het element op de view of de view zelf.
Zo’n outlet moeten we toevoegen op de File’s Owner en vervolgens verbinden aan het item. Geef de outlet een zinvolle naam. Het type hoef je niet expliciet op te geven, maar dat doe ik zelf liever wel. Je kunt dan minder snel fouten maken bij het verbinden van outlets aan UI elementen. Een outlet verbind je aan een UI element door vanaf het Attributes scherm een lijntje te trekken naar het betreffende UI element. In figuur 3 zie je hoe dit gaat.
Figuur 3: Interface Builder
Naast outlets kent Cocoa Touch ook “actions”. Dit zijn events die worden afgevuurd zodra een gebruiker iets in de UI doet. Actions maak je op een soortgelijke manier aan als outlets. Je verbindt ze op dezelfde manier aan UI elementen, waarna je kiest aan welke gebeurtenis je de action wilt koppelen. Voor het afhandelen van het indrukken van een knop (UIButton), verbind je bijvoorbeeld met het TouchUpInside event dat de knop genereert.
Wanneer we nu de .xib opslaan, pikt MonoTouch automatisch de wijzigingen op, en werkt het de .designer.cs bij. Bijvoorbeeld voor de UITableView die onze lijst gaat tonen, vinden we nu een nieuwe property in de code onder de naam die we bij de outlet hebben opgegeven. In Listing 2 zie je dat in de gegenereerde categoriesTable property code wordt aangeroepen om de brug te leggen tussen de MonoTouch en de Objective-C wereld. In het gebruik van de property merk je hier vervolgens niets meer van.
[MonoTouch.Foundation.Connect("categoriesTable")]
private MonoTouch.UIKit.UITableView categoriesTable {
get {
this.__mt_categoriesTable =
((MonoTouch.UIKit.UITableView)
(this.GetNativeField("categoriesTable")));
return this.__mt_categoriesTable;
}
set {
this.__mt_categoriesTable = value;
this.SetNativeField("categoriesTable", value);
}
}
Listing 2: Gegenereerde code voor een outlet in een .xib.designer.cs file
We kunnen nu beginnen met de implementatie van de ViewController. De CategoriesViewController leidt af van UIViewController. We kunnen hierin een aantal methods overridden om in te haken in de lifecycle van de UI. Een aantal methods die worden aangeroepen door het OS zijn bijvoorbeeld:
· ViewDidLoad: wanneer de XIB is ingeladen door het OS, wordt deze method aangeroepen op de ViewController. Vanaf dit moment zijn de outlets op je ViewController geinitialiseerd en kun je ze gebruiken. Dit is het moment waarop je bijvoorbeeld event handlers kunt koppelen aan UI elementen.
· ViewWillAppear: deze method wordt aangeroepen net voordat de view zichtbaar wordt. Op dit moment kun je bijvoorbeeld langdurige acties op de achtergrond starten, zoals het communiceren met een service.
· ViewDidAppear: wordt aangeroepen zodra de view zichtbaar is geworden. Hier zou je bijvoorbeeld animaties kunnen starten, of UI elementen manipuleren.
In code voorbeeld 3 heb ik ervoor gekozen om ViewWillAppear te overriden. Hier roep ik de BusinessLogicLayer aan om de categorieen op te halen bij eBay. Dit is een service-operatie op de OData service, dus deze actie kan even duren. Om de UI responsive te houden, wil je dit soort acties liever op de achtergrond laten uitvoeren, net zoals je dat in Silverlight of op Windows Phone 7 gewend bent. Ik start daarom een nieuwe thread, waarin ik de categorieen ophaal. Ik doe dit met de classes uit de System.Threading namespace, dus op de “.NET manier”. Je kunt dit ook volledig met Cocoa Touch classes doen, door gebruik te maken van de MonoTouch.Foundation.NSThread class. Dit geldt voor heel veel code constructies. Zolang het code is die niet direct met de UI te maken heeft, raad ik aan om zo dicht mogelijk bij het .NET framework te blijven, omdat dit de herbruikbaarheid bevordert.
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
ThreadStart threadStart = delegate {
// Laat zien dat er netwerkactiviteit is; dit zie je
// in de statusbalk
UIApplication.SharedApplication
.NetworkActivityIndicatorVisible = true;
// Roep de business logic aan
var items = BusinessLogicLayer.GetCategories();
// De UI mag alleen worden bijgewerkt op de UI thread
InvokeOnMainThread(() => DisplayCategories(items));
};
Thread t = new Thread(threadStart);
t.Start();
}
// Property om de data in te bewaren en beschikbaar te
// stellen aan de view. Dit zou je in een Model class
// kunnen stoppen.
public IEnumerable<Category> Items { get; set; }
private void DisplayCategories
(IEnumerable<Category> categories)
{
// Bewaar de categorieen in een lokale property
Items = categories;
// Stop de netwerk indicator
UIApplication.SharedApplication
.NetworkActivityIndicatorVisible = false;
// Toon de items in de UITableView
categoriesTable.DataSource =
new CategoriesTableDataSource(this);
categoriesTable.Delegate =
new CategoriesTableDelegate(this);
categoriesTable.ReloadData();
}
Listing 3: Implementatie van de UIViewController
In de listing zie je dat voor het tonen van de items in de UITableView twee classes in het spel zijn: een CategoriesTableDataSource en een CategoriesTableDelegate.
Om data in een UITableView te tonen, gebruikt Cocoa Touch een UITableViewDataSource. Gebruikersinteractie met een UITableView wordt afgehandeld door een UITableViewDelegate.
In Objective-C heet dit een “protocol”, die je in een UIViewController kunt implementeren. Een protocol lijkt op een interface, in de zin dat het een aantal methods en/of properties voorschrijft, maar waar je voor een interface al deze items moet implementeren, kun je ervoor kiezen om een protocol slechts gedeeltelijk te implementeren. Om dit te laten werken in C# hebben de makers van MonoTouch ervoor gekozen om de DataSource en Delegate niet als interface te implementeren, maar als base class. Als je afleidt van base classes, kun je immers ook selectief zijn welke operaties je wel of niet wilt overriden. Members die je moet implementeren zijn abstract gemaakt, optionele members zijn virtual. Hiermee kom je dicht bij het karakter van het protocol in Objective-C.
Dit betekent wel dat je in MonoTouch voor de DataSource en Delegate aparte classes moet maken. Multiple inheritance is immers niet mogelijk in .NET. Vaak worden deze classes als geneste classes van de UIViewController geimplementeerd.
Eerst de DataSource: listing 4 toont de implementatie van deze class. Er zijn twee verplichte methods om te overriden: NumberOfSections en GetCell. NumberOfSections geeft het aantal items dat in totaal in de UITableView moet worden getoond. Ik heb een constructor gemaakt waarin ik een back-reference naar de UIViewController kan meegeven. Hierdoor kan de DataSource de Items property uitlezen en gebruiken om het aantal items te tellen en cellen te genereren.
GetCell moet UITableViewCell objecten genereren op basis van de data. Het OS geeft aan welke rij er getoond moet worden. Met deze index halen we het juiste item op uit de collectie in het model en maken we een cell aan. Cellen die niet zichtbaar zijn kunnen worden hergebruikt voor andere data. Op deze manier kun je geheugen besparen. Hiervoor bevat de UITableView de operatie DequeueReusableCell, waaraan je een identifier meegeeft voor het type cell dat je wilt recyclen. Ik maak hier een standaard UITableViewCell aan, waarop je een plaatje of een tekst kunt laten zien. Je kunt echter ook zelf een subclass maken met meer UI elementen en andere opmaak. De meeste Twitter clients zijn hier een voorbeeld van.
private class CategoriesTableDataSource:
UITableViewDataSource
{
private CategoriesViewController _controller;
public CategoriesTableDataSource
(CategoriesViewController controller)
{
_controller = controller;
}
public override int RowsInSection
(UITableView tableView, int section)
{
return _controller.Items.Count();
}
public override UITableViewCell GetCell
(UITableView tableView, NSIndexPath indexPath)
{
// Cellen die niet zichtbaar zijn doordat de view
// verschoven is, kunnen worden hergebruikt om
// geheugen te besparen
const string REUSE_IDENTIFIER = "CategoryCell";
var cell = tableView.
DequeueReusableCell(REUSE_IDENTIFIER);
if (cell == null)
{
// Is er geen herbruikbare cell, dan maken we er
// eentje aan.
cell = new UITableViewCell
(UITableViewCellStyle.Default, REUSE_IDENTIFIER);
cell.Tag = Environment.TickCount;
}
cell.TextLabel.Text = _controller.Items
.ElementAt(indexPath.Row).Name;
return cell;
}
}
private class CategoriesTableDelegate:
UITableViewDelegate
{
private CategoriesViewController _controller;
public CategoriesTableDelegate
(CategoriesViewController controller)
{
// Bewaar een referentie naar de UIViewController
_controller = controller;
}
public override void RowSelected
(UITableView tableView, NSIndexPath indexPath)
{
if (_controller.NavigationController != null)
{
var itemsList = new ItemsViewController();
itemsList.CategoryId = _controller.Items
.ElementAt(indexPath.Row).Id;
// Push de view controller op de navigatie stack.
// De tweede parameter geeft aan dat het met
// animatie gebeurt.
_controller.NavigationController.PushViewController
(itemsList, true);
}
}
}
Listing 4: DataSource en Delegate
Dan willen we nog dat bij het selecteren van een categorie, de applicatie navigeert naar een nieuwe pagina, waarop de items in die categorie worden getoond. Dit doe je vanuit de Delegate. In Listing 4 vind je ook de implementatie van de Delegate.
Ook hier gebruik ik weer een back-reference naar de UIViewController, zodat ik bij de NavigationController property kan komen. Je kunt dit wat meer ontkoppelen door bijvoorbeeld events te publiceren op de Delegate die je in de UIViewController afhandelt.
Door de RowSelected method te overriden, kunnen we actie ondernemen wanneer de gebruiker een item in de lijst aanraakt. In het voorbeeld instantieer ik een nieuwe UIViewController voor het weergeven van de items en geef ik een CategoryId property door als filter. Deze kan de ItemsViewController gebruiken om de juiste items op te halen bij eBay.
De navigatie wordt geregeld via de NavigationController property op de UIViewController. Deze wordt automatisch gekoppeld wanneer een UIView en UIViewController worden geladen binnen de context van een UINavigationController. Via de PushViewController method kunnen we naar een ander scherm navigeren. Je kunt hierbij aangeven of dit met of zonder animatie moet. De implementatie van de ItemsViewController lijkt verder op die van de CategoriesViewController.
Alle eindjes aan elkaar knopen
Nu we de views hebben gemaakt, willen we deze in een navigatiestructuur hangen, zodat ze worden getoond en de gebruiker door de schermen kan navigeren. Hiervoor gaan we naar het startpunt van de applicatie.
Zodra een iOS app is opgestart, wordt de controle overgelaten aan de AppDelegate. De AppDelegate vind je in Main.cs. Na het opstarten roept het OS de method DidFinishLaunching aan op deze class. Indien van toepassing krijg je in deze method allerlei state mee, die je kunt afhandelen. Bijvoorbeeld wanneer je push notificaties gebruikt in je app, dan krijg je de payload van zo’n notificatie mee in de “options” parameter. Deze handel je dan af in DidFinishLaunching.
In DidFinishLauching start je ook het hoofdscherm op. Hiervoor is bij het aanmaken van het MonoTouch project al een .xib aangemaakt: MainWindow.xib. Doorgaans regel je in die .xib de navigatiestructuur. Je kunt bijvoorbeeld een UITabBarController combineren met een UINavigationController op iedere tab, of alleen een enkele view tonen. Ik heb ervoor gekozen om een UINavigationController toe te voegen in Interface Builder. Zie figuur 4.
In het Attributes venster geef ik vervolgens op dat als Root View Controller onze CategoriesViewController moet worden ingeladen. Dit doe je op twee plekken: bij “NIB Name” (NIB is de binaire vertaling van een XIB) geef je aan waar de view vandaan moet komen. Je kunt een view en een viewcontroller volledig van elkaar gescheiden houden, maar in ons geval vullen we hier ook CategoriesViewController in. Verder moet je ook aangeven welke controller class moet worden gekoppeld aan de view. Onder Class Identity vullen we daarom ook CategoriesViewController in. Je kunt hier o.a. ook de titel manipuleren die in de navigatiebalk komt te staan. Hiermee hebben we declaratief in Interface Builder onze view in de navigatie opgenomen. Je kunt desgewenst ook alles vanuit code regelen.
Figuur 4: Een view controller koppelen aan een UINavigationController
Vervolgens heb ik voor de UINavigationController een outlet aangemaakt op de AppDelegate. In DidFinishLaunching hoef ik dan alleen nog te zorgen dat de view van de UINavigationController zichtbaar wordt. Listing 5 toont de implementatie van DidFinishLaunching.
// This method is invoked when the application has loaded
// its UI and its ready to run
public override bool FinishedLaunching
(UIApplication app, NSDictionary options)
{
// TODO: hier handel je o.a. notificaties af,
// en laad je settings in
window.AddSubview (navigationController.View);
window.MakeKeyAndVisible ();
return true;
}
Listing 5: Implementatie van DidFinishLaunching
De resulterende app kun je bewonderen in figuur 5.
Figuur 5: De resulterende app
Mag dat allemaal wel van Apple?
Apple staat bekend om hun strakke regie over alles wat er gebeurt op iOS en in de AppStore. In april 2010 heeft Apple de voorwaarden voor developers aangepast. Het kwam erop neer dat er alleen native applicaties in de AppStore werden toegelaten, en dat er gebruik moest worden gemaakt van Objective-C en XCode als ontwikkeltools. Iedereen zag dat als de doodsteek van onder andere MonoTouch en de cross compiler van Adobe. De wijziging leidde tot veel commotie in de developer community, waarna Apple besloot om deze sectie te versoepelen. De stelregel die ze nu hanteren is dat apps mogen worden gebouwd in eender welke ontwikkelomgeving, zolang ze maar geen code downloaden van het internet.
Overigens is in de tijd dat de restrictie van kracht was geen enkele MonoTouch app uit de AppStore geweigerd. De reden is dat een native app gemaakt met MonoTouch nauwelijks tot niet te onderscheiden is van een app geschreven in Objective-C.
Conclusie
MonoTouch is een krachtig framework dat de productiviteit, herbruikbaarheid en bekendheid van .NET naadloos verbindt met de kracht van Cocoa Touch. Het geeft .NET developers de gelegenheid hun vleugels uit te slaan naar iOS. Door een slim applicatie-ontwerp toe te passen kan bovendien een groot deel van de code worden hergebruikt met Windows Phone 7 en Android.
Links