Compartir a través de


Multiplataforma

Compartir código de la interfaz de usuario en plataformas móviles con Xamarin.Forms

Jason Smith

Con Xamarin, puede usar C# para crear bonitas aplicaciones móviles nativas y compartir la mayoría del código entre plataformas. Tradicionalmente, había que diseñar una interfaz de usuario independiente para cada plataforma de destino. Pero con Xamarin.Forms, puede crear una interfaz de usuario que se represente de forma nativa en todas ellas.

Xamarin.Forms es una capa de abstracción de interfaz de usuario entre plataformas. Puede usarse para compartir la interfaz de usuario y el código de back-end entre plataformas, y aun así ofrecer una experiencia de interfaz de usuario completamente nativa. Puesto que son nativos, los controles y widgets tienen la apariencia de cada plataforma de destino.

Xamarin.Forms es totalmente compatible con el modelo de diseño Model-View-ViewModel (MVVM), por lo que se pueden enlazar elementos de página con propiedades y comandos en una clase de modelo de vista.

Si prefiere diseñar las páginas de forma declarativa, puede usar XAML, un lenguaje de marcado con características como diccionarios de recursos, recursos dinámicos, enlaces de datos, comandos, desencadenadores y comportamientos.

Xamarin.Forms tiene una pequeña API que es fácil de usar. Si necesita disponer de un mayor acceso a la interfaz de usuario nativa de una plataforma, puede crear vistas personalizadas y representadores específicos de la plataforma. Parece complicado, pero es simplemente una manera de obtener acceso a las interfaces de usuario nativas y hay muchos ejemplos en el sitio web de Xamarin que le ayudarán a conseguirlo.

Puede comenzar con cualquiera de las páginas, diseños y vistas ya preparados que se suministran con Xamarin.Forms. A medida que su aplicación empiece a crecer y descubra nuevos casos de uso y oportunidades de diseño, puede confiar en la compatibilidad de Xamarin.Forms con XAML, MVVM, los representadores personalizados específicos de la plataforma y una variedad de otras características, como las animaciones y las plantillas de datos.

Ofreceré una visión general de la funcionalidad de Xamarin con ejemplos específicos para ilustrar cómo se comparte la mayor parte de su código de interfaz de usuario entre las distintas plataformas de destino y se incorpora código específico de una plataforma cuando sea necesario.

Introducción

En primer lugar, abra el Administrador de paquetes de NuGet en Visual Studio o Xamarin Studio y compruebe si hay nuevas versiones de Xamarin.Forms. Dado que no recibirá notificaciones sobre las nuevas versiones solo por abrir una solución Xamarin.Forms en el IDE, la única forma de asegurarse de que dispone de las últimas mejoras es comprobar si existen paquetes de NuGet actualizados.

Crear una solución de Xamarin.Forms Cuando esté seguro de que tiene la versión más reciente de Xamarin.Forms, cree una solución de aplicación vacía (Xamarin.Forms Portable).

La solución tiene tres proyectos específicos de la plataforma y una biblioteca de clases portables (PCL). Cree las páginas en el PCL. Comience creando una página de inicio de sesión básica.

Usar C# para crear una página Agregue una clase al proyecto PCL. A continuación, agregue controles (denominados "vistas" en Xamarin), como se muestra en la figura 1.

Figura 1 Agregar vistas (controles)

public class LogInPage : ContentPage
{
  public LogInPage()
  {
    Entry userEntry = new Entry { Placeholder = "Username" };
    Entry passEntry =
      new Entry { Placeholder = "Password", IsPassword = true };
    Button submit = new Button { };
    Content = new StackLayout
    {
      Padding = 20,
      VerticalOptions = LayoutOptions.Center,
      Children = { userEntry, passEntry, submit }
    };
  }
}

Para mostrar la página cuando se inicia una aplicación, abra la clase MyApp y asigne una instancia de ella a la propiedad de MainPage:

public class MyApp : Application
{
  public MyApp()
  {
    MainPage = new LogInPage();
  }
}

Es un buen momento para explicar la clase Application. A partir de la v1.3.0, todas las aplicaciones de Xamarin.Forms contendrán esta clase. Es el punto de entrada de una aplicación de Xamarin.Forms y, entre otras cosas, ofrece eventos de ciclo de vida, así como un almacén de datos persistente (el diccionario Properties) para cualquier dato serializable. Si debe obtener una instancia de esta clase desde cualquier lugar en su aplicación, puede usar la propiedad estática Application.Current.

En el ejemplo anterior, eliminé el código predeterminado dentro de la clase application y lo reemplacé con una sola línea de código que hace que LogInPage aparezca cuando se ejecuta la aplicación. Aparece cuando se ejecuta la aplicación porque este código asigna una página (LogInPage) a la propiedad MainPage de la clase Application. Debe establecerla en el constructor de la clase Application.

También puede reemplazar los tres métodos de esta clase:

  • El método OnStart, al que se llama cuando una aplicación se inicia por primera vez.
  • El método OnSleep, al que se llama cuando la aplicación está a punto de entrar en un estado de segundo plano.
  • El método OnResume, al que se llama cuando una aplicación vuelve desde un estado de segundo plano.

En general, las páginas no son muy interesantes hasta que se conectan con algún tipo de datos o comportamiento, por lo que mostraré cómo hacerlo.

Enlazar una página con datos Si usa el patrón de diseño MVVM, podría crear una clase como la de la figura 2 que implemente la interfaz INotifyPropertyChanged.

Figura 2 Implementación de la interfaz INotifyPropertyChanged

public class LoginViewModel : INotifyPropertyChanged
{
  private string usrnmTxt;
  private string passWrd;
  public string UsernameText
  {
    get { return usrnmTxt; }
    set
    {
      if (usrnmTxt == value)
        return;
      usrnmTxt = value;
      OnPropertyChanged("UsernameText");
    }
  }
  public string PassWordText
  {
    get { return passWrd; }
    set
    {
      if (passWrd == value)
        return;
      passWrd = value;
      OnPropertyChanged("PassWrd");
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
  private void OnPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
    {
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Puede enlazar las vistas de la página de inicio de sesión con las propiedades de esa clase, como se muestra en la figura 3.

Figura 3 Vistas de enlace a la clase Properties

public LogInPage()
{
  Entry userEntry = new Entry { Placeholder = "Username" };
  userEntry.SetBinding(Entry.TextProperty, "UsernameText");
  Entry passEntry =
    new Entry { Placeholder = "Password", IsPassword = true };
  passEntry.SetBinding(Entry.TextProperty, "PasswordText");
  Button submit = new Button { Text = "Submit" };
  Content = new StackLayout
    {
      Padding = 20,
      VerticalOptions = LayoutOptions.Center,
      Children = { userEntry, passEntry, submit }
    };
  BindingContext = new LoginViewModel();
}

Para obtener más información sobre cómo realizar un enlace con datos en una aplicación Xamarin.Forms, consulte "From Data Bindings to MVVM" en el sitio de Xamarin bit.ly/1uMoIUX.

Usar XAML para crear una página En aplicaciones pequeñas, la creación de sus interfaces de usuario con C# es un enfoque perfectamente razonable. Sin embargo, a medida que crece el tamaño de la aplicación, es posible que deba escribir mucho código repetitivo. Puede evitar este problema mediante el uso de XAML en lugar de código C#.

Agregue un elemento de página XAML Forms a su proyecto de PCL. A continuación, agregue el marcado a la página, como se muestra en la figura 4.

Figura 4 Agregar marcado a una página XAML Forms

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
  x:Class="Jason3.LogInPage"
  xmlns:local="clr-namespace:XamarinForms;assembly=XamarinForms"> 
    <StackLayout VerticalOptions="Center">
      <StackLayout.BindingContext>
        <local:LoginViewModel />
      </StackLayout.BindingContext>
      <Entry Text="{Binding UsernameText}" Placeholder="Username" />
      <Entry Text="{Binding PasswordText}"
        Placeholder="Password" IsPassword="true" />
      <Button Text="Login" Command="{Binding LoginCommand}" />
    </StackLayout>
</ContentPage>

El XAML de la figura 4 le resultará familiar si ha escrito aplicaciones de Windows Presentation Foundation (WPF). Sin embargo, las etiquetas son diferentes porque hacen referencia a tipos de Xamarin.Forms. Además, el elemento raíz hace referencia a una subclase de la clase Xamarin.Forms.Element. Todos los archivos XAML de una aplicación Xamarin.Forms deben hacer esto.

Para obtener más información acerca del uso de XAML para crear páginas en una aplicación de Xamarin.Forms, consulte "XAML for Xamarin.Forms" en el sitio web de Xamarin bit.ly/1xAKvRN.

Diseño para una plataforma específica

Xamarin.Forms tiene un número relativamente pequeño de API en comparación con otras plataformas móviles nativas. Eso permite que sea más fácil diseñar las páginas, pero a veces Xamarin.Forms no representa una vista exactamente de la forma que desea en una o varias de las plataformas de destino.

Si se encuentra con esta barrera, simplemente cree una vista personalizada, que es sencillamente una subclase de cualquier vista que esté disponible en Xamarin.Forms.

Para que la vista aparezca en una página, amplíe al representador de vista. En casos más avanzados, puede incluso crear un representador personalizado desde cero. El código de representación personalizado es específico para una plataforma, por lo que no se puede compartir. Pero el coste de este enfoque puede valer la pena para incorporar características nativas y facilidad de uso para la aplicación.

Crear una vista personalizada En primer lugar, cree una subclase de cualquier vista que esté disponible en Xamarin.Forms. Este es el código para dos vistas personalizadas:

public class MyEntry : Entry {}
public class RoundedBoxView : BoxView {}

Ampliar un representador existente En la figura 5 se amplía el representador que se ocupa de la vista Entry para la plataforma de iOS. Esta clase se sitúa en el proyecto de la plataforma de iOS. Este representador establece el color y el estilo del campo de texto nativo subyacente.

Figura 5 Ampliación de un representador existente

[assembly: ExportRenderer (typeof (MyEntry), typeof (MyEntryRenderer))]
namespace CustomRenderer.iOS
{
  public class MyEntryRenderer : EntryRenderer
  {
    protected override void OnElementChanged
      (ElementChangedEventArgs<Entry> e)
    {
      base.OnElementChanged (e);
      if (e.OldElement == null) {
        var nativeTextField = (UITextField)Control;
        nativeTextField.BackgroundColor = UIColor.Gray;
        nativeTextField.BorderStyle = UITextBorderStyle.Line;
      }
    }
  }
}

Puede hacer cualquier cosa que desee en el representador porque se está haciendo referencia a las API nativas. Si desea ver el ejemplo que contiene este fragmento de código, consulte "Xamarin.Forms Custom Renderer" en bit.ly/1xTIjmR.

Crear un representador desde cero Puede crear un representador completamente nuevo que no amplíe ningún otro representador. Será necesario más trabajo, pero tiene sentido crear uno si desea realizar cualquiera de estas acciones:

  • Reemplazar el representador de una vista.
  • Agregar un nuevo tipo de vista a la solución.
  • Agregar un control nativo o una página nativa a la solución.

Por ejemplo, si desea agregar un control UIView nativo a una página en la versión de iOS de su aplicación, podría agregar un representador personalizado al proyecto de iOS, como se muestra en la figura 6.

Figura 6 Agregar un control UIView nativo a un aplicación de iOS con un representador personalizado

[assembly: ExportRendererAttribute(typeof(RoundedBoxView),
  typeof(RoundedBoxViewRenderer))]
namespace RBVRenderer.iOS
{
  public class RoundedBoxViewRenderer :
    ViewRenderer<RoundedBoxView,UIView>
  {
    protected override void OnElementChanged(
      ElementChangedEventArgs<RoundedBoxView> e)
    {
      base.OnElementChanged(e);
      var rbvOld = e.OldElement;
      if (rbvOld != null)
      {
        // Unhook any events from e.OldElement here.
      }
      var rbv = e.NewElement;
      if (rbv != null)
      {
        var shadowView = new UIView();
        // Set properties on the UIView here.
        SetNativeControl(shadowView);
        // Hook up any events from e.NewElement here.
      }
    }
  }
}

El patrón general que aparece en este representador garantiza que se pueda usar en los diseños virtualizados, como una vista de lista, que trataré más adelante.

Si desea ver el ejemplo que contiene este fragmento de código, consulte bit.ly/xf-customrenderer.

Agregar propiedades a una vista personalizada Puede agregar propiedades a una vista personalizada, pero asegúrese de hacerlos enlazables para que se puedan enlazar con propiedades en un modelo de vista o con otros tipos de datos. Aquí se muestra una propiedad enlazable en la vista personalizada RoundedBoxView:

public class RoundedBoxView : BoxView
{
  public static readonly BindableProperty CornerRadiusProperty =
    BindableProperty.Create<RoundedBoxView, double>(p => p.CornerRadius, 0);
  public double CornerRadius
  {
    get { return (double)base.GetValue(CornerRadiusProperty);}
    set { base.SetValue(CornerRadiusProperty, value);}
  }
}

Para conectar nuevas propiedades con el representador, reemplace el método OnElementPropertyChanged del procesador y agregue código que se ejecute cuando la propiedad cambie:

protected override void OnElementPropertyChanged(object sender,    
  System.ComponentModel.PropertyChangedEventArgs e)
{
  base.OnElementPropertyChanged(sender, e);
  if (e.PropertyName ==
    RoundedBoxView.CornerRadiusProperty.PropertyName)
      childView.Layer.CornerRadius = (float)this.Element.CornerRadius;
}

Para obtener más información acerca de la creación de vistas personalizadas y representadores personalizados, consulte "Customizing Controls for Each Platform" en bit.ly/11pSFhL.

Páginas, diseños y vistas: Los bloques de creación de Xamarin.Forms

He mostrado algunos elementos, pero hay muchos más. Este sería un buen momento para conocerlos.

Las aplicaciones de Xamarin.Forms contienen páginas, diseños y vistas. Una página contiene uno o varios diseños, y un diseño contiene una o más vistas. La vista term se utiliza en Xamarin para describir lo que podría utilizarse para llamar a un control. En total, el marco de Xamarin.Forms contiene cinco tipos de páginas, siete tipos de diseños y 24 tipos de vistas. Puede obtener más información sobre ellas en xamarin.com/forms. Comentaré algunos tipos de página importantes más adelante, pero primero dedicaré un momento a revisar algunos de los diseños que se pueden usar en las aplicaciones. Xamarin.Forms contiene cuatro diseños primarios:

  • StackLayout: StackLayout coloca los elementos secundarios en una sola línea que se puede orientar de forma vertical u horizontal. Puede anidar StackLayouts para crear jerarquías visuales complejas. Puede controlar cómo se organizan las vistas en un StackLayout con las propiedades VerticalOptions y Horizontal­Options de cada vista secundaria.
  • Cuadrícula: La cuadrícula organiza las vistas en filas y columnas. Este diseño es similar al que obtiene con WPF y Silverlight, salvo porque puede agregar espacio entre las filas y columnas. Puede hacerlo mediante las propiedades RowSpacing y ColumnSpacing de la cuadrícula.
  • RelativeLayout: RelativeLayout posiciona vistas mediante restricciones con respecto a otras vistas.
  • AbsoluteLayout: AbsoluteLayout permite colocar vistas secundarias de dos maneras: en una posición absoluta o proporcionalmente en relación con el elemento primario. Esto puede resultar útil si piensa crear una división y superpone las jerarquías. En la figura 7 se muestra un ejemplo.

Figura 7 Uso de AbsoluteLayout

void AbsoluteLayoutView()
{
  var layout = new AbsoluteLayout();
  var leftHalfOfLayoutChild = new BoxView { Color = Color.Red };
  var centerAutomaticallySizedChild =
    new BoxView { Color = Color.Green };
  var absolutelyPositionedChild = new BoxView { Color = Color.Blue };
  layout.Children.Add(leftHalfOfLayoutChild,
    new Rectangle(0, 0, 0.5, 1),
    AbsoluteLayoutFlags.All);
  layout.Children.Add(centerAutomaticallySizedChild,
    new Rectangle(
    0.5, 0.5, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize),
    AbsoluteLayoutFlags.PositionProportional);
  layout.Children.Add(
    absolutelyPositionedChild, new Rectangle(10, 20, 30, 40));
}

Observe que todos los diseños proporcionan una propiedad denominada Children. Puede utilizar esta propiedad para obtener acceso a miembros adicionales. Por ejemplo, puede utilizar la propiedad Children del diseño Grid para agregar y eliminar filas y columnas, así como para especificar las extensiones de las filas y columnas.

Mostrar datos en una lista de desplazamiento

Puede mostrar datos en una lista de desplazamiento mediante un formulario de vista de lista (denominado ListView). Esta vista funciona bien porque los representadores de cada celda de la lista están virtualizados. Puesto que cada celda está virtualizada, es importante que controle correctamente el evento OnElementChanged de los representadores personalizados que se creen para las celdas o las listas mediante un patrón similar al mostrado anteriormente.

En primer lugar, defina una celda, como se muestra en la figura 8. Todas las plantillas de datos de un control ListView deben utilizar una Cell como elemento raíz.

Figura 8 Definición de una celda de un control ListView

public class MyCell : ViewCell
{
  public MyCell()
  {
    var nameLabel = new Label();
    nameLabel.SetBinding(Label.TextProperty, "Name");
    var descLabel = new Label();
    descLabel.SetBinding(Label.TextProperty, "Description");
    View = new StackLayout
    {
      VerticalOptions = LayoutOptions.Center,
      Children = { nameLabel, descLabel }
    };
  }
}

A continuación, defina un origen de datos y establezca la propiedad ItemTemplate de la ListView en una nueva plantilla de datos. La plantilla de datos se basa en la clase MyCell creada anteriormente:

var items = new[] {
  new { Name = "Flower", Description = "A lovely pot of flowers." },
  new { Name = "Tuna", Description = "A can of tuna!" },
  // ... Add more items
};
var listView = new ListView
{
  ItemsSource = items,
  ItemTemplate = new DataTemplate(typeof(MyCell))
};

Puede hacerlo en XAML mediante el siguiente marcado:

<ListView ItemsSource="{Binding Items}">
  <ListView.ItemTemplate>
    <ViewCell>
      <StackLayout VerticalOptions="center">
      <Label Text="{Binding Name}" />
      <Label Text="{Binding Description}" />
      </StackLayout>
    </ViewCell>
  </ListView.ItemTemplate>
</ListView>

La mayoría de las aplicaciones contienen más de una página, por lo que deberá permitir que los usuarios se desplacen de una página a otra. Las páginas siguientes tienen compatibilidad integrada para la navegación de páginas y admiten la presentación de página modal a pantalla completa:

  • TabbedPage
  • MasterDetailPage
  • NavigationPage
  • CarouselPage

Puede agregar las páginas como elementos secundarios en cualquiera de estas cuatro páginas y obtener navegación de manera gratuita.

TabbedPage Una TabbedPage muestra una matriz de pestañas en la parte superior de la pantalla. Suponiendo que el proyecto PCL contiene páginas denominadas LogInPage, DirectoryPage y AboutPage, puede agregarlas todas a una TabbedPage con el código siguiente:

var tabbedPage = new TabbedPage
{
  Children =
  {
    new LoginPage { Title = "Login", Icon = "login.png" },
    new DirectoryPage { Title = "Directory", Icon = "directory.png" },
    new AboutPage { Title = "About", Icon = "about.png" }
  }
};

En este caso, es importante establecer las propiedades Title e Icon de cada página, para que aparezca algo en las pestañas de la página. No todas las plataformas representan iconos. Eso depende del diseño de la pestaña de la plataforma.

Si abre esta página en un dispositivo móvil, la primera pestaña aparecería como seleccionada. Sin embargo, este comportamiento se puede cambiar si se establece la propiedad CurrentPage de la página TabbedPage.

NavigationPage Una NavigationPage administra la navegación y la experiencia de usuario de la pila de páginas. Esta página ofrece el tipo de patrón de navegación de aplicaciones móviles más común. Se le agregan las páginas de esta forma:

var loginPage = new LoginPage();
var navigationPage = new NavigationPage(loginPage);
loginPage.LoginSuccessful += async (o, e) => await
  navigationPage.PushAsync(new DirectoryPage());

Observe el uso de PushAsync como una manera de hacer que los usuarios naveguen a una página concreta (en este caso, a DirectoryPage). En una NavigationPage, se insertan (push) páginas en una pila y se sacan (pop) a medida que los usuarios navegan hacia atrás, a la página anterior.

Los métodos PushAsync y PopAsync de una NavigationPage son asincrónicos, por lo que el código debe esperarlos y no insertar o sacar ninguna página nueva mientras se ejecuta la tarea. La tarea de cada método se devuelve después de que la animación de una inserción o extracción se complete.

Para mayor comodidad, todas las vistas, diseños y páginas de Xamarin.Forms contienen una propiedad Navigation. Esta propiedad es una interfaz de proxy que contiene los métodos PushAsync y PopAsync de una instancia de NavigationPage. Puede usar esa propiedad para navegar a una página en lugar de llamar a los métodos PushAsync y PopAsync directamente en la instancia de NavigationPage. También puede usar NavigationProperty para obtener los métodos PushModalAsync y PopModalAsync. Esto es útil si desea reemplazar el contenido de toda la pantalla con una nueva página modal. No es obligatorio disponer de una NavigationPage en la pila de elemento primario de una vista para usar la propiedad Navigation de una vista, pero se podría producir un error en las operaciones PushAsync y PopAsync no modales.

Una nota sobre de los patrones de diseño Como práctica general, contemple agregar NavigationPages como elementos secundarios de TabbedPages, y TabbedPages como elementos secundarios de MasterDetailPages. Ciertos tipos de patrones pueden producir una experiencia de usuario no deseable. Por ejemplo, la mayoría de plataformas no recomiendan agregar una TabbedPage como un elemento secundario de una NavigationPage.

Animar las vistas en una página

Puede crear una experiencia más atractiva si anima las vistas en una página, algo que puede realizar de dos maneras. Bien seleccionado las animaciones integradas que vienen con Xamarin.Forms o bien creando una usted mismo mediante la API de animación.

Por ejemplo, podría crear un efecto de atenuación si llama a la animación FadeTo de una vista. La animación FadeTo está integrada en una vista, por lo que es fácil de usar:

async Task SpinAndFadeView(View view)
{
  await view.FadeTo(20, length: 200, easing: Easing.CubicInOut);
}

Puede encadenar una sucesión de animaciones mediante el uso de la palabra clave await. Cada animación se ejecuta una vez se ha completado la anterior. Por ejemplo, puede girar una vista justo antes de que aparezca progresivamente y se centre:

async Task SpinAndFadeView(View view)
{
  await view.RotateTo(180);
  await view.FadeTo(20, length: 200, easing: Easing.CubicInOut);
}

Si tiene problemas para lograr el efecto deseado, puede utilizar la API de animación completa. En el siguiente código, la vista se atenúa a medias a través de la rotación:

void SpinAndFadeView(View view)
{
  var animation = new Animation();
  animation.Add(0, 1, new Animation(
    d => view.Rotation = d, 0, 180, Easing.CubicInOut));
  animation.Add(0.5, 1, new Animation(
    d => view.Opacity = d, 1, 0, Easing.Linear));
  animation.Commit(view, "FadeAndRotate", length: 250);
}

Este ejemplo compone cada animación en una única instancia Animation y ejecuta la secuencia de animación completa mediante el método Commit. Dado que esta animación no está vinculada a una vista concreta, puede aplicar la animación a cualquiera de ellas.

Resumen

Xamarin.Forms es una apasionante forma nueva de crear aplicaciones móviles nativas de varias plataformas. Se puede usar para crear una interfaz de usuario que se represente de forma nativa en iOS, Android y Windows Phone. Se puede compartir casi todo el código entre plataformas.

Xamarin Inc. creó Xamarin y Xamarin.Forms para hacer posible que los desarrolladores de C# se sumergieran en el desarrollo móvil prácticamente de la noche a la mañana. Si ha llevado a cabo desarrollo para Windows en tiempo de ejecución, WPF o Silverlight, verá que Xamarin.Forms es un puente sencillo en el mundo del desarrollo móvil nativo multiplataforma. Puede instalar Xamarin hoy e inmediatamente empezar a usar C# para crear atractivas aplicaciones nativas que se ejecuten en dispositivos iOS, Android y Windows Phone.


Jason Smith es el responsable de ingeniería técnica de Xamarin Inc. en San Francisco y actualmente lidera el proyecto Xamarin.Forms. Ha sido uno de los principales arquitectos de Xamarin.Forms, antes contribuyó al proyecto de Xamarin Studio y formó parte de la investigación inicial que condujo a la creación de Xamarin Test Cloud.

Gracias al siguiente experto técnico de Microsoft por revisar este artículo: Norm Estabrook
Norm Estabrook ha sido desarrollador de contenidos en Microsoft durante más de 10 años, con un énfasis especial en ayudar a los desarrolladores para que creen aplicaciones móviles nativas y extensiones de Office con Visual Studio. Ha publicado varios artículos en MSDN Library y reside en el noroeste de Washington con su esposa, sus dos hijos y un adorable Schnauzer miniatura ciego