HWPanModalPresentationAnimator.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. //
  2. // HWPanModalPresentationAnimator.m
  3. // HWPanModal
  4. //
  5. // Created by heath wang on 2019/4/29.
  6. //
  7. #import "HWPanModalPresentationAnimator.h"
  8. #import "HWPanModalAnimator.h"
  9. #import "UIViewController+LayoutHelper.h"
  10. #import "HWPanContainerView.h"
  11. #import "UIView+HW_Frame.h"
  12. #import "HWPageSheetPresentingAnimation.h"
  13. #import "HWShoppingCartPresentingAnimation.h"
  14. @interface HWPresentingVCTransitionContext : NSObject <HWPresentingViewControllerContextTransitioning>
  15. @property (nonatomic, weak) UIViewController *fromVC;
  16. @property (nonatomic, weak) UIViewController *toVC;
  17. @property (nonatomic, assign) NSTimeInterval duration;
  18. @property (nonatomic, strong) UIView *containerView;
  19. - (instancetype)initWithFromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC duration:(NSTimeInterval)duration containerView:(UIView *)containerView;
  20. @end
  21. @interface HWPanModalPresentationAnimator ()
  22. @property (nonatomic, assign) TransitionStyle transitionStyle;
  23. @property (nullable, nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator API_AVAILABLE(ios(10.0));
  24. @property (nonatomic, strong) HWPresentingVCTransitionContext *presentingVCTransitionContext;
  25. @property (nonatomic, assign) PanModalInteractiveMode interactiveMode;
  26. @end
  27. @implementation HWPanModalPresentationAnimator
  28. - (instancetype)initWithTransitionStyle:(TransitionStyle)transitionStyle interactiveMode:(PanModalInteractiveMode)mode {
  29. self = [super init];
  30. if (self) {
  31. _transitionStyle = transitionStyle;
  32. _interactiveMode = mode;
  33. if (transitionStyle == TransitionStylePresentation) {
  34. if (@available(iOS 10.0, *)) {
  35. _feedbackGenerator = [UISelectionFeedbackGenerator new];
  36. [_feedbackGenerator prepare];
  37. } else {
  38. // Fallback on earlier versions
  39. }
  40. }
  41. }
  42. return self;
  43. }
  44. /**
  45. * 弹出controller动画
  46. */
  47. - (void)animatePresentation:(id<UIViewControllerContextTransitioning>)context {
  48. UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
  49. UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
  50. if (!toVC && !fromVC)
  51. return;
  52. UIViewController<HWPanModalPresentable> *presentable = [self panModalViewController:context];
  53. if ([presentable shouldEnableAppearanceTransition]) {
  54. // If you are implementing a custom container controller, use this method to tell the child that its views are about to appear or disappear.
  55. [fromVC beginAppearanceTransition:NO animated:YES];
  56. [self beginAppearanceTransitionForController:toVC isAppearing:YES animated:YES];
  57. }
  58. CGFloat yPos = presentable.shortFormYPos;
  59. if ([presentable originPresentationState] == PresentationStateLong) {
  60. yPos = presentable.longFormYPos;
  61. } else if ([presentable originPresentationState] == PresentationStateMedium) {
  62. yPos = presentable.mediumFormYPos;
  63. }
  64. UIView *panView = context.containerView.panContainerView ?: toVC.view;
  65. panView.frame = [context finalFrameForViewController:toVC];
  66. panView.hw_top = context.containerView.frame.size.height;
  67. if ([presentable isHapticFeedbackEnabled]) {
  68. if (@available(iOS 10.0, *)) {
  69. [self.feedbackGenerator selectionChanged];
  70. }
  71. }
  72. [HWPanModalAnimator animate:^{
  73. panView.hw_top = yPos;
  74. } config:presentable completion:^(BOOL completion) {
  75. if ([presentable shouldEnableAppearanceTransition]) {
  76. [fromVC endAppearanceTransition];
  77. [self endAppearanceTransitionForController:toVC];
  78. }
  79. if (@available(iOS 10.0, *)) {
  80. self.feedbackGenerator = nil;
  81. }
  82. [context completeTransition:completion];
  83. }];
  84. self.presentingVCTransitionContext = [[HWPresentingVCTransitionContext alloc] initWithFromVC:fromVC toVC:toVC duration:[presentable transitionDuration] containerView:context.containerView];
  85. [self presentAnimationForPresentingVC:presentable];
  86. }
  87. /**
  88. * 使弹出controller消失动画
  89. */
  90. - (void)animateDismissal:(id<UIViewControllerContextTransitioning>)context {
  91. UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
  92. UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
  93. if (!fromVC && !toVC)
  94. return;
  95. UIViewController<HWPanModalPresentable> *presentable = [self panModalViewController:context];
  96. if ([presentable shouldEnableAppearanceTransition]) {
  97. [self beginAppearanceTransitionForController:fromVC isAppearing:NO animated:YES];
  98. [toVC beginAppearanceTransition:YES animated:YES];
  99. }
  100. UIView *panView = context.containerView.panContainerView ?: fromVC.view;
  101. self.presentingVCTransitionContext = [[HWPresentingVCTransitionContext alloc] initWithFromVC:fromVC toVC:toVC duration:[presentable transitionDuration] containerView:context.containerView];
  102. // user toggle pan gesture to dismiss.
  103. if ([context isInteractive]) {
  104. [self interactionDismiss:context fromVC:fromVC toVC:toVC presentable:presentable panView:panView];
  105. } else {
  106. [self springDismiss:context fromVC:fromVC toVC:toVC presentable:presentable panView:panView];
  107. }
  108. }
  109. - (void)springDismiss:(id <UIViewControllerContextTransitioning>)context fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC presentable:(UIViewController <HWPanModalPresentable> *)presentable panView:(UIView *)panView {
  110. CGFloat offsetY = 0;
  111. HWPanModalShadow *shadowConfig = [presentable contentShadow];
  112. if (shadowConfig.shadowColor) {
  113. // we should make the panView move further to hide the shadow effect.
  114. offsetY = offsetY + shadowConfig.shadowRadius + shadowConfig.shadowOffset.height;
  115. if ([presentable showDragIndicator]) {
  116. offsetY += [presentable customIndicatorView] ? [presentable customIndicatorView].indicatorSize.height : 13;
  117. }
  118. }
  119. [HWPanModalAnimator dismissAnimate:^{
  120. [self dismissAnimationForPresentingVC:presentable];
  121. panView.hw_top = (context.containerView.frame.size.height + offsetY);
  122. } config:presentable completion:^(BOOL completion) {
  123. [fromVC.view removeFromSuperview];
  124. if ([presentable shouldEnableAppearanceTransition]) {
  125. [self endAppearanceTransitionForController:fromVC];
  126. [toVC endAppearanceTransition];
  127. }
  128. [context completeTransition:completion];
  129. }];
  130. }
  131. - (void)interactionDismiss:(id <UIViewControllerContextTransitioning>)context fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC presentable:(UIViewController <HWPanModalPresentable> *)presentable panView:(UIView *)panView {
  132. [HWPanModalAnimator smoothAnimate:^{
  133. if (self.interactiveMode == PanModalInteractiveModeSideslip) {
  134. panView.hw_left = panView.hw_width;
  135. }
  136. [self dismissAnimationForPresentingVC:presentable];
  137. } duration:[presentable dismissalDuration] completion:^(BOOL completion) {
  138. // 因为会有手势交互,所以需要判断transitions是否cancel
  139. BOOL finished = ![context transitionWasCancelled];
  140. if (finished) {
  141. [fromVC.view removeFromSuperview];
  142. if ([presentable shouldEnableAppearanceTransition]) {
  143. [self endAppearanceTransitionForController:fromVC];
  144. [toVC endAppearanceTransition];
  145. }
  146. context.containerView.userInteractionEnabled = YES;
  147. }
  148. [context completeTransition:finished];
  149. }];
  150. }
  151. #pragma mark - presenting VC animation
  152. - (void)presentAnimationForPresentingVC:(UIViewController<HWPanModalPresentable> *)presentable {
  153. id<HWPresentingViewControllerAnimatedTransitioning> presentingAnimation = [self presentingVCAnimation:presentable];
  154. if (presentingAnimation) {
  155. [presentingAnimation presentAnimateTransition:self.presentingVCTransitionContext];
  156. }
  157. }
  158. - (void)dismissAnimationForPresentingVC:(UIViewController<HWPanModalPresentable> *)presentable {
  159. id<HWPresentingViewControllerAnimatedTransitioning> presentingAnimation = [self presentingVCAnimation:presentable];
  160. if (presentingAnimation) {
  161. [presentingAnimation dismissAnimateTransition:self.presentingVCTransitionContext];
  162. }
  163. }
  164. - (UIViewController <HWPanModalPresentable> *)panModalViewController:(id <UIViewControllerContextTransitioning>)context {
  165. switch (self.transitionStyle) {
  166. case TransitionStylePresentation: {
  167. UIViewController *controller = [context viewControllerForKey:UITransitionContextToViewControllerKey];
  168. if ([controller conformsToProtocol:@protocol(HWPanModalPresentable)]) {
  169. return (UIViewController <HWPanModalPresentable> *) controller;
  170. } else {
  171. return nil;
  172. }
  173. }
  174. case TransitionStyleDismissal: {
  175. UIViewController *controller = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
  176. if ([controller conformsToProtocol:@protocol(HWPanModalPresentable)]) {
  177. return (UIViewController <HWPanModalPresentable> *) controller;
  178. } else {
  179. return nil;
  180. }
  181. }
  182. }
  183. }
  184. #pragma mark - UIViewControllerAnimatedTransitioning
  185. - (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
  186. switch (self.transitionStyle) {
  187. case TransitionStylePresentation: {
  188. [self animatePresentation:transitionContext];
  189. }
  190. break;
  191. case TransitionStyleDismissal: {
  192. [self animateDismissal:transitionContext];
  193. }
  194. default:
  195. break;
  196. }
  197. }
  198. - (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext {
  199. if (transitionContext && [self panModalViewController:transitionContext]) {
  200. UIViewController<HWPanModalPresentable> *controller = [self panModalViewController:transitionContext];
  201. return [controller transitionDuration];
  202. }
  203. return kTransitionDuration;
  204. }
  205. #pragma mark - presenting animated transition
  206. - (id<HWPresentingViewControllerAnimatedTransitioning>)presentingVCAnimation:(UIViewController<HWPanModalPresentable> *)presentable {
  207. switch ([presentable presentingVCAnimationStyle]) {
  208. case PresentingViewControllerAnimationStylePageSheet:
  209. return [HWPageSheetPresentingAnimation new];
  210. case PresentingViewControllerAnimationStyleShoppingCart:
  211. return [HWShoppingCartPresentingAnimation new];
  212. case PresentingViewControllerAnimationStyleCustom:
  213. return [presentable customPresentingVCAnimation];
  214. default:
  215. return nil;
  216. }
  217. }
  218. #pragma mark - private method
  219. - (void)beginAppearanceTransitionForController:(UIViewController *)viewController isAppearing:(BOOL)isAppearing animated:(BOOL)animated {
  220. // Fix `The unbalanced calls to begin/end appearance transitions` warning.
  221. if (![viewController isKindOfClass:UINavigationController.class]) {
  222. [viewController beginAppearanceTransition:isAppearing animated:animated];
  223. }
  224. }
  225. - (void)endAppearanceTransitionForController:(UIViewController *)viewController {
  226. if (![viewController isKindOfClass:UINavigationController.class]) {
  227. [viewController endAppearanceTransition];
  228. }
  229. }
  230. @end
  231. @implementation HWPresentingVCTransitionContext
  232. - (instancetype)initWithFromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC duration:(NSTimeInterval)duration containerView:(UIView *)containerView {
  233. self = [super init];
  234. if (self) {
  235. _fromVC = fromVC;
  236. _toVC = toVC;
  237. _duration = duration;
  238. _containerView = containerView;
  239. }
  240. return self;
  241. }
  242. - (__kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key {
  243. if ([key isEqualToString:UITransitionContextFromViewControllerKey]) {
  244. return self.fromVC;
  245. } else if ([key isEqualToString:UITransitionContextToViewControllerKey]) {
  246. return self.toVC;
  247. }
  248. return nil;
  249. }
  250. - (NSTimeInterval)transitionDuration {
  251. return self.duration;
  252. }
  253. @end