添加HWPanModal和FLAnimatedImage

This commit is contained in:
2025-11-05 22:04:56 +08:00
parent efdcf60ed1
commit abf32e8457
97 changed files with 10853 additions and 2067 deletions

View File

@@ -0,0 +1,27 @@
//
// HWPanModalAnimator.h
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import <Foundation/Foundation.h>
#import <HWPanModal/HWPanModalPresentable.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^AnimationBlockType)(void);
typedef void(^AnimationCompletionType)(BOOL completion);
static NSTimeInterval kTransitionDuration = 0.5;
@interface HWPanModalAnimator : NSObject
+ (void)animate:(AnimationBlockType)animations config:(nullable id <HWPanModalPresentable>)config completion:(nullable AnimationCompletionType)completion;
+ (void)dismissAnimate:(AnimationBlockType)animations config:(nullable id <HWPanModalPresentable>)config completion:(AnimationCompletionType)completion;
+ (void)smoothAnimate:(AnimationBlockType)animations duration:(NSTimeInterval)duration completion:(nullable AnimationCompletionType)completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,40 @@
//
// HWPanModalAnimator.m
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import "HWPanModalAnimator.h"
@implementation HWPanModalAnimator
+ (void)animate:(AnimationBlockType)animations config:(nullable id<HWPanModalPresentable>)config completion:(AnimationCompletionType)completion {
[HWPanModalAnimator animate:animations config:config startingFromPercent:1 isPresentation:YES completion:completion];
}
+ (void)dismissAnimate:(AnimationBlockType)animations config:(nullable id<HWPanModalPresentable>)config completion:(AnimationCompletionType)completion {
[HWPanModalAnimator animate:animations config:config startingFromPercent:1 isPresentation:NO completion:completion];
}
+ (void)animate:(AnimationBlockType)animations config:(nullable id <HWPanModalPresentable>)config startingFromPercent:(CGFloat)animationPercent isPresentation:(BOOL)flag completion:(AnimationCompletionType)completion {
NSTimeInterval duration;
if (flag) {
duration = config ? [config transitionDuration] : kTransitionDuration;
} else {
duration = config ? [config dismissalDuration] : kTransitionDuration;
}
duration = duration * MAX(animationPercent, 0);
CGFloat springDamping = config ? [config springDamping] : 1.0;
UIViewAnimationOptions options = config ? [config transitionAnimationOptions] : UIViewAnimationOptionPreferredFramesPerSecondDefault;
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:springDamping initialSpringVelocity:0 options:options animations:animations completion:completion];
}
+ (void)smoothAnimate:(AnimationBlockType)animations duration:(NSTimeInterval)duration completion:(nullable AnimationCompletionType)completion {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:animations completion:completion];
}
@end

View File

@@ -0,0 +1,17 @@
//
// HWPanModalInteractiveAnimator.h
// HWPanModal
//
// Created by heath wang on 2019/5/14.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HWPanModalInteractiveAnimator : UIPercentDrivenInteractiveTransition
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,16 @@
//
// HWPanModalInteractiveAnimator.m
// HWPanModal
//
// Created by heath wang on 2019/5/14.
//
#import "HWPanModalInteractiveAnimator.h"
@implementation HWPanModalInteractiveAnimator
- (CGFloat)completionSpeed {
return 0.618;
}
@end

View File

@@ -0,0 +1,28 @@
//
// HWPanModalPresentationAnimator.h
// HWPanModal
//
// Created by heath wang on 2019/4/29.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "HWPanModalPresentationDelegate.h"
typedef NS_ENUM(NSInteger, TransitionStyle) {
TransitionStylePresentation,
TransitionStyleDismissal,
};
NS_ASSUME_NONNULL_BEGIN
@interface HWPanModalPresentationAnimator : NSObject <UIViewControllerAnimatedTransitioning>
- (instancetype)initWithTransitionStyle:(TransitionStyle)transitionStyle interactiveMode:(PanModalInteractiveMode)mode;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,314 @@
//
// 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) {
// transitionscancel
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

View File

@@ -0,0 +1,51 @@
//
// HWCustomPresentingVCAnimatedTransitioning.h
// HWPanModal
//
// Created by heath wang on 2019/6/12.
//
#ifndef HWCustomPresentingVCAnimatedTransitioning_h
#define HWCustomPresentingVCAnimatedTransitioning_h
NS_SWIFT_NAME(PanModalPresentingViewControllerContextTransitioning)
@protocol HWPresentingViewControllerContextTransitioning <NSObject>
/**
* Returns a view controller involved in the transition.
* @return The view controller object for the specified key or nil if the view controller could not be found.
*/
- (__kindof UIViewController * _Nullable )viewControllerForKey:(nonnull UITransitionContextViewControllerKey)key;
/**
* The Animation duration gets from ViewController which conforms HWPanModalPresentable
* - (NSTimeInterval)transitionDuration;
*/
- (NSTimeInterval)transitionDuration;
/**
* Transition container, from UIViewControllerContextTransitioning protocol
*/
@property(nonnull, nonatomic, readonly) UIView *containerView;
@end
NS_SWIFT_NAME(PanModalPresentingViewControllerAnimatedTransitioning)
@protocol HWPresentingViewControllerAnimatedTransitioning <NSObject>
/**
* Write you custom animation when present.
*/
- (void)presentAnimateTransition:(nonnull id<HWPresentingViewControllerContextTransitioning>)context NS_SWIFT_NAME(presentTransition(context:));
/**
* Write you custom animation when dismiss.
*/
- (void)dismissAnimateTransition:(nonnull id<HWPresentingViewControllerContextTransitioning>)context NS_SWIFT_NAME(dismissTransition(context:));
@end
#endif /* HWCustomPresentingVCAnimatedTransitioning_h */

View File

@@ -0,0 +1,17 @@
//
// HWPageSheetPresentingAnimation.h
// HWPanModal-iOS10.0
//
// Created by heath wang on 2019/9/5.
//
#import <Foundation/Foundation.h>
#import "HWPresentingVCAnimatedTransitioning.h"
NS_ASSUME_NONNULL_BEGIN
@interface HWPageSheetPresentingAnimation : NSObject <HWPresentingViewControllerAnimatedTransitioning>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,29 @@
//
// HWPageSheetPresentingAnimation.m
// HWPanModal-iOS10.0
//
// Created by heath wang on 2019/9/5.
//
#import "HWPageSheetPresentingAnimation.h"
@implementation HWPageSheetPresentingAnimation
- (void)presentAnimateTransition:(nonnull id <HWPresentingViewControllerContextTransitioning>)context {
NSTimeInterval duration = [context transitionDuration];
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:0.9 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
CGFloat scale = 1 - statusBarHeight * 2 / CGRectGetHeight(fromVC.view.bounds);
fromVC.view.transform = CGAffineTransformMakeScale(scale, scale);
} completion:^(BOOL finished) {
}];
}
- (void)dismissAnimateTransition:(nonnull id <HWPresentingViewControllerContextTransitioning>)context {
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
toVC.view.transform = CGAffineTransformIdentity;
}
@end

View File

@@ -0,0 +1,17 @@
//
// HWShoppingCartPresentingAnimation.h
// HWPanModal-iOS10.0
//
// Created by heath wang on 2019/9/5.
//
#import <Foundation/Foundation.h>
#import "HWPresentingVCAnimatedTransitioning.h"
NS_ASSUME_NONNULL_BEGIN
@interface HWShoppingCartPresentingAnimation : NSObject <HWPresentingViewControllerAnimatedTransitioning>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,39 @@
//
// HWShoppingCartPresentingAnimation.m
// HWPanModal-iOS10.0
//
// Created by heath wang on 2019/9/5.
//
#import "HWShoppingCartPresentingAnimation.h"
@implementation HWShoppingCartPresentingAnimation
- (void)presentAnimateTransition:(nonnull id <HWPresentingViewControllerContextTransitioning>)context {
NSTimeInterval duration = [context transitionDuration];
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
CGFloat scale = 1 - statusBarHeight * 2 / CGRectGetHeight(fromVC.view.bounds);
[UIView animateWithDuration:duration * 0.4 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
CATransform3D tran = CATransform3DIdentity;
tran.m34 = -1 / 1000.0f;
tran = CATransform3DRotate(tran, M_PI / 16, 1, 0, 0);
tran = CATransform3DTranslate(tran, 0, 0, -100);
fromVC.view.layer.transform = tran;
} completion:^(BOOL finished) {
[UIView animateWithDuration:duration * 0.6 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
fromVC.view.layer.transform = CATransform3DMakeScale(scale, scale, 1);
} completion:^(BOOL finished) {
}];
}];
}
- (void)dismissAnimateTransition:(nonnull id <HWPresentingViewControllerContextTransitioning>)context {
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
toVC.view.layer.transform = CATransform3DIdentity;
}
@end