Desde que soy usuario de OS X he intentado varias veces desarrollar alguna aplicación que pueda aprovechar todo el poder de la plataforma .NET y las delicias gráficas de las interfaces gráficas creadas con Cocoa y sus frameworks para Mac.
La necesidad de utilizar .NET sobre el sistema operativo impulsado por Steve Jobs está cubierta desde hace un buen tiempo por la gente de Mono y su implementación multiplataforma del CLR y gran mayoría de las librerías incluidas en el .NET Framework.
El problema radicaba en como poder interactuar de forma simple y comoda con una UI hecha en Cocoa y no con el - bastante sovietico - port de Windows.Forms que incluye Mono, o con librerías graficas como GTK que se ven bastante alienigenas dentro de la Mac.
Investigando un poco, encontré que existen varios proyectos que hacen de bridge entre el mundo de Mono y .NET y los frameworks provistos por Cocoa.
Inicialmente, utilicé la librería publicada en el mismo sitio de Mono: Cocoa#. El problema fue que, pese a su simplicidad, tenía demasiados problemas de performance y estabilidad, por lo que quedó descartada.
Hace no mucho leí de otro bridge que, apartentemente, tiene bastante aceptación: Monobjc. Estas librerías permiten utilizar tanto una interfaz creada en Cocoa y sus frameworks desde una aplicación .NET como exponer - con ciertas restricciones - clases hechas en .NET dentro de una aplicación nativa hecha en Cocoa y ObjectiveC.
En este caso voy a explicar, paso a paso, como crear una nueva aplicación que nos permita navegar por la web cuya (única) View esté desarrollada con Cocoa utilizando el Interface Builder y el Controller y (ausente) Model estén hechos en C#
Requerimientos
Para empezar vamos a crear una nueva View utilizando Interface Builder (se recomienda tener al menos conocimientos básicos sobre como utilizar Interface Builder).
Al abrir Interface Builder seleccionamos el template de Cocoa Application
Una vez creado el proyecto, podremos ver la ventana principal creada para la aplicación, su menú, el listado de objetos existentes, el inspector donde podrémos modificar propiedades de los controles y la librería de controles disponibles desde Cocoa
En la librería de controles buscamos los controles de WebView, TextField y Button y los agregamos a la ventana, como se ve en la imagen. Pueden modificar las propiedades de estos controles en el inspector para permitirles ajustar su tamaño de acuerdo al resizing de la ventana.
Con la ventana lista, agregamos un nuevo Object a la ventana que contiene la lista de objetos disponibles, y cambiamos el nombre de su clase por ObjectController. Este será el objeto que represente al Controller que crearemos desde C# mas adelante.
Dentro de las actions disponibles para el controller deberemos agregar "browse:" que corresponderá a la acción de iniciar la navegación del URL escrito en el TextField anterior.
De la misma manera, deberémos exponer los Outlets "address", "window" y "browser" como se muestra en la imagen, los cuales asociaremos al TextField, la ventana y el WebView.
El último paso de esta parte será hacer el link entre las acciones y outlets definidos en el BrowserController y los controles creados dentro de la UI. Para hacer esto mantenemos apretada la tecla control y dibujamos una linea hacia cada uno de los controles. De esta manera aparecerá un menú para vincularlos hacia los outlets disponibles en el Controller.
De forma análoga se debe dibujar una linea desde el botón hacia el BrowserController, para asociar el click a la action "browse:"
Para asegurarse de haber vinculado todo de forma correcta, al hacer click derecho sobre el BrowserController se deberá ver un panel similar a este:
Con la interfaz lista, guardamos la ventana como un nuevo archivo NIB (ojo! guardar como NIB y NO como XIB!).
Ahora, a programar el controller y el bootstrap de la aplicación en C#
Para hacerlo, pueden utilizar cualquier IDE de su agrado. En particular, yo uso MonoDevelop.
Deberémos crear un proyecto el cual referencie a las assemblies de Monobjc, Monobjc.Cocoa y Monobjc.WebKit ya que son las que utilizamos dentro de la interfaz.
Dentro del proyecto crearemos dos clases: la primera será el bootstrap de la aplicación y la segunda sera el BrowserController
Para hacer el bootstrap deberémos crear una clase muy similar al siguiente ejemplo:
using Monobjc;
using Monobjc.Cocoa;
using System;
namespace zPod.CocoaBrowser
{
public class Program
{
public Program ()
{
}
public static void Main (String[] args)
{
ObjectiveCRuntime.LoadFramework ("Cocoa");
ObjectiveCRuntime.LoadFramework ("WebKit");
ObjectiveCRuntime.Initialize ();
NSApplication.Bootstrap ();
NSAutoreleasePool pool = new NSAutoreleasePool();
NSApplication.LoadNib ("MainWindow.nib");
NSApplication.RunApplication ();
}
}
}
Las primeras lineas del método Main cargan los frameworks de ObjectiveC que utilizaremos. En este caso "Cocoa" y "WebKit".
Luego de cargar los frameworks se inicializa el bridge con el runtime de ObjectiveC y se dispara la aplicación.
Como verán, antes de cargar el NIB se crea un nuevo NSAutoreleasePool. Esto sirve para evitar leaking por parte de ObjectiveC.
Finalmente llamamos al metodo LoadNib, el cual cargará la interfaz grafica creada anteriormente y lanzamos la aplicacion con el método RunApplication.
La creación del BrowserController es muy sencilla: en la misma hay que crear propiedades que corresponderán a los outlets definidos en la interfaz y metodos para atrapar las actions disparadas desde la misma.
Las clases marcadas con el atributo [ObjectiveCClass] serán expuestas al runtime de ObjectiveC. Las propiedades decoradas con el atributo [ObjectiveCField] representan los outlets y los métodos decorados con [ObjectiveCMessage] sirven para atrapar las actions disparadas por la View.
using System;
using Monobjc;
using Monobjc.Cocoa;
using Monobjc.WebKit;
namespace zPod.CocoaBrowser
{
[ObjectiveCClass]
public class BrowserController : NSObject
{
[ObjectiveCField]
public NSTextField address;
[ObjectiveCField]
public WebView browser;
[ObjectiveCField]
public NSWindow window;
public BrowserController ()
{
}
public BrowserController (IntPtr nativePointer) : base(nativePointer)
{
}
[ObjectiveCMessage("browse:")]
public void Browse (Id sender)
{
string url = address.Cell.Title;
browser.MainFrameURL = url;
}
[ObjectiveCMessage("awakeFromNib")]
public void AwakeFromNib ()
{
window.Title = "CocoaBrowser - zPod";
}
}
}
En este ejemplo, se ve el método AwakeFromNib, el cual se ejecutará al finalizar de cargar la vista. En el mismo, seteamos el título de la ventana.
Para atrapar la accion "browse", creamos un metodo con su correspondiente atributo y dentro del mismo cambiamos la URL del MainFrame del WebView.
Una vez listo esto, solo queda probar la aplicación.
Según la documentación de Monobjc, se tendría que poder ejecutar un script de NAnt que proveen ellos y crear una estructura de directorios específica. Dado que eso no me está funcionando (por algún motivo NAnt no encuentra a mono) armé un pequeño script de bash que arma todo lo necesario para tener nuestra App empaquetada y lista para ejecutar.
Aca dejo para bajar esta aplicación de ejemplo como proyecto de MonoDevelop, el cual al compilarse dejará en el directorio bin/Debug todos los archivos necesarios para armar el paquete. Lo único que hay que hacer es, desde la consola, ir al directorio /bin/Debug del proyecto y ejecutar:
./MakePackage CocoaBrowser
Con esto se generará un nuevo paquete de aplicación ejecutable, cuyo resultado es:
Ahora, a divertirse!
Saludos!
Zaiden