10+ years of app development
Everything from a single source
50+ successful app projects

Blog

UISplitViewController - mehrere Detailansichten mit UINavigationController verwenden

In einem aktuellen Projekt besteht die Anforderung eine mehrere Ebenen tiefe Navigation mit entsprechenden Unterebenen für die Navigation umzusetzen. Da es sich um eine iPad-App handelt, bietet sich für die Navigation der UISplitViewController mit entsprechenden UINavigationController für den Master, die linke Seite, und die Details, die rechte Seite, an.

Ich bin dabei auf das Problem gestoßen, dass beim Wechsel der Detailansichten der Hinweise auf das Menü im Portrait-Modus verschwindet. Nachfolgend möchte ich beschreiben, wie ich das Problem lösen konnte.

Voraussetzungen schaffen

Innerhalb von FinishedLaunching wird der SplitViewController instanziiert und dem Window als RootViewController zugewiesen.

var splitViewController = new UISplitViewController();
splitViewController.Delegate = new SplitViewControllerDelegate();

var detailViewController = new UIViewController();
var navigationRootController = new MainNavigationController();

splitViewController.ViewControllers = new UIViewController[]{ new UINavigationController(navigationRootController), new UINavigationController(detailViewController) };
...
window.RootViewController = splitViewController;

Weil im Portrait-Mode die linke Navigation, der sogenannte Master, ausgeblendet wird, ist für den Benutzer der App nicht sofort erkenntlich, dass er weitere Funktionen darüber erreichen kann. Wie hilfreich wäre es da, wenn die App einen entsprechen Hinweise einblenden würde?

Mit wenig Aufwand lässt sich das sehr schnell realisieren. Es muss lediglich die Delegate-Property des SplitViewControllers genutzt werden.

Dazu erstellen wir uns eine eigene UISplitViewDelegate-Implementierung.

class SplitViewControllerDelegate : UISplitViewControllerDelegate
{
    public override bool ShouldHideViewController(UISplitViewController svc, UIViewController viewController, UIInterfaceOrientation inOrientation)
    {
        return inOrientation == UIInterfaceOrientation.Portrait || inOrientation == UIInterfaceOrientation.PortraitUpsideDown;
    }
    [Export("splitViewController:willHideViewController:withBarButtonItem:forPopoverController:")]
    public void WillHideViewController(UISplitViewController splitController, UIViewController viewController, UIBarButtonItem barButtonItem, UIPopoverController popoverController)
    {
        barButtonItem.Title = viewController.Title;
 
        var detailNavController = splitController.ViewControllers[1] as UINavigationController;
        var detailViewController = detailNavController.TopViewController;
		detailViewController.NavigationItem.SetLeftBarButtonItem(barButtonItem, true);
    }

    [Export("splitViewController:willShowViewController:invalidatingBarButtonItem:")]
    public void WillShowViewController(UISplitViewController svc, UIViewController vc, UIBarButtonItem button)
    {
		svc.ChildViewControllers[1].ChildViewControllers[0].NavigationItem.SetLeftBarButtonItem(null, true);
    }
}

Fehlen noch die Controller für Master und Detailansicht. Ihr findet sie im Repository zu dieser App. An dieser Stelle möchte ich lediglich zeigen wie die Detailansicht entsprechend der Auswahl im Master ausgetauscht wird. Dazu betrachten wir die RowSelected-Methode der entsprechenden TableViewSource.

public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    switch (indexPath.Row)
    {
        case 0:
            var demoCtrl1 = new DemoController1();

            parentController.SplitViewController.ViewControllers = new UIViewController[]{ parentController.SplitViewController.ViewControllers[0], new UINavigationController(demoCtrl1) };
            break;
        case 1:
            var demoCtrl2 = new DemoController2();

            parentController.SplitViewController.ViewControllers = new UIViewController[]{ parentController.SplitViewController.ViewControllers[0], new UINavigationController(demoCtrl2) };
            break;
    }
}

Das Problem

Die App kann nun zu verschiedene Ebenen verschiedene Detailansichten bereitstellen. Man könnte jetzt zufrieden sein, würde nicht der Menü-Hinweis bei jedem Wechsel der Detailansicht verschwinden und erst mit einem Wechsel zur horizontalen und wieder zurück zur Portrait-Ansicht sichtbar werden.

Die Lösung

Die Lösung hat mich ein bisschen Zeit gekostet, denn laut Dokumentation ist der SplitViewControllerDelegate zum Einblenden des Menüs sowie des Hinweises verantwortlich. Das besondere ist, dass er tatsächlich nur beim Wechsel zwischen Portrait und horizontaler Ansicht ausgelöst wird. Hinzu kommt, dass wir beim Wechsel der Detailansicht die komplette Hierarchie austauschen. Das wiederum ist notwendig, weil sich der RootController eines UINavigationControllers nicht austauschen lässt. Zu diesem Zeitpunkt verlieren wir den Hinweis.

Mit einer kleinen Modifikation im SplitViewDelegate und in der RowSelected Methode lässt sich das Problem beheben.

SplitViewDelegate erweitern

Die Implementierung des Delegates wird um eine UIBarButtonItem-Property erweitert.

public UIBarButtonItem BarButtonItem
{
    get;
    private set;
}

Die WillHideViewController-Methode wird um die Zeile BarButtonItem = barButtonItem; erweitert um die Property zu setzen.

[Export("splitViewController:willHideViewController:withBarButtonItem:forPopoverController:")]
public void WillHideViewController(UISplitViewController splitController, UIViewController viewController, UIBarButtonItem barButtonItem, UIPopoverController popoverController)
{
    ...
    BarButtonItem = barButtonItem;
}

TableViewSource anpassen

In den TableViewSourcen greifen wir auf die Property zu und fügen so den Hinweistext einfach unseren neuen Detailansichten hinzu.

public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    var barButtonItem = (parentController.SplitViewController.Delegate as SplitViewControllerDelegate).BarButtonItem;

    switch (indexPath.Row)
    {
        case 1:
            var customerData = new CustomerDataController();

            if (barButtonItem != null)
                customerData.NavigationItem.SetLeftBarButtonItem(barButtonItem, false);

            parentController.SplitViewController.ViewControllers = new UIViewController[]{ parentController.SplitViewController.ViewControllers[0], new UINavigationController(customerData) };
            break;
        case 0:
            var userInfo = new UserInformationController();

            if (barButtonItem != null)
                userInfo.NavigationItem.SetLeftBarButtonItem(barButtonItem, false);

            parentController.SplitViewController.ViewControllers = new UIViewController[]{ parentController.SplitViewController.ViewControllers[0], new UINavigationController(userInfo) };
            break;
    }
}

Das war es auch schon. Der Code als ganzes steht auf GitHub zur Verfügung. Es ist ein funktionierendes Beispiel wie man ein UISplitViewController mit mehreren Master und Detailansichten verwenden kann.

Sebastian Seidel

Sebastian Seidel

As a mobile enthusiast and managing director of Cayas Software GmbH, it is very important to me to support my team and our customers in discovering new potential and growing together. Here I mainly write about the development of Android and iOS apps with Xamarin and .NET MAUI.

Related articles

Voice input facilitates documentation - A hackathon topic
Voice input facilitates documentation - A hackathon topic

Voice input makes it possible to intuitively record food eaten and drunk without having to look at a device or tap. Instead of laboriously entering everything by hand, users can simply record their meals and snacks by voice command. This approach can lower the inhibition threshold and encourage users to continuously document their eating habits. This saves time and encourages regular documentation.

7 steps to migrate from Xamarin.Forms to .NET MAUI
7 steps to migrate from Xamarin.Forms to .NET MAUI

With the end of support for Xamarin approaching in May 2024, developers are busy migrating existing Xamarin.Forms projects to .NET MAUI as its successor. So are we, of course. In this article, I'll show 7 steps we've always had to take during the transition to make your upgrade .NET MAUI easier.

Top 3 alternatives to Xamarin.UITest for .NET MAUI
Top 3 alternatives to Xamarin.UITest for .NET MAUI

UI testing is an essential part of mobile app development to ensure that the app delivers the best possible user experience and meets the needs and expectations of its users. But how do we do that in .NET MAUI when Xamarin.UITest is not fully compatible anymore? Let's look at 3 alternatives to Xamarin.UITest.