Search code examples
iosswiftipaduiviewcontroller

Two views in a UIViewController and a sizer in-between - iPad


I am trying to have a viewController with two UIViews side to side for iPad screens. The left view will have a tableview while the right view will be the contents of the selected tableview cell. Similar to how the iPad mail app is (which uses a UISplitViewController) but in portrait orientation the left view (the tableview) should stay there and not go away (UISplitViewController makes it go away).

I was able to get this to work by just using constraints, but in addition the side-to-side views, I need a vertical bar between the views where I can shift the views to either side to see more of the left or right view. This bar is similar to the bar in the Notability app when looking at two notes together (see attached screenshot). When I tried to make the bar move, the constraints restrict me from letting it happen. What is the best way to do this?

Notability Screenshot


Solution

  • Best way to do that would be to not use UISplitViewController. Instead, just use full-screen UIViewController as a container, have two container views separated by the bar inside, add your table view controller and details controller as child view controllers into it.

    You can align bar horizontally to white superview.center.x (let's name this constraint barAlignToSuperviewCenterConstraint), you can easily move it by changing constant of the constraint to the offset from the center.

    To do so,

    • attach UIPanGestureRecognizer to the superview of the bar which is also superview for the panels (let's name it barDragRecognizer).
    • in the recognizer's callback, when pan recognizer is in state .began, set its translation to the current value of barAlignToSuperviewCenterConstraint.constant:

      barDragRecognizer.setTranslation(CGPoint(x: barAlignToSuperviewCenterConstraint.constant, y: 0) in: bar.superview)
      
    • in the recognizer's callback, when pan recognizer is in one of the states .changed, .ended, .cancelled, set value of the constraint's constant to recognizer's translation value:

      barAlignToSuperviewCenterConstraint.constant = barDragRecognizer.translation(in: bar.superview).x;
      

    To make your recognizer receive touches only from the bar, assign your view controller as delegate of the recognizer and implement the following:

    func gestureRecognizer(UIGestureRecognizer recognizer, shouldReceive: UITouch touch) -> Bool {
        let barExtendedBounds = bar.bounds.insetBy(dx: -20, dy: 0)
        return (recognizer == barDragRecognizer && barExtendedBounds.constains(touch.location(in: bar)))
    }