Shell navigacija u .NET MAUI 10: TabBar, Flyout, rute i parametri (2026)

Praktičan vodič za Shell navigaciju u .NET MAUI 10: FlyoutItem, TabBar, Routing.RegisterRoute, prosljeđivanje parametara, prilagodba Flyouta, modalna navigacija, deep linking i uobičajene greške s rješenjima.

.NET MAUI Shell Navigacija Vodič (2026)
On this page
  1. Što je .NET MAUI Shell i čemu služi?
  2. Osnove Shell strukture: FlyoutItem, Tab i ShellContent
  3. Konfiguracija AppShell.xaml, praktičan primjer
  4. Kako navigirati u .NET MAUI Shell?
  5. Kako proslijediti parametre kroz Shell.Current.GoToAsync?
  6. Prilagodba Flyout izbornika i TabBar-a
  7. Modalna navigacija, back button i lifecycle
  8. Deep linking sa Shell URI-jem
  9. Uobičajene greške i kako ih riješiti
  10. Česta pitanja

Ažurirano: 18. rujna 2026.

Shell navigacija u .NET MAUI 10 je ugrađeni sustav navigacije koji objedinjuje Flyout izbornik, TabBar i URI-bazirano usmjeravanje u jedan hijerarhijski model, čime uklanja potrebu za ručnim slaganjem NavigationPage stogova. U ovom vodiču pokrivamo kako definirati AppShell.xaml, registrirati rute, prosljeđivati parametre, prilagoditi Flyout i TabBar, obraditi lifecycle događaje i implementirati deep linking, sve na primjerima za .NET MAUI 10 (studeni 2025.), s napomenama za razlike prema Xamarin.Forms Shellu.

Iskreno, nakon što sam preseljavao dva veća Xamarin.Forms projekta na MAUI 10, Shell je onaj dio koji najviše štedi vrijeme. Nema više triju stranica s pola implementiranom navigacijom.

Što je .NET MAUI Shell i čemu služi?

Shell je krovni kontejner koji definira strukturu cijele mobilne aplikacije, bočni izbornik (Flyout), donje tabove i stog stranica, koristeći jedan XAML dokument (AppShell.xaml) i konzistentan URI model. Umjesto da ručno slažete NavigationPage, TabbedPage i FlyoutPage hijerarhije kao u Xamarin.Forms, Shell to radi deklarativno kroz elemente FlyoutItem, Tab i ShellContent. Rezultat je manje boilerplate koda, jasnija struktura navigacije i ujednačeno ponašanje na iOS-u, Androidu, macOS-u (Mac Catalyst) i Windowsu.

Shell je posebno koristan kada aplikacija ima 3+ glavne sekcije (dashboard, katalog, profil, postavke). Nudi ugrađeni pretraživač u naslovnoj traci (SearchHandler), ikone i bedževe na tabovima, animirane prijelaze između stranica i URI-based navigaciju koja se prirodno mapira na push notifikacije i deep linkove. U .NET MAUI 10 (studeni 2025.) Shell donosi nekoliko važnih poboljšanja: brže renderiranje TabBar-a na Androidu, bolja podrška za dinamičko dodavanje/uklanjanje stavki i ispravke za Shell.Current.GoToAsync unutar zadataka koji se ne pokreću na UI dretvi. Ako još niste migrirali sa Xamarin.Forms, pogledajte naš vodič za migraciju na .NET MAUI 10. Shell je jedan od dijelova koji najviše dobiva migracijom.

Osnove Shell strukture: FlyoutItem, Tab i ShellContent

Shell hijerarhija ima tri ključna elementa. FlyoutItem je stavka u bočnom izborniku (Flyout), tipično predstavlja jednu logičku sekciju aplikacije. Tab unutar FlyoutItem-a definira donji tab bar; jedan FlyoutItem može sadržavati više Tab-ova. ShellContent je konkretna stranica (izvedena iz ContentPage) koja se prikazuje kada je taj tab aktivan. Kada FlyoutItem sadrži samo jedan ShellContent, možete koristiti skraćeni sintaksni oblik gdje FlyoutItem direktno preuzima ContentTemplate.

Osim toga, dostupni su i pomoćni elementi: TabBar (definira tabove BEZ Flyouta, korisno za nakon-login iskustvo), FlyoutHeader/FlyoutFooter (statične zone u Flyoutu, obično logo i verzija), MenuItem (klikabilna stavka u Flyoutu koja pokreće akciju umjesto navigacije, npr. "Odjava") i Shell.SearchHandler (ugrađeno pretraživanje u naslovnoj traci). Svaka od tih klasa je bindable. Možete koristiti x:DataType i kompilirane binding-e za maksimalne performanse.

Konfiguracija AppShell.xaml, praktičan primjer

Prikazujemo minimalni AppShell.xaml s Flyoutom, dvije stavke i unutar prve stavke tri donja taba. Ovaj primjer je testiran na .NET MAUI 10.0.0 (novembar 2025.) i radi identično na iOS 18 i Android 15.

<?xml version="1.0" encoding="UTF-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:pages="clr-namespace:MyApp.Views"
       x:Class="MyApp.AppShell"
       Shell.FlyoutBehavior="Flyout"
       Title="MyApp">

    <Shell.FlyoutHeader>
        <Grid HeightRequest="140" BackgroundColor="{StaticResource Primary}">
            <Label Text="MyApp" TextColor="White"
                   FontSize="24" VerticalOptions="Center"
                   HorizontalOptions="Center" />
        </Grid>
    </Shell.FlyoutHeader>

    <FlyoutItem Title="Naslovna" Icon="home.png">
        <Tab Title="Feed" Icon="feed.png">
            <ShellContent ContentTemplate="{DataTemplate pages:FeedPage}"
                          Route="feed" />
        </Tab>
        <Tab Title="Trendovi" Icon="trending.png">
            <ShellContent ContentTemplate="{DataTemplate pages:TrendingPage}"
                          Route="trending" />
        </Tab>
        <Tab Title="Spremljeno" Icon="bookmarks.png">
            <ShellContent ContentTemplate="{DataTemplate pages:SavedPage}"
                          Route="saved" />
        </Tab>
    </FlyoutItem>

    <FlyoutItem Title="Profil" Icon="profile.png">
        <ShellContent ContentTemplate="{DataTemplate pages:ProfilePage}"
                      Route="profile" />
    </FlyoutItem>

    <MenuItem Text="Odjava" IconImageSource="logout.png"
              Clicked="OnLogoutClicked" />
</Shell>

U code-behind datoteci AppShell.xaml.cs registrirajte rute koje ne žive kao ShellContent. To su tipično detail stranice do kojih se dolazi navigacijom:

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

        Routing.RegisterRoute("details", typeof(Views.DetailsPage));
        Routing.RegisterRoute("compose", typeof(Views.ComposePage));
        Routing.RegisterRoute("settings", typeof(Views.SettingsPage));
    }

    private async void OnLogoutClicked(object sender, EventArgs e)
    {
        await SecureStorage.Default.SetAsync("auth_token", string.Empty);
        await Shell.Current.GoToAsync("//login");
    }
}

Kako navigirati u .NET MAUI Shell?

Kako navigirati u .NET MAUI Shell? Koristite Shell.Current.GoToAsync(string uri). URI shema odgovara ruti definiranoj u AppShell.xaml ili registriranoj preko Routing.RegisterRoute. Ključna pravila prefiksa:

  • //, apsolutna ruta od korijena Shell hijerarhije. Primjer: await Shell.Current.GoToAsync("//profile"); odvodi korisnika na Profil FlyoutItem bez obzira gdje se trenutno nalazi.
  • / (jedan slash), apsolutna ruta unutar trenutnog Tab konteksta.
  • bez prefiksa, relativna push navigacija; sljedeća stranica se dodaje na trenutni stog. Primjer: await Shell.Current.GoToAsync("details");.
  • .., natrag jedan korak (ekvivalent PopAsync). Možete lančati: "../.." za dva koraka.

U ViewModelu koji je bindan na FeedPage to bi izgledalo:

[RelayCommand]
private async Task OpenArticleAsync(Article article)
{
    var parameters = new Dictionary<string, object>
    {
        ["ArticleId"] = article.Id,
        ["FromScreen"] = "Feed"
    };

    await Shell.Current.GoToAsync("details", parameters);
}

Napomena: Shell.Current.GoToAsync je sigurno pozvati s bilo koje UI-vezane dretve, ali ne s background dretve u .NET MAUI 10. Ako navigirate nakon Task.Run-a ili završetka HTTP zahtjeva, obavijestite UI dretvu s MainThread.BeginInvokeOnMainThread. Za sve MVVM primjere na ovoj stranici pretpostavljamo CommunityToolkit.Mvvm i [RelayCommand] generator.

Kako proslijediti parametre kroz Shell.Current.GoToAsync?

Parametre možete proslijediti na tri načina. Prvi je query string u URI-ju: await Shell.Current.GoToAsync($"details?id={id}");. Ovo je najjednostavniji način, ali su vrijednosti ograničene na string (URL-encoded). Drugi je Dictionary<string, object>: await Shell.Current.GoToAsync("details", parameters);. Ovo podržava složene tipove (npr. cijele modele). Treći je ShellNavigationQueryParameters (novo u .NET MAUI 8+, poboljšano u 10), koje čuva stanje u navigacijskom stogu.

Na strani prijemnika, ciljna stranica ILI ViewModel deklarira svojstva s atributom [QueryProperty]:

[QueryProperty(nameof(ArticleId), "ArticleId")]
[QueryProperty(nameof(FromScreen), "FromScreen")]
public partial class DetailsPageViewModel : ObservableObject
{
    [ObservableProperty]
    private int articleId;

    [ObservableProperty]
    private string fromScreen;

    partial void OnArticleIdChanged(int value)
    {
        // Pokreće se automatski kad Shell postavi vrijednost.
        _ = LoadArticleAsync(value);
    }

    private async Task LoadArticleAsync(int id)
    {
        var article = await _articleService.GetByIdAsync(id);
        Title = article.Title;
        Body = article.Body;
    }
}

Prilagodba Flyout izbornika i TabBar-a

Prilagodba Flyout izbornika u .NET MAUI Shell se radi kroz Shell.FlyoutHeaderTemplate, Shell.FlyoutFooterTemplate i Shell.ItemTemplate. Šablona stavki dobiva BindingContext koji je sama FlyoutItem instanca, što znači da imate pristup Title, Icon i FlyoutIcon svojstvima:

<Shell.ItemTemplate>
    <DataTemplate>
        <Grid HeightRequest="52" Padding="16,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="32" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Image Source="{Binding FlyoutIcon}" WidthRequest="24" />
            <Label Grid.Column="1" Text="{Binding Title}"
                   VerticalOptions="Center" FontSize="15" />
            <Label Grid.Column="2" Text="{Binding BadgeCount}"
                   IsVisible="{Binding HasBadge}"
                   TextColor="White" BackgroundColor="Red"
                   Padding="6,2" />
        </Grid>
    </DataTemplate>
</Shell.ItemTemplate>

Za TabBar, ključna svojstva su Shell.TabBarBackgroundColor, Shell.TabBarTitleColor, Shell.TabBarUnselectedColor i Shell.TabBarIsVisible. Ako želite sakriti tab bar na jednoj stranici (npr. tijekom checkouta), postavite Shell.SetTabBarIsVisible(page, false) u code-behindu te stranice ili preko attached property u XAML-u: Shell.TabBarIsVisible="False". Za dinamičko dodavanje FlyoutItem-a u runtime-u koristite Shell.Current.Items.Add(new FlyoutItem { ... }). Od .NET MAUI 10 ovo je stabilno na Androidu (u ranijim verzijama je bilo intermitentnih crash-eva, što sam osobno lovio na jednom klijentskom projektu u ožujku).

Modalna navigacija, back button i lifecycle

Za modalnu stranicu (fullscreen, bez back navigation stoga) postavite Shell.PresentationMode na ciljnu stranicu prije navigacije, ili deklarativno u XAML-u:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             Shell.PresentationMode="ModalAnimated"
             Shell.NavBarIsVisible="False"
             x:Class="MyApp.Views.LoginPage">
    <!-- ... -->
</ContentPage>

Dostupne vrijednosti: NotAnimated, Animated (default push), ModalAnimated, ModalNotAnimated. Za povratak koristite await Shell.Current.GoToAsync(".."), koji funkcionira i za modalne i za push stranice.

Za presretanje sistemskog back gumba (Android) preoverride-ajte OnBackButtonPressed() na stranici, ili elegantnije, koristite Shell.BackButtonBehavior attached property s vlastitim ICommand-om:

<Shell.BackButtonBehavior>
    <BackButtonBehavior Command="{Binding ConfirmDiscardCommand}"
                        IconOverride="close_x.png" />
</Shell.BackButtonBehavior>

Lifecycle metode koje se pokreću tijekom Shell navigacije: OnAppearing() i OnDisappearing() na ContentPage-u, a od .NET MAUI 10 preporuča se koristiti OnNavigatedTo(NavigatedToEventArgs) i OnNavigatedFrom(NavigatedFromEventArgs), jer se pouzdano pokreću i pri deep-link i modal presentation scenarijima. Za ViewModel-e koji trebaju znati o navigaciji, implementirajte custom IQueryAttributable sučelje umjesto [QueryProperty] atributa kada vam treba potpuna kontrola:

public class DetailsViewModel : ObservableObject, IQueryAttributable
{
    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("ArticleId", out var raw)
            && int.TryParse(raw?.ToString(), out var id))
        {
            _ = LoadAsync(id);
        }
    }

    private async Task LoadAsync(int id) { /* ... */ }
}

Deep linking sa Shell URI-jem

Deep linking u .NET MAUI 10 povezuje vanjski URL (npr. https://myapp.com/article/42) s internom Shell rutom. Podrška je različita po platformi: iOS koristi Universal Links (Associated Domains + apple-app-site-association JSON), Android koristi App Links (Digital Asset Links + IntentFilter u AndroidManifest.xml), a Windows koristi protocol activation. Zajednički entry point u .NET MAUI je preoverride OnAppLinkRequestReceived(Uri uri) u App.xaml.cs:

protected override async void OnAppLinkRequestReceived(Uri uri)
{
    base.OnAppLinkRequestReceived(uri);

    // https://myapp.com/article/42 -> //home/feed/details?ArticleId=42
    if (uri.Segments.Length >= 3 && uri.Segments[1] == "article/")
    {
        var articleId = uri.Segments[2].TrimEnd('/');
        await Shell.Current.GoToAsync(
            $"//home/feed/details?ArticleId={articleId}");
    }
}

Prije nego što ovo počne raditi, potrebno je registrirati domenu u Platforms/iOS/Entitlements.plist (Associated Domains) i u Platforms/Android/AndroidManifest.xml (autoVerify="true" intent filter). Za detaljnu konfiguraciju platformskih manifestacija konzultirajte službenu dokumentaciju Shell navigacije na Microsoft Learn i Apple-ov vodič za Universal Links.

Uobičajene greške i kako ih riješiti

U produkcijskim MAUI projektima često se javlja nekoliko istih problema sa Shell navigacijom. Evo pet koji se stalno pojavljuju na GitHub issuesima za dotnet/maui repozitorij, s praktičnim rješenjima. Prva dva su me osobno kopala dovoljno da ih sad zapamtim napamet.

1. "Route not found" iznimka

Uzrok: ruta nije registrirana ili je pogrešno napisana u GoToAsync. Ruta razlikuje mala/velika slova. Rješenje: potvrdite da je Routing.RegisterRoute("details", typeof(DetailsPage)) pozvana PRIJE GoToAsync poziva. Ako navigirate iz App.xaml.cs konstruktora, prebacite navigaciju u OnStart().

2. QueryProperty svojstvo je uvijek null

Uzrok: neusklađen ključ u drugom argumentu atributa i imenu proslijeđenog parametra. Rješenje: točan primjer: [QueryProperty(nameof(ArticleId), "ArticleId")] i navigacija s "details?ArticleId=42" ili parameters["ArticleId"] = 42. Uočite kako se drugi argument ("ArticleId") MORA slagati s parametrom.

3. TabBar treperi ili nestaje na Androidu

Uzrok: dinamičko mijenjanje Shell.Items kolekcije tijekom animacije. Rješenje: sva mutiranja radite prije Shell.Current.CurrentItem = ... ili nakon završetka Navigated događaja. U .NET MAUI 10 preporuka je koristiti Shell.Current.Dispatcher.Dispatch(() => { ... }) za grupiranje promjena.

4. Modal stranica se prikazuje ispod TabBar-a

Uzrok: Shell.PresentationMode nije postavljen ili je stranica ostala u istoj FlyoutItem hijerarhiji. Rješenje: dodajte Shell.PresentationMode="ModalAnimated" u root ContentPage-a i Shell.TabBarIsVisible="False".

Uzrok: OnAppLinkRequestReceived se pokreće prije nego što je AppShell u potpunosti inicijaliziran. Rješenje: čekajte na Loaded događaj ili koristite Shell.Current?.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(300), ...) za odgodu navigacije. Također potvrdite da Task.Run pozivi ne zaklanjaju UI dretvu.

Česta pitanja

Koja je razlika između Shell-a i NavigationPage-a?

NavigationPage je stog stranica bez ugrađenog izbornika ili tabova. Morate ih ručno kombinirati s FlyoutPage i TabbedPage. Shell objedinjuje sve troje i dodaje URI-based navigaciju, ugrađeni SearchHandler i deep linking. U novim .NET MAUI 10 projektima Shell je preporučeni default; NavigationPage ostaje za jednostavne push-only tokove.

Može li Shell raditi bez Flyout izbornika?

Da. Ako želite samo tabove (bez bočnog izbornika), koristite TabBar element direktno unutar Shell-a umjesto FlyoutItem-a. Ako aplikacija ima samo linearan tok (login → home), možete koristiti Shell samo za jednu stranicu i push navigaciju bez Flyouta i bez TabBar-a.

Kako pretraživati stranice u Shell-u pomoću SearchHandler-a?

Dodajte Shell.SearchHandler u XAML dijete ContentPage-a. Naslijedite SearchHandler klasu i preoverride-ajte OnQueryChanged i OnItemSelected. Rezultati se prikazuju u dropdownu ispod naslovne trake i podržavaju ikone, prilagođene šablone i debouncing od 250ms po defaultu.

Podržava li .NET MAUI Shell iOS i Android jednako?

Da, iako se vizualni stil prirodno prilagođava platformi: iOS koristi bottom tab bar u iOS Human Interface Guidelines stilu s haptic feedback-om, dok Android koristi Material 3 bottom navigation s ripple animacijama. U .NET MAUI 10 Windows i Mac Catalyst dobivaju automatsku sidebar prezentaciju Flyouta na širokim ekranima.

Kako testirati Shell navigaciju u unit testovima?

Izdvojite navigacijsku logiku u sučelje INavigationService koje ovija Shell.Current.GoToAsync. U testovima injektirajte mock implementaciju i provjerite jesu li ispravni URI-jevi pozvani s očekivanim parametrima. Za integracijske testove koristite Appium ili .NET MAUI TestHarness, više detalja u našem vodiču za testiranje .NET MAUI aplikacija.