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.

comments powered by Disqus