UISplitViewController and UICollectionView

Continuing from the previous post that used a UICollectionView to display a grid of images from Bing, the following example adds a UICollectionView to a UISplitViewController.

When the row is selected, a new search is issued and the resulting images are updated in the UICollectionView. To connect the selection in the UISplitViewController’s master controller, which is an MT.D DialogViewController in this case, to the UICollectionViewController contained in the UISplitViewController’s detail controller, an event is raised when a row is selected:

public AnimalsController () : base (null)
{
    Root = new RootElement ("Animals") {
        new Section () {
            from animal in animals
            select (Element) new StringElement(animal, () => {
            if(AnimalSelected != null)
                AnimalSelected(this, new AnimalSelectedEventArgs{Animal = animal});
            })
         }};
 }

Then the UISplitViewController’s implementation handles this event to communicate to the UICollectionViewController:

animalImageController = new BingImageGridViewController (layout);

animalsController.AnimalSelected += (sender, e) => {
    animalImageController.LoadImages (e.Animal);
};

In the LoadImages method, the UICollectionViewController calls Bing for the selected item and uploads the UICollectionView when the data is returned by calling ReloadData:


public void LoadImages (string search)
{
    UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;

    bing = new Bing ((results) => {
        InvokeOnMainThread (delegate {
            imageUrls = results;
            CollectionView.ReloadData ();
            UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
        });
    });

    bing.ImageSearch (search);
}

The sample is available at https://github.com/mikebluestein/BingImageGrid/tree/master/BingImageGridSplit

Note: you’ll need a Bing API key, which you can get at http://datamarket.azure.com

Using a UISplitViewController to Create a Master-Detail iPad App with MonoTouch

The UISplitViewController is a new controller that is designed for the iPad to create and manage two separate views (and their controllers) side-by-side of each other. This lends itself well to a master-detail scenario, where the master list in kept on the left and the detail view fills the remaining space on the right, when in landscape orientation. In portrait the left-hand view is hidden and can be presented using the new UIPopoverController in coordination with the UISplitViewControllerDelegate. I’ll touch on some of the details of this here and then provide a basic working sample to get you started.

In MonoDevelop you can create an iPad app using the iPad Window-based project template. After creating a new app, open up MainWindow.xib and add a UISplitViewController. The default UISplitViewController adds UINavigationController containing a UITableViewController on the left side of the split view and a UIViewController on the right. The table will contain the master list. When you select a row, the detail view will update on the right.

Programming the table is identical to the way you do it on the iPhone. Add a class to the project in MonoDevelop with all the code for a UITableViewController subclass (decorated with the Register attribute ) and then in Interface Builder, you can set the class of the UITableViewController in the Identity Inspector as usual (I named it MasterTableViewController). For the detail view, we’ll load it from a separate xib. Create a new View Interface with Controller called DetailViewController and then, back in Interface Builder, you can set the class of the right-hand view controller to DetailViewController and the nib name to DetailViewController. The default template for the DetailViewController.xib will be that of an iPhone at the time of this writing. To make it look like an iPad at design-time, before adding anything to the contained view, you can use File>Create iPad version in Interface Builder to create a copy with the iPad template and overwite the existing iPhone one. Then you can open the new xib in Interface Builder and the designer will be sized for an iPad. I added a label to the detail view which I updated with the text of the row selected in the table along with the outlet for it in the DetailViewController. Also in the DetailViewController’s view, I added a UINavgationBar to the top, also with an outlet.

Here’s what Interface Builder looks like for the MainWindow.xib when complete:

and here is the DetailViewController.xib (there is a label UILabel in the center of the View with the text removed):

When you rotate between landscape and portrait orientations, the left (master) view will be shown and hidden respectively. A button is added to the UINavigationBar in portrait orientation in order to present the master view in the new UIPopoverController. The button and the UIPopoverController are both passed into the UISplitViewControllerDelegate when the left view will be hidden (when entering portrait). As the orientation changes you can manage adding and removing the
button and the existence of the popover by subclassing WillHideViewController and WillShowViewController.


public class SplitDelegate : UISplitViewControllerDelegate
{

public override void WillHideViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc)
{
DetailViewController dvc = svc.ViewControllers[1] as DetailViewController;

if (dvc != null) {
dvc.AddNavBarButton (barButtonItem);
dvc.Popover = pc;
}
}

public override void WillShowViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem button)
{
DetailViewController dvc = svc.ViewControllers[1] as DetailViewController;

if (dvc != null) {
dvc.RemoveNavBarButton ();
dvc.Popover = null;
}
}

}

The DetailViewController has the implementation to add and remove the button:


...
public void AddNavBarButton (UIBarButtonItem button)
{
button.Title = "Master List";
navBar.TopItem.SetLeftBarButtonItem (button, false);
}

public void RemoveNavBarButton ()
{
navBar.TopItem.SetLeftBarButtonItem (null, false);
}

The popover is dismissed when you touch outside its area. I also programatically dismiss it after a cell is selected (when in portait orientation) within the DetailViewController’s implementation when the detail data (a string in this simple example) is set.


public partial class DetailViewController : UIViewController
{
UIPopoverController _pc;
string _detail;

public UIPopoverController Popover {
get { return _pc; }
set { _pc = value; }
}

public string Detail {
get { return _detail; }
set {
_detail = value;

if (_pc != null) {
_pc.Dismiss (true);
}

detailLabel.Text = _detail;
}
}
...

One final note, you need to override ShouldAutorotateToInterfaceOrientation and return true for both the DetailViewController and the MasterViewController so that the UISplitViewController can make orientation changes to the ui appropriately, even thought the MasterViewController is not displayed in portrait mode other than in the popover.

The final running app looks like this:

The code for the sample project is available here: