Utilizando agente macOS Hosted para compilar iOS en VSTS

Introducción

Visual Studio Team Services paso a paso ha logrado tener un sistema de builds sencillo de configurar / utilizar y sobretodo muy completo.

Recientemente se añadieron máquinas Hosted Linux que venían a sumarse a las opciones ya disponibles con Windows. Ahora llega el turno de macOS!.

Agente macOS Hosted

Al crear la Build para un proyecto iOS, tenemos disponible una plantilla Xamarin.iOS practicamente preparada. Tras seleccionar la plantilla Xamarin.iOS, llega la hora de seleccionar agente…

Hosted macOS Preview

Y ahora tenemos la opción Hosted macOS Preview!

En el momento de escribir este artículo, las máquinas macOS cuentan con OS X 10.12.6 y todo lo necesario para compilar, no solo proyectos iOS, también Android, etc. Puedes ver un listado completo de contenido preparado e instalado en la máquina macOS en este enlace.

 

Build

¿Personalizar versión herramientas utilizadas?

Podemos seleccionar manualmente la versión a utilizar del SDK de Xamarin. Para ello, antes del paso de compilación, es necesario ejecutar una línea de comandos donde vamos a establecer la versión de Mono asociada con el SDK de Xamarin. Veamos un ejemplo:

/bin/bash –c "sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh 5_4_1"

En el caso anterior se selecciona la versión 5.4.2 (se reemplazan . por _). Puedes ver las versiones a elegir en este enlace.

Si utilizas XCode, quizás también te intere seleccionar la versión. Para hacerlo, antes del paso xcodebuild, se debe ejecutar el siguiente comando:

/bin/bash –c "sudo xcode-select -s /Applications/Xcode_8.3.3.app/Contents/Developer"

Las versiones de XCode que se pueden utilizar se pueden ver en este enlace.

Más información

[Codemotion 2017] Taller de Xamarin

Codemotion 2017

Otro año más, Codemotion se celebrará en Madrid los días 24 y 25 de noviembre en el Campus de Montepríncipe de la Universidad CEU San Pablo. Como en años anteriores, es una cita imprescindible para el mundo del desarrollo de software.

Codemotion 2017

Taller Xamarin

Este año volveré a tener la oportunidad de participar en el evento aunque a diferencia de años anteriores, donde participé con sesiones técnicas, estaré con un taller.

El Sábado 25 de Noviembre, de 12:15h a 14:00h tendremos un taller de desarrollo de aplicaciones multiplataforma con Xamarin. En este Lab veremos como desarrollar aplicaciones nativas multiplataforma para iOS, Android, Windows, macOS y Linux compartiendo la mayor cantidad de código posible utilizando Xamarin.Forms.

Requisitos:

  • Visual Studio 2015 o 2017 para Windows (la versión Community es perfectamente válida) o Visual Studio para macOS.
  • Asegúrate de tener instalado Xamarin.
  • Emuladores de Android.
  • Muchas ganas!

Material:

¿Nos vemos allí?

Más información

[Xamarin.Forms] Layout Compression

Introducción

La evolución de Xamarin.Forms continua centrada en varios puntos donde destaca con fuerza la búsqueda de rendimiento. Con Xamarin.Forms 2.4.0 se introducía (en Android) los Fast Renderers. El concepto de los Fast Renderers es sencillo, simplificar al máximo los costes de renderizado de cada elemento visual.

Ahora, con la Preview de la versión 2.5.0 recibimos también Layout Compression.

¿Qué es Layout Compression?, ¿cómo afecta en nuestras aplicaciones?.

Layout Compression

Para conocer el concepto de Layout Compression junto con su aporte vamos a utilizar una ejemplo lo más “real” posible. Recientemente preparamos un ejemplo donde realizábamos la interfaz de Netflix con Xamarin.Forms. Este será nuestro ejemplo.

Al realizar un diseño cuidado de una aplicación móvil utilizamos una serie de elementos visuales. Estos elementos visuales, a su vez, en cada platataforma en determinadas ocasiones serán envueltos en paneles y otros elementos visuales para facilitar su uso y gestión. A la hora de renderizar cada vista, se realizan una serie de cálculos de tamaño y posicionamiento de cada elemento. Al contar con mayor complejidad, contaremos con mayor anidación de elementos, mayor número de iteraciones serán necesarias para calcular todo.

Layout Compression permite indicar elementos con anidamiento innecesario y optar a no crear layout. Con esta opción habilitada los layouts se optimizarán en tiempo de compilación permitiendo obtener un mejor rendimiento en tiempo de ejecución.

Con Xamarin Inspector podemos tener una listado completo de la jerarquía que compone la interfaz de usuario. Vamos a aplicar Fast Renderers junto con Layout Compression para hacer una comparativa directa cada vista de la aplicación Xamarin.Forms replicando la interfaz de Netflix.

Vamos a comenzar activando Fast Renderers. Para ello, en el proyecto Android, en su actividad principal:

global::Xamarin.Forms.Forms.SetFlags("FastRenderers_Experimental");

Posteriormente es el momento de analizar la aplicación, identificar Layouts (Grid, StackLayout, AbsoluteLayout, RelativeLayout) para comprimir:

<Grid
     CompressedLayout.IsHeadless="true">
</Grid>

¿Resultados?

Vista de elección de perfil

Partimos en la vista de selección de perfil. Con Xamarin Inspector realizamos un recuento del número total de Renderers necesarios en esta vista llegando a un total de 71 Renderers.

Elección de perfil

Tras activar Fast Renderers y utilizar Layout Compression reducimos el número hasta los 51.

Elección de perfil utilizando Layout Compression
  • Default: 71 Renderers.
  • Layout Compression: 51 Renderers.

Vista principal

Continuamos realizando el mismo proceso con la vista principal de la aplicación. Estamos ante una vista de mayor complejidad. El número total de Renderers sube hasta los 223.

Vista principal

Y utilizando Xamarin.Forms 2.5.0 Pre-Release bajamos a las 169.

Vista principal usando Layout Compression
  • Default: 223 Renderers.
  • Layout Compression: 169 Renderers.

Vista de detalles

Llegamos a la vista más compleja con 296 Renderers.

Detalles de una película

Que reducimos hasta 229 utilizando Layout Compression.

Detalles utilizando Layout Compression
  • Default: 296 Renderers.
  • Layout Compression: 229 Renderers.

A tener en cuenta

La compresión de un Layout hace que se no cree su Renderer por lo tanto, no funcionarían aspectos relacionados con el Layout como:

  • Color de fondo.
  • Gestos.
  • Transformaciones.
  • Etc.

Revisa con cuidado si haces uso de TapGestureRecognizer u  otra característica para decidir si puedes comprimir o no el Layout.

Conclusiones

En este ejemplos concreto (hemos intentado utilizar un ejemplo con cierta complejidad a nivel visual) la media de reducción de Renderers es de un 24%.  Se exactamente que te estas preguntando, ¿como impactará en mi aplicación?, ¿será más rápida?. La respuesta es si. Antes que nada ten en cuenta que afecta a Android e iOS. En nuestro ejemplo, la aplicación si se nota más fluida aunque todo dependerá de diversos factores como el dispositivo, versión del sistema operativo, etc. En un Oneplus 3 (Snapdragon 820, 6GB de RAM) apenas se notan diferencias mientras que con un Nexus 5 (Snapdragon 800, 2 GB de RAM) si se nota mejora al cargar las páginas.

¿Y a ti que te parece?, ¿has realizado pruebas?. Te animo a compartir tus resultados en los comentarios de la entrada!

Más información

[Xamarin.Forms] .NET Standard 2.0 y Entity Framework Core

Introducción

Cuando desarrollamos aplicaciones móviles, en una gran cantidad de ocasiones, vamos a necesitar trabajar y gestionar datos. Podemos necesitar sencillas clave-valor para gestionar la configuración de la aplicación, o bien, datos más complejos relacionales utilizando una base de datos local como SQLite o Realm.

A la hora de trabajar con datos, una opción muy familiar para desarrolladores .NET es Entity Framework.

¿Podemos utilizar Entity Framework desde una aplicación Xamarin?, ¿qué limitaciones encontramos?.

En este artículo, vamos a ver paso a paso como llegar a utilizar Entity Framework Core en una aplicación Xamarin.Forms.

NOTA: Este artículo se focaliza en aprender como utilizar Entity Framework Core en una aplicación Xamarin. No tiene como objetivo dar a conocer conceptos básicos de Entity Framework.

Entity Framework Core

Entity Framework es un ORM (object-relational mapper) que permite a los desarrolladores trabajar con una base de datos utilizando objetos .NET. Con un largo recorrido, la primera versión de Entity Framework aparecía en verano de 2008, ha ido evolucionando en base a las necesidades de desarrolladores. Como proyecto Open Source, ha contado con importantes contribuciones.

En verano de 2016, llega .NET Core y junto al mismo algunas tecnologías relacionadas, como Entity Framework Core.

Cuando hablamos de Entity Framework Core nos referimos a una versión ligera, extensible y además multiplataforma de Entity Framework.

NOTA: Si cuentas con experiencia con Entity Framework encontrarás Core muy familiar. Sin embargo, recuerda que es una versión más ligera y que hay opciones no disponibles (por ejemplo, lazy loading).

Con el lanzamiento de .NET Standard 2.0 Preview para UWP junto con el lanzamiento de Entity Framework Core 2.0, podemos desarrollar aplicaciones móviles para iOS, Android y UWP con EF y SQLite. A pesar de no contar con todas las opciones de Entity Framework, la versión Core es muy interesante por diversos motivos:

  • Fácil de arrancar.
  • Se integra bien con Linq.
  • Se puede utilizar en diferentes plataformas gracias a .NET Core (Android, macOS, Linux, etc.).

NOTA: Puedes ver una tabla comparativa ente EF Core y EF 6 en este enlace.

Comenzamos

Partimos de una aplicación Xamarin.Forms creada con la plantilla disponible:

Nueva App Xamarin.Forms

Librería .NET Standard

Nuestro objetivo es tener una librería .NET Standard, una librería con una especificación de APIs .NET diseñada para estar disponible en todos los runtimes .NET. Esta librería actuará de forma similar a una PCL, será el lugar donde añadiremos el código común compartido de todas las aplicaciones móviles además de la lógica de Entity Framework Core.

Tenemos varias opciones para tener la librería. Podemos crear una librería .NET Standard nueva que reemplazará a la PCL (la acabaremos borrando):

Nueva librería .NET Standard

O podemos editar la PCL para convertirla en una librería .NET Standard.

<Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
     <PackageTargetFallback>$(PackageTargetFallback);portable-win+net45+wp8+win81+wpa8</PackageTargetFallback>
     </PropertyGroup>
</Project>

Vamos a utilizar .NET Standard 2.0:

.NET Standard 2.0

NOTA: Para poder utilizar Entity Framework 2.0, es necesario utilizar una .NET Standard versión 2.0. Necesitas tener instalado .NET Core SDK 2.0.

Añadir Microsoft.EntityFrameworkCore.Sqlite

Tenemos que añadir paquete NuGet de Entity Framework en nuestros proyectos. Para ello, hacemos clic derecho en la gestión de paquetes NuGet de la solución y añadimos Microsoft.EntityFrameworkCore.Sqlite.

Microsoft.EntityFrameworkCore.Sqlite

Trabajando con Entity Framework

Tras añadir el paquete de Entity Framework podemos comenzar a trabajar!.  Si ya has trabajado previamente con Entity Framework no te será desconocido el contexto DbContext.

En Entity Framework llamamos contexto a la puerta de enlace entre el modelo que utilizamos y el framework que se encarga de conectar con al base de datos y de realizar el mapeo de las diferentes operaciones con comando de datos. DbContext será el responsable de:

  • Conexiones de base de datos.
  • Operaciones de datos tales como consultas y persistencia.
  • Seguimiento de cambios.
  • Mapeo de datos.
  • Gestión de transacciones.
  • Etc.

Vamos a desarrollar una aplicacación que permita tomar notas.

Comenzamos definiendo nuestros Modelos que se encargarán de definir el esquema de nuestra base de datos.

Modelos

public class TodoItem
{
     [Key]
     public int Id { get; set; }
     public string Name { get; set; }
     public string Notes { get; set; }
     public bool Done { get; set; }
}

Crear el contexto

A continuación, nos aseguramos que nuestra clase TodoItem es parte de nuestro DbContext. Definimos el contexto:

class DatabaseContext : DbContext
{
     public DbSet<TodoItem> TodoItems { get; set; }

     public DatabaseContext()
     {
          this.Database.EnsureCreated();
     }

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
          var dbPath = DependencyService.Get<IDatabaseService>().GetDatabasePath();
          optionsBuilder.UseSqlite($"Filename={dbPath}");
     }
}

De igual forma a cuando trabajamos directamente con SQLite por ejemplo, necesitamos código específico por plataforma para acceder a la ruta de la base de datos adecuada.

Android

public class DatabaseService : IDatabaseService
{
      public string GetDatabasePath()
      {
           return Path.Combine(
           System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), 
           AppSettings.DatabaseName);
      }
}

iOS

public class DatabaseService : IDatabaseService
{
     public string GetDatabasePath()
     {
         return Path.Combine(Environment.GetFolderPath(
         Environment.SpecialFolder.MyDocuments), 
         "..", 
         "Library", 
         AppSettings.DatabaseName);
     }
}

NOTA: Es importante utilizar el servicio de dependencia de Xamarin.Forms parar resolver la dependencia específica de la plataforma donde se ejecuta la aplicación:

[assembly: Dependency(typeof(DatabaseService))]

Insertar nuevos registros

La clase DbSet<TEntity> representa una colección para una entidad dada dentro del modelo y es la vía de entrada a las operaciones de la base de datos. Las clases DbSet<TEntity> se agregan como propiedades al DbContext.

Para insertar nuevos registros a la colección representada por el DbSet, utilizamos el método DbSet.Add:

_context.TodoItems.Add(item);

Esto añade los cambios al DbSet en memoria, es necesario utilizar el método SaveChanges para actualizar la base de datos.

_context.SaveChanges();

Actualizar registro

Para actualizar una entidad, se deben realizar los cambios necesarios en el valor de la propiedad o propiedades de la misma y utilizar el método Update junto a  SaveChanges.

_context.TodoItems.Update(todoItem);

Obtener información desde la base de datos

La forma más común utilizada para obtener varias entidades es utilizar el método ToList:

public IList<TodoItem> GetAll()
{
     return _context.TodoItems.ToList();
}

Si lo que se busca es recuperar una sola instancia de una entidad, se puede usar el método First o Single, dependiendo de si espera que haya más de una fila que coincida con los criterios.

Eliminar registro

Por último, para eliminar registros, utilizamos el método Remove disponible en la clase DbSet además de, SaveChanges.

public void Remove(TodoItem item)
{
     _context.TodoItems.Remove(item);
     _context.SaveChanges();
}

Puedes descargar el ejemplo realizado desde GitHub:

Ver GitHub

Recuerda, cualquier comentario, duda o pregunta es bienvenida en los comentarios de la entrada.

Más información

[Xamarin.Forms] Probando Live XAML, actualizando XAML al vuelo!

Productividad trabajando con XAML

Conseguir aplicaciones con una interfaz rica, elegante y perfectamente adaptada a cada plataforma requiere un proceso de repetición de cambio, compilación, depuración y vuelta a repetir.

Contamos con opciones interesantes para solventar este problema como Previewer, Gorilla Player o Xamarin Live Player. Ya vimos todas las herramientas anteriores, ahora, nos llega el turno de probar otra opción muy interesante, Live XAML.

Live XAML

Live XAML es una extensión disponible para Visual Studio tanto en Windows como en macOS que se encarga de inyectar el código necesario para reaccionar ante cualquier cambio de XAML. Esto significa que, con tu aplicación en modo depuración en un emulador o en un dispositivo, puedes ver al vuelo cualquier cambio en XAML.

La instalación

Desde la página oficial de la herramienta podemos descargar una extensión para Visual Studio disponible tanto para Windows como para macOS.

Tras descargar e instalar la extensión todo listo. Uno de los grandes puntos positivos de la herramienta es su facilidad de uso, tras instalar, para utilizar la herramienta en cada proyecto bastará con indicarle a la herramienta que añada paquete NuGet en la librería compartida llamado LiveXAML o bien, añadir el paquete a mano.

LiveXAML NuGet

Comenzamos con un ejemplo sencillo

Partimos de un proyecto básico, la plantilla vacía de Xamarin.Forms. Lanzamos el proyecto en depuración.

Comenzamos con las pruebas más sencillas posibles, cambiar propiedades básicas como el texto o el color del Label añadido por defecto:

Continuamos con otros requisitos básicos al desarrollar en aplicaciones, el uso de recursos y estilos.

La herramienta se comporta sin problemas al utilizar recursos tanto a nivel de elemento, como a nivel de página como a nivel de aplicación.

Probamos posibilidades más complejas

Tiene buena pinta, pero…¿y será válido con aplicaciones más complejas?. Recientemente, en el blog vimos un recopilatorio de ejemplos Xamarin.Forms con intefaz atractiva. Entre ese recopilatorio se encuentra un ejemplo que intenta reproducir la interfaz de Netflix en Xamarin.Forms. Vamos a utilizarla.

Podemos realizar pruebas con enlace a datos en sus difentes modos.

La cabecera con transparencias haciendo uso de Custom Renderer, efecto Parallax, el uso de controles personales para listados, etc. Estamos trabajando con una página relativamente compleja. Podemos realizar cambios al vuelo y realizar pruebas directamente. Por ejemplo, cambios en el tamaño de la cabecera y pruebas con el efecto Parallax:

Puedes descargar el ejemplo de Netflix desde GitHub:

Ver GitHub¿Aumentamos la complejidad?

¿Funcionaría con .NET Standard?, ¿y con librerías personalizadas?. Pasamos a continuación a ver un ejemplo haciendo uso de .NET Standard, SkiaSharp y la librería Microcharts que aporta diversas gráficas de gran belleza y posibilidades creadas con SkiaSharp.

Creamos un pequeño control que añade animaciones a la gráfica. Ajustar la velocidad de la animación, tamaño de fuentes, elementos, etc. Sería fantástico poder aplicar estos cambios al vuelo, sin necesidad de parar, recompilar, esperar el despliegue, etc., ¿verdad?.

Fíjate que en la cabecera de la gráfica hacemos también uso de efectos. Aplicamos un efecto que añade sombras al texto.

Puedes descargar el ejemplo desde GitHub:

Ver GitHub

Conclusiones

Tras todas las pruebas que has podido ver anteriormente (más muchas otras), los resultados son excelentes. No he encontrado errores de renderizado, comunicación o funcionamiento en general de la herramienta. Así que, debemos destacar su fácil instalación y su buen hacer. Tampoco es necesario aprender nada nuevo ya que todo se integra en Visual Studio y con el flujo habitual. Eso si, recuerda, estamos ante una herramienta que tiene coste.

Más información

[Material SVQXDG] Introducción a Embeddinator-4000

El evento

El pasado 17 de Octubre desde SVQXDG, grupo de desarrolladores Xamarin de Sevilla, se organizaba un evento donde vimos tanto las librerías de Bindings como Embedinnator-4000.

El material

Pude participar en el evento con una sesión hablando de Embeddinator-4000, una herramienta que permite generar librerías nativas tanto para iOS como para Android desde una librería .NET

Tienes la presentación utilizada disponible en SlideShare.

Vimos aspectos como:

  • Un introducción a la herramienta.
  • Instalación.
  • Uso básico.
  • Ejemplos con iOS.
  • Ejemplo con Android.

Puedes descargar todos los ejemplos utilizados desde GitHub:

Ver GitHubGracias a todos los asistentes, nos vemos en la próxima!

Más información

[Xamarin.Forms] Transiciones entre páginas

Introducción

En todas las plataformas, las aplicaciones móviles incluyen animaciones que otorgan movimiento, fluidez y focalizan la atención del usuario en las zonas deseadas. Actualmente no son un extra o añadido en las aplicaciones, sino una parte importante en la experiencia y usabilidad de las mismas.

Como desarrolladores, debemos no solo cuidar por supuesto el correcto funcionamiento de la aplicación, sino que debemos preocuparnos también por la usabilidad y la experiencia otorgada, donde entran en juego las animaciones.

Entre el conjunto de posibilidades a la hora de animar elementos, las transiciones entre páginas son un punto destacado para conseguir trasmitir fluidez y sensación de continuidad.

¿Cómo aplicamos transiciones entre páginas en Xamarin.Forms?.

En este artículo, vamos a ver como aplicar diferentes transiciones entre páginas en aplicaciones Xamarin.Forms.

Navegar entre páginas y animaciones

Entre los diferentes patrones habituales utilizados en el desarrollo móvil, el más utilizado es la navegación en pila. En Xamarin.Forms la clase NavigationPage ofrece una experiencia de navegación jerárquica donde el usuario puede navegar a través de las páginas tanto hacia delante como hacia atrás.

Para navegar de una página a otra, la aplicación añadirá (push) una nueva página en el navigation stack o pila de navegación.

await Navigation.PushAsync (new Page());

Para navegar atrás, a la página anterior, la aplicación eliminará (pop) la página actual de la pila de navegación, y a partir de ese momento la última página disponible en la pila pasará a ser la página activa.

await Navigation.PopAsync ();

Por defecto, tanto al navegar hacia delante como hacia atrás, se aplica una transición entre páginas. Podemos desactivar la animación tanto al navegar hacia delante:

await Navigation.PushAsync (new Page(), false);

Como hacia atrás:

await Navigation.PopAsync (false);

Transiciones personalizadas

Pero…¿y si necesitamos/queremos aplicar una transición personalizada en nuestra aplicación?. Las transiciones entre páginas son un aspecto bien cubierto en cada plataforma. Accediendo a cada una de ellas, con código específico por plataforma, podremos crear experiencias personalizadas. Si, lo habrás imaginado, vamos a utilizar un Custom Renderer.

En nuestra librería portable o net standard, comenzamos creando un nuevo control que hereda de NavigationPage:

public class TransitionNavigationPage : NavigationPage
{
     public TransitionNavigationPage() : base()
     {
 
     }

     public TransitionNavigationPage(Page root) : base(root)
     {

     }
}

Necesitamos determinar que animación entre un conjunto deseamos aplicar. Para conseguir este objetivo, primero creamos una enumeración con todos los tipos diferentes de transiciones que podremos utilizar:

public enum TransitionType
{
     Fade,
     Flip,
     Scale,
     SlideFromLeft,
     SlideFromRight,
     SlideFromTop,
     SlideFromBottom
}

Creamos una BindableProperty en nuestro control:

public static readonly BindableProperty TransitionTypeProperty =
     BindableProperty.Create("TransitionType", typeof(TransitionType), typeof(TransitionNavigationPage), TransitionType.SlideFromLeft);

public TransitionType TransitionType
{
     get { return (TransitionType)GetValue(TransitionTypeProperty); }
     set { SetValue(TransitionTypeProperty, value); }
}

Todo listo en nuestro control!.

Nuestra interfaz de usuario será sumamente simple, un listado de botones.

Nuestra interfaz

En cada caso, vamos a navegar a una página de detalles aplicando el tipo de animación correspondiente a cada botón.

Utilizaremos nuestro control TransitionNavigationPage en lugar de NavigationPage:

MainPage = new TransitionNavigationPage(new MainView());

Utilizar una transición u otra es tan sencillo como establecer la propiedad TransitionType (desde XAML o C#):

transitionNavigationPage.TransitionType = TransitionType.Fade;

Transiciones en Android

En Android, desde Lollipop, se dedicó un gran esfuerzo en mejorar el sistema de transiciones disponibles. Con la llegada de Material Design, llegó una nueva oleada de opciones. Tenemos la posibilidad de aplicar transiciones entre Activities y entre Fragments.

Utilizaremos la clase FragmentTransaction que nos permite realizar diferentes operaciones entre las que se encuentra, establecer la animación a aplicar en la transición utilizando el método SetCustomAnimations.

Transiciones en iOS

En el caso de iOS, aunque dependerá del tipo de animación a aplicar, utilizaremos principalmente la clase CATransition. Esta clase permite trabajar con la funcionalidad de animaciones del Core pudiendo aplicar una animación a un Layer completo.

Código específico de plataforma

En cada plataforma, vamos a crear una nueva clase TransitionNavigationPageRenderer donde realizaremos la implementación de nuestro Custom Renderer.

[assembly: ExportRenderer(typeof(TransitionNavigationPage.Controls.TransitionNavigationPage), typeof(TransitionNavigationPageRenderer))]
namespace TransitionNavigationPage.iOS.Renderers
{
     public class TransitionNavigationPageRenderer : NavigationRenderer
     {
 
     } 
}

En Android, utilizaremos el método SetupPageTransition para modificar la animación a utilizar.

protected override void SetupPageTransition(FragmentTransaction transaction, bool isPush)
{
     switch (_transitionType)
     {
          case TransitionType.Fade:
          break;
          case TransitionType.Flip:
          break;
          case TransitionType.Scale:
          break;
          case TransitionType.SlideFromLeft:
          break;
          case TransitionType.SlideFromRight:
          break;
          case TransitionType.SlideFromTop:
          break;
          case TransitionType.SlideFromBottom:
          break;
          default:
          break;
     }
}

En el caso de iOS, utilizaremos los métodos PushViewController y PopViewController para detectar cuando navegamos hacia delante y hacia atrás para aplicar animaciones personalizadas.

Fade

Comenzamos aplicando una de las animaciones más sencillas y posiblemente utilizadas en muchas ocasiones, Fade.

En Android, creamos un archivo XML con la definición de la animación en la carpeta anim dentro de Resources.

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:fillAfter="true">
 <alpha
      android:duration="1000"
      android:fromAlpha="0.0"
      android:toAlpha="1.0" />
</set>

Utilizamos el recurso en el método SetupPageTransition en nuestro Custom Renderer:

transaction.SetCustomAnimations(Resource.Animation.fade_in, Resource.Animation.fade_out);

En el caso de iOS:

  • Establecemos la propiedad Alpha a cero.
  • Utilizando el método Animate, animamos Alpha para establecerlo a valor 1.0.
View.Alpha = 0.0f;
View.Transform = CGAffineTransform.MakeIdentity();

UIView.Animate(0.5f, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Alpha = 1.0f;
     },
     null
);

Sencillo, ¿no?.

Flip

En Android, volvemos a crear la animación:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <scale
      android:interpolator="@android:anim/linear_interpolator"
      android:fromXScale="0.0"
      android:toXScale="1.0"
      android:fromYScale="0.7"
      android:toYScale="1.0"
      android:fillAfter="false"
      android:startOffset="200"
      android:duration="200" />
 <translate
      android:fromXDelta="50%"
      android:toXDelta="0"
      android:startOffset="200"
      android:duration="200"/>
</set>

Jugamos con la escala y translación de la vista para conseguir el “efecto óptico” buscado. Y la aplicamos de nuevo, utilizando SetCustomAnimations:

transaction.SetCustomAnimations(Resource.Animation.fade_in, Resource.Animation.fade_out);

En iOS, aplicamos una animación donde aplicamos una transformación a la View.

var m34 = (nfloat)(-1 * 0.001);
var initialTransform = CATransform3D.Identity;
initialTransform.m34 = m34;
initialTransform = initialTransform.Rotate((nfloat)(1 * Math.PI * 0.5), 0.0f, 1.0f, 0.0f);

View.Alpha = 0.0f;
View.Layer.Transform = initialTransform;
UIView.Animate(0.5f, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Layer.AnchorPoint = new CGPoint((nfloat)0.5, 0.5f);
          var newTransform = CATransform3D.Identity;
          newTransform.m34 = m34;
          View.Layer.Transform = newTransform;
          View.Alpha = 1.0f;
     },
     null
);

NOTA: Utilizando ObjectAnimator podemos aplciar transformaciones mucho más efectivas para realizar esta animación en Android. Fue introducido con API Level 11 (Android 3.0).

Scale

Animación en Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:fillAfter="true">
 <alpha
      android:duration="100"
      android:fromAlpha="0.0"
      android:toAlpha="1.0" />
 <scale
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:duration="1000"
      android:fromXScale="0.5"
      android:fromYScale="0.5"
      android:pivotX="50%"
      android:pivotY="50%"
      android:toXScale="1.0"
      android:toYScale="1.0" />
</set>

Y en iOS:

View.Alpha = 0.0f;
View.Transform = CGAffineTransform.MakeScale((nfloat)0.5, (nfloat)0.5);

UIView.Animate(duration, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Alpha = 1.0f;
          View.Transform = CGAffineTransform.MakeScale((nfloat)1.0, (nfloat)1.0);
     },
     null
);

En ambos casos, jugamos con la opacidad y la escala de la vista.

 

SlideFromLeft

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="-100%" android:toXDelta="0%"
          android:fromYDelta="0%" android:toYDelta="0%"
          android:duration="300"/>
</set>

De las animaciones más sencillas, una simple translación.

En iOS:

var transition = CATransition.CreateAnimation();
transition.Duration = 0.5f;
transition.Type = CAAnimation.TransitionPush;
transition.Subtype = CAAnimation.TransitionFromLeft;
View.Layer.AddAnimation(transition, null);

Aprovechamos CATransition para aplicar animaciones de transición desde izquierda, derecha

SlideFromRight

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
      <translate
           android:fromXDelta="100%" android:toXDelta="0%"
           android:fromYDelta="0%" android:toYDelta="0%"
           android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromLeft;

SlideFromTop

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="0%" android:toXDelta="0%"
          android:fromYDelta="-100%" android:toYDelta="0%"
          android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromTop;

SlideFromBottom

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="0%" android:toXDelta="0%"
          android:fromYDelta="100%" android:toYDelta="0%"
          android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromBottom;

Puedes descargar el ejemplo realizado desde GitHub:

Ver GitHub

¿Más opciones?

Realizando una combinación de las opciones realizadas podemos abordar muchas otras transiciones habituales. Existen otro tipo de transiciones que han ido ganando peso en Material Design, etc.

Por ejemplo, en Android hablamos de opciones como CircularReveal.

Circular Reveal

Podemos conseguir este efecto utilizando el método CreateCircularReveal disponible en ViewAnimationUtils.

En próximos artículos, podemos abordar de nuevo este punto viendo otras opciones como la anterior. ¿Qué transiciones sueles utilizar?, ¿cuál te gustaría ver?. Recuerda, cualquier duda o comentario es bienvenida en los comentarios de la entrada.

Más información

[Material] dotNetMálaga 2017

El evento

El pasado sábado 30 de Septiembre, tenía lugar en Málaga la dotNetMálaga 2017. Una jornada con múltiples sesiones técnicas en varios tracks, talleres, regalos, sorteos y mucho networking.

El resultado fue un fantástico día de comunidad con muchas preguntas, ayuda y tiempo para charlar entre todos.

El material

Pude participar en el evento con una de las sesiones técnicas hablando de pequeños trucos y consejos para conseguir el mejor rendimiento posible en Xamarin.Forms junto con un taller de desarrollo de aplicaciones para Hololens.

Rendimiento y Xamarin.Forms

¿Sabes el ciclo de vida de un Layout?, ¿qué opciones de Layout son más óptimas?, ¿cómo afectan los Bindings al rendimiento y como tratarlos?, ¿rendimiento en listados?, ¿fast renderers?, ¿que tener en cuenta al crear Custom Renders o Behaviors?. Intentamos dar respuesta a todas estas preguntas en esta sesión.

Puedes descargar todos los ejemplos utilizados desde GitHub:

Ver GitHubTaller Hololens con Wave Engine

Por la tarde continuamos con un divertido taller realizando una aplicación para Hololens con Wave Engine. Nuestro objetivo fue crear el sistema solar con posibilidad de realizar Air Tap sobre cada planeta para obtener información además de hacer uso de comandos de voz.

HoloPlanets

En el taller vimos:

  • Crear App 3D con el sistema solar.
  • Uso de modelos 3D.
  • Crear proyectos para HoloLens.
  • Gestión de cámara y posicionamiento.
  • Air Tap.
  • Comandos de voz.

La presentación utilizada:

Puedes descargar cada paso dado en el taller desde GitHub:

Ver GitHub

Gracias a ponentes y patrocinadores, por supuesto a todos los asistentes y mi enhorabuena a toda la organización. ¿Vamos pensando en la dotNetMálaga 2018?.

Más información

[Xamarin.Forms] Personalizando la NavigationPage: colores, fuentes, imágenes y más!

La NavigationPage

Xamarin.Forms cuenta con diferentes páginas que aportan diferentes experiencias de navegación.

Entre las opciones, una de las más utilizadas es la experiencia ofrecida por NavigationPage. Esta experiencia de navegación consiste en una navegación jerárquica e la que el usuario puede navegar por las páginas hacia delante y hacia atrás. Se implementa la navegacion como una pila de páginas (LIFO).

El layout de la NavigationPage

El layout de la NavigationPage es dependiente de cada plataforma:

  • En iOS, se utiliza una barra de navegación en la parte superior de la página mostrando título, y si es necesario, el botón de navegar atrás.
  • En Android, se muestra también una barra de navegación en la parte superior con título, icono y botón de navegar atrás.
  • En Windows, se muestra el título en una CommandBar situada en la parte superior. El botón de navegación atrás aparecerá en la barra de título de la ventana en escritorio y el botón virtual atrás en la parte inferior en móvil.

¿Cómo podemos personalizar la barra de navegación?

En este artículo vamos a aprender como:

  • Ocultar la barra de navegación.
  • Personalizar colores de la barra, del título, etc.
  • Añadir un logo personalizado en la barra de navegación.
  • Personalizar la fuente del título.
  • Personalizar el icono del botón atrás.

Ocultar la barra de navegación

En determinadas páginass puede interesarnos no mostrar la barra de navegación. Es bastante sencillo controlar este comportamiento gracias al método SetHasNavigationBar.

En C#:

NavigationPage.SetHasNavigationBar(this, false);

En XAML:

NavigationPage.HasNavigationBar="False"

Personalizando colores

Continuamos con otra necesidad bastante común en muchas aplicaciones, personalizar el color de fondo y del título de la aplicación.

Con la propiedad BarBackgroundColor podemos definir el color de fondo de la barra de navegación mientras que con la propiedad BarTextColor definimos el color del título.

<NavigationPage 
     xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     BarBackgroundColor="{StaticResource BarBackgroundColor}" 
     BarTextColor="{StaticResource AccentColor}" >
</NavigationPage>

El resultado en Android:

Personalización de colores en Android

En iOS:

Personalización de colores en iOS

Y UWP:

Personalizando colores en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Fuente personalizada en el título

El uso de fuentes personalizadas suele ayudar a potenciar la imagen de marca en muchas aplicaciones móviles. Existen multitud de casos donde el título de la barra de navegación cuenta con una fuente personalizada aplicada.

¿Cómo logramos esto?.

Vamos a necesitar código específico en cada plataforma. Entran en juego los Custom Renderers.

Android

En Android comenzamos añadiendo la fuente a utilizar en la carpeta de Assets.

Tras añadir la fuente, creamos un Custom Render.

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.Droid.Renderers
{
     public class CustomNavigationPageRenderer : NavigationPageRenderer
     {
           private Android.Support.V7.Widget.Toolbar _toolbar;

           public override void OnViewAdded(Android.Views.View child)
           {
                base.OnViewAdded(child);

                if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
                {
                     _toolbar = (Android.Support.V7.Widget.Toolbar)child;
                     _toolbar.ChildViewAdded += OnToolbarChildViewAdded;
                }
          }

          protected override void Dispose(bool disposing)
          {
               base.Dispose(disposing);

               if(disposing)
               {
                     _toolbar.ChildViewAdded -= OnToolbarChildViewAdded;
               }
          }

          private void OnToolbarChildViewAdded(object sender, ChildViewAddedEventArgs e)
          {
               var view = e.Child.GetType();

               if (e.Child.GetType() == typeof(Android.Widget.TextView))
               {
                    var textView = (Android.Widget.TextView)e.Child;
                    var spaceFont = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, "Trashtalk.ttf");
                    textView.Typeface = spaceFont;
                     _toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
               }
          }
     }
}

Accedemos a la Toolbar (tras ser añadida) y posteriormente accedemos a sus elementos hijos, para acceder al título (TextView) y modificar la fuente.

Este mismo proceso será muy similar en otras plataformas.

iOS

También vamos a necesitar un Custom Render:

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.iOS.Renderers
{
     public class CustomNavigationPageRenderer : NavigationRenderer
     {
          protected override void OnElementChanged(VisualElementChangedEventArgs e)
          {
               base.OnElementChanged(e);

              if (e.NewElement != null)
              {
                   var textAttributes = new UITextAttributes();
                   textAttributes.Font = UIFont.FromName("Trashtalk", 20);
                   UINavigationBar.Appearance.SetTitleTextAttributes(textAttributes);
              }
          }
     }
}

Bastante sencillo. Accedemos a la NavigationBar en iOS y aprovechamos el método SetTitleTextAttributes para personalizar la fuente.

UWP

Y llegamos a Windows.

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.UWP.Renderers
{
     public class CustomNavigationPageRenderer : NavigationPageRenderer
     {
          private CommandBar _commandBar;

          public CustomNavigationPageRenderer()
          {
                ElementChanged += OnElementChanged;
          }

          private void OnElementChanged(object sender, VisualElementChangedEventArgs e)
          {
               ElementChanged -= OnElementChanged;
               ContainerElement.Loaded += OnContainerElementLoaded;
          }

          private void OnContainerElementLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
          {
               ContainerElement.Loaded -= OnContainerElementLoaded;
               _commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
               var commandBarContent = ((Border)_commandBar.Content);
               var textBlock = commandBarContent.Child as TextBlock;

               if(textBlock != null)
               {
                    textBlock.FontFamily = new FontFamily("/Assets/Trashtalk.ttf#Trashtalk");
               }
           }
     }
}

Muy similar a lo realizado anteriormente en otras plataformas. Accedemos a la CommandBar utilizada como barra de navegación. A continuación, buscamos el TextBlock utilizado como título y utilizando la propiedad FontFamily, modificamos la fuente.

NOTA: Se puede definir el Element o parte compartida del Custom Renderer donde definir propiedades como el nombre de la fuente a utilizar o propiedades de tipo bool para aplicar la fuente personalizada o no en base a necesidades de cada página y hacer todo más personalizable.

El resultado en Android:

Fuente personalizada en título

En iOS:

Fuente personalizada en iOS

Y en Windows:

Fuente personalizada en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Añadir un logo

Hemos hablado previamente del uso de fuentes personalizadas para conseguir trasmitir mejor la imagen de marca. Otro uso habitual es el de incluir una imagen (logo) en la barra de navegación.

¿Cómo lo conseguimos?.

Android

En Android podemos modificar el contenido de la Toolbar utilizando un layout AXML. Tras añadir la imagen a utilizar como logo en los recursos del proyecto, definimos un nuevo Layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/toolbar"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="?attr/colorPrimary"
     android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
     android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
     android:elevation="5dp">
     <ImageView
          android:id="@+id/logoImageLayout"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:adjustViewBounds="true"
          android:src="@drawable/xamarinlogo" />
</android.support.v7.widget.Toolbar>

Sólo faltaría utilizarlo desde nuestra actividad principal:

ToolbarResource = Resource.Layout.ToolbarLogo;

iOS

En el caso de iOS, conseguimos el resultado con un sencillo Custom Render:

[assembly: ExportRenderer(typeof(MainView), typeof(LogoPageRenderer))]
namespace CustomLogoNavigationPage.iOS.Renderers
{
     public class LogoPageRenderer : PageRenderer
     {
          public override void ViewWillAppear(bool animated)
          {
               base.ViewWillAppear(animated);

               var image = UIImage.FromBundle("xamarin-logo.png");
               var imageView = new UIImageView(new CGRect(0, 0, 140, 70));

               imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
               imageView.Image = image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);

               if (NavigationController != null)
               {
                     NavigationController.TopViewController.NavigationItem.TitleView = imageView;
               }
          }
     }
}

Accedemos a la NavigationBar y añadimos un UIImage utilizando la propiedad TitleView.

Windows

En Windows, el Custom Render necesario es muy similar a lo visto previamente:

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(LogoPageRenderer))]
namespace CustomLogoNavigationPage.UWP.Renderers
{
     public class LogoPageRenderer : NavigationPageRenderer
     {
          private CommandBar _commandBar;

          public LogoPageRenderer()
          {
               ElementChanged += OnElementChanged;
          }

          private void OnElementChanged(object sender, VisualElementChangedEventArgs e)
          {
               ElementChanged -= OnElementChanged;
               ContainerElement.Loaded += OnContainerElementLoaded;
          }

          private void OnContainerElementLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
          {
               ContainerElement.Loaded -= OnContainerElementLoaded;
               _commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
               var commandBarContent = ((Border)_commandBar.Content);

               var image = new Windows.UI.Xaml.Controls.Image();
               image.Source = new BitmapImage(new Uri("ms-appx:///Assets/xamarin-logo.png"));

               commandBarContent.Child = image;
          }
     }
}

Creamosun control Image y lo añadimos en la CommandBar.

El resultado en Android:

Logo en Android

En iOS:

Logo en iOS

Y en Windows:

Logo en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

¿One more thing?

¿Podemos personalizar aún más la barra de navegación?. Por supuesto!. Podemos tener necesidades como:

  • Personalizar el icono de navegación atrás.
  • Personalizar el texto asociado al botón para navegar atrás.
  • Insertar elementos como barras de búsqueda.
  • Etc.

Este conjunto de cambios, los veremos en otro artículo.

Recuerda que cualquier comentario o pregunta es bienvenido en los comentarios del artículo!.

Más información

[Xamarin.Forms] Localización de Aplicaciones

Introducción

Cuando desarrollamos nuestras aplicaciones, un factor importante a tener en cuenta es el público objetivo de la misma. Analizando este punto debemos determinar si distribuimos nuestra aplicación a nivel nacional, en multiples países o a nivel mundial. Si decidimos la última opción debemos de tener en cuenta que aunque las probabilidades de llegar a una mayor cantidad de público aumentan considerablemente, no todo el mundo habla español por lo que seguimos restringidos.

¿Qué podemos hacer?

En este artículo vamos a realizar los pasos necesarios para localizar una aplicación Xamarin.Forms , ¿te apuntas?.

Globalización en Apps Xamarin.Forms

El proceso de globalización consiste en preparar nuestra aplicación para “todo el mundo”. La aplicación debe estar preparada para mostrar la información en diferentes idiomas. Los textos y recursos de nuestra aplicación deben ser diferentes en base al idioma configurado por el dispositivo.

Archivos RESX

Para la gestión de textos en cada idioma utilizaremos archivos RESX. Tendremos un archivo de recursos diferente por cada idioma soportado. Cada archivo se basa en clave-valor. Las claves se repetirán en cada archivo mientras que el valor será la cadena localizada al idioma correspondiente.

Creando los archivos RESX

En la librería portable, comenzamos creando una carpeta Resources con el objetivo de mantener todos los archivos organizados. A continuación, crearemos un archivo de recursos por cada idioma soportado. Para crear el archivo de recursos, hacemos clic derecho sobre la recien creada carpeta Resources, Añadir > Nuevo archivo > General > Resource File.

Nuevo archivo de recursos

Nombra al archivo como Resources o AppResources (nombres habituales) para el idioma por defecto, inglés. Por cada idioma soportado, se debe repetir el proceso llamando al archivo AppResources.{nombre cultura}.resx.

NOTA: Puedes ver un listado completo de países, idiomas y su cultura en este enlace.

Añadiendo soporte al idioma español:

Español

Tras ambos archivos, si abrimos un archivo de recurso veremos algo como lo siguiente:

RESX

Añadiremos las claves en la comuna Nombre y el valor localizado a cada idioma en la columna Valor. Posteriormente, accederemos a cada valor localizado utilizando la clave. En base al idioma, se accederá a un archivo de recursos u otro. De esta forma, podremos mantener de forma sencilla los textos localizados y ofecer una aplicación localizada a diferentes idiomas.

Utilizando los archivos desde C#

Tras añadir un valor:

Añadimos valores

Podemos acceder al valor desde código C# de forma sumamente sencilla:

Label label = new Label()
{
     Text = AppResources.LanguageText
};

Si se lanza la aplicación en español y en inglés el valor estará localizado en cada idioma.

Y ahora desde XAML

Utilizar valores localizados utillizando archivos de recursos es rápido y directo desde código C#. Sin embargo, desde XAML necesitamos “algo más”. Por defecto, no tenemos ninguna extensión de marcado que nos permita indicar el valor de la clave que buscamos en el archivo de recursos para permitir localizar la aplicación. Sin embargo, si podemos crear extensiones de marcado personalizadas utilizando la interfaz IMarkupExtension:

[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
     const string ResourceId = "XamarinFormsLocalization.Resources.AppResources";

     public string Text { get; set; }

     public object ProvideValue(IServiceProvider serviceProvider)
     {
          if (Text == null)
               return null;

          ResourceManager resourceManager = new ResourceManager(ResourceId, typeof(TranslateExtension).GetTypeInfo().Assembly);

          return resourceManager.GetString(Text, CultureInfo.CurrentCulture);
     }
}

Utilizamos la clase ResourceManager para acceder al archivo de recurso correspondiente. A continuación, se utiliza el método GetString para obtener el valor asociado a la clave pasada como parámetro en la propiedad Text.

¿Y cómo lo utilizamos?.

Sencillo, primero declaramos el espacio de nombres en XAML necesario:

xmlns:extensions="clr-namespace:XamarinFormsLocalization.MarkupExtensions"

Y a continuación:

 <Label 
      Text="{extensions:TranslateExtension Text=LanguageText}" />

Utillizamos la extensión pasando como parámetro la clave deseada en la propiedad Text. También podemos utilizar la extensión de marcado de forma menos verbosa:

<Label 
     Text="{extensions:Translate LanguageText}" />

Otros recursos

Una aplicación móvil no se compone exclusivamente de textos. Existen otra serie completa de recursos muy utilizados. Entre los recursos más utilizados, imágenes.

Normalmente se cuentan con imágenes que se pueden ver exactamente igual en diferentes idiomas, pero no así con todas.

¿Cómo gestionamos la localización de imágenes por idioma?

Es una tarea que debemos realizar en cada plataforma de forma específica.

Android

Utilizamos la carpeta drawable dentro de Resources para la gestión de imágenes. Para contar con imágenes diferentes por idioma, podemos crear carpetas con el nombre drawable-{código del idioma}. Android se basa en esta estructura de carpetas con sufijos para localizar imágenes.

Imágenes localizadas en Android

NOTA: Para soportar diferentes imágenes para resoluciones diferentes y además basándonos en el idioma, seguimos utilizando la misma estructura de sufijos en carpetas. Por ejemplo: drawable-es-xdpi.

iOS

En este caso, iOS, utiliza Localization Projects o .lproj para contener imágenes por idioma junto con recursos de cadenas. Dentro de la carpeta Resources debemos crear {códido de idioma}.lproj por cada idioma soportado.

Localización de imágenes en iOS

Cada carpeta correspondiente a un idioma contará con sus versiones de imágenes. En caso de que una carpeta de un idioma no cuenta con imágenes, se utilizarán las correspondientes del idioma por defecto.

NOTA: Para pantallas retina se pueden añadir imágenes @2x y @3x.

UWP

En el caso de aplicaciones universales, organizaremos dentro de la carpeta Assets los recursos por idioma en carpetas con el nombre de la cultura.

Localización de imágenes en UWP

No tenemos un mecanismo que automáticamente seleccione la imagen correspondiente a cada idioma. Por este motivo, vamos a crear un sencillo Custom Renderer.

xmlns:extensions="clr-namespace:XamarinFormsLocalization.MarkupExtensions"
[assembly: ExportRenderer(typeof(Image), typeof(XamarinFormsLocalization.UWP.Renderers.LocalizedImageRenderer))]
namespace XamarinFormsLocalization.UWP.Renderers
{
     public class LocalizedImageRenderer : ImageRenderer
     {
          protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
          {
               base.OnElementChanged(e);

               if (e.NewElement != null)
               {
                    var fileImageSource = e.NewElement.Source as FileImageSource;

                    if (fileImageSource != null)
                    {
                         var fileName = fileImageSource.File;
                         string currentUICulture = CultureInfo.CurrentUICulture.ToString();
                         e.NewElement.Source = Path.Combine("Assets/" + currentUICulture + "/" + fileName);
                    }
               }
          }
     }
}

Creamos un Custom Render en UWP del control Image. Se obtiene la ruta de la imágen a la que se añade la ruta correcta utilizando CurrentUICulture. De esta forma, automáticamente, en base al idioma se accederá a una ruta u otra.

El resultado de la aplicación en inglés:

Inglés

Y en español:

Español

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Existen otra serie de puntos interesantes relacionados con la localización de aplicaciones como por ejemplo la localización del nombre de la aplicación que veremos en otro artículo.

Recuerda, cualquier duda o comentario es bienvenido!

Más información