Scott Kilbourn Scott Kilbourn - 1 month ago 22
iOS Question

UISegue positioning UIView halfway

I'm trying to use a custom transition to position a

UIView
so that it slides up from the bottom, but with the top stopping half way up the screen. I have that working, but after briefly stopping at the halfway point, the
UIView
opens the rest of the way and takes up the entire screen. This is the code I have that's doing it.

override func perform() {
//Coming in here, we already have this information. The segue knows the source
//and destination view controllers.
var firstView = self.sourceViewController.view as UIView!
var secondView = self.destinationViewController.view as UIView!

//Get the screen width and height, for calculations.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height

//Specify the initial position of the second view. We're sliding from
//bottom to top, so put it off the bottom of the screen.
secondView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)

//Insert the second view above the first view. The windows are all in a stack. Doing
//this inserts the second view at the top of the statck. It is not yet visible.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondView, aboveSubview: firstView)

//Animate the transition.
UIView.animateWithDuration(0.5, animations: { () -> Void in
secondView.frame = CGRectOffset(secondView.frame, 0.0, -screenHeight/2)

}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}


I set a breakpoint in Finished, and that's the code that is causing the second view to take up the entire screen. What can I do to make the second view take up only the bottom half of the screen.

Answer

The 'perform' method is used to animate the transition from your source to your destination controllers, but this final step:

self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
            animated: false,
            completion: nil)

presents the destination controller, which as far as iOS knows is a full screen controller, which is why you see the jump at the end.

You've got a couple of options:

1. Faking it (option A):

  • In the storyboard select your segue and change the presentation option from 'Default' (default value is 'Fullscreen') to 'Current Context'.
  • In your destination controller set the root view backgroundColor to clear.
  • Add a new view that takes up half the height of the view and transfer everyone into that view.

This will only work with really simple layouts and might be a headache in the future if you

2. Faking it (option B)

Don't use a view controller - just present use a view that animates from the bottom of the screen.

If you're using Xcode 7 you can drop a UIView into the bar at the top of the scene and when you tap on it Xcode will expand it out so you can add subviews etc. If you destination view is a simple transient view (like a menu), and you're ready to use Xcode 7 this could be a good option.

3. Use UIPresentationController (recommended)

Instead of using a custom segue, switch to using a UIPresentationController. This potentially involves a lot of boiler plate, but it's really pretty simple.

Here's a bare-bones example project with a presentation controller which is as simple as this:

class HalfScreenPresentation: UIPresentationController {

    override func frameOfPresentedViewInContainerView() -> CGRect {

        let containerFrame = self.containerView!.frame

        return CGRect(x: 0, y: containerFrame.height/2, width: containerFrame.width, height: containerFrame.height/2)

    }
}

This works because your animation has the controller being presented/dismissed from the bottom of the screen (which is the default transition!) if you wanted to change this animation (e.g. slide from the top, or the side, or some other transition) you'd also need to provide an object that meets UIViewControllerAnimatedTransitioning requirements (which for the most part would be as simple as copy-and-paste of code in your perform method).

I'm really enjoying the UIPresentationController APIs, and hope you do too!