Compartir vía


Navegación en Xamarin.Forms Shell

Xamarin.Forms Shell incluye una experiencia de navegación basada en el URI que emplea rutas para navegar a cualquier página de la aplicación, sin tener que seguir una jerarquía de navegación establecida. También ofrece la posibilidad de navegar hacia atrás sin tener que visitar todas las páginas de la pila de navegación.

La clase Shell define las siguientes propiedades relacionadas con la navegación:

Las propiedades BackButtonBehavior, CurrentItem y CurrentState están respaldadas por objetos BindableProperty, lo que significa que estas propiedades pueden ser destinos de los enlaces de datos.

La navegación se realiza mediante la invocación del método GoToAsync, desde la clase Shell. Cuando la navegación está a punto de realizarse, se activa un evento Navigating y, cuando finaliza, se activa un evento Navigated.

Nota:

La navegación todavía se puede realizar en una aplicación de Shell mediante la propiedad Navigation. Para más información, consulte Navegación jerárquica.

Rutas

La navegación se realiza en una aplicación de Shell mediante la especificación de un URI al que navegar. Los URI de navegación pueden tener tres componentes:

  • Una ruta, que define la ruta de acceso al contenido que existe como parte de la jerarquía visual de Shell.
  • Una página. Las páginas que no existen en la jerarquía visual de Shell se pueden insertar en la pila de navegación desde cualquier lugar dentro de una aplicación de Shell. Por ejemplo, una página de detalles no se definirá en la jerarquía visual de Shell, pero se puede insertar en la pila de navegación si es necesario.
  • Uno o varios parámetros de consulta. Los parámetros de consulta son parámetros que se pueden pasar a la página de destino durante la navegación.

Cuando un URI de navegación incluye los tres componentes, la estructura es: //route/page?queryParameters

Registro de rutas

Se pueden definir rutas en objetos FlyoutItem, TabBar, Tab y ShellContent mediante sus propiedades Route:

<Shell ...>
    <FlyoutItem ...
                Route="animals">
        <Tab ...
             Route="domestic">
            <ShellContent ...
                          Route="cats" />
            <ShellContent ...
                          Route="dogs" />
        </Tab>
        <ShellContent ...
                      Route="monkeys" />
        <ShellContent ...
                      Route="elephants" />  
        <ShellContent ...
                      Route="bears" />
    </FlyoutItem>
    <ShellContent ...
                  Route="about" />                  
    ...
</Shell>

Nota:

Todos los elementos de la jerarquía de Shell tienen asociada una ruta. Si no se establece una ruta, se genera una en tiempo de ejecución. Sin embargo, no se garantiza que las rutas generadas sean coherentes entre distintas sesiones de aplicación.

En el ejemplo anterior se crea la siguiente jerarquía de ruta, que se puede usar en la navegación mediante programación:

animals
  domestic
    cats
    dogs
  monkeys
  elephants
  bears
about

Para desplazarse al objeto ShellContent de la ruta dogs, el URI de la ruta absoluta es //animals/domestic/dogs. Igualmente, para desplazarse al objeto ShellContent de la ruta about, el URL de la ruta absoluta es //about.

Advertencia

Si se detecta una ruta duplicada, se produce una excepción ArgumentException al inicio de la aplicación. También se producirá esta excepción si dos o más rutas del mismo nivel de la jerarquía comparten el nombre de ruta.

Registro de rutas de la página de detalles

En el constructor de subclases de Shell, o en cualquier otra ubicación que se ejecute antes de invocar una ruta, se pueden registrar explícitamente rutas adicionales para cualquier página que no esté representada en la jerarquía visual de Shell. Esto se realiza mediante el método Routing.RegisterRoute:

Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));

En este ejemplo se registran como rutas páginas de detalles que no están definidas en la subclase Shell. Se puede navegar después a estas páginas desde cualquier lugar de la aplicación con la navegación basada en URI. Las rutas de estas páginas se conocen como rutas globales.

Advertencia

Se producirá una excepción ArgumentException si el método Routing.RegisterRoute intenta registrar la misma ruta en dos o más tipos diferentes.

Como alternativa, se pueden registrar las páginas en diferentes jerarquías de ruta:

Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));

En este ejemplo se habilita la navegación contextual por las páginas, donde la navegación a la ruta details desde la página de la ruta monkeys muestra MonkeyDetailPage. De forma similar, al desplazarse a la ruta details desde la página de la ruta elephants se muestra ElephantDetailPage. Para obtener más información, consulte Navegación contextual.

Nota:

Si es necesario, el registro de las páginas cuyas rutas se han registrado con el método Routing.RegisterRoute se puede cancelar con el método Routing.UnRegisterRoute.

Realización de la navegación

Para realizar la navegación, se debe obtener primero una referencia a la subclase Shell. Esta referencia se puede obtener mediante la conversión de la propiedad App.Current.MainPage en un objeto Shell o por medio de la propiedad Shell.Current. La navegación se puede realizar entonces llamando al método GoToAsync en el objeto Shell. Este método lleva a una clase ShellNavigationState y devuelve un objeto Task que se completa una vez completada la animación de navegación. El objeto ShellNavigationState se construye mediante el método GoToAsync, desde una propiedad string o Uri, y tiene su propiedad Location establecida en el argumento string o Uri.

Importante

Cuando se navega a una ruta de la jerarquía visual de Shell, no se crea una pila de navegación. Sin embargo, cuando se navega a una página que no está en la jerarquía visual de Shell, se crea una pila de navegación.

El estado actual de navegación del objeto Shell se puede recuperar mediante la propiedad Shell.Current.CurrentState, que incluye el URI de la ruta mostrada en la propiedad Location.

Rutas absolutas

La navegación se puede realizar mediante la especificación de un URI absoluto válido como argumento del método GoToAsync:

await Shell.Current.GoToAsync("//animals/monkeys");

En este ejemplo se navega a la página de la ruta monkeys, donde dicha ruta se define en un objeto ShellContent. El objeto ShellContent que representa la ruta monkeys es un elemento secundario de un objeto FlyoutItem, cuya ruta es animals.

Rutas relativas

La navegación se puede realizar también mediante la especificación de un URI relativo válido como argumento del método GoToAsync. El sistema de enrutamiento intenta hacer coincidir el URI con un objeto ShellContent. Por lo tanto, si todas las rutas de una aplicación son únicas, la navegación se puede realizar con solo especificar el nombre de ruta único como un URI relativo.

Se admiten los formatos siguientes de ruta relativa:

Formato Descripción
route En la jerarquía de ruta se buscará la ruta especificada, hacia arriba desde la posición actual. La página que coincida se insertará en la pila de navegación.
/route La jerarquía de ruta se buscará a partir de la ruta especificada, hacia abajo desde la posición actual. La página que coincida se insertará en la pila de navegación.
//route En la jerarquía de ruta se buscará la ruta especificada, hacia arriba desde la posición actual. La página que coincida reemplazará la pila de navegación.
///route En la jerarquía de ruta se buscará la ruta especificada, hacia abajo desde la posición actual. La página que coincida reemplazará la pila de navegación.

En el ejemplo siguiente se navega a la página de la ruta monkeydetails:

await Shell.Current.GoToAsync("monkeydetails");

En este ejemplo, la ruta monkeyDetails se busca hacia arriba en la jerarquía hasta que se encuentra la página que coincide. Cuando se encuentra, se inserta en la pila de navegación.

Navegación contextual

Las rutas relativas permiten la navegación contextual. Por ejemplo, considere la siguiente jerarquía de ruta:

monkeys
  details
bears
  details

Cuando se muestra la página registrada para la ruta monkeys, al navegar a la ruta details se mostrará la página registrada para la ruta monkeys/details. Igualmente, cuando se muestra la página registrada para la ruta bears, al navegar a la ruta details se mostrará la página registrada para la ruta bears/details. Para información sobre cómo registrar las rutas en este ejemplo, consulte Registro de rutas de página.

Navegación hacia atrás

La navegación hacia atrás se puede llevar a cabo especificando ".." como argumento en el método GoToAsync:

await Shell.Current.GoToAsync("..");

La navegación hacia atrás con ".." también se puede combinar con una ruta:

await Shell.Current.GoToAsync("../route");

En este ejemplo, se realiza la navegación hacia atrás y, después, se navega a la ruta especificada.

Importante

Desplazarse hacia atrás y luego a una ruta especificada solo es posible si la navegación hacia atrás sitúa al usuario en la ubicación actual en la jerarquía de rutas para navegar a la ruta especificada.

Del mismo modo, es posible desplazarse hacia atrás varias veces y, seguidamente, navegar a una ruta especificada:

await Shell.Current.GoToAsync("../../route");

En este ejemplo, la navegación hacia atrás se realiza dos veces y, después, se navega a la ruta especificada.

Además, los datos se pueden pasar a través de las propiedades de consulta al navegar hacia atrás:

await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");

En este ejemplo, se realiza la navegación hacia atrás y el valor del parámetro de consulta se pasa al parámetro de consulta en la página anterior.

Nota:

Los parámetros de consulta se pueden anexar a cualquier solicitud de navegación hacia atrás.

Para obtener más información sobre cómo pasar datos al navegar, vea Pasar datos.

Rutas no válidas

Los siguientes formatos de ruta no son válidos:

Formato Explicación
//page o ///page Actualmente, las rutas globales no pueden ser la única página en la pila de navegación. Por lo tanto, no se admite el enrutamiento absoluto a rutas globales.

El uso de estos formatos de ruta dará como resultado una excepción Exception.

Advertencia

Al intentar navegar a una ruta inexistente, se producirá una excepción ArgumentException.

Depuración de la navegación

Algunas de las clases de Shell se representan con DebuggerDisplayAttribute, que especifica cómo el depurador muestra una clase o campo. Esto puede ayudar a depurar las solicitudes de navegación puesto que se muestran los datos relacionados con la solicitud de navegación. Por ejemplo, en la captura de pantalla siguiente se muestran las propiedades CurrentItem y CurrentState del objeto Shell.Current:

Captura de pantalla del depurador

En este ejemplo, la propiedad CurrentItem, de tipo FlyoutItem, muestra el título y la ruta de acceso del objeto FlyoutItem. Igualmente, la propiedad CurrentState, de tipo ShellNavigationState, muestra el URI de la ruta mostrada dentro de la aplicación de Shell.

La clase Tab define una propiedad Stack, de tipo IReadOnlyList<Page>, que representa la pila de navegación actual en el objeto Tab. La clase también proporciona los siguientes métodos de navegación reemplazables:

  • GetNavigationStack, que devuelve IReadOnlyList<Page>, la pila de navegación actual.
  • OnInsertPageBefore, que se llama cuando se llama a INavigation.InsertPageBefore.
  • OnPopAsync, que devuelve Task<Page> y se llama cuando se llama a INavigation.PopAsync.
  • OnPopToRootAsync, que devuelve Task y se llama cuando se llama a INavigation.OnPopToRootAsync.
  • OnPushAsync, que devuelve Task y se llama cuando se llama a INavigation.PushAsync.
  • OnRemovePage, que se llama cuando se llama a INavigation.RemovePage.

En el ejemplo siguiente se muestra cómo se invalida el método OnRemovePage:

public class MyTab : Tab
{
    protected override void OnRemovePage(Page page)
    {
        base.OnRemovePage(page);

        // Custom logic
    }
}

En este ejemplo, los objetos MyTab se deben consumir en la jerarquía visual de Shell en lugar de los objetos Tab.

La clase Shell define un evento Navigating, que se desencadena cuando está a punto de realizarse la navegación, ya sea debido a la navegación mediante programación o a la interacción del usuario. El objeto ShellNavigatingEventArgs que acompaña al evento Navigating proporciona las siguientes propiedades:

Propiedad Tipo Descripción
Current ShellNavigationState Identificador URI base de la página actual.
Source ShellNavigationSource El tipo de navegación que se ha producido.
Target ShellNavigationState Identificador URI que representa el destino de la navegación.
CanCancel bool Valor que indica si es posible cancelar la navegación.
Cancelled bool Valor que indica si la navegación se ha cancelado.

Además, la clase ShellNavigatingEventArgs proporciona un método Cancel, que se puede usar para cancelar la navegación, y un método GetDeferral, que devuelve un token de ShellNavigatingDeferral que se puede usar para completar la navegación. Para obtener más información sobre el aplazamiento de la navegación, vea Aplazamiento de navegación.

La clase Shell también define un evento Navigated, que se desencadena cuando se ha completado la navegación. El objeto ShellNavigatedEventArgs que acompaña al evento Navigated proporciona las siguientes propiedades:

Propiedad Tipo Descripción
Current ShellNavigationState Identificador URI base de la página actual.
Previous ShellNavigationState Identificador URI de la página anterior.
Source ShellNavigationSource El tipo de navegación que se ha producido.

Importante

Se llama al método OnNavigating cuando se genera el evento Navigating. Del mismo modo, se llama al método OnNavigated cuando se genera el evento Navigated. Ambos métodos se pueden invalidar en la subclase Shell para interceptar las solicitudes de navegación.

Ambas clases, ShellNavigatedEventArgs y ShellNavigatingEventArgs, tienen propiedades Source, de tipo ShellNavigationSource. Esta enumeración proporciona los valores siguientes:

  • Unknown
  • Push
  • Pop
  • PopToRoot
  • Insert
  • Remove
  • ShellItemChanged
  • ShellSectionChanged
  • ShellContentChanged

Por lo tanto, se puede interceptar la navegación con una invalidación de OnNavigating y se pueden realizar acciones en función del origen de navegación. Por ejemplo, el código siguiente muestra cómo cancelar la navegación hacia atrás si no se han guardado los datos de la página:

protected override void OnNavigating(ShellNavigatingEventArgs args)
{
    base.OnNavigating(args);

    // Cancel any back navigation.
    if (args.Source == ShellNavigationSource.Pop)
    {
        args.Cancel();
    }
// }

La navegación de shell se puede interceptar y completar, o bien cancelar, según lo que elija el usuario. Esto se puede conseguir invalidando el método OnNavigating de la subclase Shell y llamando al método GetDeferral en el objeto ShellNavigatingEventArgs. Este método devuelve un token de ShellNavigatingDeferral que tiene un método Complete, que se puede usar para completar la solicitud de navegación:

public MyShell : Shell
{
    // ...
    protected override async void OnNavigating(ShellNavigatingEventArgs args)
    {
        base.OnNavigating(args);

        ShellNavigatingDeferral token = args.GetDeferral();

        var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
        if (result != "Yes")
        {
            args.Cancel();
        }
        token.Complete();
    }    
}

En este ejemplo, se muestra una hoja de acción que invita al usuario a completar la solicitud de navegación o cancelarla. La navegación se cancela invocando el método Cancel en el objeto ShellNavigatingEventArgs. La navegación se completa invocando el método Complete en el token de ShellNavigatingDeferral obtenido mediante el método GetDeferral en el objeto ShellNavigatingEventArgs.

Advertencia

El método GoToAsync generará una excepción InvalidOperationException si un usuario intenta navegar mientras hay un aplazamiento de navegación pendiente.

Pasar datos

Al realizar la navegación mediante programación basada en URI, los datos pueden pasarse como parámetros de consulta. Esto se consigue anexando ? después de una ruta, seguido de un identificador de parámetro de consulta, =, y un valor. Por ejemplo, el código siguiente se ejecuta en la aplicación de ejemplo cuando un usuario selecciona un elefante en ElephantsPage:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}

En este ejemplo de código se recupera el elefante actualmente seleccionado en CollectionView y se navega a la ruta elephantdetails; además, se pasa elephantName como parámetro de consulta.

Hay dos formas de recibir datos de navegación:

  1. La clase que representa la página a la que se navega, o la clase correspondiente al objeto BindingContext de la página, se puede decorar con un elemento QueryPropertyAttribute por cada parámetro de consulta. Para obtener más información, vea Procesamiento de datos de navegación mediante atributos de propiedad de consulta.
  2. La clase que representa la página a la que se navega, o la clase correspondiente al objeto BindingContext de la página, puede implementar la interfaz IQueryAttributable. Para obtener más información, vea Procesamiento de datos de navegación mediante un único método.

Procesamiento de datos de navegación mediante atributos de propiedad de consulta

Para recibir datos de navegación, se puede decorar la clase receptora con un objeto QueryPropertyAttribute por cada parámetro de consulta:

[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            LoadAnimal(value);
        }
    }
    ...

    void LoadAnimal(string name)
    {
        try
        {
            Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
            BindingContext = animal;
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }    
}

El primer argumento de QueryPropertyAttribute especifica el nombre de la propiedad que recibirá los datos, mientras que el segundo argumento especifica el identificador del parámetro de consulta. Por tanto, el elemento QueryPropertyAttribute del ejemplo anterior especifica que la propiedad Name recibirá los datos pasados al parámetro de consulta name del URI en la llamada al método GoToAsync. El establecedor de la propiedad Name llama al método LoadAnimal para recuperar el objeto Animal para el objeto name y lo establece como la propiedad BindingContext de la página.

Nota:

Los valores de parámetro de consulta que se reciben a través de QueryPropertyAttribute se descodifican automáticamente como dirección URL.

Procesamiento de datos de navegación mediante un único método

Para recibir datos de navegación, se puede implementar la interfaz IQueryAttributable en la clase receptora. La interfaz IQueryAttributable especifica que la clase encargada de la implementación debe implementar el método ApplyQueryAttributes. Este método tiene un argumento query, de tipo IDictionary<string, string>, que contiene los datos que se pasan durante la navegación. Cada clave del diccionario es un identificador de parámetro de consulta, y su valor es el valor del parámetro de consulta. La ventaja de usar este método es que los datos de navegación se pueden procesar con un único método, lo que puede resultar útil cuando hay varios elementos de datos de navegación que deben procesarse como un todo.

En el siguiente ejemplo se muestra una clase de modelo de vista que implementa la interfaz IQueryAttributable:

public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Monkey { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        // The query parameter requires URL decoding.
        string name = HttpUtility.UrlDecode(query["name"]);
        LoadAnimal(name);
    }

    void LoadAnimal(string name)
    {
        try
        {
            Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
            OnPropertyChanged("Monkey");
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }
    ...
}

En este ejemplo, el método ApplyQueryAttributes recupera el valor del parámetro de consulta name del URI incluido en la llamada de método GoToAsync. Tras ello, se llama al método LoadAnimal para recuperar el objeto Animal, donde se establece como el valor de la propiedad Monkey a la que se enlazan datos.

Importante

Los valores de parámetro de consulta que se reciben a través de la interfaz IQueryAttributable no se descodifican automáticamente como dirección URL.

Paso y procesamiento de varios parámetros de consulta

Se pueden pasar varios parámetros de consulta conectándolos con &. Por ejemplo, el código siguiente pasa dos elementos de datos:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}

En este ejemplo de código se recupera el elefante actualmente seleccionado en CollectionView y se navega a la ruta elephantdetails; además, se pasan elephantName y elephantLocation como parámetros de consulta.

Para recibir varios elementos de datos, la clase que representa la página a la que se navega, o la clase correspondiente al objeto BindingContext de la página, se puede decorar con un elemento QueryPropertyAttribute por cada parámetro de consulta:

[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            // Custom logic
        }
    }

    public string Location
    {
        set
        {
            // Custom logic
        }
    }
    ...    
}

En este ejemplo, la clase se decora con un objeto QueryPropertyAttribute para cada parámetro de consulta. El primer objeto QueryPropertyAttribute especifica que la propiedad Name recibirá los datos pasados en el parámetro de consulta name, mientras que el segundo objeto QueryPropertyAttribute especifica que la propiedad Location recibirá los datos pasado en el parámetro de consulta location. En ambos casos, los valores de los parámetros de consulta se especifican en el URI en la llamada al método GoToAsync.

Opcionalmente, para procesar datos de navegación mediante un único método, se puede implementar la interfaz IQueryAttributable en la clase que representa la página a la que se navega, o la clase correspondiente al objeto BindingContext de la página:

public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Elephant { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        string name = HttpUtility.UrlDecode(query["name"]);
        string location = HttpUtility.UrlDecode(query["location"]);
        ...        
    }
    ...
}

En este ejemplo, el método ApplyQueryAttributes recupera el valor de los parámetros de consulta name y location del URI incluido en la llamada de método GoToAsync.

Comportamiento del botón Atrás

La apariencia y el comportamiento del botón Atrás se pueden volver a definir estableciendo la propiedad adjunta BackButtonBehavior en un objeto BackButtonBehavior. La clase BackButtonBehavior define las propiedades siguientes:

  • Command, de tipo ICommand, que se ejecuta cuando se presiona el botón Atrás.
  • CommandParameter, de tipo object, que es el parámetro que se pasa al objeto Command.
  • IconOverride, de tipo ImageSource, el icono usado para el botón Atrás.
  • IsEnabled, de tipo boolean, indica si se ha habilitado el botón Atrás. El valor predeterminado es true.
  • TextOverride, de tipo string, el texto usado para el botón Atrás.

Todas estas propiedades están respaldados por objetos BindableProperty, lo que significa que las propiedades pueden ser destinos de los enlaces de datos.

En el código siguiente se muestra un ejemplo de cómo volver a definir la apariencia y el comportamiento del botón Atrás:

<ContentPage ...>    
    <Shell.BackButtonBehavior>
        <BackButtonBehavior Command="{Binding BackCommand}"
                            IconOverride="back.png" />   
    </Shell.BackButtonBehavior>
    ...
</ContentPage>

El código de C# equivalente es el siguiente:

Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
    Command = new Command(() =>
    {
        ...
    }),
    IconOverride = "back.png"
});

La propiedad Command se establece en ICommand para ejecutarse cuando se presiona el botón Atrás y la propiedad IconOverride se establece en el icono que se usa para el botón Atrás:

Captura de pantalla de la invalidación de un icono de botón Atrás de Shell en iOS y Android