AAPLCustomPresentationController.m 18 KB

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