Hibit_iOS/Pods/HWPanModal/Sources/Animator/HWPanModalPresentationAnimator.m
2024-06-07 11:41:02 +08:00

315 lines
11 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HWPanModalPresentationAnimator.m
// HWPanModal
//
// Created by heath wang on 2019/4/29.
//
#import "HWPanModalPresentationAnimator.h"
#import "HWPanModalAnimator.h"
#import "UIViewController+LayoutHelper.h"
#import "HWPanContainerView.h"
#import "UIView+HW_Frame.h"
#import "HWPageSheetPresentingAnimation.h"
#import "HWShoppingCartPresentingAnimation.h"
@interface HWPresentingVCTransitionContext : NSObject <HWPresentingViewControllerContextTransitioning>
@property (nonatomic, weak) UIViewController *fromVC;
@property (nonatomic, weak) UIViewController *toVC;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, strong) UIView *containerView;
- (instancetype)initWithFromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC duration:(NSTimeInterval)duration containerView:(UIView *)containerView;
@end
@interface HWPanModalPresentationAnimator ()
@property (nonatomic, assign) TransitionStyle transitionStyle;
@property (nullable, nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator API_AVAILABLE(ios(10.0));
@property (nonatomic, strong) HWPresentingVCTransitionContext *presentingVCTransitionContext;
@property (nonatomic, assign) PanModalInteractiveMode interactiveMode;
@end
@implementation HWPanModalPresentationAnimator
- (instancetype)initWithTransitionStyle:(TransitionStyle)transitionStyle interactiveMode:(PanModalInteractiveMode)mode {
self = [super init];
if (self) {
_transitionStyle = transitionStyle;
_interactiveMode = mode;
if (transitionStyle == TransitionStylePresentation) {
if (@available(iOS 10.0, *)) {
_feedbackGenerator = [UISelectionFeedbackGenerator new];
[_feedbackGenerator prepare];
} else {
// Fallback on earlier versions
}
}
}
return self;
}
/**
* 弹出controller动画
*/
- (void)animatePresentation:(id<UIViewControllerContextTransitioning>)context {
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
if (!toVC && !fromVC)
return;
UIViewController<HWPanModalPresentable> *presentable = [self panModalViewController:context];
if ([presentable shouldEnableAppearanceTransition]) {
// If you are implementing a custom container controller, use this method to tell the child that its views are about to appear or disappear.
[fromVC beginAppearanceTransition:NO animated:YES];
[self beginAppearanceTransitionForController:toVC isAppearing:YES animated:YES];
}
CGFloat yPos = presentable.shortFormYPos;
if ([presentable originPresentationState] == PresentationStateLong) {
yPos = presentable.longFormYPos;
} else if ([presentable originPresentationState] == PresentationStateMedium) {
yPos = presentable.mediumFormYPos;
}
UIView *panView = context.containerView.panContainerView ?: toVC.view;
panView.frame = [context finalFrameForViewController:toVC];
panView.hw_top = context.containerView.frame.size.height;
if ([presentable isHapticFeedbackEnabled]) {
if (@available(iOS 10.0, *)) {
[self.feedbackGenerator selectionChanged];
}
}
[HWPanModalAnimator animate:^{
panView.hw_top = yPos;
} config:presentable completion:^(BOOL completion) {
if ([presentable shouldEnableAppearanceTransition]) {
[fromVC endAppearanceTransition];
[self endAppearanceTransitionForController:toVC];
}
if (@available(iOS 10.0, *)) {
self.feedbackGenerator = nil;
}
[context completeTransition:completion];
}];
self.presentingVCTransitionContext = [[HWPresentingVCTransitionContext alloc] initWithFromVC:fromVC toVC:toVC duration:[presentable transitionDuration] containerView:context.containerView];
[self presentAnimationForPresentingVC:presentable];
}
/**
* 使弹出controller消失动画
*/
- (void)animateDismissal:(id<UIViewControllerContextTransitioning>)context {
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
if (!fromVC && !toVC)
return;
UIViewController<HWPanModalPresentable> *presentable = [self panModalViewController:context];
if ([presentable shouldEnableAppearanceTransition]) {
[self beginAppearanceTransitionForController:fromVC isAppearing:NO animated:YES];
[toVC beginAppearanceTransition:YES animated:YES];
}
UIView *panView = context.containerView.panContainerView ?: fromVC.view;
self.presentingVCTransitionContext = [[HWPresentingVCTransitionContext alloc] initWithFromVC:fromVC toVC:toVC duration:[presentable transitionDuration] containerView:context.containerView];
// user toggle pan gesture to dismiss.
if ([context isInteractive]) {
[self interactionDismiss:context fromVC:fromVC toVC:toVC presentable:presentable panView:panView];
} else {
[self springDismiss:context fromVC:fromVC toVC:toVC presentable:presentable panView:panView];
}
}
- (void)springDismiss:(id <UIViewControllerContextTransitioning>)context fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC presentable:(UIViewController <HWPanModalPresentable> *)presentable panView:(UIView *)panView {
CGFloat offsetY = 0;
HWPanModalShadow *shadowConfig = [presentable contentShadow];
if (shadowConfig.shadowColor) {
// we should make the panView move further to hide the shadow effect.
offsetY = offsetY + shadowConfig.shadowRadius + shadowConfig.shadowOffset.height;
if ([presentable showDragIndicator]) {
offsetY += [presentable customIndicatorView] ? [presentable customIndicatorView].indicatorSize.height : 13;
}
}
[HWPanModalAnimator dismissAnimate:^{
[self dismissAnimationForPresentingVC:presentable];
panView.hw_top = (context.containerView.frame.size.height + offsetY);
} config:presentable completion:^(BOOL completion) {
[fromVC.view removeFromSuperview];
if ([presentable shouldEnableAppearanceTransition]) {
[self endAppearanceTransitionForController:fromVC];
[toVC endAppearanceTransition];
}
[context completeTransition:completion];
}];
}
- (void)interactionDismiss:(id <UIViewControllerContextTransitioning>)context fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC presentable:(UIViewController <HWPanModalPresentable> *)presentable panView:(UIView *)panView {
[HWPanModalAnimator smoothAnimate:^{
if (self.interactiveMode == PanModalInteractiveModeSideslip) {
panView.hw_left = panView.hw_width;
}
[self dismissAnimationForPresentingVC:presentable];
} duration:[presentable dismissalDuration] completion:^(BOOL completion) {
// 因为会有手势交互所以需要判断transitions是否cancel
BOOL finished = ![context transitionWasCancelled];
if (finished) {
[fromVC.view removeFromSuperview];
if ([presentable shouldEnableAppearanceTransition]) {
[self endAppearanceTransitionForController:fromVC];
[toVC endAppearanceTransition];
}
context.containerView.userInteractionEnabled = YES;
}
[context completeTransition:finished];
}];
}
#pragma mark - presenting VC animation
- (void)presentAnimationForPresentingVC:(UIViewController<HWPanModalPresentable> *)presentable {
id<HWPresentingViewControllerAnimatedTransitioning> presentingAnimation = [self presentingVCAnimation:presentable];
if (presentingAnimation) {
[presentingAnimation presentAnimateTransition:self.presentingVCTransitionContext];
}
}
- (void)dismissAnimationForPresentingVC:(UIViewController<HWPanModalPresentable> *)presentable {
id<HWPresentingViewControllerAnimatedTransitioning> presentingAnimation = [self presentingVCAnimation:presentable];
if (presentingAnimation) {
[presentingAnimation dismissAnimateTransition:self.presentingVCTransitionContext];
}
}
- (UIViewController <HWPanModalPresentable> *)panModalViewController:(id <UIViewControllerContextTransitioning>)context {
switch (self.transitionStyle) {
case TransitionStylePresentation: {
UIViewController *controller = [context viewControllerForKey:UITransitionContextToViewControllerKey];
if ([controller conformsToProtocol:@protocol(HWPanModalPresentable)]) {
return (UIViewController <HWPanModalPresentable> *) controller;
} else {
return nil;
}
}
case TransitionStyleDismissal: {
UIViewController *controller = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
if ([controller conformsToProtocol:@protocol(HWPanModalPresentable)]) {
return (UIViewController <HWPanModalPresentable> *) controller;
} else {
return nil;
}
}
}
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
switch (self.transitionStyle) {
case TransitionStylePresentation: {
[self animatePresentation:transitionContext];
}
break;
case TransitionStyleDismissal: {
[self animateDismissal:transitionContext];
}
default:
break;
}
}
- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext {
if (transitionContext && [self panModalViewController:transitionContext]) {
UIViewController<HWPanModalPresentable> *controller = [self panModalViewController:transitionContext];
return [controller transitionDuration];
}
return kTransitionDuration;
}
#pragma mark - presenting animated transition
- (id<HWPresentingViewControllerAnimatedTransitioning>)presentingVCAnimation:(UIViewController<HWPanModalPresentable> *)presentable {
switch ([presentable presentingVCAnimationStyle]) {
case PresentingViewControllerAnimationStylePageSheet:
return [HWPageSheetPresentingAnimation new];
case PresentingViewControllerAnimationStyleShoppingCart:
return [HWShoppingCartPresentingAnimation new];
case PresentingViewControllerAnimationStyleCustom:
return [presentable customPresentingVCAnimation];
default:
return nil;
}
}
#pragma mark - private method
- (void)beginAppearanceTransitionForController:(UIViewController *)viewController isAppearing:(BOOL)isAppearing animated:(BOOL)animated {
// Fix `The unbalanced calls to begin/end appearance transitions` warning.
if (![viewController isKindOfClass:UINavigationController.class]) {
[viewController beginAppearanceTransition:isAppearing animated:animated];
}
}
- (void)endAppearanceTransitionForController:(UIViewController *)viewController {
if (![viewController isKindOfClass:UINavigationController.class]) {
[viewController endAppearanceTransition];
}
}
@end
@implementation HWPresentingVCTransitionContext
- (instancetype)initWithFromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC duration:(NSTimeInterval)duration containerView:(UIView *)containerView {
self = [super init];
if (self) {
_fromVC = fromVC;
_toVC = toVC;
_duration = duration;
_containerView = containerView;
}
return self;
}
- (__kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key {
if ([key isEqualToString:UITransitionContextFromViewControllerKey]) {
return self.fromVC;
} else if ([key isEqualToString:UITransitionContextToViewControllerKey]) {
return self.toVC;
}
return nil;
}
- (NSTimeInterval)transitionDuration {
return self.duration;
}
@end