View Controller Containment in SoftLayer Mobile HD

SoftLayer Mobile HD version 1.2 introduced dual-pane browser functionality to provide a superior experience while interacting with our highly scalable cloud storage system, Object Storage. The browser takes advantage of a new feature in Apple’s iOS 5: view controller containment.

The iPad Object Storage browser incorporates functionality similar to that found in the Mac OS X Finder’s file browser that should be familiar to Mac OS X users. The metaphor has been reworked slightly so that it fits well on a mobile device screen. Like the Finder, the SoftLayer Mobile HD Object Storage browser has multiple panes representing the object hierarchy of Object Storage’s virtual file system. In order to fit this multi-pane navigation to the iPad’s smaller screen, the SoftLayer design team uses only two panes to represent the Object Storage structure.

Figure 1 Dual-pane Object Storage browser

The user navigates through the Object Storage hierarchy by tapping on files and folders. Tapping storage container or folder allows the user to navigate through the Object Storage hierarchy. As the user moves deeper, the panes shift to the right revealing the deeper levels of the hierarchy. Tapping a file, or selecting the accessory button next to a container or folder, presents additional information about the object. A browser-like back button, located in the left upper corner of the screen, allows for navigation back toward the root of the storage system.

Operations such as creating new containers, uploading files and refreshing folder content are readily available through a context sensitive action menu. (Figure 2)


Figure 2 Context sensitive action menu

The user can switch between storage clusters at any time while browsing by tapping on the button with globe icon located in the right upper corner of the screen and selecting desired cluster. (Figure 3)


Figure 3 Storage cluster selection

Large Object Storage data sets require a quick and reliable way to find the right objects quickly; therefore the Object Storage module provides search functionality. This is accessed using the search bar immediately above the browser, right at the users fingertips. When a search term is entered into the search bar, the dual-pane browser will context switch to search mode and display any objects found which meet the search terms provided. When done searching, a user can either navigate back from search results or simply clear the search bar field to return the browser to its normal operation. (Figure 4)


Figure 4 Account search


Figure 5 Object Storage Module High Level Design

Object Storage Module was designed to have an SLStorageModule class acting as a container for browser panes. Its only purpose is to manage browser panes i.e.: adding, removing and transitioning between the panes. The SLStorageModule takes advantage of iOS feature called: view controller containment; since iOS 5.0 subclasses of UIViewController can act as containers for other view controllers. This means that the container view controller can easily manage the presentation and interaction behavior of other view controllers. The following methods of the UIViewController class provide the convenient child view controller functionality:
*addChildViewController:
*removeFromParentViewController
*transitionFromViewController:toViewController:duration:options:animations:completion:
*willMoveToParentViewController:
*didMoveToParentViewController:
*automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers

The logic that controls what view controllers the dual-pane browser is supposed to display for the next level is left to sub-view controllers, called Browser Panes, which know about the Object Storage content and have enough knowledge to make that decision.

The SLStorageModule exposes pane operations for its child view controllers through push:from:animated and pop:animated methods. (Figure 6 and Figure 7)

- (void) push: (UIViewController *) newViewController from: (UIViewController *) existingViewController animated: (BOOL) animated
{
    [self addChildViewController: newViewController];
 
    float animationDuration = 0.0;
    if (animated)
    {
        animationDuration = 0.5;
    }
 
    [self.view addSubview: newViewController.view];
 
    [newViewController willMoveToParentViewController: self];
 
    if ([self.childViewControllers count] <h2> 1)
    {
        [newViewController viewWillAppear: YES];
        [self notifyViewWillMoveToLeftPane: newViewController];
        [self moveOffScreenLeft: newViewController];
        [UIView animateWithDuration:animationDuration animations:^{
            [self moveToLeftPane: newViewController];
        } completion:^(BOOL finished) {
            [self notifyViewDidMoveToLeftPane: newViewController];
            [newViewController didMoveToParentViewController: self];
            [newViewController viewDidAppear: YES];
        }];
    }
    else if ([self.childViewControllers count] </h2> 2) 
    {
        [newViewController viewWillAppear: YES];
        [self notifyViewWillMoveToRightPane: newViewController];
        [self moveOffScreenRight: newViewController];
        [UIView animateWithDuration: animationDuration animations:^{
            [self moveToRightPane: newViewController];
        } completion: ^(BOOL finished) {
            [self notifyViewDidMoveToRightPane: newViewController];
            [newViewController didMoveToParentViewController: self]; 
            [newViewController viewDidAppear: YES];
        }];
    }
    else 
    {
        if ([self isLeftPaneController: existingViewController])
        {            
            NSUInteger newViewControllerIndex = [self.childViewControllers count] - 1;
            UIViewController *currentRightViewController = [self.childViewControllers objectAtIndex: newViewControllerIndex - 1];
 
            [self moveToRightPane: newViewController];
            [currentRightViewController willMoveToParentViewController: self];
 
            [self transitionFromViewController: currentRightViewController 
                              toViewController: newViewController
                                      duration: animationDuration
                                       options: UIViewAnimationOptionTransitionCrossDissolve
                                    animations: ^{
                                    } 
                                    completion: ^(BOOL finshed) {
 
                                        [self notifyViewDidMoveToRightPane: newViewController];
 
                                        [currentRightViewController removeFromParentViewController];
                                        [newViewController didMoveToParentViewController: self];
                                    }
             ];
            [currentRightViewController removeFromParentViewController];
        }
        else
        {
            self.backButton.enabled = YES;
 
            [self moveOffScreenRight: newViewController];
 
            NSUInteger newViewControllerIndex = [self.childViewControllers count] - 1;
            UIViewController *currentLeftViewController = [self.childViewControllers objectAtIndex: newViewControllerIndex - 2];
            UIViewController *currentRightViewController = [self.childViewControllers objectAtIndex: newViewControllerIndex - 1];
 
            [self transitionFromViewController: currentLeftViewController 
                              toViewController: newViewController
                                      duration: animationDuration
                                       options: 0 
                                    animations: ^{
                                        [self moveOffScreenLeft: currentLeftViewController];
                                        [self moveToLeftPane: currentRightViewController];
                                        [self moveToRightPane: newViewController];
                                    } 
                                    completion: ^(BOOL finished) {
 
                                        [self notifyViewDidMoveToLeftPane: currentRightViewController];
 
                                        [self notifyViewDidMoveToRightPane: newViewController];
 
                                        [newViewController didMoveToParentViewController: self];
                                    }
             ];
        }
    }
}

Figure 6 Push method

- (void) pop: (BOOL) collapseFolderHierarchy animated: (BOOL) animated
{
    float animationDuration = 0.0;
    if (animated)
    {
        animationDuration = 0.5;
    }
 
    if ([self shouldExitSearch])
    {
        [self exitSearch: self];
    }
    else if ([self.childViewControllers count] > 2)
    {
        NSUInteger rightViewControllerIndex = [self.childViewControllers count] - 1;
        UIViewController *currentRightViewController = [self.childViewControllers objectAtIndex: rightViewControllerIndex];
        UIViewController *currentLeftViewController = [self.childViewControllers objectAtIndex: rightViewControllerIndex - 1];
        UIViewController *prevLevelViewController = [self.childViewControllers objectAtIndex: rightViewControllerIndex - 2];
 
        [self applyDualPaneViewControllerLook: prevLevelViewController];
 
		[self moveOffScreenLeft: prevLevelViewController];
 
        [currentRightViewController willMoveToParentViewController: self];
 
        [self transitionFromViewController: currentRightViewController
                          toViewController: prevLevelViewController
                                  duration: animationDuration
                                   options: 0
                                animations: ^{
                                    [self moveOffScreenRight: currentRightViewController];
                                    [self moveToRightPane: currentLeftViewController];
                                    [self moveToLeftPane: prevLevelViewController];
                                }
                                completion: ^(BOOL finshed) {
                                    [self notifyViewDidMoveToLeftPane: prevLevelViewController];
 
                                    [self notifyViewDidMoveToRightPane: currentLeftViewController];
 
                                    [currentRightViewController removeFromParentViewController];
                                    if (collapseFolderHierarchy)
                                    {   
                                        [self popUntilEntireHierarchyIsCollapsed: collapseFolderHierarchy animated: animated];
                                    }
                                    if ([self.childViewControllers count] <= 2 &&
                                        ![self isInSearchMode])
                                    {
                                        self.backButton.enabled = NO;
                                    }
                                }
         ];   
    }
}

Figure 7 Pop method

The Objective-C Protocols SLStorageModuleDelegate and SLSubViewControllerDelegate allow for loose coupling, and formalized communication, between SLStorageModule and its children.

As you can see, the SoftLayer dual-pane browser with its friendly user interface and robust design makes a perfect tool for SoftLayer Object Storage exploration.

Hope you enjoy using SoftLayer Mobile HD application, available from the Apple App Store.

Check it out today!

Pawel

Was this helpful?