Application setup and DI

Configure services, configuration, logging, the root shell, lifecycle callbacks, and app-wide appearance.

Every SkeleKit app starts with SkeleApplication.CreateBuilder(). The builder collects services, page registrations, app-wide settings, and exactly one root shell before creating the application.

Main.cs
using Microsoft.Extensions.DependencyInjection;
using MyApp;
using SkeleKit;

SkeleApplication.CreateBuilder()
    .UseServices(services =>
    {
        services.AddSingleton<IAccountStore, AccountStore>();
        services.AddTransient<HomeViewModel>();
        services.AddTransient<SettingsViewModel>();
    })
    .UseTint(Colors.Indigo)
    .Stack<HomeView>(preferLargeTitles: true)
    .Build()
    .Run(args);

Build() is supplied by the page source generator. It applies generated [Page] registrations, then creates the application and service provider. Run(args) starts the native iOS main loop.

Choose a shell

Configure one of the following:

  • SinglePage<TView>() hosts one page without navigation chrome.
  • Stack<TView>() creates a navigation controller with the given root page.
  • Tabs(...) gives every primary tab its own navigation stack.

Calling none of them is an error. The shell is app-level structure; individual pages can still present modals and use the system services.

See tabs and iPad for the tab-shell builder and navigation stacks for page-to-page navigation.

Services

UseServices exposes the normal Microsoft.Extensions.DependencyInjection service collection. Use singleton and transient registrations as usual. SkeleKit does not create a dependency-injection scope per page or navigation operation, so a scoped service resolved from the app's root provider effectively lives with that provider; do not use scoped lifetime when you expect page-local state.

SkeleKit adds these services when the application is built:

Every View exposes the same services through its protected Navigator, Sharer, SystemPicker, and Haptics properties. Use those when an interaction belongs to view code, including custom controls, collection templates, and tab accessories.

Registered ViewModels receive their dependencies through ordinary constructors. A typed page then receives its ViewModel:

public sealed class AccountViewModel(IAccountStore accounts)
{
    public IAccountStore Accounts { get; } = accounts;
}

[Page]
public sealed class AccountView : ContentView<AccountViewModel>
{
    public AccountView(AccountViewModel viewModel) : base(viewModel)
    {
        Content = new Label { Text = Bind(vm => vm.Accounts.CurrentName) };
    }
}

The built provider is available through SkeleApplication.Current.Services after startup. Prefer the protected View properties in view code and constructor injection everywhere else. Resolve application-specific services directly when app-level accessory views or callbacks are created outside page construction.

Configuration and options

ConfigureAppConfiguration adds providers to one shared IConfiguration instance. SkeleKit does not choose a source; the app can use JSON, in-memory values, remote configuration, or any other Microsoft configuration provider.

.ConfigureAppConfiguration(configuration =>
{
    configuration.AddJsonStream(OpenBundledConfiguration());
})
.UseServices(services =>
{
    services.AddOptions<ApiOptions>()
        .BindConfiguration("Api");
})

Application services should normally consume the typed options:

public sealed class ApiClient(IOptions<ApiOptions> options)
{
    readonly ApiOptions configuration = options.Value;
}

Inject IConfiguration directly only when a service needs arbitrary sections or keys. Configuration providers and options binding extensions come from their corresponding Microsoft packages; for the example above, those are Microsoft.Extensions.Configuration.Json and Microsoft.Extensions.Options.ConfigurationExtensions.

Logging

SkeleKit registers Microsoft logging and sends its own diagnostics through ILogger. Add the providers and filters the app needs with ConfigureLogging:

.ConfigureLogging(logging =>
{
    logging.SetMinimumLevel(LogLevel.Information);
    logging.AddSimpleConsole(options => options.SingleLine = true);
})

SkeleKit does not add an output provider by default. AddSimpleConsole requires Microsoft.Extensions.Logging.Console; other providers, including Apple or rolling-file providers, can be added in the same callback. Application services receive ILogger<T> through constructor injection.

Lifecycle

Application lifecycle callbacks complement the per-page appearance callbacks. Register them while building the app:

.UseLifecycle(
    background: () => syncService.Pause(),
    foreground: () => _ = syncService.ResumeAsync())

The background callback runs when the scene enters the background. The foreground callback runs as it returns. These callbacks are synchronous Actions. Start asynchronous work deliberately and handle its errors; the lifecycle hook itself is not awaited.

Appearance

UseAppearance sets the initial window appearance:

.UseAppearance(Appearance.System)

The running app can switch it later can be done using SkeleApplication.Current.Appearance. System follows the device setting. Light and Dark set UIKit's override on every connected application window. Semantic Colors continue to resolve through UIKit for the selected appearance.

Tint

UseTint supplies the inherited accent color for windows, navigation chrome, and controls:

.UseTint(Colors.Indigo)

Changing SkeleApplication.Current.Tint reapplies it to visible page hosts and app-level tab accessories. The transition animates over a short duration unless the user has enabled Reduce Motion. A view or page can still set a more specific Tint or BarTint.

Themes and image loading

Two other builder options: UseTheme registers reusable and implicit styles, while UseImageLoader replaces the default remote image loader. See styles and themes and Image custom loading for their full setup.