iOS : Dragging enabled custom UISplitViewController

iOS : Dragging enabled custom UISplitViewController

One of the most used view controller in iOS development is the UISplitViewControllerThe UISplitViewController class is a container view controller that manages the presentation of two side-by-side view controllers. You can use this class to implement a parent-child or master-detail interface, in which the left-side view controller presents a list of items and the right-side presents details of the selected item.
The UISplitViewController has no visible UI element. Its job is to manage the presentation of its two child view controllers and transitions between different orientations.

But there are few limitations in its use :

1) UISplitViewController is available only for iPad not iPhone.
2)  It hides its left pane when iPad is changed to portrait orientation.
3) Apple documentation says that, it should always be at the root of your app interface.
4) UISplitViewController cannot be used in tab-bars.
5) Parent/Child pane cannot be re-sized.

So the purpose of this article is to overcome these limitations and get you started with the basic CustomSpiltViewController.

Here are steps that you need to follow to implement it :

1) Create a viewcontroller that would act as main controller of CustomSplitViewController. This main viewcontroller will initialize the child, separator and parent. Events that are required to decrease and increase the size of child will also be handled inside this viewcontroller. There will be no visible UI of this viewcontroller.
2) ParentViewController ( Parent, Master)
This viewcontroller acts as a parent. View (Visible UI) of this viewcontroller is usually towards left. Although this can be customized and changed to right side as necessary.
3) SeparatorViewController:
SeparatorViewController is nothing but a view controller having a button. We can increase/decrease size of Child/Parent by dragging this button. When user will drag this button, the dragged distance will be calculated and a notification will be broadcasted with this distance. CustomSplitViewController (Main) will handle this broadcasted notification.
4)ChildViewController (Child, Detail) :
ChildViewController will act as the child. View (Visible UI) of this viewcontroller is usually towards right. However as mentioned earlier this can be customized.

AppDelegate.m :

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   ParentViewController *pvc= [[ParentViewController alloc]initWithNibName:@"ParentViewController" bundle:nil];
   ChildViewController *cvc = [[ChildViewController alloc]initWithNibName:@"ChildViewController" bundle:[NSBundle mainBundle]];
   SeparatorViewController *svc = [[SeparatorViewController alloc]initWithNibName:@"SeparatorViewController" bundle:[NSBundle mainBundle]];
   CustomSplitViewController *csvc = [[CustomSplitViewController alloc]initwithParent:pvc Separator:svc andChild:cvc];
   self.window.rootViewController= csvc;
   [self.window makeKeyAndVisible];
   return YES;
}

The above code initializes all the view controllers. Then, these view controllers are added to the CustomSplitViewController. The CustomSplitViewController will be at root of application in this example. If  required, you can use it any where as you need.

CustomSplitViewController :

Create properties for ParentViewController, ChildViewController and SeparatorViewController in CustomSplitViewcontroller :

@property (nonatomic, retain) ParentViewController *parentController;
@property (nonatomic, retain) ChildViewController *childController;
@property (nonatomic, retain) SeparatorViewController *separatorController;

Synthesize them in CustomSplitViewcontroller.m and add the initializing method .

- (CustomSplitViewController*) initwithParent:(ParentViewController*)parentVC Separator:(SeparatorViewController *)SeparatorVC andChild:(ChildViewController*)ChildsVC
{
   self.parentController=parentVC;
   self.childController=ChildsVC;
   self.separatorController = SeparatorVC;
   return self;
}

- ( void ) Viewdidload
{
   // Here we register to listen for broadcasted notification from Separator view controller.
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ChildSizeChanged:) name:@"ChildSizeChanged" object:nil];
   [self.view addSubview:self.parentController.view];
   [self.view addSubview:self.separatorController.view];
   [self.view addSubview:self.childController.view];
   //Now you can set what ever size you want for ParentViewController, ChildViewController and SeparatorViewController.
   // START is the height from top...For e.g if I want my CustomSplitViewcontroller to be start below tool bar than I will set START = 45;
   // WEIGHT and HEIGHT are the width and height of screen on which the CustomSplitViewcontroller is to be shown.
   // ZERO = 0
   // SEPARATOR_WIDTH is the width of separator.
   // MIN_CHILD_SIZE = 500

   int parentWidth = 200;
   int separatorwidth = 25;
   int childWidth = WIDTH - parentWidth-separatorwidth;
   [[NSUserDefaults standardUserDefaults] setInteger:childWidth forKey:@"ChildWidth"];

   self.parentController.view.frame=CGRectMake(ZERO,START ,parentWidth,HEIGHT-START );
   self.separatorController.view.frame=CGRectMake(parentWidth,START ,25,HEIGHT-START );
   self.childController.view.frame=CGRectMake(parentWidth+separatorwidth,START ,childWidth,HEIGHT-START );
}

The ParentViewController(1), ChildViewController(2) and SepartorViewController(3) should individually look like shown in above image.

In above method what we did is all the three Parent, Child and Separator View Controllers  ( initialized in AppDelegate.m ) are set to properties and their size are set accordingly.

Also we have added a observer for notification those will be broadcasted when size of Parent and Child will be changed.

Now in SeparatorViewController :

- (void)viewDidLoad
{
   [super viewDidLoad];
   // We are adding the Pan Gesture Recognizer so that the drag action can be catched.
   UIPanGestureRecognizer *separtorpanReg = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(ChangeChildWidth:)];
   [separtorpanReg setMinimumNumberOfTouches:1];
   [separtorpanReg setMaximumNumberOfTouches:1];
   [self.separtorButton addGestureRecognizer:separtorpanReg ];//separator button is the button in SeparatorViewController. By dragging this button we can change child-parent sizes
}

We are registering the separator button to handle to pan gesture. Now when ever user will drag the separator button, method  SeparatorViewController::ChangeChildWidth() will be called.

- (void) ChangeChildWidth: (UIPanGestureRecognizer *) ges
{
   CGPoint newPoint= [ges translationInView:self.view];
   int distance = self.separtorButton.frame.origin.x-newPoint.x;
   NSDictionary *Dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:distance] forKey:@”ChildSizeChanged”];
   [[NSNotificationCenter defaultCenter]postNotificationName:@”ChildSizeChanged” object:nil userInfo:Dict];
   if (ges.state == UIGestureRecognizerStateEnded) {
     lastChildDistance =0;//we need to set ‘lastChildDistance ‘ to zero which is used in CustomSplitViewcontroller::ChildSizeChanged(). you can make that variable static, create a method and set it to zero}
}

Above method calculates the distance by which user has dragged. This calculated distance is stored in NSDictionary. After that a local notification is broadcasted with that dictionary in it.

This broadcasted message will be handled by CustomSplitViewController::ChildSizeChanged().

CustomSplitViewController :

- ( void )ChildSizeChanged:(NSNotification *) notification
{
   int childWidth =  [[NSUserDefaults standardUserDefaults] integerForKey:@"ChildWidth"]; //child width
   NSInteger newDist = [[[notification userInfo] objectForKey:@"ChildSizeChanged"]integerValue];
   int newDiffereddist = newDist - lastChildDistance;//lastChildDistance is the width of current width of child
   lastChildDistance = newDist ;
   int differedDistance = newDiffereddist +childWidth ;
   int newLVWidth = WIDTH-SEPARATOR_WIDTH-differedDistance ;
   if (differedDistance >= (WIDTH-SEPARATOR_WIDTH))
   {
      self.separatorController.view.frame=CGRectMake(ZERO,START,SEPARATOR_WIDTH,HEIGHT-START);
      self.parentController.view.frame=CGRectMake(ZERO,START,WIDTH,HEIGHT);
      self.childController.view.frame=CGRectMake(SEPARATOR_WIDTH,START,WIDTH-SEPARATOR_WIDTH,HEIGHT-START);
      [[NSUserDefaults standardUserDefaults]setInteger:(WIDTH-SEPARATOR_WIDTH) forKey:@"ChildWidth"];
   }
   else if (differedDistance <  MIN_CHILD_SIZE )
   {
      self.parentController.view.frame=CGRectMake(ZERO,START,WIDTH,HEIGHT);
      self.separatorController.view.frame=CGRectMake(WIDTH-45,START,SEPARATOR_WIDTH,HEIGHT-START);    self.childController.view.frame=CGRectMake((WIDTH-MIN_CHILD_SIZE),START,MIN_CHILD_SIZE,HEIGHT-START);
      [[NSUserDefaults standardUserDefaults]setInteger:MIN_CHILD_SIZE forKey:@"ChildWidth"];
   }
   else
   {
      self.separatorController.view.frame=CGRectMake(newLVWidth,START,SEPARATOR_WIDTH,HEIGHT-START);
      self.parentController.view.frame=CGRectMake(ZERO,START,WIDTH,HEIGHT);        self.childController.view.frame=CGRectMake(newLVWidth+SEPARATOR_WIDTH,START,WIDTH-(newLVWidth+SEPARATOR_WIDTH),HEIGHT-START);
      [[NSUserDefaults standardUserDefaults]setInteger:WIDTH-(newLVWidth+SEPARATOR_WIDTH) forKey:@"ChildWidth"];
   }
}

We will get the dragged-distance from the notification. Based on that distance, its decided whether Child size should be increased or decreased. The new calculated distance is stored in NSUserDefaults so that childs width can be persisted among multiple sessions.

Hence you are done with Customized drag-enabled UISplitViewController.!!!!
Below are images for iPhone and iPad.

Enjoy dragging !!!!

Author: Rahul K.
Contact us:
info@prototechsolutions.com
ProtoTech Solutions