SignatureCustomPresentationController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. //
  2. // SignatureCustomPresentationController.m
  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 "SignatureCustomPresentationController.h"
  13. #define CORNER_RADIUS 16.f
  14. @interface SignatureCustomPresentationController () <UIViewControllerAnimatedTransitioning>
  15. @property (nonatomic, strong) UIView *dimmingView;
  16. @property (nonatomic, strong) UIView *presentationWrappingView;
  17. @end
  18. @implementation SignatureCustomPresentationController
  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 = self.containerView.frame.size.width;
  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. }
  201. #pragma mark -
  202. #pragma mark UIViewControllerAnimatedTransitioning
  203. - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
  204. {
  205. return [transitionContext isAnimated] ? 0.35 : 0;
  206. }
  207. // The presentation animation is tightly integrated with the overall
  208. // presentation so it makes the most sense to implement
  209. // <UIViewControllerAnimatedTransitioning> in the presentation controller
  210. // rather than in a separate object.
  211. //
  212. - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
  213. {
  214. UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  215. UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  216. UIView *containerView = transitionContext.containerView;
  217. // For a Presentation:
  218. // fromView = The presenting view.
  219. // toView = The presented view.
  220. // For a Dismissal:
  221. // fromView = The presented view.
  222. // toView = The presenting view.
  223. UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
  224. // If NO is returned from -shouldRemovePresentersView, the view associated
  225. // with UITransitionContextFromViewKey is nil during presentation. This
  226. // intended to be a hint that your animator should NOT be manipulating the
  227. // presenting view controller's view. For a dismissal, the -presentedView
  228. // is returned.
  229. //
  230. // Why not allow the animator manipulate the presenting view controller's
  231. // view at all times? First of all, if the presenting view controller's
  232. // view is going to stay visible after the animation finishes during the
  233. // whole presentation life cycle there is no need to animate it at all — it
  234. // just stays where it is. Second, if the ownership for that view
  235. // controller is transferred to the presentation controller, the
  236. // presentation controller will most likely not know how to layout that
  237. // view controller's view when needed, for example when the orientation
  238. // changes, but the original owner of the presenting view controller does.
  239. UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
  240. BOOL isPresenting = (fromViewController == self.presentingViewController);
  241. // This will be the current frame of fromViewController.view.
  242. CGRect __unused fromViewInitialFrame = [transitionContext initialFrameForViewController:fromViewController];
  243. // For a presentation which removes the presenter's view, this will be
  244. // CGRectZero. Otherwise, the current frame of fromViewController.view.
  245. CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromViewController];
  246. // This will be CGRectZero.
  247. CGRect toViewInitialFrame = [transitionContext initialFrameForViewController:toViewController];
  248. // For a presentation, this will be the value returned from the
  249. // presentation controller's -frameOfPresentedViewInContainerView method.
  250. CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toViewController];
  251. // We are responsible for adding the incoming view to the containerView
  252. // for the presentation (will have no effect on dismissal because the
  253. // presenting view controller's view was not removed).
  254. [containerView addSubview:toView];
  255. if (isPresenting) {
  256. toViewInitialFrame.origin = CGPointMake(CGRectGetMinX(containerView.bounds), CGRectGetMaxY(containerView.bounds));
  257. toViewInitialFrame.size = toViewFinalFrame.size;
  258. toView.frame = toViewInitialFrame;
  259. } else {
  260. // Because our presentation wraps the presented view controller's view
  261. // in an intermediate view hierarchy, it is more accurate to rely
  262. // on the current frame of fromView than fromViewInitialFrame as the
  263. // initial frame (though in this example they will be the same).
  264. fromViewFinalFrame = CGRectOffset(fromView.frame, 0, CGRectGetHeight(fromView.frame));
  265. }
  266. NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
  267. [UIView animateWithDuration:transitionDuration animations:^{
  268. if (isPresenting)
  269. toView.frame = toViewFinalFrame;
  270. else
  271. fromView.frame = fromViewFinalFrame;
  272. } completion:^(BOOL finished) {
  273. // When we complete, tell the transition context
  274. // passing along the BOOL that indicates whether the transition
  275. // finished or not.
  276. BOOL wasCancelled = [transitionContext transitionWasCancelled];
  277. [transitionContext completeTransition:!wasCancelled];
  278. }];
  279. }
  280. #pragma mark -
  281. #pragma mark UIViewControllerTransitioningDelegate
  282. // If the modalPresentationStyle of the presented view controller is
  283. // UIModalPresentationCustom, the system calls this method on the presented
  284. // view controller's transitioningDelegate to retrieve the presentation
  285. // controller that will manage the presentation. If your implementation
  286. // returns nil, an instance of UIPresentationController is used.
  287. //
  288. - (UIPresentationController*)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
  289. {
  290. NSAssert(self.presentedViewController == presented, @"You didn't initialize %@ with the correct presentedViewController. Expected %@, got %@.",
  291. self, presented, self.presentedViewController);
  292. return self;
  293. }
  294. // The system calls this method on the presented view controller's
  295. // transitioningDelegate to retrieve the animator object used for animating
  296. // the presentation of the incoming view controller. Your implementation is
  297. // expected to return an object that conforms to the
  298. // UIViewControllerAnimatedTransitioning protocol, or nil if the default
  299. // presentation animation should be used.
  300. //
  301. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  302. {
  303. return self;
  304. }
  305. // The system calls this method on the presented view controller's
  306. // transitioningDelegate to retrieve the animator object used for animating
  307. // the dismissal of the presented view controller. Your implementation is
  308. // expected to return an object that conforms to the
  309. // UIViewControllerAnimatedTransitioning protocol, or nil if the default
  310. // dismissal animation should be used.
  311. //
  312. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  313. {
  314. return self;
  315. }
  316. @end