Creación de interfaces de usuario de iOS en código en Xamarin.iOS

La interfaz de usuario de una aplicación de iOS es como un escaparate: la aplicación normalmente obtiene una ventana, pero la puede llenar con los objetos que necesite. Dichos objetos y su disposición pueden cambiarse según lo que quiera mostrarse en la aplicación. Los objetos de este escenario, lo que el usuario ve, se denominan "vistas". Para compilar una sola pantalla en una aplicación, las vistas se apilan unas sobre otras en una jerarquía de vistas de contenido, que esta, a su vez, se administra a través de un controlador de vistas único. Las aplicaciones con varias pantallas tienen varias jerarquías de vistas de contenido, cada una con su propio controlador de vistas. La aplicación coloca las vistas en la ventana para crear una jerarquía de vistas de contenido diferente basándose en la pantalla en la que se encuentra el usuario.

En el diagrama siguiente se muestran las relaciones entre la ventana, las vistas, las subvistas y el controlador de vistas que, de forma conjunta, proporcionan la interfaz de usuario a la pantalla del dispositivo:

This diagram illustrates the relationships between the Window, Views, Subviews, and View Controller

Estas jerarquías de vistas se pueden construir mediante el Interface Builder de Xcode, pero es bueno tener una comprensión fundamental de cómo trabajar completamente en el código. En este artículo se describen algunos puntos básicos para ponerse en marcha con el desarrollo de la interfaz de usuario de solo código.

Creación de un proyecto de solo código

Plantilla de proyecto en blanco de iOS

En primer lugar, cree un proyecto de iOS en Visual Studio con el proyecto Archivo > Nuevo proyecto > Visual C# > iPhone y iPad > Aplicación iOS (Xamarin), que se muestra a continuación:

New Project Dialog

A continuación, seleccione la plantilla de proyecto Aplicación en blanco:

Select a Template Dialog

La plantilla Proyecto vacío agrega 4 archivos al proyecto:

Project Files

  1. AppDelegate.cs: contiene una subclase UIApplicationDelegate, AppDelegate, que se usa para controlar eventos de aplicación desde iOS. La ventana de la aplicación se crea en el método de AppDelegate llamado FinishedLaunching.
  2. Main.cs: contiene el punto de entrada de la aplicación, que especifica la clase para el AppDelegate.
  3. Info.plist: archivo de lista de propiedades que contiene información de configuración de la aplicación.
  4. Entitlements.plist: archivo de lista de propiedades que contiene información sobre las funcionalidades y permisos de la aplicación.

Las aplicaciones de iOS se compilan mediante el patrón MVC. La primera pantalla que muestra una aplicación se crea a partir del controlador de vista raíz de la ventana. Consulte la guía Hello, iOS Multiscreen para obtener más información sobre el propio patrón MVC.

La implementación del AppDelegate agregado por la plantilla crea la ventana de aplicación, de la cual solo hay una para cada aplicación de iOS y hace que sea visible con el código siguiente:

public class AppDelegate : UIApplicationDelegate
{
    public override UIWindow Window
            {
                get;
                set;
            }

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

Si tuviera que ejecutar esta aplicación ahora, es probable que obtenga una excepción que indique que Application windows are expected to have a root view controller at the end of application launch. Vamos a agregar un controlador y convertirlo en el controlador de vista raíz de la aplicación.

Agregar un controlador

La aplicación puede contener muchos controladores de vista, pero debe tener un controlador de vista raíz para controlar todos los controladores de vista. Agregue un controlador a la ventana mediante la creación de una instancia de UIViewController y su establecimiento en la propiedad Window.RootViewController:

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;

        Window.RootViewController = controller;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

Cada controlador tiene una vista asociada, a la que se puede acceder desde la propiedad View. El código anterior cambia la propiedad BackgroundColor de la vista a UIColor.LightGray para que sea visible, como se muestra a continuación:

The View's background is a visible light gray

Podríamos establecer cualquier subclase de UIViewController como la RootViewController de esta manera, incluidos los controladores de UIKit, así como los que escribimos nosotros mismos. Por ejemplo, el código siguiente agrega un UINavigationController como RootViewController:

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;
        controller.Title = "My Controller";

        var navController = new UINavigationController(controller);

        Window.RootViewController = navController;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

Esto genera el controlador anidado dentro del controlador de navegación, como se muestra a continuación:

The controller nested within the navigation controller

Creación de un controlador de vista

Ahora que hemos visto cómo agregar un controlador como el RootViewController de la ventana, veamos cómo crear un controlador de vista personalizado en el código.

Agregue una nueva clase denominada CustomViewController como se muestra a continuación:

La clase debe heredar de UIViewController, que se encuentra en el espacio de nombres UIKit, como se muestra:

using System;
using UIKit;

namespace CodeOnlyDemo
{
    class CustomViewController : UIViewController
    {
    }
}

Inicialización de la vista

UIViewController contiene un método denominado ViewDidLoad al que se llama cuando el controlador de la vista se carga por primera vez en la memoria. Este es un lugar adecuado para realizar la inicialización de la vista, como establecer sus propiedades.

Por ejemplo, el código siguiente agrega un botón y un controlador de eventos para insertar un nuevo controlador de vista en la pila de navegación cuando se presiona el botón:

using System;
using CoreGraphics;
using UIKit;

namespace CodyOnlyDemo
{
    public class CustomViewController : UIViewController
    {
        public CustomViewController ()
        {
        }

        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            View.BackgroundColor = UIColor.White;
            Title = "My Custom View Controller";

            var btn = UIButton.FromType (UIButtonType.System);
            btn.Frame = new CGRect (20, 200, 280, 44);
            btn.SetTitle ("Click Me", UIControlState.Normal);

            var user = new UIViewController ();
            user.View.BackgroundColor = UIColor.Magenta;

            btn.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (user, true);
            };

            View.AddSubview (btn);

        }
    }
}

Para cargar este controlador en la aplicación y mostrar la navegación sencilla, cree una nueva instancia de CustomViewController. Cree un nuevo controlador de navegación, pase la instancia del controlador de vista y establezca el nuevo controlador de navegación en el RootViewController de la ventana en AppDelegate como antes:

var cvc = new CustomViewController ();

var navController = new UINavigationController (cvc);

Window.RootViewController = navController;

Ahora, cuando se carga la aplicación, el CustomViewController se carga dentro de un controlador de navegación:

The CustomViewController is loaded inside a navigation controller

Al hacer clic en el botón, se insertará un nuevo controlador de vista en la pila de navegación:

A new View Controller pushed onto the navigation stack

Creación de la jerarquía de vistas

En el ejemplo anterior, empezamos a crear una interfaz de usuario en el código agregando un botón al controlador de vista.

Las interfaces de usuario de iOS se componen de una jerarquía de vistas. Se agregan vistas adicionales, como etiquetas, botones, controles deslizantes, etc. como subvistas de alguna vista primaria.

Por ejemplo, vamos a editar el CustomViewController para crear una pantalla de inicio de sesión donde el usuario puede escribir un nombre de usuario y una contraseña. La pantalla constará de dos campos de texto y un botón.

Adición de los campos de texto

En primer lugar, quite el botón y el controlador de eventos que se agregaron en la sección Inicialización de la vista.

Agregue un control para el nombre de usuario mediante la creación e inicialización de un UITextField y, a continuación, agréguelo a la jerarquía de vistas, como se muestra a continuación:

class CustomViewController : UIViewController
{
    UITextField usernameField;

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        View.BackgroundColor = UIColor.Gray;

        nfloat h = 31.0f;
        nfloat w = View.Bounds.Width;

        usernameField = new UITextField
        {
            Placeholder = "Enter your username",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 82, w - 20, h)
        };

        View.AddSubview(usernameField);
    }
}

Cuando creamos el UITextField, establecimos la propiedad Frame para definir su ubicación y tamaño. En iOS, la coordenada 0,0 está en la parte superior izquierda con +x a la derecha y +y hacia abajo. Después de establecer el Frame junto con un par de otras propiedades, llamamos a View.AddSubview para agregar el UITextField a la jerarquía de vistas. Esto convierte a usernameField en una subvista de la instancia UIView a la que hace referencia la propiedad View. Se agrega una subvista con un orden z superior a su vista primaria, por lo que aparece delante de la vista primaria en la pantalla.

A continuación se muestra la aplicación con el UITextField incluido:

The application with the UITextField included

Podemos agregar un UITextField para la contraseña de forma similar, solo que esta vez establecemos la propiedad SecureTextEntry en true, como se muestra a continuación:

public class CustomViewController : UIViewController
{
    UITextField usernameField, passwordField;
    public override void ViewDidLoad()
    {
       // keep the code the username UITextField
        passwordField = new UITextField
        {
            Placeholder = "Enter your password",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 114, w - 20, h),
            SecureTextEntry = true
        };

      View.AddSubview(usernameField);
      View.AddSubview(passwordField);
   }
}

Al establecer SecureTextEntry = true se oculta el texto escrito en el UITextField por el usuario como se muestra a continuación:

Setting SecureTextEntry true hides the text entered by the user

Adición del botón

A continuación, agregaremos un botón para que el usuario pueda enviar el nombre de usuario y la contraseña. El botón se agrega a la jerarquía de vistas como cualquier otro control, pasándolo como argumento al método AddSubview de la vista primaria de nuevo.

El código siguiente agrega el botón y registra un controlador de eventos para el evento TouchUpInside:

var submitButton = UIButton.FromType (UIButtonType.RoundedRect);

submitButton.Frame = new CGRect (10, 170, w - 20, 44);
submitButton.SetTitle ("Submit", UIControlState.Normal);

submitButton.TouchUpInside += (sender, e) => {
    Console.WriteLine ("Submit button pressed");
};

View.AddSubview(submitButton);

Con esto en su lugar, la pantalla de inicio de sesión aparece ahora como se muestra a continuación:

The login screen

A diferencia de las versiones anteriores de iOS, el fondo del botón predeterminado es transparente. Modificar la propiedad BackgroundColor del botón cambia esto:

submitButton.BackgroundColor = UIColor.White;

Dará lugar a un botón cuadrado en lugar del botón redondeado típico. Para obtener el borde redondeado, use el siguiente fragmento de código:

submitButton.Layer.CornerRadius = 5f;

Con estos cambios, la vista tendrá este aspecto:

An example run of the view

Adición de varias vistas a la jerarquía de vistas

iOS proporciona una instalación para agregar varias vistas a la jerarquía de vistas mediante AddSubviews.

View.AddSubviews(new UIView[] { usernameField, passwordField, submitButton });

Adición de la funcionalidad del botón

Cuando se hace clic en un botón, los usuarios esperarán que suceda algo. Por ejemplo, se muestra una alerta o la navegación se realiza en otra pantalla.

Vamos a agregar código para insertar un segundo controlador de vista en la pila de navegación.

En primer lugar, cree el segundo controlador de vista:

var loginVC = new UIViewController () { Title = "Login Success!"};
loginVC.View.BackgroundColor = UIColor.Purple;

A continuación, agregue la funcionalidad al evento TouchUpInside:

submitButton.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (loginVC, true);
            };

La navegación se muestra a continuación:

The navigation is illustrated in this chart

Tenga en cuenta que, de forma predeterminada, cuando se usa un controlador de navegación, iOS proporciona a la aplicación una barra de navegación y un botón Atrás para permitirle volver a través de la pila.

Iteración a través de la jerarquía de vistas

Es posible recorrer en iteración la jerarquía de subvistas y seleccionar cualquier vista determinada. Por ejemplo, para buscar cada UIButton y asignar a ese botón otro BackgroundColor, se puede usar el siguiente fragmento de código.

foreach(var subview in View.Subviews)
{
    if (subview is UIButton)
    {
         var btn = subview as UIButton;
         btn.BackgroundColor = UIColor.Green;
    }
}

Sin embargo, esto no funcionará si la vista en iteración es como UIView, ya que todas las vistas volverán a ser como UIView dado que los objetos agregados a la vista primaria heredan de UIView.

Control de giro

Si el usuario gira el dispositivo a horizontal, los controles no cambian el tamaño adecuado, como se muestra en la captura de pantalla siguiente:

If the user rotates the device to landscape, the controls do not resize appropriately

Una manera de corregir esto es estableciendo la propiedad AutoresizingMask en cada vista. En este caso, queremos que los controles se extiendan horizontalmente, por lo que estableceríamos cada AutoresizingMask. El ejemplo siguiente es para usernameField, pero lo mismo tendría que aplicarse a cada gadget en la jerarquía de vistas.

usernameField.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;

Ahora, cuando giramos el dispositivo o el simulador, todo se extiende para rellenar el espacio adicional, como se muestra a continuación:

All the controls stretch to fill the additional space

Creación de vistas personalizadas

Además de usar controles que forman parte de UIKit, también se pueden usar vistas personalizadas. Se puede crear una vista personalizada heredando de UIView e invalidando Draw. Vamos a crear una vista personalizada y agregarla a la jerarquía de vistas que se va a mostrar.

Heredar de UIView

Lo primero que debemos hacer es crear una clase para la vista personalizada. Para ello, usaremos la plantilla Clase en Visual Studio para agregar una clase vacía denominada CircleView. La clase base debe establecerse en UIView, que como recordaremos está en el espacio de nombres UIKit. También necesitaremos el espacio de nombres System.Drawing. Los otros espacios de nombres System.* no se usarán en este ejemplo, por lo que no dude en quitarlos.

La clase debería tener el siguiente aspecto:

using System;

namespace CodeOnlyDemo
{
    class CircleView : UIView
    {
    }
}

Dibujo en un UIView

Cada UIView tiene un método Draw al que llama el sistema cuando debe dibujarse. Draw nunca debe llamarse directamente. El sistema lo llama durante el procesamiento del bucle de ejecución. La primera vez a través del bucle de ejecución después de agregar una vista a la jerarquía de vistas, se llama a su método Draw. Las llamadas posteriores a Draw se producen cuando la vista está marcada como necesaria para dibujarse llamando a SetNeedsDisplay o SetNeedsDisplayInRect en la vista.

Podemos agregar código de dibujo a nuestra vista agregando este código dentro del método invalidado Draw, como se muestra a continuación:

public override void Draw(CGRect rect)
{
    base.Draw(rect);

    //get graphics context
    using (var g = UIGraphics.GetCurrentContext())
    {
        // set up drawing attributes
        g.SetLineWidth(10.0f);
        UIColor.Green.SetFill();
        UIColor.Blue.SetStroke();

        // create geometry
        var path = new CGPath();
        path.AddArc(Bounds.GetMidX(), Bounds.GetMidY(), 50f, 0, 2.0f * (float)Math.PI, true);

        // add geometry to graphics context and draw
        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.FillStroke);
    }
}

Dado que CircleView es una UIView, también podemos establecer propiedades UIView. Por ejemplo, podemos establecer el BackgroundColor en el constructor:

public CircleView()
{
    BackgroundColor = UIColor.White;
}

Para usar la CircleView que acabamos de crear, podemos agregarla como subvista a la jerarquía de vistas en un controlador existente, como hicimos con UILabels y UIButton anteriores, o podemos cargarla como vista de un nuevo controlador. Vamos a hacer esto último.

Carga de una vista

UIViewController tiene un método denominado LoadView al que llama el controlador para crear su vista. Este es un lugar adecuado para crear una vista y asignarla a la propiedad View del controlador.

En primer lugar, necesitamos un controlador, así que cree una nueva clase vacía denominada CircleController.

En CircleController agregue el código siguiente para establecer la View en una CircleView (no debe llamar a la implementación de base en la invalidación):

using UIKit;

namespace CodeOnlyDemo
{
    class CircleController : UIViewController
    {
        CircleView view;

        public override void LoadView()
        {
            view = new CircleView();
            View = view;
        }
    }
}

Por último, es necesario presentar el controlador en tiempo de ejecución. Para ello, agreguemos un controlador de eventos en el botón enviar que hemos agregado anteriormente, como se indica a continuación:

submitButton.TouchUpInside += delegate
{
    Console.WriteLine("Submit button clicked");

    //circleController is declared as class variable
    circleController = new CircleController();
    PresentViewController(circleController, true, null);
};

Ahora, cuando ejecutamos la aplicación y pulsamos el botón enviar, se muestra la nueva vista con un círculo:

The new view with a circle is displayed

Creación de una pantalla de inicio

Se muestra una pantalla de inicio cuando la aplicación se inicia como una manera de mostrar a los usuarios que es adaptable. Dado que se muestra una pantalla de inicio cuando se carga la aplicación, no se puede crear en el código, ya que la aplicación todavía se está cargando en la memoria.

Al crear un proyecto de iOS en Visual Studio, se proporciona una pantalla de inicio en forma de un archivo .xib, que se puede encontrar en la carpeta Recursos dentro del proyecto.

Esto se puede editar haciendo doble clic en él y abriéndolo en el Interface Builder de Xcode.

Apple recomienda que se use un archivo .xib o Storyboard para las aplicaciones que tienen como destino iOS 8 o posterior. Al iniciar cualquiera de los archivos en el Interface Builder de Xcode, puede usar clases de tamaño y diseño automático para adaptar el diseño para que se vea bien y se muestre correctamente para todos los tamaños de dispositivo. Se puede usar una imagen de inicio estático además de un archivo .xib o Storyboard para permitir la compatibilidad con aplicaciones destinadas a versiones anteriores.

Para obtener más información sobre cómo crear una pantalla de inicio, consulte los documentos siguientes:

Importante

A partir de iOS 9, Apple recomienda que los guiones gráficos se usen como método principal para crear una pantalla de inicio.

Creación de una imagen de inicio para aplicaciones anteriores a iOS 8

Se puede usar una imagen estática además de una pantalla de inicio de .xib o Storyboard si la aplicación tiene como destino versiones anteriores a iOS 8.

Esta imagen estática se puede establecer en el archivo Info.plist o como catálogo de activos (para iOS 7) en la aplicación. Deberá proporcionar imágenes independientes para cada tamaño de dispositivo (320x480, 640x960, 640x1136) en el que se pueda ejecutar la aplicación. Para obtener más información sobre los tamaños de la pantalla de inicio, vea la guía Imágenes de pantalla de inicio.

Importante

Si la aplicación no tiene ninguna pantalla de inicio, es posible que observe que no se ajusta completamente a la pantalla. Si este es el caso, debe asegurarse de incluir, al menos, una imagen de 640x1136 denominada Default-568@2x.png a su Info.plist.

Resumen

En este artículo se describe cómo desarrollar aplicaciones iOS mediante programación en Visual Studio. Hemos visto cómo crear un proyecto a partir de una plantilla de proyecto vacía, que describe cómo crear y agregar un controlador de vista raíz a la ventana. A continuación, mostramos cómo usar controles de UIKit para crear una jerarquía de vistas dentro de un controlador para desarrollar una pantalla de aplicación. A continuación, hemos examinado cómo hacer que las vistas se establezcan adecuadamente en diferentes orientaciones y vimos cómo crear una vista personalizada mediante la subclases UIView, además de cómo cargar la vista dentro de un controlador. Por último, hemos explorado cómo agregar una pantalla de inicio a una aplicación.