Ever wanted that sliding sidebar or an alert with a image picker? Apple has many great ways of presenting view controllers, but sometimes we want something different, something new. For that we subclass UIPresentationController
. However there’s a few concepts that you’ll need to wrap your head around first. In this lesson we’ll create a few standard controllers to explain the custom controllers, then make and animate our own custom controller sliding side bar. We’ll build the presentation controller step by step, so you know how it really works.
Custom Presentation Anatomy in Alert Views.
Make a new project called SwiftCustomPresentation, with a universal device and Swift as the language. Once loaded go to the launchscreen.storyboard. I like to have some indication that things are working, and since Apple removed the default launch screen layout, I tend to add my own. Set the background color to Yellow(#FFFF00). Drag a label on the launch screen and title it Hello in 26 point size. Click the alignment auto layout button at the bottom of the storyboard and check on Horizontally in Container and Vertically in Container set to 0. Update the frames for Items of new constraints, then click Add 2 constraints.
You now have a nice label in the center of the launch screen
Go to the storyboard. Make the background Yellow(#FFFF00). Drag a button to the center of the storyboard. Title the label Hello, Pizza as a 26 point size system font. Make the text color Dark Blue(#0000AA) Just like the label on the launch screen, align the button to the center of the storyboard. Click the alignment auto layout button at the bottom of the storyboard and check on Horizontally in Container and Vertically in Container set to 0. Update the frames for Items of new constraints, then click Add 2 constraints.
Open the assistant editor. Control drag from the button to the code. Create an action entitled showAlert
. Close the assistant editor for now. Ooen the ViewController.swift file. Change the action to this:
@IBAction func showAlert(sender: UIButton){ helloAlert() }
Now add the following code to create an alert
func helloAlert(){ let alert = UIAlertController( title: "Hello Slice", message: "Ready for your slice?", preferredStyle: .Alert) let action = UIAlertAction( title: "Okay", style: .Default, handler: nil) alert.addAction(action) presentViewController(alert, animated: true, completion: nil) }
This is code to present the simplest alert possible. We create a simple alert and give it one action, an Okay button that dismisses the alert. The last line is the important one for our discussion:
presentViewController(alert, animated: true, completion: nil)
As of iOS8, all modal view controllers present with this one method. You tell the method which controller you want to present, set a few properties on it and it does the work, no matter what your device. It relieves a lot of the hard work of developers specifying exceptions for individual devices.
Set your simulator for an iPhone 6s. Build and run. When the Hello pizza button shows in the simulator, tap it. You get your alert.
We’ve made this alert to illustrate the three parts you need to know about any custom view controller.
Our yellow background is the presenting view controller, the controller that calls the presentViewController
method The alert, which is the presented view controller, is the controller presentViewController
uses in its parameters.
presentingViewController.presentViewController(presentedViewController, animated:true,completion:nil)
In an alert and in other view controllers such as popovers, the presentViewController
method adds a special view between the presented and presenting view controllers called a chrome. The chrome is the difference or padding between the bounds of the presented controller, and the bounds of the presenting controller. Usually it is a solid color with some alpha value. Apple tends to use a gray color for chrome, which is why yellow is looking like a dark orange yellow.
Adding a Modal View
Alerts help us explain the anatomy of a presentation controller. However they are rather automatic in their execution. Let’s add a simple modal view controller to play with custom view controllers. Press Command-N and select a new Cocoa Touch Class ModalViewController. Do check on Also Create XIB file. This is one of those places I prefer xibs due to portability over the storyboard.
Save the file. Go to the ModalViewController.xib file. Change the background to Dark Blue(#0000AA). Add two labels to the xib. In one make the text Ready for your slice? , a Font of 26.0 Bold, and White(#FFFFFF) text. Set the Alignment to Left justified. Set the Lines property to 0 and the Line Breaks to Word Wrap. This will let the label vary the number of lines to fit the text, and word wrap the results. Your attributes should look like this:
For the second label, add a short paragraph. I used a passage from Episode 4 of my podcast A Slice of App Pie.
This is the other thing about persistence: It does not see success as an absolute. It is not a Bool, a true/false variable, but a Double, a real number. It is not just black or white, but every color. You are not a success, but you are at a degree of success. Your past experience fuels your future success.
Set the Font to 16.0 Regular, and White(#FFFFFF) text. Set the Alignment to Full Justified. Set the Lines property to 0 and the Line Breaks to Word Wrap.
Select the text paragraph and Control drag down and to the right until you are over the blue of the background.
Release the mouse button and you will see an auto layout menu. Hold down shift and Select Trailing Space to Container, Bottom Space to Container and Equal Widths:
Click the Add Constraints button. Now control-drag from the Ready label to the paragraph label. In the auto layout menu that appears, Shift-Select Vertical Spacing, Trailing, and Equal Widths
Click the Add Constraints button. Select the paragraph and go to the size inspector . You’ll see a list of constraints:
Your values will most likely be different than the illustration. Click the edit button for the Trailing Space to: Superview. A popup appears. Change the Constant to 8.
Click the Equal Width: to Superview edit button. Change the Multiplier to 0.9.
Edit the Bottom Space to: Superview constraint. Change the Constant to 20.
Edit the Align Trailing Space to: Ready for yo… constraint. Change the Constant to 0.
Edit the Top Space to: Ready for yo… constraint. Change the Constant to 20.
Edit the Equal Width to: Ready for yo… constraint. Change the Multiplier to 0.5.
If you’ve never worked with auto Layout before, what we just did is anchor the bottom right corner of the paragraph to the bottom right corner of the xib. with a margin of 20 points on the bottom and 8 points on the right. We set the width of the paragraph to 90% of the width of the xib. The Label above it, acting as a title, we made half as long as the paragraph, and aligned to the right side of the paragraph, 20 points up.
Click the triangle tie fighter which is the auto layout resolve button. You get a menu like this:
Select for the All Views in View section Update Frames. You now have a layout like this:
We’ll dismiss this modal with a swipe to the right. In the object library, find the swipe gesture:
Drag it to the blue of the xib and release. Nothing will happen there but in the document outline (if you don’t have it open click the button) you will see a new object listed
Select the Swipe Gesture Recognizer and in the attributes menu, you should see the following:
These are the defaults and what we want: a single finger swipe to the right. Open the assistant editor and control-drag the Swipe Gesture Recognizer from the document outline to the code. Create an action named dismissModal.
In the code that appears, dismiss the vew controller:
@IBAction func dismissModal(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) }
Presenting the View Controller.
Go back to the ViewController.swift File. Add the following method:
func presentModal() { let helloVC = ModalViewController( nibName: "ModalViewController", bundle: nil) helloVC.modalTransitionStyle = .CoverVertical presentViewController(helloVC, animated: true, completion: nil) }
The code gets the view controller and the xib then presents it. We’re using the default settings for a modal in this case.
Go to the storyboard. Open the assistant editor if not already open. Drag a Swipe Gesture recognizer to the storyboard. Select it in the the document outline and change the Swipe attribute to Up. Control-drag from the Swipe Gesture recognizer and make an outlet
@IBAction func swipeUp( sender: UISwipeGestureRecognizer) { presentModal() }
Build and run.You should be able to swipe up and show the blue controller.
The title is too small to fit on one line and adapts to two. Rotate the phone (in the simulator press Command right-arrow) and you get a slightly different layout:
Swipe right and the view dismisses
Custom Presentation #1:
A Big Alert View
We’ve got a working modal view controller. We did this for several reasons. This proves there’s no magic in what we are about to do. It also reinforces how we do this in a standard presentation controller.
Adding the Custom Presentation Controller
Create a new class by pressing Command-N on the key board in Xcode. Make a new cocoa touch class MyCustomPresentationController subclassing UIPresentationController
. You end up with an empty class:
class MyCustomPresentationController: UIPresentationController { }
Our first task is to add the chrome. We’ll also add a constant for the chrome color. Add this to the class
let chrome = UIView() let chromeColor = UIColor( red:0.0, green:0.0, blue:0.8, alpha: 0.4)
A presentation controller needs to do three things: Start the presentation, end the presentation and size the presentation. The UIPresentationController
class has three methods we override to do this: presentationTransitionWillBegin
, dismissalTransitionWillBegin
and frameOfPresentedViewInContainerView
. There is a fourth method containerViewWillLayoutSubviews
which may sound familiar to those who have worked with code and auto layout. When there is a change to the layout, usually rotation, this last method will make sure we resize everything.
First we present the controller. In our code we control what the chrome does. To our custom presentation controller, add the following method
override func presentationTransitionWillBegin() { chrome.frame = containerView!.bounds chrome.alpha = 1.0 chrome.backgroundColor = chromeColor containerView!.insertSubview(chrome, atIndex: 0) }
First we set the size of the chrome by the size of containerView
. The containerView
property is a view which is an ancestor of the presenting view in the view hierarchy. containerView
gives us a view that is bigger to or the same size as the presenting view. Setting the chrome to this size means the chrome will cover everything. We then set the color and alpha of chrome
, and then add chrome
to the container view.
Our next method to implement is dismissalTransitionWillBegin.
We’ll remove the chrome from the view hierarchy here. Add this code:
override func dismissalTransitionWillBegin() { self.chrome.removeFromSuperview() }
We’ll change the size of the presented view so we can see the chrome. Size changes take two methods. The first is frameOfPresentedViewInContainerView
. Add the following code:
override func frameOfPresentedViewInContainerView() -> CGRect { return containerView!.bounds.insetBy(dx: 30, dy: 30) }
This returns the frame of the container. We used the insetBy
function to shrink it 30 points within the container view. This will give us a effect similar to an alert.
To make sure adaptive layout changes everything, we need one more method. add this:
override func containerViewWillLayoutSubviews() { chrome.frame = containerView!.bounds presentedView()!.frame = frameOfPresentedViewInContainerView() }
Our first line changes the chrome’s size to fit the new size of the view. The second one makes sure that the presented view is the correct size by calling the method we just wrote frameOfPresentedViewInContainerView
.
Adding the Delegate
Custom presentation controllers run through a delegate. Above the MyCustomPresentationController
class, add the following:
class MyTransitioningDelegate : NSObject, UIViewControllerTransitioningDelegate { func presentationControllerForPresentedViewController( presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController ) -> UIPresentationController? { return MyCustomPresentationController( presentedViewController: presented, presentingViewController: presenting) } }
This is a subclass of the UIViewControllerTransitioningDelegate
. Though the parameters are long, the single function in the delegate returns our presentation controller, using the designated initializer for the class.
That is all we need for a bare bones custom controller. Our next step is to use it.
Using the Custom Presentation Controller
We’ll add a left swipe gesture for presentation this controller. Go to the storyboard. Drag a swipe gesture control on the view. Set the Swipe attribute this time for Left. Open the assistant editor and control-drag this gesture to the ViewController
class, making a new action swipeLeft. Change the new function to this:
@IBAction func swipeLeft(sender: UISwipeGestureRecognizer) { presentCustomModal() }
Close the assistant editor and go to the ViewController.swift code. Add the following to the ViewController
class, under to the presentModal
function so you can compare the two
func presentCustomModal(){ let helloVC = ModalViewController( nibName: "ModalViewController", bundle: nil) helloVC.transitioningDelegate = myTransitioningDelegate helloVC.modalPresentationStyle = .Custom helloVC.modalTransitionStyle = .CrossDissolve presentViewController(helloVC, animated: true, completion: nil) }
Like the standard modal controller, we get the controller and present it. We did change the transition style to tell the difference between the two controllers, and as you’ll see a cross dissolve makes a better transition for this type of view.
Unlike presentModal
, we set the modalPresentationStyle
to .Custom
so presentViewController
knows to look for a custom presentation controller. However it won’t work without setting the view controller’s transitioningDelegate
property. The transitioning delegate tells the view controller where the custom transition is. If this is nil
, the presentation is a standard presentation, not a custom one.
We haven’t set the delegate yet. At the top of the ViewController
class, add this line:
let myTransitioningDelegate = MyTransitioningDelegate()
Build and Run. Once Hello,Pizza appears, swipe left and the modal view appears with chrome around it.
Rotate the device .
Swipe right and the view disappears
Why Apple Uses Gray Chrome
You’ll notice that the chrome does not turn blue, but instead tuns green. As a transparent color, it blends with the color under it, sometimes not in the most attractive way. This is why Apple uses gray for chrome — you never have the problem with gray. If we change the chrome’s backgroundColor
like this:
//let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors let chromeColor = UIColor(white: 0.5, alpha: 0.6) //gray dims the background
Then build and run. We get a slightly different effect:
You’ll notice in both these cases however, the presented controller seems to glow. This is not because of code, but a trick of your brain called Simultaneous contrast . The brain has a hard time dealing with two hues that are complements next to each other, so it starts to make things up to compensate. I used two colors most likely to do this to each other, blue and yellow. There are two strategy you can use to prevent this. One is use less jarring colors in your color scheme for your app. The second is to change the chrome to have a high alpha value and not be as transparent. Change the code to this:
//let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors //let chromeColor = UIColor(white: 0.4, alpha: 0.6) //gray dims the background let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.7) // not as transparent
The chrome is very blue, and the effect is far less.
Set the chrome back to the gray for the rest of the tutorial.
Animating the Chrome
You’ll notice that the chrome appears and disappears rather abruptly. We need to add some animation to the chrome so it transitions smoothly. There is an object called a transition coordinator that automatically loads into the view controller during presentation. It gives you a way to do animation parallel to the main animation. There’s a method in the transition coordinator animateAlongTransition:completion:
that we’ll use to fade the chrome as the presented controller fades in.
In the MyCustomPresentationController
class, change the method to this
override func presentationTransitionWillBegin() { chrome.frame = containerView!.bounds chrome.alpha = 0.0 //start with a invisible chrome //chrome.alpha = 1.0 chrome.backgroundColor = chromeColor containerView!.insertSubview(chrome, atIndex: 0) presentedViewController.transitionCoordinator()!.animateAlongsideTransition( {context in self.chrome.alpha = 1.0 }, completion: nil) }
We changed to code to start the chrome with an alpha
of 0. The transitionCoordinator
function returns the transition coordinator as an optional value. We use its animateAlongsideTransition
method to transition for the invisible chrome to a visible one.
In the presentationTransitionWillBegin
, we do nothing in the completion closure of animateAlongsideTransition
. On the other hand, we’ll remove the chrome in the dismissalTransitionWillBegin
. Add this code.
override func dismissalTransitionWillBegin() { presentedViewController.transitionCoordinator()!.animateAlongsideTransition( { context in self.chrome.alpha = 0.0 }, completion: {context in self.chrome.removeFromSuperview() } ) //self.chrome.alpha = 0.0 //self.chrome.removeFromSuperview() }
We fade out the chrome, and once the chrome completely fades at the end of the main animation, we remove the view.
With those changes, build and run. The chrome fades in and out smoothly
Custom Presentation #2:
A Sidebar
A comma use for a custom presentation controller is a toolbar or side bar. Let’s modify the code to make a sidebar on the right side of our device.
Changing the frame
For a side bar we are setting the view on one side of the contentView
instead of the center as we do with insetBy
. Change frameOfPresentedViewInContainerView
to
override func frameOfPresentedViewInContainerView() -> CGRect { //return containerView!.bounds.insetBy(dx: 30, dy: 30) //center the presented view let newBounds = containerView!.bounds let newWidth:CGFloat = newBounds.width / 3.0 // 1/3 width of view for bar let newXOrigin = newBounds.width - newWidth //set the origin from the right side return CGRect(x: newXOrigin, y: newBounds.origin.y, width: newWidth, height: newBounds.height) }
We commented out the insetBy
, and took control of the frame directly. We take the size of the container view, and divide by three to make a frame size a third the width of the container. We also set the origin to one-third less the width of the container view to make the bar on the right. Build and run. It’s there, but portrait does not look so good.
Landscape looks a lot better, since it has more space
We can change the proportions to fit better. We’ll make it 3/4 of the width. Change the code to this:
var newWidth:CGFloat = newBounds.width * 0.75 // 3/4 width of view for bar
This will work, but will be way too big on an iPad or iPhone 6 plus in landscape. We can use a trait collection to reduce the size to one third on the bigger devices. add this to the code, just under the newWidth
declaration:
if containerView!.traitCollection.horizontalSizeClass == .Regular{ newWidth = newBounds.width / 3.0 //1/3 view on regular width }
Change your simulator to an iPhone 6s plus. Build and run. In portrait, we get a 3/4 view
In landscape, with the regular width size class, we get a 1/3.
Change to an iPad Air 2 simulator. We get 1/3 on both landscape and portrait.
Animating the Side Bar
Side bars look best when they transition by sliding in. While we can animate the chrome with animateAlonsideTransition
, we cannot do so for the frame of the presented controller. To do that we need to use another class, UIViewControllerAnimatedTransitioning
.
While making a new class is probably a better way of doing this, I’m going to keep it together with the presentation controller. Just under the import UIKit
in MyCustomPresentationController.swift,
add the following:
class MyAnimationController: NSObject,UIViewControllerAnimatedTransitioning{ var isPresenting :Bool let duration :NSTimeInterval = 0.5 }
The UIViewControllerAnimatedTransitioning
protocol contains a series of functions to create an animation for both dismissal and presentation. It has two required functions, where we use these two properties.
We added two properties to our class. isPresenting
will be our indicator if this is a dismissal or presentation. To make it easier to use, add this to the class:
init(isPresenting: Bool) { self.isPresenting = isPresenting super.init() }
This will set the value when we initialize the class. We’ll use it in a required method animateTransition
. This method tells the system what to do when there is a transition. We’ll set it up to do both a presentation animation and a dismissal. Add this code:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) { if isPresenting { animatePresentationTransition(transitionContext) } else { animateDismissalTransition(transitionContext) } }
The parameter transitionContext
type UIViewControllerContextTransitioning
contains all the information you need to do the animation transition, including the presented and presenting controllers and the container view. We’ll pass those on to two more functions we’ll write shortly to animate presentation and dismissal of the presenting view controller.
Our other property, duration
will set the time for the animation duration in the other required function. Add this code:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return duration }
This satisfies the two required functions for the protocol. We are still getting errors for out two animation functions though. Add these to the class:
func animatePresentationTransition(transitionContext: UIViewControllerContextTransitioning){ } func animateDismissalTransition(transitionContext: UIViewControllerContextTransitioning){ }
Adding a presentation Animation
Our first step is to add a presentation animation in animatePresentationTransition
. We animate using one of the UIView animation class methods animateWithDuration
. There are different version for different effects, whihc you can read more about in the UIView Class Refrence. All of these will use closures to describe the end state of the animation. We set the presenting view controller to a state where we will star the animation, then in the animation block give its final state. The animateWithDuration
does the rest.
To present the controller’s animation, we’ll need a few thing from the the transition context. Add this to the code for animatePresentationTransition
// Get everything you need let presentingViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let finalFrameForVC = transitionContext.finalFrameForViewController(presentingViewController) let containerView = transitionContext.containerView() let bounds = UIScreen.mainScreen().bounds
We get the presetingviewController
to be able to set its state before and after animation. We also get the container view for adding the presenting view controller. The finalFrameForVC
is the CGFrame
that will be the end of the animation. We’ll use bounds to give us the bounds of the visible screen.
The next step is to change the frame of the presenting view controller to where it should be before the animation begins. we want it off to the right side of the visible view. Since we are not moving anything in the Y direction, we want to have our starting point as the width of the visible view. Add this code:
//move our presenting controller to the correct place, and add it to the view presentingViewController.view.frame = CGRectOffset(finalFrameForVC, bounds.size.width, 0) presentingViewController.view.alpha = 0.0 containerView!.addSubview(presentingViewController.view)
Besides the presenting view moving off of stage right, I also dimmed the view to an alpha
of 0.0. I then added the view to the container view. The next step is the big one — add the animation with this code:
//animate the transition UIView.animateWithDuration( transitionDuration(transitionContext), //set above delay: 0.0, options: .CurveEaseOut, //Animation options animations: { //code for animation in closure presentingViewController.view.frame = finalFrameForVC presentingViewController.view.alpha = 1.0 }, completion: { finished in //completion handler closure transitionContext.completeTransition(true) } )
There’s a lot of unpack in this method’s parameters. We start with a duration we get from the transitionDuration
method we defined earlier. Next thre is a delay to begin the animation, which we leave at ). The next parameter options
takes a value from UIViewAnimationOptions
to describe the behavior of the animation.
The next parameter, animations, describes the final state of the animation. In our code it sets the view controller’s frame to the final frame fro the presentation, and sets the alpha
to 1.
Once the animation is complete there is a completion handler, where we set in the transition context a flag that says we are done animating.
For a dismissal, we do the same in reverse.Add this to animateDismissalTransition
:
let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey) let containerView = transitionContext.containerView()
We first get our presented controller view and the container view. This time, the current position of the frame is the position we start from, so there is no setting the initial frame of the animation. Instead we got straight into the animation, which animates the presented controller view to the edge of the container view. Add the animation:
// Animate the presented view off the side UIView.animateWithDuration( transitionDuration(transitionContext), delay: 0.0, options: .CurveEaseIn, animations: { presentedControllerView!.frame.origin.x += containerView!.frame.width presentedControllerView!.alpha = 0.0 }, completion: {(completed: Bool) -> Void in transitionContext.completeTransition(completed) } )
Add the Animations to the Delegate
The UIViewControllerTransitioningDelegate
does more than just hold the custom transition controller. It has optional methods for animation. Add to MyTransitioningDelegate
the following
func animationControllerForPresentedController( presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController ) -> UIViewControllerAnimatedTransitioning? { return MyAnimationController(isPresenting:true) }
After a long list of parameters, we return a MyAnimationController
with isPresenting
set to true. We set to false for a dismissal in this function
func animationControllerForDismissedController( dismissed: UIViewController ) -> UIViewControllerAnimatedTransitioning? { return MyAnimationController(isPresenting:false) }
Build and run. We now have an animated custom transition
More Things to Try.
This rather long tutorial just breaks the ice in what you can do with a custom presentation. You do not have to use a xib, for example, Storyboards works just as well, with either segues or storyboard id’s plus a reference to the delegate. There are many more animation options as well.
Also a word of caution. I intentionally used a lot of optional values as explicit values to keep things simple and readable. You might not want to be as careless as I am here, and check for nil
or optional chain much more often than I did in this code.
The Whole Code
ViewController.swift
// // ViewController.swift // SwiftCustomPresentation // // Created by Steven Lipton on 4/7/16. // Copyright © 2016 MakeAppPie.Com. All rights reserved. // import UIKit class ViewController: UIViewController, UIViewControllerTransitioningDelegate { let myTransitioningDelegate = MyTransitioningDelegate() //MARK: Actions @IBAction func showAlert(sender: UIButton){ helloAlert() } @IBAction func swipeLeft(sender: UISwipeGestureRecognizer) { presentCustomModal() } @IBAction func swipeUp(sender: UISwipeGestureRecognizer) { presentModal() } //MARK: Instance methods func helloAlert(){ let alert = UIAlertController( title: "Hello Slice", message: "Ready for your slice?", preferredStyle: .Alert) let action = UIAlertAction( title: "Okay", style: .Default, handler: nil) alert.addAction(action) presentViewController(alert, animated: true, completion: nil) } func presentModal() { let helloVC = ModalViewController(nibName: "ModalViewController", bundle: nil) helloVC.modalTransitionStyle = .CoverVertical presentViewController(helloVC, animated: true, completion: nil) } func presentCustomModal(){ let helloVC = ModalViewController(nibName: "ModalViewController", bundle: nil) helloVC.transitioningDelegate = myTransitioningDelegate helloVC.modalPresentationStyle = .Custom helloVC.modalTransitionStyle = .CrossDissolve presentViewController(helloVC, animated: true, completion: nil) } //MARK: Life Cycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
MyCustomPresentationController.swift
// // MyCustomPresentationController.swift // SwiftCustomPresentation // // Created by Steven Lipton on 4/7/16. // Copyright © 2016 MakeAppPie.Com. All rights reserved. // import UIKit class MyAnimationController: NSObject,UIViewControllerAnimatedTransitioning{ var isPresenting :Bool let duration :NSTimeInterval = 0.75 init(isPresenting: Bool) { self.isPresenting = isPresenting super.init() } func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return duration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { if isPresenting { animatePresentationTransition(transitionContext) } else { animateDismissalTransition(transitionContext) } } func animatePresentationTransition(transitionContext: UIViewControllerContextTransitioning){ // Get everything you need let presentingViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let finalFrameForVC = transitionContext.finalFrameForViewController(presentingViewController) let containerView = transitionContext.containerView() let bounds = UIScreen.mainScreen().bounds //move our presenting controller to the correct place, and add it to the view presentingViewController.view.frame = CGRectOffset(finalFrameForVC, bounds.size.width, 0) presentingViewController.view.alpha = 0.0 containerView!.addSubview(presentingViewController.view) //animate the transition UIView.animateWithDuration( transitionDuration(transitionContext), //set above delay: 0.0, options: .CurveEaseOut, //Animation options animations: { //code for animation in closure presentingViewController.view.frame = finalFrameForVC presentingViewController.view.alpha = 1.0 }, completion: { finished in //completion handler closure transitionContext.completeTransition(true) } ) } func animateDismissalTransition(transitionContext: UIViewControllerContextTransitioning){ let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey) let containerView = transitionContext.containerView() // Animate the presented view off the side UIView.animateWithDuration( transitionDuration(transitionContext), delay: 0.0, options: .CurveEaseInOut, animations: { presentedControllerView!.frame.origin.x += containerView!.frame.width presentedControllerView!.alpha = 0.0 }, completion: {(completed: Bool) -> Void in transitionContext.completeTransition(completed) } ) } } class MyTransitioningDelegate : NSObject, UIViewControllerTransitioningDelegate { func presentationControllerForPresentedViewController( presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController ) -> UIPresentationController? { return MyCustomPresentationController( presentedViewController: presented, presentingViewController: presenting) } func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return MyAnimationController(isPresenting:true) } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return MyAnimationController(isPresenting:false) } } class MyCustomPresentationController: UIPresentationController { let chrome = UIView() //let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors let chromeColor = UIColor(white: 0.4, alpha: 0.6) //gray dims the background //let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.7) // not as transparent override func presentationTransitionWillBegin() { chrome.frame = containerView!.bounds chrome.alpha = 0.0 //chrome.alpha = 1.0 chrome.backgroundColor = chromeColor containerView!.insertSubview(chrome, atIndex: 0) var newframe = frameOfPresentedViewInContainerView() newframe.origin.x = newframe.width presentedViewController.view.frame = newframe presentedViewController.transitionCoordinator()!.animateAlongsideTransition( { context in self.chrome.alpha = 1.0 self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView() }, completion: nil) } override func dismissalTransitionWillBegin() { presentedViewController.transitionCoordinator()!.animateAlongsideTransition( { context in self.chrome.alpha = 0.0 }, completion: {context in self.chrome.removeFromSuperview() } ) //self.chrome.alpha = 0.0 //self.chrome.removeFromSuperview() } override func frameOfPresentedViewInContainerView() -> CGRect { //return containerView!.bounds.insetBy(dx: 30, dy: 30) let newBounds = containerView!.bounds var newWidth:CGFloat = newBounds.width * 0.75 // 3/4 width of view for bar //var newWidth:CGFloat = newBounds.width / 3.0 // 1/3 width of view for bar if containerView!.traitCollection.horizontalSizeClass == .Regular{ newWidth = newBounds.width / 3.0 //1/3 view on regular width } let newXOrigin = newBounds.width - newWidth //set the origin 1/3 from the right side return CGRect(x: newXOrigin, y: newBounds.origin.y, width: newWidth, height: newBounds.height) } /* if containerView!.traitCollection.horizontalSizeClass == .Regular { newWidth = newBounds.width * 0.33 } else { //compact and unknown 80% newWidth = newBounds.width * 0.8 } */ override func containerViewWillLayoutSubviews() { chrome.frame = containerView!.bounds presentedView()!.frame = frameOfPresentedViewInContainerView() } }
ModalViewController.swift
// // ModalViewController.swift // SwiftCustomPresentation // // Created by Steven Lipton on 4/7/16. // Copyright © 2016 MakeAppPie.Com. All rights reserved. // import UIKit class ModalViewController: UIViewController { @IBAction func dismissModal(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } }
Leave a Reply