Pular para o conteúdo

.NET MAUI Cheat Sheet

Overview

.NET MAUI (Multi-platform App UI) is Microsoft’s cross-platform framework for creating native mobile and desktop apps with C# and XAML. It is the evolution of Xamarin.Forms, providing a single project structure that targets Android, iOS, macOS, and Windows. MAUI enables developers to share UI code and business logic across platforms while still accessing native platform APIs when needed. The framework uses native controls on each platform, ensuring apps look and feel native to their respective operating systems.

MAUI integrates with the broader .NET ecosystem, offering access to NuGet packages, dependency injection, and modern C# features. It supports both XAML-based declarative UI and C# Markup for code-first UI development. The framework includes built-in handlers that map cross-platform controls to native platform controls, a flexible layout system, and comprehensive support for platform-specific customization through conditional compilation and platform folders.

Installation

# Install .NET 8 SDK or later
# Windows
winget install Microsoft.DotNet.SDK.8

# macOS
brew install dotnet-sdk

# Linux (Ubuntu)
sudo apt-get install dotnet-sdk-8.0

# Verify installation
dotnet --version
dotnet workload list

# Install MAUI workload
dotnet workload install maui

# Additional requirements:
# Android: Android SDK via Visual Studio or standalone
# iOS/macOS: Xcode on macOS
# Windows: Visual Studio 2022 17.8+

# Create new MAUI project
dotnet new maui -n MyMauiApp
cd MyMauiApp

# Build and run
dotnet build
dotnet build -t:Run -f net8.0-android
dotnet build -t:Run -f net8.0-ios
dotnet build -t:Run -f net8.0-maccatalyst
dotnet build -t:Run -f net8.0-windows10.0.19041.0

Project Structure

MyMauiApp/
├── App.xaml                  # Application resources
├── App.xaml.cs               # Application entry point
├── AppShell.xaml             # Navigation shell
├── MainPage.xaml             # Main page UI
├── MainPage.xaml.cs          # Main page code-behind
├── MauiProgram.cs            # Host builder setup
├── Platforms/
│   ├── Android/              # Android-specific code
│   │   ├── AndroidManifest.xml
│   │   └── MainApplication.cs
│   ├── iOS/                  # iOS-specific code
│   │   ├── AppDelegate.cs
│   │   └── Info.plist
│   ├── MacCatalyst/          # macOS-specific code
│   └── Windows/              # Windows-specific code
├── Resources/
│   ├── Fonts/
│   ├── Images/
│   ├── Raw/
│   └── Styles/
│       ├── Colors.xaml
│       └── Styles.xaml
└── MyMauiApp.csproj

XAML Basics

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage"
             Title="Home">

    <ScrollView>
        <VerticalStackLayout Spacing="16" Padding="20">
            <Label Text="Welcome to MAUI!"
                   FontSize="28"
                   FontAttributes="Bold"
                   HorizontalOptions="Center" />
            
            <Entry x:Name="nameEntry"
                   Placeholder="Enter your name"
                   MaxLength="50" />
            
            <Button Text="Say Hello"
                    Clicked="OnHelloClicked"
                    BackgroundColor="{StaticResource Primary}"
                    TextColor="White" />
            
            <Image Source="dotnet_bot.png"
                   HeightRequest="200"
                   Aspect="AspectFit" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

Common Controls

ControlDescription
<Label>Display text
<Button>Clickable button
<Entry>Single-line text input
<Editor>Multi-line text input
<Image>Display image
<Switch>Boolean toggle
<Slider>Range selector
<Picker>Dropdown selection
<DatePicker>Date selection
<CheckBox>Boolean checkbox
<ProgressBar>Progress indicator
<ActivityIndicator>Loading spinner
<CollectionView>Scrollable list of items
<Frame>Container with border/shadow
<Border>Customizable border container

Layouts

<!-- VerticalStackLayout -->
<VerticalStackLayout Spacing="10">
    <Label Text="Item 1" />
    <Label Text="Item 2" />
</VerticalStackLayout>

<!-- HorizontalStackLayout -->
<HorizontalStackLayout Spacing="8">
    <Image Source="icon.png" WidthRequest="40" />
    <Label Text="Title" VerticalOptions="Center" />
</HorizontalStackLayout>

<!-- Grid -->
<Grid RowDefinitions="Auto,*,50"
      ColumnDefinitions="*,2*"
      RowSpacing="8" ColumnSpacing="8">
    <Label Grid.Row="0" Grid.Column="0" Text="Header" Grid.ColumnSpan="2" />
    <Label Grid.Row="1" Grid.Column="0" Text="Left" />
    <Label Grid.Row="1" Grid.Column="1" Text="Right" />
    <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Text="Submit" />
</Grid>

<!-- FlexLayout -->
<FlexLayout Direction="Row" Wrap="Wrap" JustifyContent="SpaceEvenly">
    <Frame WidthRequest="120" HeightRequest="120" />
    <Frame WidthRequest="120" HeightRequest="120" />
    <Frame WidthRequest="120" HeightRequest="120" />
</FlexLayout>

<!-- AbsoluteLayout -->
<AbsoluteLayout>
    <BoxView Color="Blue"
             AbsoluteLayout.LayoutBounds="0,0,1,1"
             AbsoluteLayout.LayoutFlags="All" />
    <Label Text="Overlay"
           AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1"
           AbsoluteLayout.LayoutFlags="PositionProportional" />
</AbsoluteLayout>

Data Binding and MVVM

// ViewModel
public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string name = string.Empty;

    [ObservableProperty]
    private ObservableCollection<TodoItem> items = new();

    [RelayCommand]
    private async Task LoadItemsAsync()
    {
        var data = await _service.GetItemsAsync();
        Items = new ObservableCollection<TodoItem>(data);
    }

    [RelayCommand]
    private void AddItem()
    {
        if (!string.IsNullOrWhiteSpace(Name))
        {
            Items.Add(new TodoItem { Title = Name });
            Name = string.Empty;
        }
    }
}
<!-- XAML Binding -->
<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels">
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>
    
    <VerticalStackLayout>
        <Entry Text="{Binding Name}" Placeholder="New item" />
        <Button Text="Add" Command="{Binding AddItemCommand}" />
        
        <CollectionView ItemsSource="{Binding Items}">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="model:TodoItem">
                    <Label Text="{Binding Title}" Padding="10" />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>
<!-- AppShell.xaml -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:views="clr-namespace:MyApp.Views">
    
    <TabBar>
        <ShellContent Title="Home" Icon="home.png"
                      ContentTemplate="{DataTemplate views:HomePage}" />
        <ShellContent Title="Settings" Icon="settings.png"
                      ContentTemplate="{DataTemplate views:SettingsPage}" />
    </TabBar>
    
    <FlyoutItem Title="Profile" Icon="profile.png">
        <ShellContent ContentTemplate="{DataTemplate views:ProfilePage}" />
    </FlyoutItem>
</Shell>
// Register routes
Routing.RegisterRoute("details", typeof(DetailsPage));
Routing.RegisterRoute("edit", typeof(EditPage));

// Navigate with parameters
await Shell.Current.GoToAsync($"details?id={item.Id}");
await Shell.Current.GoToAsync("..");  // Go back

// Receive parameters
[QueryProperty(nameof(ItemId), "id")]
public partial class DetailsPage : ContentPage
{
    public string ItemId { get; set; }
}

Configuration and Dependency Injection

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

        // Register services
        builder.Services.AddSingleton<IApiService, ApiService>();
        builder.Services.AddSingleton<IDatabase, SqliteDatabase>();
        
        // Register ViewModels
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<DetailViewModel>();
        
        // Register Pages
        builder.Services.AddTransient<MainPage>();
        builder.Services.AddTransient<DetailPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

Platform-Specific Code

// Conditional compilation
#if ANDROID
    var intent = new Android.Content.Intent(Android.Content.Intent.ActionView);
#elif IOS
    UIKit.UIApplication.SharedApplication.OpenUrl(new NSUrl(url));
#elif WINDOWS
    await Windows.System.Launcher.LaunchUriAsync(new Uri(url));
#endif

// Partial classes for platform implementations
// Shared: Services/DeviceService.cs
public partial class DeviceService
{
    public partial string GetDeviceId();
}

// Platforms/Android/Services/DeviceService.cs
public partial class DeviceService
{
    public partial string GetDeviceId()
    {
        return Android.Provider.Settings.Secure.GetString(
            Android.App.Application.Context.ContentResolver,
            Android.Provider.Settings.Secure.AndroidId);
    }
}

Advanced Usage

// Custom Handler
public class CustomEntryHandler : EntryHandler
{
    protected override void ConnectHandler(MauiTextField platformView)
    {
        base.ConnectHandler(platformView);
#if ANDROID
        platformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
#elif IOS
        platformView.BorderStyle = UIKit.UITextBorderStyle.None;
#endif
    }
}

// Register handler
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<Entry, CustomEntryHandler>();
});

// Behaviors
public class NumericValidationBehavior : Behavior<Entry>
{
    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnTextChanged;
        base.OnAttachedTo(entry);
    }

    void OnTextChanged(object sender, TextChangedEventArgs e)
    {
        bool isValid = double.TryParse(e.NewTextValue, out _);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

// Secure storage
await SecureStorage.SetAsync("auth_token", token);
var token = await SecureStorage.GetAsync("auth_token");

// Preferences
Preferences.Set("username", "john");
var name = Preferences.Get("username", "default");

Troubleshooting

IssueSolution
Android emulator not foundInstall via Android SDK Manager; ensure HAXM/hypervisor enabled
iOS build failsEnsure Xcode and command line tools installed; check provisioning profiles
Hot Reload not workingEnable in Visual Studio settings; ensure debugger attached
XAML IntelliSense brokenClean solution and rebuild; restart Visual Studio
Images not displayingPlace in Resources/Images; use filename without extension in XAML
NuGet restore failsRun dotnet restore; clear NuGet cache with dotnet nuget locals all --clear
Slow Android startupEnable startup tracing; use AOT compilation for release builds
CollectionView not scrollingSet HeightRequest or place inside proper layout container
Binding not updating UIImplement INotifyPropertyChanged or use [ObservableProperty]
Platform service not foundRegister in MauiProgram.cs using builder.Services