CustomPresentationController.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. Copyright (C) 2016 Apple Inc. All Rights Reserved.
  3. See LICENSE.txt for this sample’s licensing information
  4. Abstract:
  5. A custom presentation controller which slides the presenting view controller
  6. upwards to reveal the presented view controller.
  7. */
  8. #import "CustomPresentationController.h"
  9. //! The corner radius applied to the view containing the presented view
  10. //! controller.
  11. #define CORNER_RADIUS 16.f
  12. @interface CustomPresentationController () <UIViewControllerAnimatedTransitioning>
  13. @property (nonatomic, strong) UIView *dimmingView;
  14. @property (nonatomic, strong) UIView *presentationWrappingView;
  15. @end
  16. @implementation CustomPresentationController
  17. //| ----------------------------------------------------------------------------
  18. - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
  19. {
  20. self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
  21. if (self) {
  22. // The presented view controller must have a modalPresentationStyle
  23. // of UIModalPresentationCustom for a custom presentation controller
  24. // to be used.
  25. presentedViewController.modalPresentationStyle = UIModalPresentationCustom;
  26. }
  27. return self;
  28. }
  29. //| ----------------------------------------------------------------------------
  30. - (UIView*)presentedView
  31. {
  32. // Return the wrapping view created in -presentationTransitionWillBegin.
  33. return self.presentationWrappingView;
  34. }
  35. //| ----------------------------------------------------------------------------
  36. // This is one of the first methods invoked on the presentation controller
  37. // at the start of a presentation. By the time this method is called,
  38. // the containerView has been created and the view hierarchy set up for the
  39. // presentation. However, the -presentedView has not yet been retrieved.
  40. //
  41. - (void)presentationTransitionWillBegin
  42. {
  43. // The default implementation of -presentedView returns
  44. // self.presentedViewController.view.
  45. UIView *presentedViewControllerView = [super presentedView];
  46. // Wrap the presented view controller's view in an intermediate hierarchy
  47. // that applies a shadow and rounded corners to the top-left and top-right
  48. // edges. The final effect is built using three intermediate views.
  49. //
  50. // presentationWrapperView <- shadow
  51. // |- presentationRoundedCornerView <- rounded corners (masksToBounds)
  52. // |- presentedViewControllerWrapperView
  53. // |- presentedViewControllerView (presentedViewController.view)
  54. //
  55. // SEE ALSO: The note in AAPLCustomPresentationSecondViewController.m.
  56. {
  57. UIView *presentationWrapperView = [[UIView alloc] initWithFrame:self.frameOfPresentedViewInContainerView];
  58. presentationWrapperView.layer.shadowOpacity = 0.44f;
  59. presentationWrapperView.layer.shadowRadius = 13.f;
  60. presentationWrapperView.layer.shadowOffset = CGSizeMake(0, -6.f);
  61. self.presentationWrappingView = presentationWrapperView;
  62. // presentationRoundedCornerView is CORNER_RADIUS points taller than the
  63. // height of the presented view controller's view. This is because
  64. // the cornerRadius is applied to all corners of the view. Since the
  65. // effect calls for only the top two corners to be rounded we size
  66. // the view such that the bottom CORNER_RADIUS points lie below
  67. // the bottom edge of the screen.
  68. UIView *presentationRoundedCornerView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(presentationWrapperView.bounds, UIEdgeInsetsMake(0, 0, -CORNER_RADIUS, 0))];
  69. presentationRoundedCornerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  70. presentationRoundedCornerView.layer.cornerRadius = CORNER_RADIUS;
  71. presentationRoundedCornerView.layer.masksToBounds = YES;
  72. // To undo the extra height added to presentationRoundedCornerView,
  73. // presentedViewControllerWrapperView is inset by CORNER_RADIUS points.
  74. // This also matches the size of presentedViewControllerWrapperView's
  75. // bounds to the size of -frameOfPresentedViewInContainerView.
  76. UIView *presentedViewControllerWrapperView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(presentationRoundedCornerView.bounds, UIEdgeInsetsMake(0, 0, CORNER_RADIUS, 0))];
  77. presentedViewControllerWrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  78. // Add presentedViewControllerView -> presentedViewControllerWrapperView.
  79. presentedViewControllerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  80. presentedViewControllerView.frame = presentedViewControllerWrapperView.bounds;
  81. [presentedViewControllerWrapperView addSubview:presentedViewControllerView];
  82. // Add presentedViewControllerWrapperView -> presentationRoundedCornerView.
  83. [presentationRoundedCornerView addSubview:presentedViewControllerWrapperView];
  84. // Add presentationRoundedCornerView -> presentationWrapperView.
  85. [presentationWrapperView addSubview:presentationRoundedCornerView];
  86. }
  87. // Add a dimming view behind presentationWrapperView. self.presentedView
  88. // is added later (by the animator) so any views added here will be
  89. // appear behind the -presentedView.
  90. {
  91. UIView *dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
  92. dimmingView.backgroundColor = [UIColor blackColor];
  93. dimmingView.opaque = NO;
  94. dimmingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  95. [dimmingView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)]];
  96. self.dimmingView = dimmingView;
  97. [self.containerView addSubview:dimmingView];
  98. // Get the transition coordinator for the presentation so we can
  99. // fade in the dimmingView alongside the presentation animation.
  100. id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
  101. self.dimmingView.alpha = 0.f;
  102. [transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  103. self.dimmingView.alpha = 0.5f;
  104. } completion:NULL];
  105. }
  106. }
  107. //| ----------------------------------------------------------------------------
  108. - (void)presentationTransitionDidEnd:(BOOL)completed
  109. {
  110. // The value of the 'completed' argument is the same value passed to the
  111. // -completeTransition: method by the animator. It may
  112. // be NO in the case of a cancelled interactive transition.
  113. if (completed == NO)
  114. {
  115. // The system removes the presented view controller's view from its
  116. // superview and disposes of the containerView. This implicitly
  117. // removes the views created in -presentationTransitionWillBegin: from
  118. // the view hierarchy. However, we still need to relinquish our strong
  119. // references to those view.
  120. self.presentationWrappingView = nil;
  121. self.dimmingView = nil;
  122. }
  123. }
  124. //| ----------------------------------------------------------------------------
  125. - (void)dismissalTransitionWillBegin
  126. {
  127. // Get the transition coordinator for the dismissal so we can
  128. // fade out the dimmingView alongside the dismissal animation.
  129. id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
  130. [transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  131. self.dimmingView.alpha = 0.f;
  132. } completion:NULL];
  133. }
  134. //| ----------------------------------------------------------------------------
  135. - (void)dismissalTransitionDidEnd:(BOOL)completed
  136. {
  137. // The value of the 'completed' argument is the same value passed to the
  138. // -completeTransition: method by the animator. It may
  139. // be NO in the case of a cancelled interactive transition.
  140. if (completed == YES)
  141. {
  142. // The system removes the presented view controller's view from its
  143. // superview and disposes of the containerView. This implicitly
  144. // removes the views created in -presentationTransitionWillBegin: from
  145. // the view hierarchy. However, we still need to relinquish our strong
  146. // references to those view.
  147. self.presentationWrappingView = nil;
  148. self.dimmingView = nil;
  149. }
  150. }
  151. #pragma mark -
  152. #pragma mark Layout
  153. //| ----------------------------------------------------------------------------
  154. // This method is invoked whenever the presentedViewController's
  155. // preferredContentSize property changes. It is also invoked just before the
  156. // presentation transition begins (prior to -presentationTransitionWillBegin).
  157. //
  158. - (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
  159. {
  160. [super preferredContentSizeDidChangeForChildContentContainer:container];
  161. if (container == self.presentedViewController)
  162. [self.containerView setNeedsLayout];
  163. }
  164. //| ----------------------------------------------------------------------------
  165. // When the presentation controller receives a
  166. // -viewWillTransitionToSize:withTransitionCoordinator: message it calls this
  167. // method to retrieve the new size for the presentedViewController's view.
  168. // The presentation controller then sends a
  169. // -viewWillTransitionToSize:withTransitionCoordinator: message to the
  170. // presentedViewController with this size as the first argument.
  171. //
  172. // Note that it is up to the presentation controller to adjust the frame
  173. // of the presented view controller's view to match this promised size.
  174. // We do this in -containerViewWillLayoutSubviews.
  175. //
  176. - (CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize
  177. {
  178. if (container == self.presentedViewController)
  179. return ((UIViewController*)container).preferredContentSize;
  180. else
  181. return [super sizeForChildContentContainer:container withParentContainerSize:parentSize];
  182. }
  183. //| ----------------------------------------------------------------------------
  184. - (CGRect)frameOfPresentedViewInContainerView
  185. {
  186. CGRect containerViewBounds = self.containerView.bounds;
  187. CGSize presentedViewContentSize = [self sizeForChildContentContainer:self.presentedViewController withParentContainerSize:containerViewBounds.size];
  188. // The presented view extends presentedViewContentSize.height points from
  189. // the bottom edge of the screen.
  190. CGRect presentedViewControllerFrame = containerViewBounds;
  191. presentedViewControllerFrame.size.height = presentedViewContentSize.height;
  192. presentedViewControllerFrame.origin.y = CGRectGetMaxY(containerViewBounds) - presentedViewContentSize.height;
  193. return presentedViewControllerFrame;
  194. }
  195. //| ----------------------------------------------------------------------------
  196. // This method is similar to the -viewWillLayoutSubviews method in
  197. // UIViewController. It allows the presentation controller to alter the
  198. // layout of any custom views it manages.
  199. //
  200. - (void)containerViewWillLayoutSubviews
  201. {
  202. [super containerViewWillLayoutSubviews];
  203. self.dimmingView.frame = self.containerView.bounds;
  204. self.presentationWrappingView.frame = self.frameOfPresentedViewInContainerView;
  205. }
  206. #pragma mark -
  207. #pragma mark Tap Gesture Recognizer
  208. //| ----------------------------------------------------------------------------
  209. // IBAction for the tap gesture recognizer added to the dimmingView.
  210. // Dismisses the presented view controller.
  211. //
  212. - (IBAction)dimmingViewTapped:(UITapGestureRecognizer*)sender
  213. {
  214. [self.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
  215. }
  216. #pragma mark -
  217. #pragma mark UIViewControllerAnimatedTransitioning
  218. //| ----------------------------------------------------------------------------
  219. - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
  220. {
  221. return [transitionContext isAnimated] ? 0.35 : 0;
  222. }
  223. //| ----------------------------------------------------------------------------
  224. // The presentation animation is tightly integrated with the overall
  225. // presentation so it makes the most sense to implement
  226. // <UIViewControllerAnimatedTransitioning> in the presentation controller
  227. // rather than in a separate object.
  228. //
  229. - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
  230. {
  231. UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  232. UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  233. UIView *containerView = transitionContext.containerView;
  234. // For a Presentation:
  235. // fromView = The presenting view.
  236. // toView = The presented view.
  237. // For a Dismissal:
  238. // fromView = The presented view.
  239. // toView = The presenting view.
  240. UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
  241. // If NO is returned from -shouldRemovePresentersView, the view associated
  242. // with UITransitionContextFromViewKey is nil during presentation. This
  243. // intended to be a hint that your animator should NOT be manipulating the
  244. // presenting view controller's view. For a dismissal, the -presentedView
  245. // is returned.
  246. //
  247. // Why not allow the animator manipulate the presenting view controller's
  248. // view at all times? First of all, if the presenting view controller's
  249. // view is going to stay visible after the animation finishes during the
  250. // whole presentation life cycle there is no need to animate it at all — it
  251. // just stays where it is. Second, if the ownership for that view
  252. // controller is transferred to the presentation controller, the
  253. // presentation controller will most likely not know how to layout that
  254. // view controller's view when needed, for example when the orientation
  255. // changes, but the original owner of the presenting view controller does.
  256. UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
  257. BOOL isPresenting = (fromViewController == self.presentingViewController);
  258. // This will be the current frame of fromViewController.view.
  259. CGRect __unused fromViewInitialFrame = [transitionContext initialFrameForViewController:fromViewController];
  260. // For a presentation which removes the presenter's view, this will be
  261. // CGRectZero. Otherwise, the current frame of fromViewController.view.
  262. CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromViewController];
  263. // This will be CGRectZero.
  264. CGRect toViewInitialFrame = [transitionContext initialFrameForViewController:toViewController];
  265. // For a presentation, this will be the value returned from the
  266. // presentation controller's -frameOfPresentedViewInContainerView method.
  267. CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toViewController];
  268. // We are responsible for adding the incoming view to the containerView
  269. // for the presentation (will have no effect on dismissal because the
  270. // presenting view controller's view was not removed).
  271. [containerView addSubview:toView];
  272. if (isPresenting) {
  273. toViewInitialFrame.origin = CGPointMake(CGRectGetMinX(containerView.bounds), CGRectGetMaxY(containerView.bounds));
  274. toViewInitialFrame.size = toViewFinalFrame.size;
  275. toView.frame = toViewInitialFrame;
  276. } else {
  277. // Because our presentation wraps the presented view controller's view
  278. // in an intermediate view hierarchy, it is more accurate to rely
  279. // on the current frame of fromView than fromViewInitialFrame as the
  280. // initial frame (though in this example they will be the same).
  281. fromViewFinalFrame = CGRectOffset(fromView.frame, 0, CGRectGetHeight(fromView.frame));
  282. }
  283. NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
  284. [UIView animateWithDuration:transitionDuration animations:^{
  285. if (isPresenting)
  286. toView.frame = toViewFinalFrame;
  287. else
  288. fromView.frame = fromViewFinalFrame;
  289. } completion:^(BOOL finished) {
  290. // When we complete, tell the transition context
  291. // passing along the BOOL that indicates whether the transition
  292. // finished or not.
  293. BOOL wasCancelled = [transitionContext transitionWasCancelled];
  294. [transitionContext completeTransition:!wasCancelled];
  295. }];
  296. }
  297. #pragma mark -
  298. #pragma mark UIViewControllerTransitioningDelegate
  299. //| ----------------------------------------------------------------------------
  300. // If the modalPresentationStyle of the presented view controller is
  301. // UIModalPresentationCustom, the system calls this method on the presented
  302. // view controller's transitioningDelegate to retrieve the presentation
  303. // controller that will manage the presentation. If your implementation
  304. // returns nil, an instance of UIPresentationController is used.
  305. //
  306. - (UIPresentationController*)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
  307. {
  308. NSAssert(self.presentedViewController == presented, @"You didn't initialize %@ with the correct presentedViewController. Expected %@, got %@.",
  309. self, presented, self.presentedViewController);
  310. return self;
  311. }
  312. //| ----------------------------------------------------------------------------
  313. // The system calls this method on the presented view controller's
  314. // transitioningDelegate to retrieve the animator object used for animating
  315. // the presentation of the incoming view controller. Your implementation is
  316. // expected to return an object that conforms to the
  317. // UIViewControllerAnimatedTransitioning protocol, or nil if the default
  318. // presentation animation should be used.
  319. //
  320. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  321. {
  322. return self;
  323. }
  324. //| ----------------------------------------------------------------------------
  325. // The system calls this method on the presented view controller's
  326. // transitioningDelegate to retrieve the animator object used for animating
  327. // the dismissal of the presented view controller. Your implementation is
  328. // expected to return an object that conforms to the
  329. // UIViewControllerAnimatedTransitioning protocol, or nil if the default
  330. // dismissal animation should be used.
  331. //
  332. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  333. {
  334. return self;
  335. }
  336. @end