Shell navigácia v .NET MAUI: Kompletný sprievodca routingom, dátami a pokročilými vzormi

Naučte sa ovládať Shell navigáciu v .NET MAUI. Routing, predávanie dát, navigation guards, modálna navigácia, deep linking a MVVM integrácia — všetko s praktickými príkladmi kódu.

Prečo je Shell navigácia srdcom každej .NET MAUI aplikácie

Ak ste čítali náš predchádzajúci článok o MVVM architektúre v .NET MAUI, pravdepodobne ste si uvedomili jednu vec — aj tá najlepšie navrhnutá architektúra je zbytočná, ak nemáte zvládnutú navigáciu. Navigácia je nervový systém vašej aplikácie. Prepája stránky, prenáša dáta medzi obrazovkami a v konečnom dôsledku definuje, ako používateľ celú aplikáciu prežíva.

A v .NET MAUI je Shell navigácia tou najodporúčanejšou cestou, ako to robiť správne.

Shell nie je len navigačný systém. Je to kompletný UI kontajner — flyout menu, záložky, URI-based routing a kopa ďalších funkcií v jednom konzistentnom balíku. Namiesto ručného spravovania navigačného stacku jednoducho definujete trasy a navigujete cez URI reťazce, takmer ako pri webovej aplikácii. Keď som prvýkrát prešiel z klasickej NavigationPage na Shell, bol som úprimne prekvapený, o koľko jednoduchší sa kód stal.

Tak poďme na to. V tomto článku si prejdeme všetko — od vizuálnej hierarchie, cez definovanie trás, predávanie dát, navigation guards, životný cyklus stránok až po integráciu s MVVM vzorom. Všetko s praktickými príkladmi, ktoré môžete rovno použiť.

Shell vizuálna hierarchia — základ všetkého

Predtým, než začneme navigovať, musíme pochopiť, ako Shell organizuje stránky vašej aplikácie. Shell definuje vizuálnu hierarchiu pomocou niekoľkých kľúčových elementov:

  • Shell — koreňový kontajner celej navigačnej štruktúry
  • FlyoutItem — reprezentuje položku vo flyout (hamburger) menu. Používajte ho, keď vaša aplikácia potrebuje bočné menu.
  • TabBar — reprezentuje spodné záložky. Hodí sa, keď navigácia začína záložkami bez flyoutu.
  • Tab — skupina obsahu v rámci záložky. Môže obsahovať viacero ShellContent objektov zobrazených ako horné záložky.
  • ShellContent — definuje konkrétnu stránku (ContentPage) v rámci hierarchie.

Praktický príklad: Aplikácia s flyoutom a záložkami

Pozrime sa na typickú štruktúru aplikácie pre správu produktov:

<Shell x:Class="MojaAplikacia.AppShell"
       xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MojaAplikacia.Views"
       Shell.FlyoutBehavior="Flyout"
       Title="Správca produktov">

    <!-- Hlavná sekcia s tabmi -->
    <FlyoutItem Title="Prehľad"
                Icon="dashboard.png"
                Route="prehlad">
        <ShellContent Title="Dashboard"
                      ContentTemplate="{DataTemplate views:DashboardPage}"
                      Route="dashboard" />
    </FlyoutItem>

    <FlyoutItem Title="Produkty"
                Icon="products.png"
                Route="produkty">
        <Tab Title="Zoznam" Route="zoznam">
            <ShellContent Title="Všetky"
                          ContentTemplate="{DataTemplate views:ProduktZoznamPage}"
                          Route="vsetky" />
            <ShellContent Title="Obľúbené"
                          ContentTemplate="{DataTemplate views:OblubenePage}"
                          Route="oblubene" />
        </Tab>
        <Tab Title="Kategórie" Route="kategorie">
            <ShellContent Title="Prehľad kategórií"
                          ContentTemplate="{DataTemplate views:KategoriePage}"
                          Route="prehlad-kategorii" />
        </Tab>
    </FlyoutItem>

    <FlyoutItem Title="Nastavenia"
                Icon="settings.png"
                Route="nastavenia">
        <ShellContent Title="Nastavenia"
                      ContentTemplate="{DataTemplate views:NastaveniaPage}"
                      Route="hlavne-nastavenia" />
    </FlyoutItem>

</Shell>

Táto konfigurácia vytvára nasledujúcu hierarchiu trás:

prehlad
  dashboard
produkty
  zoznam
    vsetky
    oblubene
  kategorie
    prehlad-kategorii
nastavenia
  hlavne-nastavenia

Všimnite si, že každý element má vlastnosť Route. Tieto trasy tvoria URI štruktúru, cez ktorú budeme navigovať. Ak Route neurčíte, Shell vygeneruje trasu automaticky — ale pozor, automaticky generované trasy nie sú konzistentné medzi rôznymi spusteniami aplikácie. Takže ich vždy definujte explicitne. Verte mi, debugovanie náhodne generovaných trás nie je zábava.

TabBar verzus FlyoutItem

Ak vaša aplikácia nepotrebuje flyout menu a vystačíte si s klasickými spodnými záložkami, jednoducho nahraďte FlyoutItem za TabBar:

<Shell x:Class="MojaAplikacia.AppShell"
       xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MojaAplikacia.Views"
       Shell.FlyoutBehavior="Disabled">

    <TabBar>
        <ShellContent Title="Domov"
                      Icon="home.png"
                      ContentTemplate="{DataTemplate views:DomovPage}"
                      Route="domov" />
        <ShellContent Title="Hľadať"
                      Icon="search.png"
                      ContentTemplate="{DataTemplate views:HladatPage}"
                      Route="hladat" />
        <ShellContent Title="Profil"
                      Icon="profile.png"
                      ContentTemplate="{DataTemplate views:ProfilPage}"
                      Route="profil" />
    </TabBar>

</Shell>

Jeden dôležitý detail: ContentTemplate používa DataTemplate, čo znamená, že stránky sa vytvárajú lazy — až keď na ne používateľ skutočne naviguje. Toto je obrovská výhoda oproti starému TabbedPage, kde sa všetky stránky inicializovali pri štarte. Pri aplikáciách s 5+ záložkami je rozdiel v štartovacom čase naozaj citeľný.

Registrácia trás a navigácia medzi stránkami

V Shell navigácii existujú dva typy trás:

  1. Implicitné trasy — definované priamo v XAML hierarchii Shell (ako sme videli vyššie)
  2. Explicitné trasy — registrované programaticky pre stránky, ktoré nie sú súčasťou vizuálnej hierarchie (typicky detailové stránky)

Registrácia explicitných trás

Detailové stránky, modálne okná a ďalšie stránky mimo Shell hierarchie musíte registrovať v konštruktore AppShell:

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        // Registrácia detailových stránok
        Routing.RegisterRoute("produkt-detail", typeof(ProduktDetailPage));
        Routing.RegisterRoute("produkt-edit", typeof(ProduktEditPage));
        Routing.RegisterRoute("objednavka-detail", typeof(ObjednavkaDetailPage));
        Routing.RegisterRoute("fotogaleria", typeof(FotogaleriaPage));
    }
}

Dôležité: názvy trás musia byť unikátne v rámci celej aplikácie. Ak dva ShellContent elementy alebo registrované trasy zdieľajú rovnaký názov, Shell vyhodí ArgumentException pri štarte. Toto je jedna z tých chýb, ktoré vás dokážu potrápiť, pretože sa prejaví až za behu.

Absolútna navigácia s prefixom //

Absolútna navigácia resetuje celý navigačný stack a presmeruje na konkrétnu stránku v Shell hierarchii:

// Navigácia na dashboard
await Shell.Current.GoToAsync("//prehlad/dashboard");

// Navigácia na zoznam produktov
await Shell.Current.GoToAsync("//produkty/zoznam/vsetky");

// Navigácia na nastavenia
await Shell.Current.GoToAsync("//nastavenia/hlavne-nastavenia");

Prefix // v podstate hovorí Shellu: „Zabudni na aktuálny stack a prejdi priamo sem." Ideálne pre navigáciu medzi hlavnými sekciami aplikácie.

Relatívna navigácia

Relatívna navigácia pridá stránku na vrch navigačného stacku. Typicky ju použijete pre prechod na detailové stránky:

// Navigácia na detail produktu (pridá sa na stack)
await Shell.Current.GoToAsync("produkt-detail");

// Navigácia späť na predchádzajúcu stránku
await Shell.Current.GoToAsync("..");

// Navigácia späť o dve úrovne
await Shell.Current.GoToAsync("../..");

Reťazec .. funguje presne ako v súborovom systéme — vracia vás o jednu úroveň späť. Je to čistejšia alternatíva k Navigation.PopAsync() a hlavne konzistentnejšia so zvyškom Shell navigácie.

Prečo nepoužívať Navigation.PushAsync v Shell aplikáciách

Toto je úprimne jedna z najčastejších chýb, ktoré vidím u vývojárov migrujúcich z Xamarin.Forms. A je to úplne pochopiteľné — starý návyk je silný.

Problém je v tom, že Navigation.PushAsync v Shell aplikácii vytvára separátny navigačný stack, ktorý nie je synchronizovaný so Shell routingom. Výsledok? Problémy s tlačidlom Späť, nekonzistentné správanie a ťažko debugovateľné chyby. Pravidlo je jednoduché: ak používate Shell, navigujte výlučne cez Shell.Current.GoToAsync().

Predávanie dát medzi stránkami — tri spôsoby

Jedným z najdôležitejších aspektov navigácie je prenos dát medzi stránkami. Shell ponúka niekoľko mechanizmov a každý má svoje miesto.

Spôsob 1: Query parametre v URI (pre jednoduché typy)

Najjednoduchší spôsob pre primitívne typy ako string, int alebo Guid:

// Odoslanie dát
await Shell.Current.GoToAsync($"produkt-detail?produktId={produkt.Id}&nazov={Uri.EscapeDataString(produkt.Nazov)}");

Na prijímajúcej strane implementujete rozhranie IQueryAttributable:

public partial class ProduktDetailViewModel : ObservableObject, IQueryAttributable
{
    [ObservableProperty]
    private int produktId;

    [ObservableProperty]
    private string nazov;

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("produktId", out var id))
        {
            ProduktId = Convert.ToInt32(id);
        }

        if (query.TryGetValue("nazov", out var name))
        {
            Nazov = Uri.UnescapeDataString(name.ToString());
        }
    }
}

Spôsob 2: Dictionary s komplexnými objektmi (odporúčaný)

Pre predávanie celých objektov — a toto je podľa mňa najpoužívanejší prístup v praxi — použite preťaženie GoToAsync s Dictionary<string, object>:

// Odoslanie komplexného objektu
var navigationParams = new Dictionary<string, object>
{
    { "produkt", vybranyProdukt },
    { "rezimUpravy", true }
};

await Shell.Current.GoToAsync("produkt-detail", navigationParams);

Na prijímajúcej strane vo ViewModeli:

public partial class ProduktDetailViewModel : ObservableObject, IQueryAttributable
{
    [ObservableProperty]
    private Produkt aktualnyProdukt;

    [ObservableProperty]
    private bool jeVRezimuUpravy;

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("produkt", out var prod))
        {
            AktualnyProdukt = prod as Produkt;
        }

        if (query.TryGetValue("rezimUpravy", out var rezim))
        {
            JeVRezimuUpravy = Convert.ToBoolean(rezim);
        }
    }
}

Tento prístup je čistý, type-safe a (čo je dôležité) funguje bez problémov s AOT kompiláciou a trimmingom. Na rozdiel od starého QueryPropertyAttribute, ktorý využíva reflexiu a nie je bezpečný pre NativeAOT, IQueryAttributable je plne podporovaný a odporúčaný prístup v .NET MAUI.

Spôsob 3: ShellNavigationQueryParameters pre jednorazové dáta

Ak potrebujete predať dáta, ktoré by mali byť automaticky vyčistené po spracovaní:

var parameters = new ShellNavigationQueryParameters
{
    { "docasneData", novyObjekt },
    { "zdrojNavigacie", "dashboard" }
};

await Shell.Current.GoToAsync("produkt-edit", parameters);

Tu je ale dôležité vedieť jednu vec: dáta predané ako navigačné parametre zostávajú v pamäti po celú dobu života stránky a sú znovu predané pri navigácii späť. Toto správanie je zámerné, ale môže vás nepríjemne prekvapiť, ak s ním nepočítate. Ak potrebujete resetovať stav pri návrate, musíte to ošetriť manuálne v ApplyQueryAttributes.

Životný cyklus stránok v Shell navigácii

Pochopenie životného cyklu stránok je kľúčové pre správne načasovanie inicializácie dát a uvoľňovanie zdrojov. Shell rozširuje štandardný MAUI životný cyklus o niekoľko ďalších udalostí a stojí za to vedieť, v akom poradí sa spúšťajú.

Poradie udalostí pri navigácii

Keď navigujete na novú stránku, udalosti sa spúšťajú v tomto poradí:

  1. Shell.Navigating — ešte pred začatím navigácie (tu máte možnosť ju zrušiť)
  2. OnDisappearing — na stránke, z ktorej odchádzate
  3. ApplyQueryAttributes — na cieľovom ViewModeli (ak implementuje IQueryAttributable)
  4. OnNavigatedTo — na cieľovej stránke, po dokončení renderovania
  5. OnAppearing — na cieľovej stránke, keď sa stáva viditeľnou
  6. Shell.Navigated — po dokončení celej navigácie

OnAppearing vs OnNavigatedTo — kedy čo použiť

Tieto dve udalosti si vývojári často zamieňajú. Tu je kľúčový rozdiel:

  • OnNavigatedTo — volá sa po tom, čo je stránka úplne vyrenderovaná. Ideálne pre inicializáciu dát, volanie API a nastavenie stavu. Ale pozor — nie je volaný pri prepínaní záložiek v TabBar, čo mnohých prekvapí.
  • OnAppearing — volá sa zakaždým, keď sa stránka stáva viditeľnou. To zahŕňa aj návrat z detailovej stránky. Ideálne pre obnovenie dát a synchronizáciu stavu.

V praxi väčšinou používam OnAppearing ako hlavný vstupný bod, pretože pokrýva viac scenárov.

Praktická implementácia s BaseViewModel

.NET MAUI nemá vstavaný životný cyklus pre ViewModely, takže je dobrou praxou vytvoriť si základný ViewModel s virtuálnymi metódami:

public abstract partial class BaseViewModel : ObservableObject, IQueryAttributable
{
    [ObservableProperty]
    private bool jePrvotneNacitanie = true;

    [ObservableProperty]
    private bool nacitavaSa;

    public virtual void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        // Predvolená implementácia — potomkovia prepíšu podľa potreby
    }

    public virtual Task InicializovatAsync()
    {
        // Volané pri prvom zobrazení stránky
        return Task.CompletedTask;
    }

    public virtual Task ObnovitAsync()
    {
        // Volané pri každom ďalšom zobrazení (návrat späť)
        return Task.CompletedTask;
    }
}

A zodpovedajúca základná stránka:

public abstract class BasePage : ContentPage
{
    protected override async void OnAppearing()
    {
        base.OnAppearing();

        if (BindingContext is BaseViewModel vm)
        {
            if (vm.JePrvotneNacitanie)
            {
                vm.JePrvotneNacitanie = false;
                await vm.InicializovatAsync();
            }
            else
            {
                await vm.ObnovitAsync();
            }
        }
    }
}

Tento vzor vám dáva čistú kontrolu nad inicializáciou bez závislosti na konkrétnom navigačnom frameworku. Používam ho prakticky v každom MAUI projekte.

Navigation Guards — kontrola a zachytávanie navigácie

Niekedy jednoducho potrebujete zabrániť navigácii. Napríklad keď má používateľ neuložené zmeny alebo keď nie je prihlásený. Shell poskytuje udalosť Navigating, ktorá vám umožňuje navigáciu zachytiť a prípadne zrušiť.

Základný navigation guard

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Navigating += OnShellNavigating;
    }

    private async void OnShellNavigating(object sender, ShellNavigatingEventArgs e)
    {
        // Kontrola, či používateľ má neuložené zmeny
        if (e.Source == ShellNavigationSource.Pop ||
            e.Source == ShellNavigationSource.PopToRoot)
        {
            var currentPage = Shell.Current.CurrentPage;

            if (currentPage?.BindingContext is IHasUnsavedChanges viewModel
                && viewModel.MaNeulozeZmeny)
            {
                // Získanie tokenu pre odloženie navigácie
                var deferral = e.GetDeferral();

                bool opustit = await currentPage.DisplayAlert(
                    "Neuložené zmeny",
                    "Máte neuložené zmeny. Chcete naozaj odísť?",
                    "Áno, odísť",
                    "Zostať");

                if (!opustit)
                {
                    e.Cancel();
                }

                deferral.Complete();
            }
        }
    }
}

// Rozhranie pre ViewModely s neuloženými zmenami
public interface IHasUnsavedChanges
{
    bool MaNeulozeZmeny { get; }
}

Všimnite si použitie GetDeferral() — to je kľúčové. Bez neho by navigácia prebehla ešte predtým, než stihne používateľ kliknúť na dialóg.

ShellNavigationSource — rozpoznanie typu navigácie

ShellNavigatingEventArgs obsahuje vlastnosť Source, ktorá vám prezradí, ako navigácia vznikla:

  • ShellNavigationSource.Push — navigácia na novú stránku (relatívna trasa)
  • ShellNavigationSource.Pop — návrat späť o jednu úroveň
  • ShellNavigationSource.PopToRoot — návrat na koreňovú stránku
  • ShellNavigationSource.ShellItemChanged — prepnutie na inú sekciu (flyout alebo tab)
  • ShellNavigationSource.ShellSectionChanged — prepnutie na inú záložku v rámci sekcie
  • ShellNavigationSource.ShellContentChanged — prepnutie na iný obsah v rámci záložky

Tieto informácie sú neoceniteľné pre implementáciu sofistikovanejších guardov — napríklad môžete povoliť voľný presun medzi záložkami, ale vyžadovať potvrdenie pri opustení formuláru.

Autentifikačný guard — presmerovanie neprihlásených používateľov

Toto je klasický scenár, s ktorým sa stretne takmer každá aplikácia. Neprihlásený používateľ sa pokúsi otvoriť chránenú stránku a vy ho chcete presmerovať na prihlásenie. Tu je robustná implementácia:

public partial class AppShell : Shell
{
    private readonly IAuthService _authService;
    private readonly HashSet<string> _verejneTrasy = new()
    {
        "//prihlasenie",
        "//registracia",
        "//zabudnute-heslo"
    };

    public AppShell(IAuthService authService)
    {
        InitializeComponent();
        _authService = authService;

        Routing.RegisterRoute("prihlasenie", typeof(PrihlaseniePage));
        Routing.RegisterRoute("registracia", typeof(RegistraciaPage));
        Routing.RegisterRoute("zabudnute-heslo", typeof(ZabudnuteHesloPage));

        Navigating += OnNavigating;
    }

    private async void OnNavigating(object sender, ShellNavigatingEventArgs e)
    {
        // Ak je cieľ verejná trasa, povoliť navigáciu
        if (_verejneTrasy.Contains(e.Target.Location.OriginalString))
            return;

        // Ak používateľ nie je prihlásený, presmerovať
        if (!_authService.JePrihlaseny)
        {
            var deferral = e.GetDeferral();
            e.Cancel();
            await GoToAsync("//prihlasenie");
            deferral.Complete();
        }
    }
}

Jednoduchý, ale účinný vzor. HashSet s verejnými trasami zabezpečí, že prihlasovacie stránky sú vždy prístupné (inak by ste mali nekonečnú slučku presmerovania).

Integrácia navigácie s MVVM a Dependency Injection

V predchádzajúcom článku o MVVM sme si ukázali, ako nastaviť dependency injection. Teraz sa pozrime, ako navigáciu čisto integrovať s týmto vzorom.

Navigačná služba

Namiesto priameho volania Shell.Current.GoToAsync() vo ViewModeloch je oveľa lepšou praxou vytvoriť navigačnú službu. Dôvody? Testovateľnosť a nižšia väzba na konkrétny framework:

public interface INavigacnaSluzba
{
    Task NavigovatNaAsync(string trasa);
    Task NavigovatNaAsync(string trasa, Dictionary<string, object> parametre);
    Task NavigovatSpatAsync();
    Task NavigovatNaKorenAsync();
}

public class ShellNavigacnaSluzba : INavigacnaSluzba
{
    public async Task NavigovatNaAsync(string trasa)
    {
        await Shell.Current.GoToAsync(trasa);
    }

    public async Task NavigovatNaAsync(string trasa,
        Dictionary<string, object> parametre)
    {
        await Shell.Current.GoToAsync(trasa, parametre);
    }

    public async Task NavigovatSpatAsync()
    {
        await Shell.Current.GoToAsync("..");
    }

    public async Task NavigovatNaKorenAsync()
    {
        await Shell.Current.GoToAsync("//prehlad/dashboard");
    }
}

Registrácia v DI kontajneri

V MauiProgram.cs zaregistrujte navigačnú službu spolu so stránkami a ViewModelmi:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // Navigačná služba
        builder.Services.AddSingleton<INavigacnaSluzba, ShellNavigacnaSluzba>();

        // Stránky
        builder.Services.AddTransient<ProduktZoznamPage>();
        builder.Services.AddTransient<ProduktDetailPage>();
        builder.Services.AddTransient<ProduktEditPage>();

        // ViewModely
        builder.Services.AddTransient<ProduktZoznamViewModel>();
        builder.Services.AddTransient<ProduktDetailViewModel>();
        builder.Services.AddTransient<ProduktEditViewModel>();

        return builder.Build();
    }
}

Použitie navigačnej služby vo ViewModeli

public partial class ProduktZoznamViewModel : BaseViewModel
{
    private readonly INavigacnaSluzba _navigacia;
    private readonly IProduktService _produktService;

    [ObservableProperty]
    private ObservableCollection<Produkt> produkty = new();

    public ProduktZoznamViewModel(
        INavigacnaSluzba navigacia,
        IProduktService produktService)
    {
        _navigacia = navigacia;
        _produktService = produktService;
    }

    public override async Task InicializovatAsync()
    {
        NacitavaSa = true;
        try
        {
            var zoznam = await _produktService.NacitatVsetkyAsync();
            Produkty = new ObservableCollection<Produkt>(zoznam);
        }
        finally
        {
            NacitavaSa = false;
        }
    }

    [RelayCommand]
    private async Task ZobrazitDetail(Produkt produkt)
    {
        var parametre = new Dictionary<string, object>
        {
            { "produkt", produkt }
        };
        await _navigacia.NavigovatNaAsync("produkt-detail", parametre);
    }

    [RelayCommand]
    private async Task PridatNovyProdukt()
    {
        await _navigacia.NavigovatNaAsync("produkt-edit");
    }
}

Krása tohto prístupu je v tom, že ViewModel nepozná Shell, nepoužíva priamo žiadne UI API a navigačná logika je plne testovateľná pomocou mocku INavigacnaSluzba. V unit testoch jednoducho overíte, že sa volala správna metóda so správnymi parametrami.

Modálna navigácia v Shell

Shell podporuje aj modálne zobrazenie stránok, čo sa hodí pre formuláre, potvrdzovacie dialógy alebo akékoľvek interakcie, kde chcete, aby používateľ dokončil akciu pred návratom.

// Modálna navigácia — stránka sa zobrazí ako modálne okno
await Shell.Current.GoToAsync("produkt-edit", animate: true);

// Alebo s parametrami
await Shell.Current.GoToAsync("produkt-edit", true,
    new Dictionary<string, object>
    {
        { "produkt", vybranyProdukt }
    });

Pre nastavenie stránky ako modálnej použite Shell.PresentationMode priamo v XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacia.Views.ProduktEditPage"
             Shell.PresentationMode="ModalAnimated"
             Title="Upraviť produkt">

    <!-- Obsah stránky -->

</ContentPage>

Dostupné režimy prezentácie:

  • NotAnimated — štandardná navigácia bez animácie
  • Animated — štandardná navigácia s animáciou (predvolené)
  • Modal — modálne zobrazenie bez animácie
  • ModalAnimated — modálne zobrazenie s animáciou
  • ModalNotAnimated — modálne zobrazenie bez animácie

V praxi najčastejšie používam ModalAnimated — dáva používateľovi jasný vizuálny signál, že ide o modálnu interakciu.

Prispôsobenie flyout menu

Flyout menu je jedným z najsilnejších vizuálnych prvkov Shell. Dá sa rozsiahlo prispôsobiť — od hlavičky cez jednotlivé položky až po pätu.

Vlastná hlavička flyoutu

<Shell.FlyoutHeaderTemplate>
    <DataTemplate>
        <Grid HeightRequest="180"
              BackgroundColor="{StaticResource Primary}"
              Padding="20">
            <VerticalStackLayout VerticalOptions="End">
                <Image Source="logo.png"
                       HeightRequest="60"
                       WidthRequest="60"
                       HorizontalOptions="Start" />
                <Label Text="Správca produktov"
                       FontSize="20"
                       TextColor="White"
                       FontAttributes="Bold" />
                <Label Text="v2.0 | .NET MAUI 10"
                       FontSize="12"
                       TextColor="White"
                       Opacity="0.8" />
            </VerticalStackLayout>
        </Grid>
    </DataTemplate>
</Shell.FlyoutHeaderTemplate>

Vlastný vzhľad položiek flyoutu

<Shell.ItemTemplate>
    <DataTemplate>
        <Grid ColumnDefinitions="Auto,*"
              Padding="15,10"
              ColumnSpacing="15">
            <Image Source="{Binding FlyoutIcon}"
                   HeightRequest="24"
                   WidthRequest="24"
                   VerticalOptions="Center" />
            <Label Text="{Binding Title}"
                   Grid.Column="1"
                   FontSize="16"
                   VerticalOptions="Center" />
        </Grid>
    </DataTemplate>
</Shell.ItemTemplate>

Programatické ovládanie flyoutu

// Otvoriť flyout
Shell.Current.FlyoutIsPresented = true;

// Zatvoriť flyout
Shell.Current.FlyoutIsPresented = false;

// Zmeniť správanie flyoutu za behu
Shell.Current.FlyoutBehavior = FlyoutBehavior.Locked;

Deep linking — navigácia z externých zdrojov

Shell navigácia prirodzene podporuje deep linking, čo vám umožňuje otvoriť konkrétnu stránku aplikácie z externého zdroja — push notifikácie, URL odkazu alebo inej aplikácie.

Spracovanie deep linku

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
    }

    protected override Window CreateWindow(IActivationState activationState)
    {
        var window = new Window(new AppShell());

        // Spracovanie deep linku pri štarte
        if (activationState?.State != null
            && activationState.State.TryGetValue("deeplink", out var link))
        {
            MainThread.BeginInvokeOnMainThread(async () =>
            {
                await Task.Delay(500); // Počkať na inicializáciu Shell
                await Shell.Current.GoToAsync(link.ToString());
            });
        }

        return window;
    }
}

Ten Task.Delay(500) môže pôsobiť trochu „hacky" (a úprimne, aj je), ale je to praktický workaround. Shell potrebuje čas na inicializáciu a bez tohto oneskorenia by navigácia zlyhala.

Deep link z push notifikácie

// V handleri push notifikácie
public async Task SpracovatNotifikaciu(string trasaNotifikacie)
{
    // Napríklad: "produkt-detail?produktId=42"
    await Shell.Current.GoToAsync(trasaNotifikacie);
}

Výkon a osvedčené postupy pre Shell navigáciu

Na záver si poďme zhrnúť najdôležitejšie osvedčené postupy. Toto sú veci, ktoré som sa naučil (často the hard way) pri budovaní reálnych MAUI aplikácií:

1. Vždy používajte ContentTemplate

Nikdy nepoužívajte ContentPage priamo v ShellContent. Vždy ContentTemplate="{DataTemplate ...}", aby sa stránky vytvárali lazy. Toto dramaticky zrýchľuje štart aplikácie, najmä ak máte viac ako 3-4 záložky.

2. Registrujte stránky a ViewModely ako Transient

Stránky a ViewModely registrujte v DI kontajneri ako Transient, nie Singleton. Každá navigácia by mala vytvoriť novú inštanciu — zabraňuje to problémom so starými dátami a memory leakom.

3. Používajte IQueryAttributable namiesto QueryPropertyAttribute

QueryPropertyAttribute nie je bezpečný pre NativeAOT a trimming. Vždy implementujte IQueryAttributable. Je to budúcnostne bezpečný prístup a navyše vám dáva väčšiu kontrolu nad spracovaním parametrov.

4. Explicitne definujte všetky trasy

Nikdy sa nespoliehajte na automaticky generované trasy. Vždy nastavte Route na každom Shell elemente a registrujte detailové stránky cez Routing.RegisterRoute().

5. Načítavajte dáta asynchrónne

Nikdy nenačítavajte dáta synchrónne v konštruktore. Použite vzor s InicializovatAsync() volaným z OnAppearing, aby UI zostalo responzívne. Používatelia si všimnú aj 200ms zamrznutie.

6. Pozor na navigačnú históriu

Shell si pamätá navigačný stack. Ak navigujete z detailu na detail bez návratu, stack rastie. Pre scenáre, kde chcete nahradiť aktuálnu stránku (nie pridať ďalšiu), použite absolútnu navigáciu s //.

7. Testujte na všetkých platformách

Správanie navigácie sa môže mierne líšiť medzi Androidom, iOS, macOS a Windows. Zvlášť venujte pozornosť „swipe back" gestám na iOS a hardvérovému tlačidlu Späť na Androide. Tieto rozdiely vás dokážu nepríjemne zaskočiť, ak testujete len na jednej platforme.

Záver

Shell navigácia v .NET MAUI je mocný nástroj, ktorý pokrýva všetko od jednoduchých aplikácií s pár záložkami až po komplexné enterprise riešenia s autentifikačnými guardmi a deep linkingom.

Kľúčom k úspechu je dodržiavanie osvedčených postupov: GoToAsync() namiesto klasickej navigácie, IQueryAttributable pre predávanie dát, navigačná služba pre čistú MVVM integráciu a lazy loading cez ContentTemplate.

Ak ste postupovali podľa nášho článku o MVVM, teraz máte kompletný základ pre robustnú .NET MAUI aplikáciu — čistú architektúru aj zvládnutú navigáciu. V ďalších článkoch sa pozrieme na lokálne ukladanie dát a prácu s databázami, aby sme celý obrázok skompletizovali.

O Autorovi Editorial Team

Our team of expert writers and editors.