Split views

Host native two- and three-column layouts as the app shell or a tab destination, with per-column navigation and visibility.

A split view is the native UISplitViewController: a container that arranges two or three navigation stacks side by side and collapses them into one on compact size classes. In SkeleKit it is declared with SplitViewBuilder and can be the app shell or a tab destination.

App shell

Main.cs
SkeleApplication.CreateBuilder()
    .SplitView(split => split
        .Primary<FolderView>()
        .Secondary<NoteView>())
    .Build()
    .Run(args);

Primary<TView>() installs the primary column and Secondary<TView>() the detail column. The primary column starts on the leading edge and can move to the trailing edge with PrimaryEdge(...). Each column gets its own navigation stack, root page, toolbar, title, search and appearance settings, exactly like a tab. A split view requires both; validation fails at Build() when one is missing.

Style(SplitViewStyle.TripleColumn) adds a middle column declared with Supplementary<TView>(). Compact<TView>() replaces the page shown while the split view is collapsed. On iOS 26 and later, Inspector<TView>() adds the trailing inspector column; earlier versions skip the declaration. SplitViewStyle and SplitViewColumn live in SkeleKit and are available on every platform target.

Tab destinations

Any tab destination can be a split view while the other tabs keep their single-stack behavior:

.Tabs(tabs => tabs
    .Tab<HomeView>("Home", "house")
    .Split("Notes", "rectangle.split.2x1", split => split
        .Primary<NotebooksView>()
        .Secondary<NotesListView>()));

Pass TabPlacement.SidebarOnly to expose the split destination only through the adaptive sidebar. A split view can't be nested inside a tab group.

Pass TabPlacement.Hidden when the destination should never appear in the tab bar or sidebar and is only reached programmatically:

.Split(
    "Notes",
    "rectangle.split.2x1",
    split => split
        .Primary<NotebooksView>()
        .Secondary<NotesListView>(),
    TabPlacement.Hidden);

// later
await navigator.SelectTabAsync("Notes");

Every column owns an independent navigation stack. NavigationColumn(...) chooses which one the parameterless navigator calls use; the default is Secondary:

await navigator.PushAsync(new NoteViewModel(note)); // navigation column

The column overloads address a stack directly:

await navigator.PushAsync(
    SplitViewColumn.Primary,
    new FolderViewModel(folder));

await navigator.PushAsync(
    SplitViewColumn.Secondary,
    new NoteDetailViewModel(note));

await navigator.PopAsync(SplitViewColumn.Secondary);
await navigator.PopToRootAsync(SplitViewColumn.Primary);

When the split view is collapsed — an iPhone in portrait, for example — column calls route to the single stack the user can actually see, so pushes and pops still land where the user is looking. When no split view is active, such as while another tab is selected, the column overloads throw. Tapping a selected tab pops the navigation column's stack.

For a list-detail flow, update the selection and show the secondary column when the split is collapsed:

SelectedFolder = folder;

if (split.IsCollapsed)
    split.Show(SplitViewColumn.Secondary);

Column visibility

Inject ISplitView to show, hide, or query columns:

ISplitView split = ...;

split.Hide(SplitViewColumn.Primary);
split.Show(SplitViewColumn.Primary);

if (split.IsCollapsed)
    return;

// visibility queries and toggling require iOS 26 or later
if (OperatingSystem.IsIOSVersionAtLeast(26))
    split.Toggle(SplitViewColumn.Primary);

Show and Hide use UIKit's adaptive presentation and are available from iOS 18. UIKit doesn't support hiding the secondary column, so Hide(Secondary) throws; show it instead when compact navigation needs to move from the primary list to its detail. IsShowing and Toggle depend on the iOS 26 visibility API and throw PlatformNotSupportedException earlier. Toggle likewise excludes the secondary column because it can't complete its hide branch. Asking for the inspector column before iOS 26 also throws.

Badges

A split destination is a single tab. Only the navigation column's root page drives TabBadge; badges set by the other columns are ignored, so assign the badge to the page the user considers the tab's primary surface.

Layout

By default, the system chooses column widths, visibility and presentation for the available space. Supply relative weights when the content needs a particular proportion:

.SplitView(split => split
    .Primary<InspectorView>(weight: 1)
    .Secondary<CanvasView>(weight: 2)
    .PrimaryEdge(SplitViewEdge.Trailing)
    .Behavior(SplitViewBehavior.SideBySide)
    .Display(SplitViewDisplay.TwoColumns))

Weights are normalized across the expanded columns, so 1:2 gives the primary column one third of the width and the secondary column two thirds. Once any column supplies a weight, an expanded column without one contributes a weight of 1. When no weights are supplied, widths remain fully automatic. The compact column never participates in width distribution.

Behavior(...) selects automatic, side-by-side, overlay or displacement presentation. Display(...) prefers the secondary column alone, two columns or every column in the selected style. These remain preferences: the system can collapse or adapt the split when the window cannot accommodate the requested arrangement.

Limits

A split view is a destination-level container. UIKit cannot push it onto a navigation stack, so it can't be a page inside a stack, and SkeleKit doesn't build it for modal presentation. Use it as the shell or as a tab destination. For two panes inside a single page, use the layout panels instead.