DragDropGridView: The Holy Grail of Grid Layouts for MAUI

Or: How to build a drag-and-drop grid without getting your hand bitten off by the Rabbit of Caerbannog

You know that feeling when you need a simple grid layout with drag-and-drop in .NET MAUI, and you think "This should be easy, right?"

Yeah, me too.

So here we are, dear reader. Sharpnado.Maui.DragDropGridView is out, and I'm here to tell you the tale of how it came to be. Spoiler alert: it involves gesture recognizers, platform-specific quirks, and enough Android ZIndex shenanigans to make you question your life choices.

What is this sorcery?

The DragDropGridView is a high-performance grid layout control for .NET MAUI with drag-and-drop capabilities. Think of it as a CollectionView that actually lets you reorder items without descending into the depths of custom renderer hell.

With some outrageous nice features such as:

  • Drag & Drop with LongPress or Pan triggers (iOS, Android, Mac Catalyst)
  • Automatic scrolling when dragging near edges (no more "how do I scroll while dragging?" questions)
  • Custom animations for dragging, dropping, and wobbling items
  • Header support because sometimes you need a header
  • Adaptive sizing for items that just want to fit in
  • Pure .NET MAUI with just a sprinkle of MR.Gestures magic
0:00
/0:55

You can find it on NuGet:

  • Sharpnado.Maui.DragDropGridView

And the source code here:
https://github.com/roubachof/Sharpnado.GridLayout

Previously on "Game of Grids"

Let me take you back to the beginning. It all started with a simple requirement: "We need a grid where users can reorder items by dragging them around."

"Sure!" I said, full of optimism and innocence. "How hard could it be?"

Narrator voice: It was, in fact, quite hard.

The Three Questions

Like the bridgekeeper in Monty Python's Holy Grail, the grid layout asks you three questions:

  1. What... is your ColumnCount? (Easy enough)
  2. What... is your ItemsSource? (Still manageable)
  3. What... is the coordinate system of your gesture recognizers? (AAAAAHHHHH!)

Get the third question wrong, and you'll be cast into the Gorge of Eternal Peril (aka Windows platform).

The Quest Begins: Layout Management

First things first: we need a grid layout. Not just any grid, mind you, but one that can:

  • Adapt to different column counts
  • Handle spacing and padding
  • Support headers
  • Animate layout changes (because we're fancy like that)

The foundation is the DragDropGridViewManager, a custom ILayoutManager that handles measure and arrange logic:

protected override ILayoutManager CreateLayoutManager()
{
    return new DragDropGridViewManager(this);
}

Nothing too scary here. We calculate positions, arrange children, respect the column count. Standard layout stuff.

But then...

The Drag-and-Drop Dilemma

Ah, drag-and-drop. The feature that seems so simple in the spec but becomes a multi-headed Hydra in implementation.

The Great Gesture War

Here's the thing about gestures in .NET MAUI: they're platform-specific in ways that will make you cry.

On iOS? Beautiful. Smooth. Chef's kiss. 🤌

On Android? Well, let's just say that changing ZIndex during a drag operation cancels the gesture. So your dragged item can't appear on top of other items. It just... goes behind them. Like a very polite ghost.

On Windows? laughs in coordinate system inconsistencies

This is why the component uses a fork of MR.Gestures. Not because I wanted to fork a library (nobody wants to fork a library), but because reliable cross-platform gesture handling is apparently harder than establishing a constitutional peasant-based democracy.

MR.Gestures Integration

Important: This library would not exist without the excellent work of Michael Rumpler on MR.Gestures. The DragDropGridView uses a fork of MR.Gestures to enable reliable cross-platform gesture handling, particularly for LongPress-based drag-and-drop on iOS and Mac Catalyst.

The fork is maintained within this package to ensure:

  • Seamless integration with the drag-and-drop functionality
  • Consistent behavior across iOS, Android, and Mac Catalyst
  • Reliable gesture recognition without conflicts

No extra setup is required beyond calling .UseSharpnadoDragDropGridView(...), which automatically configures the MR.Gestures infrastructure for you.

Thank you, Michael Rumpler, for creating and maintaining MR.Gestures - this component would not have been possible without your foundational work! 🙏

The Trigger Modes: Pan vs. LongPress

The grid supports two drag triggers:

  1. Pan (default): Drag starts immediately when you pan
  2. LongPress: Hold, then drag (recommended on iOS to avoid accidental drags)

Why both? Because sometimes you want to scroll your ScrollView without accidentally dragging every item you touch. Context matters.

public enum DragAndDropTrigger
{
    Pan,
    LongPress
}

The LongPress implementation on iOS was particularly fun (read: painful) because you need to:

  1. Detect the long press
  2. Start tracking pan gestures
  3. Not cancel any gestures mid-flight
  4. Handle the platform-specific lifecycle events
  5. Sacrifice a rubber chicken at midnight

Okay, maybe not the last one, but I won't say I didn't consider it.

The Art of Animation

Now, here's where things get interesting. Because what good is drag-and-drop without some pizzazz?

The grid exposes four animation hooks:

public Func<View, Task>? LongPressedDraggingAnimation { get; set; }
public Func<View, Task>? LongPressedDroppingAnimation { get; set; }
public Func<View, Task>? DragAndDropItemsAnimation { get; set; }
public Func<View, Task>? DragAndDropEndItemsAnimation { get; set; }

Want your items to wobble when drag mode is enabled? Sure!
Want them to scale up when picked? Why not!
Want them to perform a full Ministry of Silly Walks routine? Well, you could, but I won't stop you.

The predefined animations are in the DragDropAnimations static class:

myGridLayout.LongPressedDraggingAnimation = 
    DragDropAnimations.Dragging.ScaleUpAsync;
myGridLayout.LongPressedDroppingAnimation = 
    DragDropAnimations.Dropping.ScaleToNormalAsync;
myGridLayout.DragAndDropItemsAnimation = 
    DragDropAnimations.Items.WobbleAsync;
myGridLayout.DragAndDropEndItemsAnimation = 
    DragDropAnimations.EndItems.StopWobbleAsync;

The wobble animation was particularly satisfying to implement. Items rotate slightly back and forth, giving that classic "I'm ready to be reordered" feel. Like they're nervously shuffling their feet before a big performance.

The ScrollView Integration Saga

Here's a fun challenge: what happens when you put a grid with drag-and-drop inside a ScrollView?

If you answered "gesture conflicts, awkward scrolling, and tears," you'd be correct!

The solution involved:

  1. Edge detection: Detect when dragging near ScrollView edges
  2. Auto-scrolling: Programmatically scroll the ScrollView during drag
  3. Gesture coordination: Let the pan gesture know when to yield to scrolling

This required traversing the visual tree to find parent ScrollViews and RefreshViews, then carefully orchestrating the gesture dance:

private void FindScrollView()
{
    Element parent = Parent;
    while (parent != null)
    {
        if (parent is ScrollView scrollView)
        {
            _scrollView = scrollView;
            return;
        }
        if (parent is RefreshView refreshView)
        {
            _refreshView = refreshView;
        }
        parent = parent.Parent;
    }
}

It's like a "Where's Waldo?" but for ScrollViews.

The Platform Limitations: A Comedy in Three Acts

Act I: Windows - The Forbidden Platform

Windows support for drag-and-drop?

No.

Why? Because the gesture coordinate systems on Windows are... special. They're like the Black Knight from Monty Python: "None shall pass!"

So on Windows, the grid works beautifully as a layout, but drag-and-drop is not available. It's a read-only grid. A very nice, very organized, but entirely static grid.

"'Tis but a scratch!" you say? More like "'Tis but a limitation of the gesture system!"

Act II: Android - The ZIndex Tragedy

On Android, changing ZIndex during a gesture cancels that gesture. This means dragged items can't be brought to the front.

So instead of the dragged item floating majestically above all others, it politely stays in its layer order. It's very civilized, if not slightly embarrassing for a drag operation.

Act III: ItemsSource Requirements

For automatic reordering to work, your ItemsSource must implement IList. Read-only collections? Sorry, no can do.

This is because we need to actually move items in the collection during drag operations. If your collection is like the French Taunter shouting "I don't want to talk to you no more!", well, we can't help you.

The Mvvm.Flux Architecture

The sample app demonstrates the Mvvm.Flux pattern, which is my take on state management in MAUI apps. Key principles:

  • Composition over inheritance: Use TaskLoaderNotifier components instead of base class madness
  • Immutability: C# records for entities (Light record in the sample)
  • Single source of truth: Domain layer is the boss
  • One-way data flow: Updates flow domain → viewmodel → view

It's like a well-organized Python sketch: everyone knows their part, no one's interrupting, and there's a clear punchline (the UI update).

Usage Example

Here's the simplest possible usage:

<gridLayout:DragDropGridView
    ColumnCount="2"
    ColumnSpacing="10"
    RowSpacing="10"
    IsDragAndDropEnabled="True"
    DragAndDropTrigger="Pan"
    ItemsSource="{Binding Items}"
    OnItemsReorderedCommand="{Binding ItemsReorderedCommand}">
    
    <gridLayout:DragDropGridView.ItemTemplate>
        <DataTemplate>
            <gridLayout:DragAndDropView>
                <Border Padding="10" Background="LightGray">
                    <Label Text="{Binding Name}" />
                </Border>
            </gridLayout:DragAndDropView>
        </DataTemplate>
    </gridLayout:DragDropGridView.ItemTemplate>
</gridLayout:DragDropGridView>

And that's it! No custom renderers, no platform-specific code (unless you count the initialization), no blood sacrifices to the gesture gods.

The Initialization Ritual

In your MauiProgram.cs:

builder
    .UseMauiApp<App>()
    .UseSharpnadoDragDropGridView(enableLogging: false);

This sets up the MR.Gestures infrastructure and prepares the gesture recognizers. Think of it as the "NI!" moment—you need to say the magic word.

Lessons Learned

  1. Gesture handling is hard: Platform differences will humble you
  2. ScrollView integration is harder: Edge cases everywhere
  3. Animations are fun: Until you discover they can leak memory
  4. Windows gestures are cursed: Just accept it and move on
  5. Android ZIndex is quirky: But at least it's consistent in its quirkiness

The Result

Despite all the challenges, the result is actually pretty great:

  • Smooth drag-and-drop on iOS, Android, and Mac Catalyst
  • Flexible animation system
  • Automatic ScrollView handling
  • Header support
  • And it actually works!

Is it the Holy Grail of grid layouts? Well, that might be overselling it. But it's a pretty good grid with drag-and-drop, and you don't need to answer three questions to use it.

Just one: "What is your column count?"

(The answer better not be "African or European swallow.")

Get It Now

Now go forth and drag-and-drop to your heart's content! And remember: if someone asks why Windows doesn't support drag-and-drop, just tell them it's only a model.


"We are the developers who say... NuGet!"