添加HWPanModal和FLAnimatedImage
This commit is contained in:
27
Pods/HWPanModal/Sources/Animator/HWPanModalAnimator.h
generated
Normal file
27
Pods/HWPanModal/Sources/Animator/HWPanModalAnimator.h
generated
Normal 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
|
||||
40
Pods/HWPanModal/Sources/Animator/HWPanModalAnimator.m
generated
Normal file
40
Pods/HWPanModal/Sources/Animator/HWPanModalAnimator.m
generated
Normal 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
|
||||
17
Pods/HWPanModal/Sources/Animator/HWPanModalInteractiveAnimator.h
generated
Normal file
17
Pods/HWPanModal/Sources/Animator/HWPanModalInteractiveAnimator.h
generated
Normal 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
|
||||
16
Pods/HWPanModal/Sources/Animator/HWPanModalInteractiveAnimator.m
generated
Normal file
16
Pods/HWPanModal/Sources/Animator/HWPanModalInteractiveAnimator.m
generated
Normal 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
|
||||
28
Pods/HWPanModal/Sources/Animator/HWPanModalPresentationAnimator.h
generated
Normal file
28
Pods/HWPanModal/Sources/Animator/HWPanModalPresentationAnimator.h
generated
Normal 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
|
||||
314
Pods/HWPanModal/Sources/Animator/HWPanModalPresentationAnimator.m
generated
Normal file
314
Pods/HWPanModal/Sources/Animator/HWPanModalPresentationAnimator.m
generated
Normal 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) {
|
||||
// 因为会有手势交互,所以需要判断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
|
||||
51
Pods/HWPanModal/Sources/Animator/HWPresentingVCAnimatedTransitioning.h
generated
Normal file
51
Pods/HWPanModal/Sources/Animator/HWPresentingVCAnimatedTransitioning.h
generated
Normal 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 */
|
||||
|
||||
|
||||
|
||||
17
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWPageSheetPresentingAnimation.h
generated
Normal file
17
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWPageSheetPresentingAnimation.h
generated
Normal 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
|
||||
29
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWPageSheetPresentingAnimation.m
generated
Normal file
29
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWPageSheetPresentingAnimation.m
generated
Normal 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
|
||||
17
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWShoppingCartPresentingAnimation.h
generated
Normal file
17
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWShoppingCartPresentingAnimation.h
generated
Normal 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
|
||||
39
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWShoppingCartPresentingAnimation.m
generated
Normal file
39
Pods/HWPanModal/Sources/Animator/PresentingVCAnimation/HWShoppingCartPresentingAnimation.m
generated
Normal 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
|
||||
18
Pods/HWPanModal/Sources/Category/UIScrollView+Helper.h
generated
Normal file
18
Pods/HWPanModal/Sources/Category/UIScrollView+Helper.h
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// UIScrollView+Helper.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/15.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIScrollView (Helper)
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL isScrolling;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
16
Pods/HWPanModal/Sources/Category/UIScrollView+Helper.m
generated
Normal file
16
Pods/HWPanModal/Sources/Category/UIScrollView+Helper.m
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// UIScrollView+Helper.m
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/15.
|
||||
//
|
||||
|
||||
#import "UIScrollView+Helper.h"
|
||||
|
||||
@implementation UIScrollView (Helper)
|
||||
|
||||
- (BOOL)isScrolling {
|
||||
return (self.isDragging && !self.isDecelerating) || self.isTracking;
|
||||
}
|
||||
|
||||
@end
|
||||
28
Pods/HWPanModal/Sources/Category/UIView+HW_Frame.h
generated
Normal file
28
Pods/HWPanModal/Sources/Category/UIView+HW_Frame.h
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// UIView+HW_Frame.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/5/20.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIView (HW_Frame)
|
||||
|
||||
@property (nonatomic, assign) CGFloat hw_left; ///< Shortcut for frame.origin.x.
|
||||
@property (nonatomic, assign) CGFloat hw_top; ///< Shortcut for frame.origin.y
|
||||
@property (nonatomic, assign) CGFloat hw_right; ///< Shortcut for frame.origin.x + frame.size.width
|
||||
@property (nonatomic, assign) CGFloat hw_bottom; ///< Shortcut for frame.origin.y + frame.size.height
|
||||
@property (nonatomic, assign) CGFloat hw_width; ///< Shortcut for frame.size.width.
|
||||
@property (nonatomic, assign) CGFloat hw_height; ///< Shortcut for frame.size.height.
|
||||
@property (nonatomic, assign) CGFloat hw_centerX; ///< Shortcut for center.x
|
||||
@property (nonatomic, assign) CGFloat hw_centerY; ///< Shortcut for center.y
|
||||
@property (nonatomic, assign) CGPoint hw_origin; ///< Shortcut for frame.origin.
|
||||
@property (nonatomic, assign) CGSize hw_size; ///< Shortcut for frame.size.
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
109
Pods/HWPanModal/Sources/Category/UIView+HW_Frame.m
generated
Normal file
109
Pods/HWPanModal/Sources/Category/UIView+HW_Frame.m
generated
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// UIView+HW_Frame.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/5/20.
|
||||
//
|
||||
|
||||
#import "UIView+HW_Frame.h"
|
||||
|
||||
@implementation UIView (HW_Frame)
|
||||
|
||||
- (CGFloat)hw_left {
|
||||
return self.frame.origin.x;
|
||||
}
|
||||
|
||||
- (void)setHw_left:(CGFloat)hwLeft {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = hwLeft;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_top {
|
||||
return self.frame.origin.y;
|
||||
}
|
||||
|
||||
- (void)setHw_top:(CGFloat)hwTop {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = hwTop;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_right {
|
||||
return self.frame.origin.x + self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setHw_right:(CGFloat)hwRight {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = hwRight - self.frame.size.width;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_bottom {
|
||||
return self.frame.origin.y + self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setHw_bottom:(CGFloat)hwBottom {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = hwBottom - self.frame.size.height;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_width {
|
||||
return self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setHw_width:(CGFloat)hwWidth {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.width = hwWidth;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_height {
|
||||
return self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setHw_height:(CGFloat)hwHeight {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.height = hwHeight;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)hw_centerX {
|
||||
return self.center.x;
|
||||
}
|
||||
|
||||
- (void)setHw_centerX:(CGFloat)hwCenterX {
|
||||
self.center = CGPointMake(hwCenterX, self.center.y);
|
||||
}
|
||||
|
||||
- (CGFloat)hw_centerY {
|
||||
return self.center.y;
|
||||
}
|
||||
|
||||
- (void)setHw_centerY:(CGFloat)hwCenterY {
|
||||
self.center = CGPointMake(self.center.x, hwCenterY);
|
||||
}
|
||||
|
||||
- (CGPoint)hw_origin {
|
||||
return self.frame.origin;
|
||||
}
|
||||
|
||||
- (void)setHw_origin:(CGPoint)hwOrigin {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin = hwOrigin;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGSize)hw_size {
|
||||
return self.frame.size;
|
||||
}
|
||||
|
||||
- (void)setHw_size:(CGSize)hwSize {
|
||||
CGRect frame = self.frame;
|
||||
frame.size = hwSize;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
32
Pods/HWPanModal/Sources/Controller/HWPanModalPresentationController.h
generated
Normal file
32
Pods/HWPanModal/Sources/Controller/HWPanModalPresentationController.h
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// HWPanModalPresentationController.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
@class HWDimmedView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@interface HWPanModalPresentationController : UIPresentationController
|
||||
|
||||
@property (nonatomic, readonly) HWDimmedView *backgroundView;
|
||||
@property (nonatomic, readonly) PresentationState currentPresentationState;
|
||||
|
||||
- (void)setNeedsLayoutUpdate;
|
||||
|
||||
- (void)updateUserHitBehavior;
|
||||
|
||||
- (void)transitionToState:(PresentationState)state animated:(BOOL)animated;
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated;
|
||||
|
||||
- (void)dismissAnimated:(BOOL)animated completion:(void (^)(void))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
599
Pods/HWPanModal/Sources/Controller/HWPanModalPresentationController.m
generated
Normal file
599
Pods/HWPanModal/Sources/Controller/HWPanModalPresentationController.m
generated
Normal file
@@ -0,0 +1,599 @@
|
||||
//
|
||||
// HWPanModalPresentationController.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import "HWPanModalPresentationController.h"
|
||||
#import "HWDimmedView.h"
|
||||
#import "HWPanContainerView.h"
|
||||
#import "UIViewController+LayoutHelper.h"
|
||||
#import "HWPanModalAnimator.h"
|
||||
#import "HWPanModalInteractiveAnimator.h"
|
||||
#import "HWPanModalPresentationDelegate.h"
|
||||
#import "UIViewController+PanModalPresenter.h"
|
||||
#import "HWPanIndicatorView.h"
|
||||
#import "UIView+HW_Frame.h"
|
||||
#import "HWPanModalPresentableHandler.h"
|
||||
|
||||
@interface HWPanModalPresentationController () <UIGestureRecognizerDelegate, HWPanModalPresentableHandlerDelegate, HWPanModalPresentableHandlerDataSource>
|
||||
|
||||
// 判断弹出的view是否在做动画
|
||||
@property (nonatomic, assign) BOOL isPresentedViewAnimating;
|
||||
@property (nonatomic, assign) PresentationState currentPresentationState;
|
||||
|
||||
@property (nonatomic, strong) id<HWPanModalPresentable> presentable;
|
||||
|
||||
// view
|
||||
@property (nonatomic, strong) HWDimmedView *backgroundView;
|
||||
@property (nonatomic, strong) HWPanContainerView *panContainerView;
|
||||
@property (nonatomic, strong) UIView<HWPanModalIndicatorProtocol> *dragIndicatorView;
|
||||
|
||||
@property (nonatomic, strong) HWPanModalPresentableHandler *handler;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanModalPresentationController
|
||||
|
||||
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(nullable UIViewController *)presentingViewController {
|
||||
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
|
||||
if (self) {
|
||||
_handler = [[HWPanModalPresentableHandler alloc] initWithPresentable:[self presentable]];
|
||||
_handler.delegate = self;
|
||||
_handler.dataSource = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - overridden
|
||||
|
||||
- (UIView *)presentedView {
|
||||
return self.panContainerView;
|
||||
}
|
||||
|
||||
- (void)containerViewWillLayoutSubviews {
|
||||
[super containerViewWillLayoutSubviews];
|
||||
[self configureViewLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Tracking the Transition Start and End
|
||||
|
||||
- (void)presentationTransitionWillBegin {
|
||||
[[self presentable] panModalTransitionWillBegin];
|
||||
|
||||
if (!self.containerView)
|
||||
return;
|
||||
|
||||
[self layoutBackgroundView:self.containerView];
|
||||
|
||||
if ([[self presentable] originPresentationState] == PresentationStateLong) {
|
||||
self.currentPresentationState = PresentationStateLong;
|
||||
} else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
|
||||
self.currentPresentationState = PresentationStateMedium;
|
||||
}
|
||||
|
||||
[self layoutPresentedView:self.containerView];
|
||||
[self.handler configureScrollViewInsets];
|
||||
|
||||
if (!self.presentedViewController.transitionCoordinator) {
|
||||
self.backgroundView.dimState = DimStateMax;
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) wkSelf = self;
|
||||
__block BOOL isAnimated = NO;
|
||||
[self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
wkSelf.backgroundView.dimState = DimStateMax;
|
||||
[wkSelf.presentedViewController setNeedsStatusBarAppearanceUpdate];
|
||||
isAnimated = YES;
|
||||
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
if (!isAnimated) {
|
||||
/// In some cases, for example, present a `hw` when a navigation controller is pushing a new vc, `animateAlongsideTransition` will not call.
|
||||
/// If not called, call it here.
|
||||
wkSelf.backgroundView.dimState = DimStateMax;
|
||||
[wkSelf.presentedViewController setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
if ([[wkSelf presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
// hack TransitionView
|
||||
[wkSelf.containerView setValue:@(YES) forKey:@"ignoreDirectTouchEvents"];
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)presentationTransitionDidEnd:(BOOL)completed {
|
||||
[[self presentable] panModalTransitionDidFinish];
|
||||
if (completed)
|
||||
return;
|
||||
|
||||
[self.backgroundView removeFromSuperview];
|
||||
[self.presentedView endEditing:YES];
|
||||
}
|
||||
|
||||
- (void)dismissalTransitionWillBegin {
|
||||
id <UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentedViewController.transitionCoordinator;
|
||||
if (!transitionCoordinator) {
|
||||
self.backgroundView.dimState = DimStateOff;
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) wkSelf = self;
|
||||
[transitionCoordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
wkSelf.dragIndicatorView.alpha = 0;
|
||||
wkSelf.backgroundView.dimState = DimStateOff;
|
||||
[wkSelf.presentedViewController setNeedsStatusBarAppearanceUpdate];
|
||||
} completion:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismissalTransitionDidEnd:(BOOL)completed {
|
||||
if (completed) {
|
||||
// break the delegate
|
||||
self.delegate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIContentContainer protocol
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
|
||||
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
||||
|
||||
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
if (self && [self presentable]) {
|
||||
[self adjustPresentedViewFrame];
|
||||
|
||||
if ([self.presentable shouldRoundTopCorners]) {
|
||||
[self addRoundedCornersToView:self.panContainerView.contentView];
|
||||
}
|
||||
[self updateDragIndicatorView];
|
||||
}
|
||||
} completion:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
[self transitionToState:self.currentPresentationState animated:NO];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - public method
|
||||
|
||||
- (void)setNeedsLayoutUpdate {
|
||||
[self configureViewLayout];
|
||||
[self adjustPresentedViewFrame];
|
||||
|
||||
[self updateBackgroundColor];
|
||||
[self updateContainerViewShadow];
|
||||
[self updateDragIndicatorView];
|
||||
[self updateRoundedCorners];
|
||||
|
||||
[self.handler observeScrollable];
|
||||
[self.handler configureScrollViewInsets];
|
||||
[self checkEdgeInteractive];
|
||||
}
|
||||
|
||||
- (void)transitionToState:(PresentationState)state animated:(BOOL)animated {
|
||||
|
||||
if (![self.presentable shouldTransitionToState:state])
|
||||
return;
|
||||
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
[self.presentable willTransitionToState:state];
|
||||
|
||||
switch (state) {
|
||||
case PresentationStateLong: {
|
||||
[self snapToYPos:self.handler.longFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
case PresentationStateMedium: {
|
||||
[self snapToYPos:self.handler.mediumFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
case PresentationStateShort: {
|
||||
[self snapToYPos:self.handler.shortFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
}
|
||||
self.currentPresentationState = state;
|
||||
[[self presentable] didChangeTransitionToState:state];
|
||||
}
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
|
||||
[self.handler setScrollableContentOffset:offset animated:animated];
|
||||
}
|
||||
|
||||
- (void)updateUserHitBehavior {
|
||||
[self checkVCContainerEventPass];
|
||||
[self checkBackgroundViewEventPass];
|
||||
}
|
||||
|
||||
#pragma mark - layout
|
||||
|
||||
- (void)adjustPresentedViewFrame {
|
||||
|
||||
if (!self.containerView)
|
||||
return;
|
||||
|
||||
CGRect frame = self.containerView.frame;
|
||||
CGSize size = CGSizeMake(CGRectGetWidth(frame), CGRectGetHeight(frame) - self.handler.anchoredYPosition);
|
||||
|
||||
self.presentedView.hw_size = frame.size;
|
||||
self.panContainerView.contentView.frame = CGRectMake(0, 0, size.width, size.height);
|
||||
self.presentedViewController.view.frame = self.panContainerView.contentView.bounds;
|
||||
[self.presentedViewController.view setNeedsLayout];
|
||||
[self.presentedViewController.view layoutIfNeeded];
|
||||
}
|
||||
|
||||
/**
|
||||
* add backGroundView并设置约束
|
||||
*/
|
||||
- (void)layoutBackgroundView:(UIView *)containerView {
|
||||
[containerView addSubview:self.backgroundView];
|
||||
[self updateBackgroundColor];
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSArray *hCons = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
|
||||
NSArray *vCons = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
|
||||
[NSLayoutConstraint activateConstraints:hCons];
|
||||
[NSLayoutConstraint activateConstraints:vCons];
|
||||
}
|
||||
|
||||
- (void)updateBackgroundColor {
|
||||
self.backgroundView.blurTintColor = [self.presentable backgroundConfig].blurTintColor;
|
||||
}
|
||||
|
||||
- (void)layoutPresentedView:(UIView *)containerView {
|
||||
if (!self.presentable)
|
||||
return;
|
||||
|
||||
self.handler.presentedView = self.presentedView;
|
||||
|
||||
[containerView addSubview:self.presentedView];
|
||||
[containerView addGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
|
||||
if ([self.presentable allowScreenEdgeInteractive]) {
|
||||
[containerView addGestureRecognizer:self.handler.screenEdgeGestureRecognizer];
|
||||
[self.handler.screenEdgeGestureRecognizer addTarget:self action:@selector(screenEdgeInteractiveAction:)];
|
||||
}
|
||||
|
||||
[self setNeedsLayoutUpdate];
|
||||
[self adjustPanContainerBackgroundColor];
|
||||
|
||||
[[self presentable] presentedViewDidMoveToSuperView];
|
||||
}
|
||||
|
||||
- (void)adjustPanContainerBackgroundColor {
|
||||
self.panContainerView.contentView.backgroundColor = self.presentedViewController.view.backgroundColor ? : [self.presentable panScrollable].backgroundColor;
|
||||
}
|
||||
|
||||
- (void)updateDragIndicatorView {
|
||||
if ([self.presentable showDragIndicator]) {
|
||||
[self addDragIndicatorViewToView:self.panContainerView];
|
||||
} else {
|
||||
self.dragIndicatorView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addDragIndicatorViewToView:(UIView *)view {
|
||||
// if has been add, won't update it.
|
||||
self.dragIndicatorView.hidden = NO;
|
||||
|
||||
CGSize indicatorSize = [self.dragIndicatorView indicatorSize];
|
||||
|
||||
if (self.dragIndicatorView.superview == view) {
|
||||
self.dragIndicatorView.frame = CGRectMake((view.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
return;
|
||||
}
|
||||
|
||||
self.handler.dragIndicatorView = self.dragIndicatorView;
|
||||
[view addSubview:self.dragIndicatorView];
|
||||
|
||||
self.dragIndicatorView.frame = CGRectMake((view.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
|
||||
[self.dragIndicatorView setupSubviews];
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
}
|
||||
|
||||
- (void)updateRoundedCorners {
|
||||
if ([self.presentable shouldRoundTopCorners]) {
|
||||
[self addRoundedCornersToView:self.panContainerView.contentView];
|
||||
} else {
|
||||
[self resetRoundedCornersToView:self.panContainerView.contentView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addRoundedCornersToView:(UIView *)view {
|
||||
CGFloat radius = [self.presentable cornerRadius];
|
||||
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerTopLeft cornerRadii:CGSizeMake(radius, radius)];
|
||||
|
||||
CAShapeLayer *mask = [CAShapeLayer new];
|
||||
mask.path = bezierPath.CGPath;
|
||||
view.layer.mask = mask;
|
||||
|
||||
// 提高性能
|
||||
view.layer.shouldRasterize = YES;
|
||||
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
|
||||
- (void)resetRoundedCornersToView:(UIView *)view {
|
||||
view.layer.mask = nil;
|
||||
view.layer.shouldRasterize = NO;
|
||||
}
|
||||
|
||||
- (void)updateContainerViewShadow {
|
||||
HWPanModalShadow *shadow = [[self presentable] contentShadow];
|
||||
if (shadow.shadowColor) {
|
||||
[self.panContainerView updateShadow:shadow.shadowColor shadowRadius:shadow.shadowRadius shadowOffset:shadow.shadowOffset shadowOpacity:shadow.shadowOpacity];
|
||||
} else {
|
||||
[self.panContainerView clearShadow];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates & stores the layout anchor points & options
|
||||
*/
|
||||
- (void)configureViewLayout {
|
||||
|
||||
[self.handler configureViewLayout];
|
||||
self.containerView.userInteractionEnabled = [[self presentable] isUserInteractionEnabled];
|
||||
}
|
||||
|
||||
#pragma mark - event passing through
|
||||
|
||||
- (void)checkVCContainerEventPass {
|
||||
BOOL eventPassValue = [[self presentable] allowsTouchEventsPassingThroughTransitionView];
|
||||
// hack TransitionView
|
||||
[self.containerView setValue:@(eventPassValue) forKey:@"ignoreDirectTouchEvents"];
|
||||
}
|
||||
|
||||
- (void)checkBackgroundViewEventPass {
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
self.backgroundView.userInteractionEnabled = NO;
|
||||
self.backgroundView.tapBlock = nil;
|
||||
} else {
|
||||
self.backgroundView.userInteractionEnabled = YES;
|
||||
__weak typeof(self) wkSelf = self;
|
||||
self.backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
|
||||
if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
|
||||
[wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - y position update
|
||||
|
||||
- (void)snapToYPos:(CGFloat)yPos animated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[HWPanModalAnimator animate:^{
|
||||
self.isPresentedViewAnimating = YES;
|
||||
[self adjustToYPos:yPos];
|
||||
} config:self.presentable completion:^(BOOL completion) {
|
||||
self.isPresentedViewAnimating = NO;
|
||||
}];
|
||||
} else {
|
||||
[self adjustToYPos:yPos];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)adjustToYPos:(CGFloat)yPos {
|
||||
self.presentedView.hw_top = MAX(yPos, self.handler.anchoredYPosition);
|
||||
|
||||
// change dim background starting from shortFormYPosition.
|
||||
if (self.presentedView.frame.origin.y >= self.handler.shortFormYPosition) {
|
||||
|
||||
CGFloat yDistanceFromShortForm = self.presentedView.frame.origin.y - self.handler.shortFormYPosition;
|
||||
CGFloat bottomHeight = self.containerView.hw_height - self.handler.shortFormYPosition;
|
||||
CGFloat percent = yDistanceFromShortForm / bottomHeight;
|
||||
self.backgroundView.dimState = DimStatePercent;
|
||||
self.backgroundView.percent = 1 - percent;
|
||||
|
||||
[self.presentable panModalGestureRecognizer:self.handler.panGestureRecognizer dismissPercent:MIN(percent, 1)];
|
||||
if (self.presentedViewController.isBeingDismissed) {
|
||||
[[self interactiveAnimator] updateInteractiveTransition:MIN(percent, 1)];
|
||||
}
|
||||
} else {
|
||||
self.backgroundView.dimState = DimStateMax;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentableHandlerDelegate
|
||||
|
||||
- (void)adjustPresentableYPos:(CGFloat)yPos {
|
||||
[self adjustToYPos:yPos];
|
||||
}
|
||||
|
||||
- (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
|
||||
[self dismiss:isInteractive mode:mode animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode animated:(BOOL)animated completion:(void (^)(void))completion {
|
||||
self.presentedViewController.hw_panModalPresentationDelegate.interactive = isInteractive;
|
||||
self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode = mode;
|
||||
[self.presentable panModalWillDismiss];
|
||||
[self.presentedViewController dismissViewControllerAnimated:animated completion:^{
|
||||
if (completion) completion();
|
||||
[self.presentable panModalDidDismissed];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismissAnimated:(BOOL)animated completion:(nonnull void (^)(void))completion {
|
||||
[self dismiss:NO mode:PanModalInteractiveModeNone animated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (void)presentableTransitionToState:(PresentationState)state {
|
||||
[self transitionToState:state animated:YES];
|
||||
}
|
||||
|
||||
- (PresentationState)getCurrentPresentationState {
|
||||
return self.currentPresentationState;
|
||||
}
|
||||
|
||||
#pragma mark - interactive handle
|
||||
|
||||
- (void)finishInteractiveTransition {
|
||||
if (self.presentedViewController.isBeingDismissed) {
|
||||
// make the containerView can not response event action.
|
||||
self.containerView.userInteractionEnabled = NO;
|
||||
[[self interactiveAnimator] finishInteractiveTransition];
|
||||
|
||||
if (self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode != PanModalInteractiveModeDragDown)
|
||||
return;
|
||||
|
||||
if ([[self presentable] presentingVCAnimationStyle] > PresentingViewControllerAnimationStyleNone) {
|
||||
[HWPanModalAnimator animate:^{
|
||||
[self presentedView].hw_top = self.containerView.frame.size.height;
|
||||
self.dragIndicatorView.alpha = 0;
|
||||
self.backgroundView.dimState = DimStateOff;
|
||||
} config:[self presentable] completion:^(BOOL completion) {
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelInteractiveTransition {
|
||||
if (self.presentedViewController.isBeingDismissed) {
|
||||
[[self interactiveAnimator] cancelInteractiveTransition];
|
||||
self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode = PanModalInteractiveModeNone;
|
||||
self.presentedViewController.hw_panModalPresentationDelegate.interactive = NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentableHandlerDataSource
|
||||
|
||||
- (CGSize)containerSize {
|
||||
return self.containerView.bounds.size;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingDismissed {
|
||||
return self.presentedViewController.isBeingDismissed;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingPresented {
|
||||
return self.presentedViewController.isBeingPresented;
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedViewAnchored {
|
||||
|
||||
if (![[self presentable] shouldRespondToPanModalGestureRecognizer:self.handler.panGestureRecognizer]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!self.isPresentedViewAnimating && self.handler.extendsPanScrolling && (CGRectGetMinY(self.presentedView.frame) <= self.handler.anchoredYPosition || HW_TWO_FLOAT_IS_EQUAL(CGRectGetMinY(self.presentedView.frame), self.handler.anchoredYPosition))) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedControllerInteractive {
|
||||
return self.presentedViewController.hw_panModalPresentationDelegate.interactive;
|
||||
}
|
||||
|
||||
- (BOOL)isFormPositionAnimating {
|
||||
return self.isPresentedViewAnimating;
|
||||
}
|
||||
|
||||
#pragma mark - Screen Gesture enevt
|
||||
|
||||
- (void)screenEdgeInteractiveAction:(UIPanGestureRecognizer *)recognizer {
|
||||
CGPoint translation = [recognizer translationInView:recognizer.view];
|
||||
CGFloat percent = translation.x / CGRectGetWidth(recognizer.view.bounds);
|
||||
CGPoint velocity = [recognizer velocityInView:recognizer.view];
|
||||
|
||||
switch (recognizer.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
[self dismiss:YES mode:PanModalInteractiveModeSideslip];
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateCancelled:
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
if (percent > 0.5 || velocity.x >= [[self presentable] minHorizontalVelocityToTriggerScreenEdgeDismiss]) {
|
||||
[self finishInteractiveTransition];
|
||||
} else {
|
||||
[self cancelInteractiveTransition];
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
|
||||
[[self interactiveAnimator] updateInteractiveTransition:percent];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkEdgeInteractive {
|
||||
//TODO: changed the user interactive, if someone else has different requirements, change it.
|
||||
self.handler.screenEdgeGestureRecognizer.enabled = [[self presentable] allowScreenEdgeInteractive];
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (id <HWPanModalPresentable>)presentable {
|
||||
if ([self.presentedViewController conformsToProtocol:@protocol(HWPanModalPresentable)]) {
|
||||
return (id <HWPanModalPresentable>) self.presentedViewController;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HWPanModalInteractiveAnimator *)interactiveAnimator {
|
||||
HWPanModalPresentationDelegate *presentationDelegate = self.presentedViewController.hw_panModalPresentationDelegate;
|
||||
return presentationDelegate.interactiveDismissalAnimator;
|
||||
}
|
||||
|
||||
- (HWDimmedView *)backgroundView {
|
||||
if (!_backgroundView) {
|
||||
if (self.presentable) {
|
||||
_backgroundView = [[HWDimmedView alloc] initWithBackgroundConfig:[self.presentable backgroundConfig]];
|
||||
} else {
|
||||
_backgroundView = [[HWDimmedView alloc] init];
|
||||
}
|
||||
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
_backgroundView.userInteractionEnabled = NO;
|
||||
} else {
|
||||
__weak typeof(self) wkSelf = self;
|
||||
_backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
|
||||
if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
|
||||
[wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return _backgroundView;
|
||||
}
|
||||
|
||||
- (HWPanContainerView *)panContainerView {
|
||||
if (!_panContainerView) {
|
||||
_panContainerView = [[HWPanContainerView alloc] initWithPresentedView:self.presentedViewController.view frame:self.containerView.frame];
|
||||
}
|
||||
|
||||
return _panContainerView;
|
||||
}
|
||||
|
||||
- (UIView<HWPanModalIndicatorProtocol> *)dragIndicatorView {
|
||||
|
||||
if (!_dragIndicatorView) {
|
||||
if ([self presentable] &&
|
||||
[[self presentable] respondsToSelector:@selector(customIndicatorView)] &&
|
||||
[[self presentable] customIndicatorView] != nil) {
|
||||
_dragIndicatorView = [[self presentable] customIndicatorView];
|
||||
// set the indicator size first in case `setupSubviews` can Not get the right size.
|
||||
_dragIndicatorView.hw_size = [[[self presentable] customIndicatorView] indicatorSize];
|
||||
} else {
|
||||
_dragIndicatorView = [HWPanIndicatorView new];
|
||||
}
|
||||
}
|
||||
|
||||
return _dragIndicatorView;
|
||||
}
|
||||
|
||||
@end
|
||||
29
Pods/HWPanModal/Sources/Delegate/HWPanModalPresentationDelegate.h
generated
Normal file
29
Pods/HWPanModal/Sources/Delegate/HWPanModalPresentationDelegate.h
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// HWPanModalPresentationDelegate.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class HWPanModalInteractiveAnimator;
|
||||
|
||||
typedef NS_ENUM(NSInteger, PanModalInteractiveMode) {
|
||||
PanModalInteractiveModeNone,
|
||||
PanModalInteractiveModeSideslip, // 侧滑返回
|
||||
PanModalInteractiveModeDragDown, // 向下拖拽返回
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWPanModalPresentationDelegate : NSObject <UIViewControllerTransitioningDelegate, UIAdaptivePresentationControllerDelegate, UIPopoverPresentationControllerDelegate>
|
||||
|
||||
@property (nonatomic, assign) BOOL interactive;
|
||||
@property (nonatomic, assign) PanModalInteractiveMode interactiveMode;
|
||||
@property (nonnull, nonatomic, strong, readonly) HWPanModalInteractiveAnimator *interactiveDismissalAnimator;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
66
Pods/HWPanModal/Sources/Delegate/HWPanModalPresentationDelegate.m
generated
Normal file
66
Pods/HWPanModal/Sources/Delegate/HWPanModalPresentationDelegate.m
generated
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// HWPanModalPresentationDelegate.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import "HWPanModalPresentationDelegate.h"
|
||||
#import "HWPanModalPresentationAnimator.h"
|
||||
#import "HWPanModalPresentationController.h"
|
||||
#import "HWPanModalInteractiveAnimator.h"
|
||||
|
||||
@interface HWPanModalPresentationDelegate ()
|
||||
|
||||
@property (nonatomic, strong) HWPanModalInteractiveAnimator *interactiveDismissalAnimator;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanModalPresentationDelegate
|
||||
|
||||
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
|
||||
return [[HWPanModalPresentationAnimator alloc] initWithTransitionStyle:TransitionStylePresentation interactiveMode:PanModalInteractiveModeNone];
|
||||
}
|
||||
|
||||
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
|
||||
return [[HWPanModalPresentationAnimator alloc] initWithTransitionStyle:TransitionStyleDismissal interactiveMode:self.interactiveMode];
|
||||
}
|
||||
|
||||
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
|
||||
if (self.interactive) {
|
||||
return self.interactiveDismissalAnimator;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source {
|
||||
UIPresentationController *controller = [[HWPanModalPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
|
||||
controller.delegate = self;
|
||||
return controller;
|
||||
}
|
||||
|
||||
#pragma mark - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
|
||||
return UIModalPresentationNone;
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (HWPanModalInteractiveAnimator *)interactiveDismissalAnimator {
|
||||
if (!_interactiveDismissalAnimator) {
|
||||
_interactiveDismissalAnimator = [[HWPanModalInteractiveAnimator alloc] init];
|
||||
}
|
||||
return _interactiveDismissalAnimator;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s", __PRETTY_FUNCTION__);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
36
Pods/HWPanModal/Sources/HWPanModal.h
generated
Normal file
36
Pods/HWPanModal/Sources/HWPanModal.h
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// HWPanModal.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/4/30.
|
||||
//
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for HWPanModal.
|
||||
FOUNDATION_EXPORT double HWPanModalVersionNumber;
|
||||
|
||||
//! Project version string for JYHitchModule.
|
||||
FOUNDATION_EXPORT const unsigned char HWPanModalVersionString[];
|
||||
|
||||
// protocol
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
|
||||
#import <HWPanModal/HWPanModalHeight.h>
|
||||
|
||||
#import <HWPanModal/HWPanModalPresenterProtocol.h>
|
||||
|
||||
// category
|
||||
#import <HWPanModal/UIViewController+PanModalDefault.h>
|
||||
#import <HWPanModal/UIViewController+Presentation.h>
|
||||
#import <HWPanModal/UIViewController+PanModalPresenter.h>
|
||||
|
||||
// custom animation
|
||||
#import <HWPanModal/HWPresentingVCAnimatedTransitioning.h>
|
||||
|
||||
// view
|
||||
#import <HWPanModal/HWPanModalIndicatorProtocol.h>
|
||||
#import <HWPanModal/HWPanIndicatorView.h>
|
||||
#import <HWPanModal/HWDimmedView.h>
|
||||
|
||||
// panModal view
|
||||
#import <HWPanModal/HWPanModalContentView.h>
|
||||
52
Pods/HWPanModal/Sources/KVO/KeyValueObserver.h
generated
Normal file
52
Pods/HWPanModal/Sources/KVO/KeyValueObserver.h
generated
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// KeyValueObserver.h
|
||||
// Lab Color Space Explorer
|
||||
//
|
||||
// Created by Daniel Eggert on 01/12/2013.
|
||||
// Copyright (c) 2013 objc.io. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
|
||||
@interface KeyValueObserver : NSObject
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
@property (nonatomic) SEL selector;
|
||||
|
||||
/// Create a Key-Value Observing helper object.
|
||||
///
|
||||
/// As long as the returned token object is retained, the KVO notifications of the @c object
|
||||
/// and @c keyPath will cause the given @c selector to be called on @c target.
|
||||
/// @a object and @a target are weak references.
|
||||
/// Once the token object gets dealloc'ed, the observer gets removed.
|
||||
///
|
||||
/// The @c selector should conform to
|
||||
/// @code
|
||||
/// - (void)nameDidChange:(NSDictionary *)change;
|
||||
/// @endcode
|
||||
/// The passed in dictionary is the KVO change dictionary (c.f. @c NSKeyValueChangeKindKey, @c NSKeyValueChangeNewKey etc.)
|
||||
///
|
||||
/// @returns the opaque token object to be stored in a property
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// @code
|
||||
/// self.nameObserveToken = [KeyValueObserver observeObject:user
|
||||
/// keyPath:@"name"
|
||||
/// target:self
|
||||
/// selector:@selector(nameDidChange:)];
|
||||
/// @endcode
|
||||
+ (NSObject *)observeObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector __attribute__((warn_unused_result));
|
||||
|
||||
/// Create a key-value-observer with the given KVO options
|
||||
+ (NSObject *)observeObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector options:(NSKeyValueObservingOptions)options __attribute__((warn_unused_result));
|
||||
|
||||
/**
|
||||
* When you call this method, observer will not work.
|
||||
* Please call observer method again.
|
||||
*/
|
||||
- (void)unObserver;
|
||||
|
||||
@end
|
||||
85
Pods/HWPanModal/Sources/KVO/KeyValueObserver.m
generated
Normal file
85
Pods/HWPanModal/Sources/KVO/KeyValueObserver.m
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// KeyValueObserver.m
|
||||
// Lab Color Space Explorer
|
||||
//
|
||||
// Created by Daniel Eggert on 01/12/2013.
|
||||
// Copyright (c) 2013 objc.io. All rights reserved.
|
||||
//
|
||||
|
||||
#import "KeyValueObserver.h"
|
||||
|
||||
//
|
||||
// Created by chris on 7/24/13.
|
||||
//
|
||||
|
||||
#import "KeyValueObserver.h"
|
||||
|
||||
@interface KeyValueObserver ()
|
||||
@property (nonatomic, weak) id observedObject;
|
||||
@property (nonatomic, copy) NSString* keyPath;
|
||||
@property (nonatomic, assign) BOOL shouldObserver;
|
||||
@end
|
||||
|
||||
@implementation KeyValueObserver
|
||||
|
||||
- (id)initWithObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector options:(NSKeyValueObservingOptions)options;
|
||||
{
|
||||
if (object == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSParameterAssert(target != nil);
|
||||
NSParameterAssert([target respondsToSelector:selector]);
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shouldObserver = YES;
|
||||
self.target = target;
|
||||
self.selector = selector;
|
||||
self.observedObject = object;
|
||||
self.keyPath = keyPath;
|
||||
[object addObserver:self forKeyPath:keyPath options:options context:(__bridge void *)(self)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSObject *)observeObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector __attribute__((warn_unused_result));
|
||||
{
|
||||
return [self observeObject:object keyPath:keyPath target:target selector:selector options:0];
|
||||
}
|
||||
|
||||
+ (NSObject *)observeObject:(id)object keyPath:(NSString*)keyPath target:(id)target selector:(SEL)selector options:(NSKeyValueObservingOptions)options __attribute__((warn_unused_result));
|
||||
{
|
||||
return [[self alloc] initWithObject:object keyPath:keyPath target:target selector:selector options:options];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
|
||||
{
|
||||
if (context == (__bridge void *)(self)) {
|
||||
[self didChange:change];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didChange:(NSDictionary *)change {
|
||||
|
||||
if (!self.shouldObserver) {
|
||||
return;
|
||||
}
|
||||
|
||||
id strongTarget = self.target;
|
||||
|
||||
if ([strongTarget respondsToSelector:self.selector]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[strongTarget performSelector:self.selector withObject:change];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self.observedObject removeObserver:self forKeyPath:self.keyPath];
|
||||
}
|
||||
|
||||
- (void)unObserver {
|
||||
self.shouldObserver = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
102
Pods/HWPanModal/Sources/Mediator/HWPanModalPresentableHandler.h
generated
Normal file
102
Pods/HWPanModal/Sources/Mediator/HWPanModalPresentableHandler.h
generated
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// HWPanModalPresentableHandler.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/10/15.
|
||||
// Copyright © 2019 Heath Wang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
|
||||
#import "HWPanModalPresentationDelegate.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HWPanModalPresentableHandlerMode) {
|
||||
HWPanModalPresentableHandlerModeViewController, // used for UIViewController
|
||||
HWPanModalPresentableHandlerModeView, // used for view
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol HWPanModalPresentableHandlerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* tell the delegate the presentable is about to update origin y
|
||||
*/
|
||||
- (void)adjustPresentableYPos:(CGFloat)yPos;
|
||||
|
||||
/**
|
||||
* tell the delegate presentable is about to change the form state
|
||||
* @param state short,medium, long
|
||||
*/
|
||||
- (void)presentableTransitionToState:(PresentationState)state;
|
||||
|
||||
|
||||
/**
|
||||
* get current CurrentPresentationState of the delegate
|
||||
*/
|
||||
- (PresentationState)getCurrentPresentationState;
|
||||
|
||||
/**
|
||||
* dismiss Controller/UIView
|
||||
* @param isInteractive only for UIViewController, pop view will ignore it.
|
||||
* @param mode only for UIViewController, pop view will ignore it.
|
||||
*/
|
||||
- (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode;
|
||||
|
||||
@optional
|
||||
- (void)cancelInteractiveTransition;
|
||||
- (void)finishInteractiveTransition;
|
||||
|
||||
@end
|
||||
|
||||
@protocol HWPanModalPresentableHandlerDataSource <NSObject>
|
||||
|
||||
- (CGSize)containerSize;
|
||||
- (BOOL)isBeingDismissed;
|
||||
- (BOOL)isBeingPresented;
|
||||
- (BOOL)isFormPositionAnimating;
|
||||
|
||||
@optional
|
||||
- (BOOL)isPresentedViewAnchored;
|
||||
- (BOOL)isPresentedControllerInteractive;
|
||||
|
||||
@end
|
||||
|
||||
@interface HWPanModalPresentableHandler : NSObject <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat shortFormYPosition;
|
||||
@property (nonatomic, assign, readonly) CGFloat mediumFormYPosition;
|
||||
@property (nonatomic, assign, readonly) CGFloat longFormYPosition;
|
||||
@property (nonatomic, assign, readonly) BOOL extendsPanScrolling;
|
||||
@property (nonatomic, assign, readonly) BOOL anchorModalToLongForm;
|
||||
@property (nonatomic, assign, readonly) CGFloat anchoredYPosition;
|
||||
|
||||
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *panGestureRecognizer;
|
||||
// make controller or view to deal with the gesture action
|
||||
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *screenEdgeGestureRecognizer;
|
||||
|
||||
@property (nonatomic, assign) HWPanModalPresentableHandlerMode mode;
|
||||
@property (nonatomic, weak) UIView<HWPanModalIndicatorProtocol> *dragIndicatorView;
|
||||
@property (nonatomic, weak) UIView *presentedView; // which used to present.
|
||||
|
||||
@property(nonatomic, weak) id <HWPanModalPresentableHandlerDelegate> delegate;
|
||||
@property(nonatomic, weak) id <HWPanModalPresentableHandlerDataSource> dataSource;
|
||||
|
||||
- (instancetype)initWithPresentable:(id <HWPanModalPresentable>)presentable;
|
||||
+ (instancetype)handlerWithPresentable:(id <HWPanModalPresentable>)presentable;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (void)observeScrollable;
|
||||
|
||||
- (void)configureScrollViewInsets;
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated;
|
||||
|
||||
- (void)configureViewLayout;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
769
Pods/HWPanModal/Sources/Mediator/HWPanModalPresentableHandler.m
generated
Normal file
769
Pods/HWPanModal/Sources/Mediator/HWPanModalPresentableHandler.m
generated
Normal file
@@ -0,0 +1,769 @@
|
||||
//
|
||||
// HWPanModalPresentableHandler.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/10/15.
|
||||
// Copyright © 2019 Heath Wang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HWPanModalPresentableHandler.h"
|
||||
#import "UIScrollView+Helper.h"
|
||||
#import "UIViewController+LayoutHelper.h"
|
||||
#import "UIView+HW_Frame.h"
|
||||
#import "KeyValueObserver.h"
|
||||
#import "HWPanModalContentView.h"
|
||||
|
||||
static NSString *const kScrollViewKVOContentOffsetKey = @"contentOffset";
|
||||
|
||||
@interface HWPanModalPresentableHandler ()
|
||||
|
||||
@property (nonatomic, assign) CGFloat shortFormYPosition;
|
||||
|
||||
@property (nonatomic, assign) CGFloat mediumFormYPosition;
|
||||
|
||||
@property (nonatomic, assign) CGFloat longFormYPosition;
|
||||
|
||||
@property (nonatomic, assign) BOOL extendsPanScrolling;
|
||||
|
||||
@property (nonatomic, assign) BOOL anchorModalToLongForm;
|
||||
|
||||
@property (nonatomic, assign) CGFloat anchoredYPosition;
|
||||
|
||||
@property (nonatomic, strong) id<HWPanModalPresentable, HWPanModalPanGestureDelegate> presentable;
|
||||
|
||||
// keyboard handle
|
||||
@property (nonatomic, copy) NSDictionary *keyboardInfo;
|
||||
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *screenEdgeGestureRecognizer;
|
||||
|
||||
// kvo
|
||||
@property (nonatomic, strong) id observerToken;
|
||||
@property (nonatomic, assign) CGFloat scrollViewYOffset;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanModalPresentableHandler
|
||||
|
||||
- (instancetype)initWithPresentable:(id <HWPanModalPresentable>)presentable {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_presentable = presentable;
|
||||
_extendsPanScrolling = YES;
|
||||
_anchorModalToLongForm = YES;
|
||||
[self addKeyboardObserver];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)handlerWithPresentable:(id <HWPanModalPresentable>)presentable {
|
||||
return [[self alloc] initWithPresentable:presentable];
|
||||
}
|
||||
|
||||
#pragma mark - Pan Gesture Event Handler
|
||||
|
||||
- (void)didPanOnView:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
if ([self shouldResponseToPanGestureRecognizer:panGestureRecognizer] && !self.keyboardInfo) {
|
||||
|
||||
switch (panGestureRecognizer.state) {
|
||||
|
||||
case UIGestureRecognizerStateBegan:
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
[self handlePanGestureBeginOrChanged:panGestureRecognizer];
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateEnded:
|
||||
case UIGestureRecognizerStateCancelled:
|
||||
case UIGestureRecognizerStateFailed: {
|
||||
[self handlePanGestureEnded:panGestureRecognizer];
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
[self handlePanGestureDidNotResponse:panGestureRecognizer];
|
||||
}
|
||||
[self.presentable didRespondToPanModalGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
|
||||
- (BOOL)shouldResponseToPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if ([self.presentable shouldRespondToPanModalGestureRecognizer:panGestureRecognizer] ||
|
||||
!(panGestureRecognizer.state == UIGestureRecognizerStateBegan || panGestureRecognizer.state == UIGestureRecognizerStateCancelled)) {
|
||||
|
||||
return ![self shouldFailPanGestureRecognizer:panGestureRecognizer];
|
||||
} else {
|
||||
// stop pan gesture working.
|
||||
panGestureRecognizer.enabled = NO;
|
||||
panGestureRecognizer.enabled = YES;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldFailPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
if ([self shouldPrioritizePanGestureRecognizer:panGestureRecognizer]) {
|
||||
// high priority than scroll view gesture, disable scrollView gesture.
|
||||
[self.presentable panScrollable].panGestureRecognizer.enabled = NO;
|
||||
[self.presentable panScrollable].panGestureRecognizer.enabled = YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([self shouldHandleShortStatePullDownWithRecognizer:panGestureRecognizer]) {
|
||||
// panGestureRecognizer.enabled = NO;
|
||||
// panGestureRecognizer.enabled = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
BOOL shouldFail = NO;
|
||||
UIScrollView *scrollView = [self.presentable panScrollable];
|
||||
if (scrollView) {
|
||||
shouldFail = scrollView.contentOffset.y > -MAX(scrollView.contentInset.top, 0);
|
||||
|
||||
// we want scroll the panScrollable, not the presentedView
|
||||
if (self.isPresentedViewAnchored && shouldFail) {
|
||||
CGPoint location = [panGestureRecognizer locationInView:self.presentedView];
|
||||
BOOL flag = CGRectContainsPoint(scrollView.frame, location) || scrollView.isScrolling;
|
||||
if (flag) {
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
}
|
||||
return flag;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)shouldHandleShortStatePullDownWithRecognizer:(UIPanGestureRecognizer *)recognizer {
|
||||
if ([self.presentable allowsPullDownWhenShortState]) return NO;
|
||||
|
||||
CGPoint location = [recognizer translationInView:self.presentedView];
|
||||
if ([self.delegate getCurrentPresentationState] == PresentationStateShort && recognizer.state == UIGestureRecognizerStateBegan) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ((self.presentedView.frame.origin.y >= self.shortFormYPosition || HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.shortFormYPosition)) && location.y > 0) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldPrioritizePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer {
|
||||
return recognizer.state == UIGestureRecognizerStateBegan && [[self presentable] shouldPrioritizePanModalGestureRecognizer:recognizer];
|
||||
}
|
||||
|
||||
- (void)respondToPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
[self.presentable willRespondToPanModalGestureRecognizer:panGestureRecognizer];
|
||||
|
||||
CGFloat yDisplacement = [panGestureRecognizer translationInView:self.presentedView].y;
|
||||
|
||||
if (self.presentedView.frame.origin.y < self.longFormYPosition) {
|
||||
yDisplacement = yDisplacement / 2;
|
||||
}
|
||||
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(adjustPresentableYPos:)]) {
|
||||
[delegate adjustPresentableYPos:self.presentedView.frame.origin.y + yDisplacement];
|
||||
}
|
||||
|
||||
[panGestureRecognizer setTranslation:CGPointZero inView:self.presentedView];
|
||||
}
|
||||
|
||||
- (BOOL)isVelocityWithinSensitivityRange:(CGFloat)velocity {
|
||||
return (fabs(velocity) - [self.presentable minVerticalVelocityToTriggerDismiss]) > 0;
|
||||
}
|
||||
|
||||
- (CGFloat)nearestDistance:(CGFloat)position inDistances:(NSArray *)distances {
|
||||
|
||||
if (distances.count <= 0) {
|
||||
return position;
|
||||
}
|
||||
|
||||
// TODO: need refine this sort code.
|
||||
NSMutableArray *tmpArr = [NSMutableArray arrayWithCapacity:distances.count];
|
||||
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionaryWithCapacity:distances.count];
|
||||
|
||||
[distances enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSNumber *number = obj;
|
||||
NSNumber *absValue = @(fabs(number.floatValue - position));
|
||||
[tmpArr addObject:absValue];
|
||||
tmpDict[absValue] = number;
|
||||
|
||||
}];
|
||||
|
||||
[tmpArr sortUsingSelector:@selector(compare:)];
|
||||
|
||||
NSNumber *result = tmpDict[tmpArr.firstObject];
|
||||
return result.floatValue;
|
||||
}
|
||||
|
||||
- (void)screenEdgeInteractiveAction:(UIPanGestureRecognizer *)gestureRecognizer {
|
||||
//
|
||||
}
|
||||
|
||||
#pragma mark - handle did Pan gesture events
|
||||
|
||||
- (void)handlePanGestureDidNotResponse:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
switch (panGestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateEnded:
|
||||
case UIGestureRecognizerStateCancelled:
|
||||
case UIGestureRecognizerStateFailed: {
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
[self cancelInteractiveTransition];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
[panGestureRecognizer setTranslation:CGPointZero inView:panGestureRecognizer.view];
|
||||
}
|
||||
|
||||
- (void)handlePanGestureBeginOrChanged:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
CGPoint velocity = [panGestureRecognizer velocityInView:self.presentedView];
|
||||
[self respondToPanGestureRecognizer:panGestureRecognizer];
|
||||
|
||||
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
|
||||
// check if toggle dismiss action
|
||||
if ([[self presentable] presentingVCAnimationStyle] > PresentingViewControllerAnimationStyleNone &&
|
||||
velocity.y > 0 &&
|
||||
(self.presentedView.frame.origin.y > self.shortFormYPosition || HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.shortFormYPosition))) {
|
||||
[self dismissPresentable:YES mode:PanModalInteractiveModeDragDown];
|
||||
}
|
||||
}
|
||||
|
||||
if (HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.anchoredYPosition) && self.extendsPanScrolling) {
|
||||
[self.presentable willTransitionToState:PresentationStateLong];
|
||||
}
|
||||
|
||||
// update drag indicator
|
||||
if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
|
||||
if (velocity.y > 0) {
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStatePullDown];
|
||||
} else if (velocity.y < 0 && self.presentedView.frame.origin.y <= self.anchoredYPosition && !self.extendsPanScrolling) {
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePanGestureEnded:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
CGPoint velocity = [panGestureRecognizer velocityInView:self.presentedView];
|
||||
/**
|
||||
* pan recognizer结束
|
||||
* 根据velocity(速度),当velocity.y < 0,说明用户在向上拖拽view;当velocity.y > 0,向下拖拽
|
||||
* 根据拖拽的速度,处理不同的情况:
|
||||
* 1.超过拖拽速度阈值时并且向下拖拽,dismiss controller
|
||||
* 2.向上拖拽永远不会dismiss,回弹至相应的状态
|
||||
*/
|
||||
|
||||
if ([self isVelocityWithinSensitivityRange:velocity.y]) {
|
||||
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
PresentationState currentState = [delegate getCurrentPresentationState];
|
||||
|
||||
if (velocity.y < 0) {
|
||||
[self handleDragUpState:currentState];
|
||||
} else {
|
||||
[self handleDragDownState:currentState];
|
||||
}
|
||||
} else {
|
||||
CGFloat position = [self nearestDistance:CGRectGetMinY(self.presentedView.frame) inDistances:@[@([self containerSize].height), @(self.shortFormYPosition), @(self.longFormYPosition), @(self.mediumFormYPosition)]];
|
||||
if (HW_TWO_FLOAT_IS_EQUAL(position, self.longFormYPosition)) {
|
||||
[self transitionToState:PresentationStateLong];
|
||||
[self cancelInteractiveTransition];
|
||||
} else if (HW_TWO_FLOAT_IS_EQUAL(position, self.mediumFormYPosition)) {
|
||||
[self transitionToState:PresentationStateMedium];
|
||||
[self cancelInteractiveTransition];
|
||||
} else if (HW_TWO_FLOAT_IS_EQUAL(position, self.shortFormYPosition) || ![self.presentable allowsDragToDismiss]) {
|
||||
[self transitionToState:PresentationStateShort];
|
||||
[self cancelInteractiveTransition];
|
||||
} else {
|
||||
if ([self isBeingDismissed]) {
|
||||
[self finishInteractiveTransition];
|
||||
} else {
|
||||
[self dismissPresentable:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self.presentable didEndRespondToPanModalGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)handleDragUpState:(PresentationState)state {
|
||||
switch (state) {
|
||||
case PresentationStateLong:
|
||||
[self transitionToState:PresentationStateLong];
|
||||
[self cancelInteractiveTransition];
|
||||
break;
|
||||
case PresentationStateMedium:
|
||||
[self transitionToState:PresentationStateLong];
|
||||
[self cancelInteractiveTransition];
|
||||
break;
|
||||
case PresentationStateShort:
|
||||
[self transitionToState:PresentationStateMedium];
|
||||
[self cancelInteractiveTransition];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleDragDownState:(PresentationState)state {
|
||||
switch (state) {
|
||||
case PresentationStateLong:
|
||||
[self transitionToState:PresentationStateMedium];
|
||||
[self cancelInteractiveTransition];
|
||||
break;
|
||||
case PresentationStateMedium:
|
||||
[self transitionToState:PresentationStateShort];
|
||||
[self cancelInteractiveTransition];
|
||||
break;
|
||||
case PresentationStateShort:
|
||||
if (![self.presentable allowsDragToDismiss]) {
|
||||
[self transitionToState:PresentationStateShort];
|
||||
[self cancelInteractiveTransition];
|
||||
} else {
|
||||
if ([self isBeingDismissed]) {
|
||||
[self finishInteractiveTransition];
|
||||
} else {
|
||||
[self dismissPresentable:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollView kvo
|
||||
|
||||
- (void)observeScrollable {
|
||||
UIScrollView *scrollView = [[self presentable] panScrollable];
|
||||
if (!scrollView) {
|
||||
// force set observerToken to nil, make sure to callback.
|
||||
self.observerToken = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
self.scrollViewYOffset = MAX(scrollView.contentOffset.y, -(MAX(scrollView.contentInset.top, 0)));
|
||||
self.observerToken = [KeyValueObserver observeObject:scrollView keyPath:kScrollViewKVOContentOffsetKey target:self selector:@selector(didPanOnScrollViewChanged:) options:NSKeyValueObservingOptionOld];
|
||||
}
|
||||
|
||||
/**
|
||||
As the user scrolls, track & save the scroll view y offset.
|
||||
This helps halt scrolling when we want to hold the scroll view in place.
|
||||
*/
|
||||
- (void)trackScrolling:(UIScrollView *)scrollView {
|
||||
self.scrollViewYOffset = MAX(scrollView.contentOffset.y, -(MAX(scrollView.contentInset.top, 0)));
|
||||
scrollView.showsVerticalScrollIndicator = [[self presentable] showsScrollableVerticalScrollIndicator];
|
||||
}
|
||||
|
||||
/**
|
||||
* Halts the scroll of a given scroll view & anchors it at the `scrollViewYOffset`
|
||||
*/
|
||||
- (void)haltScrolling:(UIScrollView *)scrollView {
|
||||
|
||||
//
|
||||
// Fix bug: the app will crash after the table view reloads data via calling [tableView reloadData] if the user scrolls to the bottom.
|
||||
//
|
||||
// We remove some element and reload data, for example, [self.dataSource removeLastObject], the previous saved scrollViewYOffset value
|
||||
// will be great than or equal to the current actual offset(i.e. scrollView.contentOffset.y). At this time, if the method
|
||||
// [scrollView setContentOffset:CGPointMake(0, self.scrollViewYOffset) animated:NO] is called, which will trigger KVO recursively.
|
||||
// So scrollViewYOffset must be less than or equal to the actual offset here.
|
||||
// See issues: https://github.com/HeathWang/HWPanModal/issues/107 and https://github.com/HeathWang/HWPanModal/issues/103
|
||||
|
||||
if (scrollView.contentOffset.y <= 0 || self.scrollViewYOffset <= scrollView.contentOffset.y) {
|
||||
[scrollView setContentOffset:CGPointMake(0, self.scrollViewYOffset) animated:NO];
|
||||
scrollView.showsVerticalScrollIndicator = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didPanOnScrollViewChanged:(NSDictionary<NSKeyValueChangeKey, id> *)change {
|
||||
|
||||
UIScrollView *scrollView = [[self presentable] panScrollable];
|
||||
if (!scrollView) return;
|
||||
|
||||
if ((![self isBeingDismissed] && ![self isBeingPresented]) ||
|
||||
([self isBeingDismissed] && [self isPresentedViewControllerInteractive])) {
|
||||
|
||||
if (![self isPresentedViewAnchored] && scrollView.contentOffset.y > 0) {
|
||||
[self haltScrolling:scrollView];
|
||||
} else if ([scrollView isScrolling] || [self isPresentedViewAnimating]) {
|
||||
|
||||
// While we're scrolling upwards on the scrollView, store the last content offset position
|
||||
if ([self isPresentedViewAnchored]) {
|
||||
[self trackScrolling:scrollView];
|
||||
} else {
|
||||
/**
|
||||
* Keep scroll view in place while we're panning on main view
|
||||
*/
|
||||
[self haltScrolling:scrollView];
|
||||
}
|
||||
} else {
|
||||
[self trackScrolling:scrollView];
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* 当present Controller,而且动画没有结束的时候,用户可能会对scrollView设置contentOffset
|
||||
* 首次用户滑动scrollView时,会因为scrollViewYOffset = 0而出现错位
|
||||
*/
|
||||
if ([self isBeingPresented]) {
|
||||
[self setScrollableContentOffset:scrollView.contentOffset animated:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollView update
|
||||
|
||||
- (void)configureScrollViewInsets {
|
||||
|
||||
// when scrolling, return
|
||||
if ([self.presentable panScrollable] && ![self.presentable panScrollable].isScrolling) {
|
||||
UIScrollView *scrollView = [self.presentable panScrollable];
|
||||
// 禁用scrollView indicator除非用户开始滑动scrollView
|
||||
scrollView.showsVerticalScrollIndicator = [self.presentable showsScrollableVerticalScrollIndicator];
|
||||
scrollView.scrollEnabled = [self.presentable isPanScrollEnabled];
|
||||
scrollView.scrollIndicatorInsets = [self.presentable scrollIndicatorInsets];
|
||||
|
||||
if (![self.presentable shouldAutoSetPanScrollContentInset]) return;
|
||||
|
||||
UIEdgeInsets insets1 = scrollView.contentInset;
|
||||
CGFloat bottomLayoutOffset = [UIApplication sharedApplication].keyWindow.rootViewController.bottomLayoutGuide.length;
|
||||
/*
|
||||
* If scrollView has been set contentInset, and bottom is NOT zero, we won't change it.
|
||||
* If contentInset.bottom is zero, set bottom = bottomLayoutOffset
|
||||
* If scrollView has been set contentInset, BUT the bottom < bottomLayoutOffset, set bottom = bottomLayoutOffset
|
||||
*/
|
||||
if (HW_FLOAT_IS_ZERO(insets1.bottom) || insets1.bottom < bottomLayoutOffset) {
|
||||
|
||||
insets1.bottom = bottomLayoutOffset;
|
||||
scrollView.contentInset = insets1;
|
||||
}
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
|
||||
if (![self.presentable panScrollable]) return;
|
||||
|
||||
UIScrollView *scrollView = [self.presentable panScrollable];
|
||||
[self.observerToken unObserver];
|
||||
|
||||
[scrollView setContentOffset:offset animated:animated];
|
||||
// wait for animation finished.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) ((animated ? 0.30 : 0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
||||
[self trackScrolling:scrollView];
|
||||
[self observeScrollable];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - layout
|
||||
|
||||
- (void)configureViewLayout {
|
||||
|
||||
if ([self.presentable isKindOfClass:UIViewController.class]) {
|
||||
UIViewController<HWPanModalPresentable> *layoutPresentable = (UIViewController<HWPanModalPresentable> *) self.presentable;
|
||||
self.shortFormYPosition = layoutPresentable.shortFormYPos;
|
||||
self.mediumFormYPosition = layoutPresentable.mediumFormYPos;
|
||||
self.longFormYPosition = layoutPresentable.longFormYPos;
|
||||
self.anchorModalToLongForm = [layoutPresentable anchorModalToLongForm];
|
||||
self.extendsPanScrolling = [layoutPresentable allowsExtendedPanScrolling];
|
||||
} else if ([self.presentable isKindOfClass:HWPanModalContentView.class]) {
|
||||
HWPanModalContentView<HWPanModalPresentable> *layoutPresentable = (HWPanModalContentView<HWPanModalPresentable> *) self.presentable;
|
||||
self.shortFormYPosition = layoutPresentable.shortFormYPos;
|
||||
self.mediumFormYPosition = layoutPresentable.mediumFormYPos;
|
||||
self.longFormYPosition = layoutPresentable.longFormYPos;
|
||||
self.anchorModalToLongForm = [layoutPresentable anchorModalToLongForm];
|
||||
self.extendsPanScrolling = [layoutPresentable allowsExtendedPanScrolling];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizerDelegate
|
||||
|
||||
/**
|
||||
* ONLY When otherGestureRecognizer is panGestureRecognizer, and target gestureRecognizer is panGestureRecognizer, return YES.
|
||||
*/
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
|
||||
if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
|
||||
return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
|
||||
}
|
||||
|
||||
if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
|
||||
return [otherGestureRecognizer isKindOfClass:UIPanGestureRecognizer.class];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前手势为screenGestureRecognizer时,其他pan recognizer都应该fail
|
||||
*/
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
|
||||
if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:)]) {
|
||||
return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer];
|
||||
}
|
||||
|
||||
|
||||
if (gestureRecognizer == self.screenEdgeGestureRecognizer && [otherGestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldRequireFailureOfGestureRecognizer:)]) {
|
||||
return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
|
||||
|
||||
if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizerShouldBegin:)]) {
|
||||
return [self.presentable hw_gestureRecognizerShouldBegin:gestureRecognizer];
|
||||
}
|
||||
|
||||
if (gestureRecognizer == self.screenEdgeGestureRecognizer) {
|
||||
CGPoint velocity = [self.screenEdgeGestureRecognizer velocityInView:self.screenEdgeGestureRecognizer.view];
|
||||
|
||||
if (velocity.x <= 0 || HW_TWO_FLOAT_IS_EQUAL(velocity.x, 0)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// check the distance to left edge
|
||||
CGPoint location = [self.screenEdgeGestureRecognizer locationInView:self.screenEdgeGestureRecognizer.view];
|
||||
CGFloat thresholdDistance = [[self presentable] maxAllowedDistanceToLeftScreenEdgeForPanInteraction];
|
||||
if (thresholdDistance > 0 && location.x > thresholdDistance) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (velocity.x > 0 && HW_TWO_FLOAT_IS_EQUAL(velocity.y, 0)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
//TODO: this logic can be updated later.
|
||||
if (velocity.x > 0 && velocity.x / fabs(velocity.y) > 2) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - UIKeyboard Handle
|
||||
|
||||
- (void)addKeyboardObserver {
|
||||
if ([self.presentable isAutoHandleKeyboardEnabled]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeKeyboardObserver {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||||
UIView<UIKeyInput> *currentInput = [self findCurrentTextInputInView:self.presentedView];
|
||||
|
||||
if (!currentInput)
|
||||
return;
|
||||
|
||||
self.keyboardInfo = notification.userInfo;
|
||||
[self updatePanContainerFrameForKeyboard];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||||
self.keyboardInfo = nil;
|
||||
|
||||
NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
UIViewAnimationCurve curve = (UIViewAnimationCurve) [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
|
||||
|
||||
[UIView beginAnimations:nil context:nil];
|
||||
[UIView setAnimationBeginsFromCurrentState:YES];
|
||||
[UIView setAnimationCurve:curve];
|
||||
[UIView setAnimationDuration:duration];
|
||||
|
||||
self.presentedView.transform = CGAffineTransformIdentity;
|
||||
|
||||
[UIView commitAnimations];
|
||||
}
|
||||
|
||||
- (void)updatePanContainerFrameForKeyboard {
|
||||
if (!self.keyboardInfo)
|
||||
return;
|
||||
|
||||
UIView<UIKeyInput> *textInput = [self findCurrentTextInputInView:self.presentedView];
|
||||
if (!textInput)
|
||||
return;
|
||||
|
||||
CGAffineTransform lastTransform = self.presentedView.transform;
|
||||
self.presentedView.transform = CGAffineTransformIdentity;
|
||||
|
||||
CGFloat textViewBottomY = [textInput convertRect:textInput.bounds toView:self.presentedView].origin.y + textInput.hw_height;
|
||||
CGFloat keyboardHeight = [self.keyboardInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
|
||||
|
||||
CGFloat offsetY = 0;
|
||||
CGFloat top = [self.presentable keyboardOffsetFromInputView];
|
||||
offsetY = self.presentedView.hw_height - (keyboardHeight + top + textViewBottomY + self.presentedView.hw_top);
|
||||
|
||||
NSTimeInterval duration = [self.keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
UIViewAnimationCurve curve = (UIViewAnimationCurve) [self.keyboardInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
|
||||
|
||||
self.presentedView.transform = lastTransform;
|
||||
[UIView beginAnimations:nil context:NULL];
|
||||
[UIView setAnimationBeginsFromCurrentState:YES];
|
||||
[UIView setAnimationCurve:curve];
|
||||
[UIView setAnimationDuration:duration];
|
||||
|
||||
self.presentedView.transform = CGAffineTransformMakeTranslation(0, offsetY);
|
||||
|
||||
[UIView commitAnimations];
|
||||
}
|
||||
|
||||
- (UIView <UIKeyInput> *)findCurrentTextInputInView:(UIView *)view {
|
||||
if ([view conformsToProtocol:@protocol(UIKeyInput)] && view.isFirstResponder) {
|
||||
// Quick fix for web view issue
|
||||
if ([view isKindOfClass:NSClassFromString(@"UIWebBrowserView")] || [view isKindOfClass:NSClassFromString(@"WKContentView")]) {
|
||||
return nil;
|
||||
}
|
||||
return (UIView <UIKeyInput> *) view;
|
||||
}
|
||||
|
||||
for (UIView *subview in view.subviews) {
|
||||
UIView <UIKeyInput> *inputInView = [self findCurrentTextInputInView:subview];
|
||||
if (inputInView) {
|
||||
return inputInView;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - delegate throw
|
||||
|
||||
- (void)transitionToState:(PresentationState)state {
|
||||
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(presentableTransitionToState:)]) {
|
||||
[delegate presentableTransitionToState:state];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelInteractiveTransition {
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(cancelInteractiveTransition)]) {
|
||||
[delegate cancelInteractiveTransition];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishInteractiveTransition {
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(finishInteractiveTransition)]) {
|
||||
[delegate finishInteractiveTransition];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissPresentable:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
|
||||
id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(dismiss:mode:)]) {
|
||||
[delegate dismiss:isInteractive mode:mode];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - dataSource handle
|
||||
|
||||
- (BOOL)isPresentedViewAnchored {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(isPresentedViewAnchored)]) {
|
||||
return [self.dataSource isPresentedViewAnchored];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingDismissed {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(isBeingDismissed)]) {
|
||||
return [self.dataSource isBeingDismissed];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingPresented {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(isBeingPresented)]) {
|
||||
return [self.dataSource isBeingPresented];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedViewControllerInteractive {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(isPresentedControllerInteractive)]) {
|
||||
return [self.dataSource isPresentedControllerInteractive];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedViewAnimating {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(isFormPositionAnimating)]) {
|
||||
[self.dataSource isFormPositionAnimating];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CGSize)containerSize {
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(containerSize)]) {
|
||||
return [self.dataSource containerSize];
|
||||
}
|
||||
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if (!_panGestureRecognizer) {
|
||||
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPanOnView:)];
|
||||
_panGestureRecognizer.minimumNumberOfTouches = 1;
|
||||
_panGestureRecognizer.maximumNumberOfTouches = 1;
|
||||
_panGestureRecognizer.delegate = self;
|
||||
}
|
||||
return _panGestureRecognizer;
|
||||
}
|
||||
|
||||
- (UIPanGestureRecognizer *)screenEdgeGestureRecognizer {
|
||||
if (!_screenEdgeGestureRecognizer) {
|
||||
_screenEdgeGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(screenEdgeInteractiveAction:)];
|
||||
_screenEdgeGestureRecognizer.minimumNumberOfTouches = 1;
|
||||
_screenEdgeGestureRecognizer.maximumNumberOfTouches = 1;
|
||||
_screenEdgeGestureRecognizer.delegate = self;
|
||||
}
|
||||
|
||||
return _screenEdgeGestureRecognizer;
|
||||
}
|
||||
|
||||
- (CGFloat)anchoredYPosition {
|
||||
CGFloat defaultTopOffset = [self.presentable topOffset];
|
||||
return self.anchorModalToLongForm ? self.longFormYPosition : defaultTopOffset;
|
||||
}
|
||||
|
||||
#pragma mark - Dealloc
|
||||
|
||||
- (void)dealloc {
|
||||
[self removeKeyboardObserver];
|
||||
}
|
||||
|
||||
@end
|
||||
48
Pods/HWPanModal/Sources/Presentable/HWPanModalHeight.h
generated
Normal file
48
Pods/HWPanModal/Sources/Presentable/HWPanModalHeight.h
generated
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// HWPanModalHeight.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, PanModalHeightType) {
|
||||
PanModalHeightTypeMax NS_SWIFT_NAME(max), // from top max
|
||||
PanModalHeightTypeMaxTopInset NS_SWIFT_NAME(topInset), // from top offset
|
||||
PanModalHeightTypeContent NS_SWIFT_NAME(content), // from bottom
|
||||
PanModalHeightTypeContentIgnoringSafeArea NS_SWIFT_NAME(contentIgnoringSafeArea), // from bottom ignore safeArea
|
||||
PanModalHeightTypeIntrinsic NS_SWIFT_NAME(intrinsic), // auto get size, There is something wrong, DO NOT recommend to use.
|
||||
};
|
||||
|
||||
struct PanModalHeight {
|
||||
PanModalHeightType heightType NS_SWIFT_NAME(type);
|
||||
CGFloat height;
|
||||
};
|
||||
|
||||
typedef struct PanModalHeight PanModalHeight;
|
||||
|
||||
/**
|
||||
* When heightType is PanModalHeightTypeMax, PanModalHeightTypeIntrinsic, the height value will be ignored.
|
||||
*/
|
||||
CG_INLINE PanModalHeight PanModalHeightMake(PanModalHeightType heightType, CGFloat height) {
|
||||
PanModalHeight modalHeight;
|
||||
modalHeight.heightType = heightType;
|
||||
modalHeight.height = height;
|
||||
return modalHeight;
|
||||
}
|
||||
|
||||
static inline BOOL HW_FLOAT_IS_ZERO(CGFloat value) {
|
||||
return (value > -FLT_EPSILON) && (value < FLT_EPSILON);
|
||||
}
|
||||
|
||||
static inline BOOL HW_TWO_FLOAT_IS_EQUAL(CGFloat x, CGFloat y) {
|
||||
CGFloat minusValue = fabs(x - y);
|
||||
CGFloat criticalValue = 0.0001;
|
||||
if (minusValue < criticalValue || minusValue < FLT_MIN) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
31
Pods/HWPanModal/Sources/Presentable/HWPanModalPanGestureDelegate.h
generated
Normal file
31
Pods/HWPanModal/Sources/Presentable/HWPanModalPanGestureDelegate.h
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// HWPanModalPanGestureDelegate.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2022/8/1.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* In this framewok, we use UIPanGestureRecognizer to control user drags behavior.
|
||||
* The internal logic, there are two panGestureRecognizers delegate will response below delegate: the main panGesture used to control darg down, another panGesture used to control screen edge dismiss.
|
||||
* Implement this delegate and custom user drag behavior.
|
||||
* WARNING: BE CAREFUL, AND KNOW WHAT YOU ARE DOING!
|
||||
*/
|
||||
@protocol HWPanModalPanGestureDelegate <NSObject>
|
||||
|
||||
- (BOOL)hw_gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
|
||||
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
|
||||
|
||||
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
|
||||
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
451
Pods/HWPanModal/Sources/Presentable/HWPanModalPresentable.h
generated
Normal file
451
Pods/HWPanModal/Sources/Presentable/HWPanModalPresentable.h
generated
Normal file
@@ -0,0 +1,451 @@
|
||||
//
|
||||
// HWPanModalPresentable.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalHeight.h>
|
||||
#import <HWPanModal/HWPresentingVCAnimatedTransitioning.h>
|
||||
#import <HWPanModal/HWPanModalIndicatorProtocol.h>
|
||||
#import <HWPanModal/HWBackgroundConfig.h>
|
||||
#import <HWPanModal/HWPanModalShadow.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, PresentationState) {
|
||||
PresentationStateShort NS_SWIFT_NAME(short),
|
||||
PresentationStateMedium NS_SWIFT_NAME(medium),
|
||||
PresentationStateLong NS_SWIFT_NAME(long),
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, PresentingViewControllerAnimationStyle) {
|
||||
// no animation for presentingVC
|
||||
PresentingViewControllerAnimationStyleNone NS_SWIFT_NAME(none),
|
||||
// page sheet animation, like iOS13 default modalPresentation style
|
||||
PresentingViewControllerAnimationStylePageSheet NS_SWIFT_NAME(pageSheet),
|
||||
// shopping cart animation, like jd/taobao shopping cart animation
|
||||
PresentingViewControllerAnimationStyleShoppingCart NS_SWIFT_NAME(shoppingCart),
|
||||
// make your own custom animation
|
||||
PresentingViewControllerAnimationStyleCustom NS_SWIFT_NAME(custom),
|
||||
};
|
||||
|
||||
/**
|
||||
* HWPanModalPresentable为present配置协议
|
||||
* 默认情况下无需实现,只需Controller/View适配该协议
|
||||
* 通过category来默认实现以下所有方法,避免继承类
|
||||
*
|
||||
* This Protocol is the core of HWPanModal, we use it to config presentation.
|
||||
* Default, you don't need to conform all of these methods, just implement what you want to customize.
|
||||
* All the config has default value, we use a `UIViewController` category to conform `HWPanModalPresentable` protocol.
|
||||
*/
|
||||
@protocol HWPanModalPresentable <NSObject>
|
||||
|
||||
#pragma mark - ScrollView Config
|
||||
|
||||
/**
|
||||
* 支持同步拖拽的scrollView
|
||||
* 如果ViewController中包含scrollView并且你想scrollView滑动和拖拽手势同时存在,请返回此scrollView
|
||||
*
|
||||
* If your ViewController has a scrollable view(UIScrollView and subclass), and you want pan gesture and scrollable both work, return it.
|
||||
*/
|
||||
- (nullable UIScrollView *)panScrollable;
|
||||
|
||||
/**
|
||||
* determine ScrollView scrollEnabled
|
||||
* default is YES
|
||||
*/
|
||||
- (BOOL)isPanScrollEnabled;
|
||||
|
||||
/**
|
||||
* scrollView指示器insets
|
||||
* Use `panModalSetNeedsLayoutUpdate()` when updating insets.
|
||||
*/
|
||||
- (UIEdgeInsets)scrollIndicatorInsets;
|
||||
|
||||
/**
|
||||
* A Boolean value that controls whether the scrollable vertical scroll indicator is visible.
|
||||
* default is YES.
|
||||
*/
|
||||
- (BOOL)showsScrollableVerticalScrollIndicator;
|
||||
|
||||
/**
|
||||
* default is YES.
|
||||
*/
|
||||
- (BOOL)shouldAutoSetPanScrollContentInset;
|
||||
|
||||
/**
|
||||
* 是否允许拖动额外拖动,如果panScrollable存在,且scrollView contentSize > (size + bottomLayoutOffset),返回YES
|
||||
* 其余情况返回NO
|
||||
*
|
||||
* If panScrollable exists, and scrollView contentSize > (size + bottomLayoutOffset), auto return YES, otherwise return NO.
|
||||
* You can make your own logic if you want, and you know what you are doing.
|
||||
*/
|
||||
- (BOOL)allowsExtendedPanScrolling;
|
||||
|
||||
#pragma mark - Offset/position
|
||||
|
||||
/**
|
||||
* Screen top offset from presented viewController
|
||||
* Default is topLayoutGuide.length + 21.0.
|
||||
*/
|
||||
- (CGFloat)topOffset;
|
||||
|
||||
/**
|
||||
* 当pan状态为short时候的高度
|
||||
* default: shortFormHeight = longFormHeight
|
||||
*/
|
||||
- (PanModalHeight)shortFormHeight;
|
||||
|
||||
/**
|
||||
* default: mediumFormHeight = longFormHeight
|
||||
*/
|
||||
- (PanModalHeight)mediumFormHeight;
|
||||
|
||||
/**
|
||||
* 当pan状态为long的高度
|
||||
*/
|
||||
- (PanModalHeight)longFormHeight;
|
||||
|
||||
/**
|
||||
* 初始弹出高度状态,默认为`shortFormHeight`
|
||||
*
|
||||
* Origin presentation height state, if you have special requirement, change it.
|
||||
* Default is `shortFormHeight`
|
||||
*/
|
||||
- (PresentationState)originPresentationState;
|
||||
|
||||
#pragma mark - Animation config
|
||||
|
||||
/**
|
||||
* spring弹性动画数值
|
||||
* Default is 0.9
|
||||
*/
|
||||
- (CGFloat)springDamping;
|
||||
|
||||
/**
|
||||
* 转场动画时间
|
||||
* Default is 0.5 second
|
||||
*/
|
||||
- (NSTimeInterval)transitionDuration;
|
||||
|
||||
/**
|
||||
* starting from version 0.6.5, Only works when dismiss
|
||||
* Default is same as `- (NSTimeInterval)transitionDuration;`
|
||||
*/
|
||||
- (NSTimeInterval)dismissalDuration;
|
||||
|
||||
/**
|
||||
* 转场动画options
|
||||
* Default is UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
|
||||
*/
|
||||
- (UIViewAnimationOptions)transitionAnimationOptions;
|
||||
|
||||
#pragma mark - AppearanceTransition
|
||||
|
||||
/**
|
||||
* If enabled, the presenting VC will invoke viewWillAppear:, viewWillDisappear:
|
||||
* Default is YES
|
||||
*/
|
||||
- (BOOL)shouldEnableAppearanceTransition;
|
||||
|
||||
#pragma mark - Background config
|
||||
|
||||
/**
|
||||
* use this object to config background alpha or blur effect
|
||||
* @return background config object
|
||||
*/
|
||||
- (HWBackgroundConfig *)backgroundConfig;
|
||||
|
||||
#pragma mark - User Interaction
|
||||
|
||||
/**
|
||||
* 该bool值控制当pan View状态为long的情况下,是否可以继续拖拽到PanModalHeight = MAX的情况
|
||||
* 默认为YES,即当已经拖拽到long的情况下不能再继续拖动
|
||||
*/
|
||||
- (BOOL)anchorModalToLongForm;
|
||||
|
||||
/**
|
||||
* 是否允许点击背景处dismiss presented Controller
|
||||
* 默认为YES
|
||||
*/
|
||||
- (BOOL)allowsTapBackgroundToDismiss;
|
||||
|
||||
|
||||
/**
|
||||
* 是否允许drag操作dismiss presented Controller
|
||||
* Default is YES
|
||||
*/
|
||||
- (BOOL)allowsDragToDismiss;
|
||||
|
||||
/// Default is YES, When return NO, and you did set shortForm, user CAN NOT pull down the view.
|
||||
- (BOOL)allowsPullDownWhenShortState;
|
||||
|
||||
/**
|
||||
min Velocity from Vertical direction that trigger dismiss action.
|
||||
Default is 300.0
|
||||
*/
|
||||
- (CGFloat)minVerticalVelocityToTriggerDismiss;
|
||||
|
||||
/**
|
||||
* 是否允许用户操作
|
||||
* Default is YES
|
||||
*/
|
||||
- (BOOL)isUserInteractionEnabled;
|
||||
|
||||
/**
|
||||
* 是否允许触觉反馈
|
||||
* Default is YES
|
||||
*/
|
||||
- (BOOL)isHapticFeedbackEnabled;
|
||||
|
||||
/**
|
||||
* 是否允许触摸事件透传到presenting ViewController/View。如果你有特殊需求的话(比如弹出一个底部视图,但是你想操作弹出视图下面的view,即presenting VC/View),可开启此功能
|
||||
*
|
||||
* Whether allows touch events passing through the transition container view.
|
||||
* In some situations, you present the bottom VC/View, and you want to operate the presenting VC/View(mapView, scrollView and etc), enable this func.
|
||||
*
|
||||
* Note: You SHOULD MUST dismiss the presented VC in the right time.
|
||||
*/
|
||||
- (BOOL)allowsTouchEventsPassingThroughTransitionView;
|
||||
|
||||
#pragma mark - Screen left egde interaction
|
||||
|
||||
/**
|
||||
* 是否允许屏幕边缘侧滑手势
|
||||
* Default is NO,not allowed this user interaction.
|
||||
*
|
||||
* Note: Currently only works on UIViewController.
|
||||
*/
|
||||
- (BOOL)allowScreenEdgeInteractive;
|
||||
|
||||
/**
|
||||
* Max allowed distance to screen left edge when you want to make screen edge pan interaction
|
||||
* Default is 0, means it will ignore this limit, full screen left edge pan will work.
|
||||
* @return distance to left screen edge
|
||||
*/
|
||||
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction;
|
||||
|
||||
/**
|
||||
* When you enabled `- (BOOL)allowScreenEdgeInteractive`, this can work.
|
||||
* min horizontal velocity to trigger screen edge dismiss if the drag didn't reach 0.5 screen width.
|
||||
* Default is 500
|
||||
*/
|
||||
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss;
|
||||
|
||||
#pragma mark - Customize presentingViewController animation
|
||||
|
||||
/**
|
||||
* Config presentingViewController animation style, this animations will work for present & dismiss.
|
||||
* Default is `PresentingViewControllerAnimationStyleNone`.
|
||||
* @return The animation style.
|
||||
*/
|
||||
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle;
|
||||
|
||||
/**
|
||||
* 自定义presenting ViewController转场动画,默认为nil
|
||||
* 注意:如果实现该方法并返回非空示例,要使该方法生效,`- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle`必须返回PresentingViewControllerAnimationStyleCustom
|
||||
*
|
||||
* custom presenting ViewController transition animation, default is nil
|
||||
* Note: If you implement this method and return non nil value, You must implement `- (PresentingViewControllerAnimationStyle)
|
||||
* presentingVCAnimationStyle` and return PresentingViewControllerAnimationStyleCustom
|
||||
*/
|
||||
- (nullable id<HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation;
|
||||
|
||||
#pragma mark - Content UI config
|
||||
|
||||
/**
|
||||
* 是否顶部圆角
|
||||
* Default is YES
|
||||
*/
|
||||
- (BOOL)shouldRoundTopCorners;
|
||||
|
||||
/**
|
||||
* 顶部圆角数值
|
||||
* Default is 8.0
|
||||
*/
|
||||
- (CGFloat)cornerRadius;
|
||||
|
||||
/**
|
||||
* presented content shadow
|
||||
* Default is None config
|
||||
*/
|
||||
- (HWPanModalShadow *)contentShadow;
|
||||
|
||||
#pragma mark - Indicator config
|
||||
|
||||
/**
|
||||
* 是否显示drag指示view
|
||||
* Default is YES,Default this method depend on `- (BOOL)shouldRoundTopCorners`
|
||||
*/
|
||||
- (BOOL)showDragIndicator;
|
||||
|
||||
/**
|
||||
* You can make the indicator customized. Just adopt `HWPanModalIndicatorProtocol`
|
||||
* Default this method return nil, Then the default indicator will be used.
|
||||
*/
|
||||
- (__kindof UIView<HWPanModalIndicatorProtocol> * _Nullable)customIndicatorView;
|
||||
|
||||
#pragma mark - Keyboard handle
|
||||
|
||||
/**
|
||||
* When there is text input view exists and becomeFirstResponder, will auto handle keyboard height.
|
||||
* Default is YES. You can disable it, handle it by yourself.
|
||||
*/
|
||||
- (BOOL)isAutoHandleKeyboardEnabled;
|
||||
|
||||
/**
|
||||
The offset that keyboard show from input view's bottom. It works when
|
||||
`isAutoHandleKeyboardEnabled` return YES.
|
||||
|
||||
@return offset, default is 5.
|
||||
*/
|
||||
- (CGFloat)keyboardOffsetFromInputView;
|
||||
|
||||
#pragma mark - Delegate
|
||||
|
||||
#pragma mark - Pan Gesture delegate
|
||||
|
||||
/**
|
||||
* 询问delegate是否需要使拖拽手势生效
|
||||
* 若返回NO,则禁用拖拽手势操作,即不能拖拽dismiss
|
||||
* 默认为YES
|
||||
*/
|
||||
- (BOOL)shouldRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
/**
|
||||
* 当pan recognizer状态为begin/changed时,通知delegate回调。
|
||||
* 当拖动presented View时,该方法会持续的回调
|
||||
* 默认实现为空
|
||||
*/
|
||||
- (void)willRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
/**
|
||||
* 内部处理完成拖动操作后触发此回调,此时view frame可能已经变化。
|
||||
* Framework has did finish logic for GestureRecognizer delegate. It will call many times when you darg.
|
||||
*/
|
||||
- (void)didRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
/**
|
||||
* 内部处理完成拖动操作后触发此回调,此时view frame可能已经变化。
|
||||
* Framework has did finish logic for GestureRecognizer delegate. It will call many times when you darg.
|
||||
*/
|
||||
- (void)didEndRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
/**
|
||||
* 是否优先执行dismiss拖拽手势,当存在panScrollable的情况下,如果此方法返回YES,则
|
||||
* dismiss手势生效,scrollView本身的滑动则不再生效。也就是说可以拖动Controller view,而scrollView没法拖动了。
|
||||
*
|
||||
* 例子:controller view上添加一个TableView,并铺满全屏,然后在controller view 顶部添加一个一定大小的viewA,
|
||||
* 这个时候会发现viewA有时候无法拖动,可以实现此delegate方法来解决
|
||||
```
|
||||
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
CGPoint loc = [panGestureRecognizer locationInView:self.view];
|
||||
// check whether user pan action in viewA
|
||||
if (CGRectContainsPoint(self.viewA.frame, loc)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
```
|
||||
* 默认为NO
|
||||
*
|
||||
* This delegate is useful when you want panGestureRecognizer has a high prioritize and
|
||||
* make scrollable does NOT scroll.
|
||||
* Example: You controller add a full size tableView, then add viewA on top of your controller view.
|
||||
* Now you find you can not drag the viewA, use this delegate to resolve problem.
|
||||
* Please refer to code above this comment.
|
||||
*
|
||||
* Default is NO
|
||||
*/
|
||||
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
|
||||
|
||||
/**
|
||||
* When you pan present controller to dismiss, and the view's y <= shortFormYPos,
|
||||
* this delegate method will be called.
|
||||
* @param percent 0 ~ 1, 1 means has dismissed
|
||||
*/
|
||||
- (void)panModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer dismissPercent:(CGFloat)percent;
|
||||
|
||||
#pragma mark - PresentationState change delegate
|
||||
/**
|
||||
* 是否应该变更panModal状态
|
||||
*/
|
||||
- (BOOL)shouldTransitionToState:(PresentationState)state;
|
||||
|
||||
/**
|
||||
* called when the Transition State will change.
|
||||
* 通知回调即将变更状态
|
||||
*/
|
||||
- (void)willTransitionToState:(PresentationState)state;
|
||||
|
||||
/**
|
||||
* PresentationState did change callback
|
||||
*/
|
||||
- (void)didChangeTransitionToState:(PresentationState)state;
|
||||
|
||||
#pragma mark - present delegate
|
||||
|
||||
/**
|
||||
* call when present transition will begin.
|
||||
*/
|
||||
- (void)panModalTransitionWillBegin;
|
||||
|
||||
/**
|
||||
* call when present transition did finish.
|
||||
*/
|
||||
- (void)panModalTransitionDidFinish;
|
||||
|
||||
/**
|
||||
* call when your custom presented vc has been added to the presentation container.
|
||||
*/
|
||||
- (void)presentedViewDidMoveToSuperView;
|
||||
|
||||
#pragma mark - Dismiss delegate
|
||||
/**
|
||||
* will dismiss
|
||||
*/
|
||||
- (void)panModalWillDismiss;
|
||||
|
||||
/**
|
||||
* Did finish dismissing
|
||||
*/
|
||||
- (void)panModalDidDismissed;
|
||||
|
||||
#pragma mark - DEPRECATED DECLARE
|
||||
|
||||
/**
|
||||
* 是否对presentingViewController做动画效果,默认该效果类似淘宝/京东购物车凹陷效果
|
||||
* 默认为NO
|
||||
*/
|
||||
- (BOOL)shouldAnimatePresentingVC DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.3.6, please use `- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle` replaced.");
|
||||
|
||||
/**
|
||||
* 背景透明度
|
||||
* Default is 0.7
|
||||
*/
|
||||
- (CGFloat)backgroundAlpha DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
|
||||
|
||||
/**
|
||||
* Blur background
|
||||
* This function can NOT coexist with backgroundAlpha
|
||||
* Default use backgroundAlpha, Once you set backgroundBlurRadius > 0, blur will work.
|
||||
* Recommend set the value 10 ~ 20.
|
||||
* @return blur radius
|
||||
*/
|
||||
- (CGFloat)backgroundBlurRadius DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
|
||||
|
||||
/**
|
||||
* blur background color
|
||||
* @return color, default is White Color.
|
||||
*/
|
||||
- (nonnull UIColor *)backgroundBlurColor DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
63
Pods/HWPanModal/Sources/Presentable/HWPanModalPresentationUpdateProtocol.h
generated
Normal file
63
Pods/HWPanModal/Sources/Presentable/HWPanModalPresentationUpdateProtocol.h
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// HWPanModalPresentationUpdateProtocol.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/17.
|
||||
//
|
||||
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
@class HWDimmedView;
|
||||
|
||||
@protocol HWPanModalPresentationUpdateProtocol <NSObject>
|
||||
/// background view, you can call `reloadConfig:` to update the UI.
|
||||
@property (nonatomic, readonly) HWDimmedView *hw_dimmedView;
|
||||
/// the root container which your custom VC's view to be added.
|
||||
@property (nonatomic, readonly) UIView *hw_rootContainerView;
|
||||
/// which view that your presented viewController's view has been added.
|
||||
@property (nonatomic, readonly) UIView *hw_contentView;
|
||||
/// current presentation State
|
||||
@property (nonatomic, readonly) PresentationState hw_presentationState;
|
||||
/**
|
||||
* force update pan modal State, short/long
|
||||
*/
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state NS_SWIFT_NAME(panModalTransitionTo(state:));
|
||||
|
||||
/**
|
||||
* force update pan modal State, short/long
|
||||
* @param state transition state
|
||||
* @param animated whether animate when set state
|
||||
*/
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated NS_SWIFT_NAME(panModalTransitionTo(state:animated:));
|
||||
|
||||
/**
|
||||
* When presented ViewController has a UIScrollView, Use This method to update UIScrollView contentOffset
|
||||
* Default it has animation
|
||||
*/
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset NS_SWIFT_NAME(panModalSetContentOffset(offset:));
|
||||
|
||||
/**
|
||||
* When presented ViewController has a UIScrollView, Use This method to update UIScrollView contentOffset
|
||||
* @param offset scrollView offset value
|
||||
* @param animated whether animate
|
||||
*/
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated NS_SWIFT_NAME(panModalSetContentOffset(offset:animated:));
|
||||
|
||||
/**
|
||||
* Note:if we present a NavigationController, and we want to pan screen edge to dismiss.
|
||||
* We MUST call this method when we PUSH/POP viewController.
|
||||
*
|
||||
*/
|
||||
- (void)hw_panModalSetNeedsLayoutUpdate NS_SWIFT_NAME(panModalSetNeedsLayoutUpdate());
|
||||
|
||||
/**
|
||||
* When you change the user touch event, like `allowsTouchEventsPassingThroughTransitionView`, you should call this method to make it work.
|
||||
* 更新用户行为,比如事件穿透
|
||||
*/
|
||||
- (void)hw_panModalUpdateUserHitBehavior NS_SWIFT_NAME(panModalUpdateUserHitBehavior());
|
||||
|
||||
/**
|
||||
* call this method to dismiss your presented VC directly
|
||||
*/
|
||||
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion NS_SWIFT_NAME(panModalDismissAnimated(animated:completion:));
|
||||
|
||||
@end
|
||||
38
Pods/HWPanModal/Sources/Presentable/UIViewController+LayoutHelper.h
generated
Normal file
38
Pods/HWPanModal/Sources/Presentable/UIViewController+LayoutHelper.h
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// UIViewController+LayoutHelper.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@class HWPanModalPresentationController;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol HWPanModalPresentableLayoutProtocol <NSObject>
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat topLayoutOffset;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat bottomLayoutOffset;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat shortFormYPos;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat mediumFormYPos;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat longFormYPos;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat bottomYPos;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Help presentedViewController/presented View to layout.
|
||||
*/
|
||||
@interface UIViewController (LayoutHelper) <HWPanModalPresentableLayoutProtocol>
|
||||
|
||||
@property (nullable, nonatomic, strong, readonly) HWPanModalPresentationController *hw_presentedVC;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
130
Pods/HWPanModal/Sources/Presentable/UIViewController+LayoutHelper.m
generated
Normal file
130
Pods/HWPanModal/Sources/Presentable/UIViewController+LayoutHelper.m
generated
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// UIViewController+LayoutHelper.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import "UIViewController+LayoutHelper.h"
|
||||
#import "HWPanModalPresentationController.h"
|
||||
#import "UIViewController+PanModalDefault.h"
|
||||
|
||||
@implementation UIViewController (LayoutHelper)
|
||||
|
||||
- (CGFloat)topLayoutOffset {
|
||||
if (@available(iOS 11, *)) {
|
||||
return [UIApplication sharedApplication].keyWindow.safeAreaInsets.top;
|
||||
} else {
|
||||
return [UIApplication sharedApplication].keyWindow.rootViewController.topLayoutGuide.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (CGFloat)bottomLayoutOffset {
|
||||
if (@available(iOS 11, *)) {
|
||||
return [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;
|
||||
} else {
|
||||
return [UIApplication sharedApplication].keyWindow.rootViewController.bottomLayoutGuide.length;
|
||||
}
|
||||
}
|
||||
|
||||
- (HWPanModalPresentationController *)hw_presentedVC {
|
||||
/*
|
||||
* Fix iOS13 bug: if we access presentationController before present VC, this will lead `modalPresentationStyle` not working.
|
||||
* refer to: https://github.com/HeathWang/HWPanModal/issues/27
|
||||
* Apple Doc: If you have not yet presented the current view controller, accessing this property creates a presentation controller based on the current value in the modalPresentationStyle property.
|
||||
*/
|
||||
|
||||
/**
|
||||
* fix bug: https://github.com/HeathWang/HWPanModal/issues/37
|
||||
*/
|
||||
if (self.presentingViewController) {
|
||||
return [self hw_getPanModalPresentationController];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (HWPanModalPresentationController *)hw_getPanModalPresentationController {
|
||||
UIViewController *ancestorsVC;
|
||||
|
||||
// seeking for the root presentation VC.
|
||||
if (self.splitViewController) {
|
||||
ancestorsVC = self.splitViewController;
|
||||
} else if (self.navigationController) {
|
||||
ancestorsVC = self.navigationController;
|
||||
} else if (self.tabBarController) {
|
||||
ancestorsVC = self.tabBarController;
|
||||
} else {
|
||||
ancestorsVC = self;
|
||||
}
|
||||
|
||||
if ([ancestorsVC.presentationController isMemberOfClass:HWPanModalPresentationController.class]) {
|
||||
return (HWPanModalPresentationController *) ancestorsVC.presentationController;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the short form Y postion
|
||||
|
||||
- Note: If voiceover is on, the `longFormYPos` is returned.
|
||||
We do not support short form when voiceover is on as it would make it difficult for user to navigate.
|
||||
*/
|
||||
- (CGFloat)shortFormYPos {
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
return self.longFormYPos;
|
||||
} else {
|
||||
CGFloat shortFormYPos = [self topMarginFromPanModalHeight:[self shortFormHeight]] + [self topOffset];
|
||||
return MAX(shortFormYPos, self.longFormYPos);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)mediumFormYPos {
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
return self.longFormYPos;
|
||||
} else {
|
||||
CGFloat mediumFormYPos = [self topMarginFromPanModalHeight:[self mediumFormHeight]] + [self topOffset];
|
||||
return MAX(mediumFormYPos, self.longFormYPos);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)longFormYPos {
|
||||
return MAX([self topMarginFromPanModalHeight:[self longFormHeight]], [self topMarginFromPanModalHeight:PanModalHeightMake(PanModalHeightTypeMax, 0)]) + [self topOffset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the container view for relative positioning as this view's frame
|
||||
is adjusted in PanModalPresentationController
|
||||
*/
|
||||
- (CGFloat)bottomYPos {
|
||||
if (self.hw_presentedVC.containerView) {
|
||||
return self.hw_presentedVC.containerView.bounds.size.height - [self topOffset];
|
||||
}
|
||||
return self.view.bounds.size.height;
|
||||
}
|
||||
|
||||
- (CGFloat)topMarginFromPanModalHeight:(PanModalHeight)panModalHeight {
|
||||
switch (panModalHeight.heightType) {
|
||||
case PanModalHeightTypeMax:
|
||||
return 0.0f;
|
||||
case PanModalHeightTypeMaxTopInset:
|
||||
return panModalHeight.height;
|
||||
case PanModalHeightTypeContent:
|
||||
return self.bottomYPos - (panModalHeight.height + self.bottomLayoutOffset);
|
||||
case PanModalHeightTypeContentIgnoringSafeArea:
|
||||
return self.bottomYPos - panModalHeight.height;
|
||||
case PanModalHeightTypeIntrinsic:
|
||||
{
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
CGSize targetSize = CGSizeMake(self.hw_presentedVC.containerView ? self.hw_presentedVC.containerView.bounds.size.width : [UIScreen mainScreen].bounds.size.width, UILayoutFittingCompressedSize.height);
|
||||
CGFloat intrinsicHeight = [self.view systemLayoutSizeFittingSize:targetSize].height;
|
||||
return self.bottomYPos - (intrinsicHeight + self.bottomLayoutOffset);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
18
Pods/HWPanModal/Sources/Presentable/UIViewController+PanModalDefault.h
generated
Normal file
18
Pods/HWPanModal/Sources/Presentable/UIViewController+PanModalDefault.h
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// UIViewController+PanModalDefault.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIViewController (PanModalDefault) <HWPanModalPresentable, HWPanModalPanGestureDelegate>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
253
Pods/HWPanModal/Sources/Presentable/UIViewController+PanModalDefault.m
generated
Normal file
253
Pods/HWPanModal/Sources/Presentable/UIViewController+PanModalDefault.m
generated
Normal file
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// UIViewController+PanModalDefault.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import "UIViewController+PanModalDefault.h"
|
||||
#import "UIViewController+LayoutHelper.h"
|
||||
|
||||
@implementation UIViewController (PanModalDefault)
|
||||
|
||||
- (UIScrollView *)panScrollable {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)topOffset {
|
||||
return self.topLayoutOffset + 21.f;
|
||||
}
|
||||
|
||||
- (PanModalHeight)shortFormHeight {
|
||||
return [self longFormHeight];
|
||||
}
|
||||
|
||||
- (PanModalHeight)mediumFormHeight {
|
||||
return [self longFormHeight];
|
||||
}
|
||||
|
||||
- (PanModalHeight)longFormHeight {
|
||||
if ([self panScrollable]) {
|
||||
[[self panScrollable] layoutIfNeeded];
|
||||
return PanModalHeightMake(PanModalHeightTypeContent, MAX([self panScrollable].contentSize.height, [self panScrollable].bounds.size.height));
|
||||
} else {
|
||||
return PanModalHeightMake(PanModalHeightTypeMax, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (PresentationState)originPresentationState {
|
||||
return PresentationStateShort;
|
||||
}
|
||||
|
||||
- (CGFloat)springDamping {
|
||||
return 0.8;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)transitionDuration {
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)dismissalDuration {
|
||||
return [self transitionDuration];
|
||||
}
|
||||
|
||||
- (UIViewAnimationOptions)transitionAnimationOptions {
|
||||
return UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState;
|
||||
}
|
||||
|
||||
- (CGFloat)backgroundAlpha {
|
||||
return 0.7;
|
||||
}
|
||||
|
||||
- (CGFloat)backgroundBlurRadius {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (nonnull UIColor *)backgroundBlurColor {
|
||||
return [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (HWBackgroundConfig *)backgroundConfig {
|
||||
return [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)scrollIndicatorInsets {
|
||||
CGFloat top = [self shouldRoundTopCorners] ? [self cornerRadius] : 0;
|
||||
return UIEdgeInsetsMake(top, 0, self.bottomLayoutOffset, 0);
|
||||
}
|
||||
|
||||
- (BOOL)showsScrollableVerticalScrollIndicator {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutoSetPanScrollContentInset {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)anchorModalToLongForm {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsExtendedPanScrolling {
|
||||
if ([self panScrollable]) {
|
||||
UIScrollView *scrollable = [self panScrollable];
|
||||
|
||||
/*
|
||||
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window.
|
||||
*/
|
||||
if (!scrollable.superview || !scrollable.window) return NO;
|
||||
|
||||
[scrollable layoutIfNeeded];
|
||||
return scrollable.contentSize.height > (scrollable.frame.size.height - self.bottomLayoutOffset);
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)allowsDragToDismiss {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)minVerticalVelocityToTriggerDismiss {
|
||||
return 300;
|
||||
}
|
||||
|
||||
- (BOOL)allowsTapBackgroundToDismiss {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowScreenEdgeInteractive {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss {
|
||||
return 500;
|
||||
}
|
||||
|
||||
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle {
|
||||
return PresentingViewControllerAnimationStyleNone;
|
||||
}
|
||||
|
||||
- (BOOL)shouldEnableAppearanceTransition {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAnimatePresentingVC {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id <HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isPanScrollEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isUserInteractionEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsPullDownWhenShortState {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isHapticFeedbackEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsTouchEventsPassingThroughTransitionView {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldRoundTopCorners {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)cornerRadius {
|
||||
return 8;
|
||||
}
|
||||
|
||||
- (HWPanModalShadow *)contentShadow {
|
||||
return [HWPanModalShadow panModalShadowNil];
|
||||
}
|
||||
|
||||
- (BOOL)showDragIndicator {
|
||||
if ([self allowsTouchEventsPassingThroughTransitionView]) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (nullable UIView <HWPanModalIndicatorProtocol> *)customIndicatorView {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isAutoHandleKeyboardEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)keyboardOffsetFromInputView {
|
||||
return 5;
|
||||
}
|
||||
|
||||
- (BOOL)shouldRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)willRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (void)didRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (void)didEndRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldTransitionToState:(PresentationState)state {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)willTransitionToState:(PresentationState)state {
|
||||
}
|
||||
|
||||
- (void)didChangeTransitionToState:(PresentationState)state {
|
||||
}
|
||||
|
||||
- (void)panModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer dismissPercent:(CGFloat)percent {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalWillDismiss {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalDidDismissed {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalTransitionWillBegin {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalTransitionDidFinish {
|
||||
|
||||
}
|
||||
|
||||
- (void)presentedViewDidMoveToSuperView {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
21
Pods/HWPanModal/Sources/Presentable/UIViewController+Presentation.h
generated
Normal file
21
Pods/HWPanModal/Sources/Presentable/UIViewController+Presentation.h
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// UIViewController+Presentation.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
#import <HWPanModal/HWPanModalPresentationUpdateProtocol.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The presented Controller can use the category to update UIPresentationController container.
|
||||
*/
|
||||
@interface UIViewController (Presentation) <HWPanModalPresentationUpdateProtocol>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
70
Pods/HWPanModal/Sources/Presentable/UIViewController+Presentation.m
generated
Normal file
70
Pods/HWPanModal/Sources/Presentable/UIViewController+Presentation.m
generated
Normal file
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// UIViewController+Presentation.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import "UIViewController+Presentation.h"
|
||||
#import "UIViewController+LayoutHelper.h"
|
||||
#import "HWPanModalPresentationController.h"
|
||||
|
||||
@interface UIViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIViewController (Presentation)
|
||||
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC transitionToState:state animated:YES];
|
||||
}
|
||||
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC transitionToState:state animated:animated];
|
||||
}
|
||||
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC setScrollableContentOffset:offset animated:animated];
|
||||
}
|
||||
|
||||
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC setScrollableContentOffset:offset animated:YES];
|
||||
}
|
||||
|
||||
- (void)hw_panModalSetNeedsLayoutUpdate {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC setNeedsLayoutUpdate];
|
||||
}
|
||||
|
||||
- (void)hw_panModalUpdateUserHitBehavior {
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC updateUserHitBehavior];
|
||||
}
|
||||
|
||||
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion{
|
||||
if (!self.hw_presentedVC) return;
|
||||
[self.hw_presentedVC dismissAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (HWDimmedView *)hw_dimmedView {
|
||||
return self.hw_presentedVC.backgroundView;
|
||||
}
|
||||
|
||||
- (UIView *)hw_rootContainerView {
|
||||
return self.hw_presentedVC.containerView;
|
||||
}
|
||||
|
||||
- (UIView *)hw_contentView {
|
||||
return self.hw_presentedVC.presentedView;
|
||||
}
|
||||
|
||||
- (PresentationState)hw_presentationState {
|
||||
return self.hw_presentedVC.currentPresentationState;
|
||||
}
|
||||
|
||||
@end
|
||||
47
Pods/HWPanModal/Sources/Presenter/HWPanModalPresenterProtocol.h
generated
Normal file
47
Pods/HWPanModal/Sources/Presenter/HWPanModalPresenterProtocol.h
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// HWPanModalPresenterProtocol.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
@class HWPanModalPresentationDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol HWPanModalPresenter <NSObject>
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL isPanModalPresented;
|
||||
/**
|
||||
* 这里我们将实现UIViewControllerTransitioningDelegate协议的delegate通过runtime存入到viewControllerToPresent中。
|
||||
* use runtime to store this prop to hw_presentedVC
|
||||
*/
|
||||
@property (nonnull, nonatomic, strong) HWPanModalPresentationDelegate *hw_panModalPresentationDelegate;
|
||||
|
||||
/**
|
||||
* Note: This method ONLY for iPad, like UIPopoverPresentationController.
|
||||
*/
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent
|
||||
sourceView:(nullable UIView *)sourceView
|
||||
sourceRect:(CGRect)rect;
|
||||
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent
|
||||
sourceView:(nullable UIView *)sourceView
|
||||
sourceRect:(CGRect)rect
|
||||
completion:(void (^ __nullable)(void))completion;
|
||||
|
||||
/**
|
||||
* Present the Controller from bottom.
|
||||
*/
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent;
|
||||
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent
|
||||
completion:(void (^ __nullable)(void))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
17
Pods/HWPanModal/Sources/Presenter/UIViewController+PanModalPresenter.h
generated
Normal file
17
Pods/HWPanModal/Sources/Presenter/UIViewController+PanModalPresenter.h
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// UIViewController+PanModalPresenter.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "HWPanModalPresenterProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIViewController (PanModalPresenter) <HWPanModalPresenter>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
64
Pods/HWPanModal/Sources/Presenter/UIViewController+PanModalPresenter.m
generated
Normal file
64
Pods/HWPanModal/Sources/Presenter/UIViewController+PanModalPresenter.m
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// UIViewController+PanModalPresenter.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/29.
|
||||
//
|
||||
|
||||
#import "UIViewController+PanModalPresenter.h"
|
||||
#import "HWPanModalPresentationDelegate.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation UIViewController (PanModalPresenter)
|
||||
|
||||
- (BOOL)isPanModalPresented {
|
||||
return [self.transitioningDelegate isKindOfClass:HWPanModalPresentationDelegate.class];
|
||||
}
|
||||
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent sourceView:(UIView *)sourceView sourceRect:(CGRect)rect completion:(void (^)(void))completion {
|
||||
|
||||
HWPanModalPresentationDelegate *delegate = [HWPanModalPresentationDelegate new];
|
||||
viewControllerToPresent.hw_panModalPresentationDelegate = delegate;
|
||||
|
||||
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad &&
|
||||
(sourceView && !CGRectEqualToRect(rect, CGRectZero))) {
|
||||
viewControllerToPresent.modalPresentationStyle = UIModalPresentationPopover;
|
||||
viewControllerToPresent.popoverPresentationController.sourceRect = rect;
|
||||
viewControllerToPresent.popoverPresentationController.sourceView = sourceView;
|
||||
viewControllerToPresent.popoverPresentationController.delegate = delegate;
|
||||
} else {
|
||||
|
||||
viewControllerToPresent.modalPresentationStyle = UIModalPresentationCustom;
|
||||
viewControllerToPresent.modalPresentationCapturesStatusBarAppearance = YES;
|
||||
viewControllerToPresent.transitioningDelegate = delegate;
|
||||
}
|
||||
|
||||
// fix for iOS 8 issue: the present action will delay.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self presentViewController:viewControllerToPresent animated:YES completion:completion];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)presentPanModal:(UIViewController <HWPanModalPresentable> *)viewControllerToPresent sourceView:(nullable UIView *)sourceView sourceRect:(CGRect)rect {
|
||||
[self presentPanModal:viewControllerToPresent sourceView:sourceView sourceRect:rect completion:nil];
|
||||
|
||||
}
|
||||
|
||||
- (void)presentPanModal:(UIViewController <HWPanModalPresentable> *)viewControllerToPresent {
|
||||
[self presentPanModal:viewControllerToPresent sourceView:nil sourceRect:CGRectZero];
|
||||
}
|
||||
|
||||
- (void)presentPanModal:(UIViewController<HWPanModalPresentable> *)viewControllerToPresent completion:(void (^)(void))completion {
|
||||
[self presentPanModal:viewControllerToPresent sourceView:nil sourceRect:CGRectZero completion:completion];
|
||||
}
|
||||
|
||||
- (HWPanModalPresentationDelegate *)hw_panModalPresentationDelegate {
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setHw_panModalPresentationDelegate:(HWPanModalPresentationDelegate *)hw_panModalPresentationDelegate {
|
||||
objc_setAssociatedObject(self, @selector(hw_panModalPresentationDelegate), hw_panModalPresentationDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
37
Pods/HWPanModal/Sources/View/HWBackgroundConfig.h
generated
Normal file
37
Pods/HWPanModal/Sources/View/HWBackgroundConfig.h
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// HWBackgroundConfig.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2020/4/17.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HWBackgroundBehavior) {
|
||||
HWBackgroundBehaviorDefault, // use background alpha
|
||||
HWBackgroundBehaviorSystemVisualEffect, // use system UIVisualEffect object
|
||||
HWBackgroundBehaviorCustomBlurEffect, // use custom blur
|
||||
};
|
||||
|
||||
@interface HWBackgroundConfig : NSObject
|
||||
|
||||
@property (nonatomic, assign) HWBackgroundBehavior backgroundBehavior;
|
||||
// ONLY works for backgroundBehavior = HWBackgroundBehaviorDefault
|
||||
@property (nonatomic, assign) CGFloat backgroundAlpha; // default is 0.7
|
||||
// ONLY works for backgroundBehavior = HWBackgroundBehaviorSystemVisualEffect
|
||||
@property (nonatomic, strong) UIVisualEffect *visualEffect; // default is UIBlurEffectStyleLight
|
||||
|
||||
// ONLY works for backgroundBehavior = HWBackgroundBehaviorCustomBlurEffect
|
||||
@property (nonatomic, strong) UIColor *blurTintColor; // default is white color
|
||||
@property (nonatomic, assign) CGFloat backgroundBlurRadius; // default is 10
|
||||
|
||||
- (instancetype)initWithBehavior:(HWBackgroundBehavior)backgroundBehavior;
|
||||
|
||||
+ (instancetype)configWithBehavior:(HWBackgroundBehavior)backgroundBehavior;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
57
Pods/HWPanModal/Sources/View/HWBackgroundConfig.m
generated
Normal file
57
Pods/HWPanModal/Sources/View/HWBackgroundConfig.m
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// HWBackgroundConfig.m
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2020/4/17.
|
||||
//
|
||||
|
||||
#import "HWBackgroundConfig.h"
|
||||
|
||||
@implementation HWBackgroundConfig
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundBehavior = HWBackgroundBehaviorDefault;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBehavior:(HWBackgroundBehavior)backgroundBehavior {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundBehavior = backgroundBehavior;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)configWithBehavior:(HWBackgroundBehavior)backgroundBehavior {
|
||||
return [[self alloc] initWithBehavior:backgroundBehavior];
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
|
||||
- (void)setBackgroundBehavior:(HWBackgroundBehavior)backgroundBehavior {
|
||||
_backgroundBehavior = backgroundBehavior;
|
||||
|
||||
switch (backgroundBehavior) {
|
||||
case HWBackgroundBehaviorSystemVisualEffect: {
|
||||
self.visualEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
|
||||
}
|
||||
break;
|
||||
case HWBackgroundBehaviorCustomBlurEffect: {
|
||||
self.backgroundBlurRadius = 10;
|
||||
self.blurTintColor = [UIColor whiteColor];
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
self.backgroundAlpha = 0.7;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
41
Pods/HWPanModal/Sources/View/HWDimmedView.h
generated
Normal file
41
Pods/HWPanModal/Sources/View/HWDimmedView.h
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// HWDimmedView.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@class HWBackgroundConfig;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, DimState) {
|
||||
DimStateMax,
|
||||
DimStateOff,
|
||||
DimStatePercent,
|
||||
};
|
||||
|
||||
typedef void(^didTap)(UITapGestureRecognizer *recognizer);
|
||||
|
||||
@interface HWDimmedView : UIView
|
||||
|
||||
@property (nonatomic, assign) DimState dimState;
|
||||
@property (nonatomic, assign) CGFloat percent;
|
||||
@property (nullable, nonatomic, copy) didTap tapBlock;
|
||||
@property (nullable, nonatomic, strong) UIColor *blurTintColor;
|
||||
|
||||
@property (nonatomic, readonly) HWBackgroundConfig *backgroundConfig;
|
||||
|
||||
/**
|
||||
* init with the max dim alpha & max blur radius.
|
||||
*/
|
||||
- (instancetype)initWithDimAlpha:(CGFloat)dimAlpha blurRadius:(CGFloat)blurRadius;
|
||||
|
||||
- (instancetype)initWithBackgroundConfig:(HWBackgroundConfig *)backgroundConfig;
|
||||
|
||||
- (void)reloadConfig:(HWBackgroundConfig *)backgroundConfig;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
202
Pods/HWPanModal/Sources/View/HWDimmedView.m
generated
Normal file
202
Pods/HWPanModal/Sources/View/HWDimmedView.m
generated
Normal file
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// HWDimmedView.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import "HWDimmedView.h"
|
||||
#import "HWVisualEffectView.h"
|
||||
#import "HWBackgroundConfig.h"
|
||||
|
||||
@interface HWDimmedView ()
|
||||
|
||||
@property (nonatomic, strong) UIView *backgroundView;
|
||||
@property (nonatomic, strong) HWVisualEffectView *blurView;
|
||||
@property (nonatomic, strong) HWBackgroundConfig *backgroundConfig;
|
||||
|
||||
@property (nonatomic, assign) CGFloat maxDimAlpha;
|
||||
@property (nonatomic, assign) CGFloat maxBlurRadius;
|
||||
@property (nonatomic, assign) CGFloat maxBlurTintAlpha;
|
||||
@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;
|
||||
@property (nonatomic, assign) BOOL isBlurMode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWDimmedView
|
||||
|
||||
- (instancetype)initWithDimAlpha:(CGFloat)dimAlpha blurRadius:(CGFloat)blurRadius {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
_maxBlurRadius = blurRadius;
|
||||
_maxDimAlpha = dimAlpha;
|
||||
[self commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_maxDimAlpha = 0.7;
|
||||
[self commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBackgroundConfig:(HWBackgroundConfig *)backgroundConfig {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.backgroundConfig = backgroundConfig;
|
||||
_maxDimAlpha = backgroundConfig.backgroundAlpha;
|
||||
_maxBlurRadius = backgroundConfig.backgroundBlurRadius;
|
||||
_blurTintColor = backgroundConfig.blurTintColor;
|
||||
|
||||
[self commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commonInit {
|
||||
_dimState = DimStateOff;
|
||||
_maxBlurTintAlpha = 0.5;
|
||||
// default, max alpha.
|
||||
_percent = 1;
|
||||
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapView)];
|
||||
[self addGestureRecognizer:_tapGestureRecognizer];
|
||||
|
||||
[self setupView];
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
self.isBlurMode = self.maxBlurRadius > 0 || self.backgroundConfig.visualEffect;
|
||||
if (self.isBlurMode) {
|
||||
[self addSubview:self.blurView];
|
||||
[self configBlurView];
|
||||
} else {
|
||||
[self addSubview:self.backgroundView];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - layout
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
// not call getter.
|
||||
_blurView.frame = self.bounds;
|
||||
_backgroundView.frame = self.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - touch action
|
||||
|
||||
- (void)didTapView {
|
||||
self.tapBlock ? self.tapBlock(self.tapGestureRecognizer) : nil;
|
||||
}
|
||||
|
||||
#pragma mark - public method
|
||||
|
||||
- (void)reloadConfig:(HWBackgroundConfig *)backgroundConfig {
|
||||
|
||||
for (UIView *view in self.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
self.backgroundConfig = backgroundConfig;
|
||||
_maxDimAlpha = backgroundConfig.backgroundAlpha;
|
||||
_maxBlurRadius = backgroundConfig.backgroundBlurRadius;
|
||||
_blurTintColor = backgroundConfig.blurTintColor;
|
||||
|
||||
[self setupView];
|
||||
|
||||
DimState state = self.dimState;
|
||||
self.dimState = state;
|
||||
}
|
||||
|
||||
#pragma mark - private method
|
||||
|
||||
- (void)updateAlpha {
|
||||
CGFloat alpha = 0;
|
||||
CGFloat blurRadius = 0;
|
||||
CGFloat blurTintAlpha = 0;
|
||||
|
||||
switch (self.dimState) {
|
||||
case DimStateMax:{
|
||||
alpha = self.maxDimAlpha;
|
||||
blurRadius = self.maxBlurRadius;
|
||||
blurTintAlpha = self.maxBlurTintAlpha;
|
||||
}
|
||||
break;
|
||||
case DimStatePercent: {
|
||||
CGFloat percent = MAX(0, MIN(1.0f, self.percent));
|
||||
alpha = self.maxDimAlpha * percent;
|
||||
blurRadius = self.maxBlurRadius * percent;
|
||||
blurTintAlpha = self.maxBlurTintAlpha * percent;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (self.isBlurMode) {
|
||||
if (self.backgroundConfig.visualEffect) return;
|
||||
|
||||
self.blurView.blurRadius = blurRadius;
|
||||
self.blurView.colorTintAlpha = blurTintAlpha;
|
||||
} else {
|
||||
self.backgroundView.alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configBlurView {
|
||||
if (self.backgroundConfig.visualEffect) {
|
||||
[_blurView updateBlurEffect:self.backgroundConfig.visualEffect];
|
||||
} else {
|
||||
_blurView.colorTint = [UIColor whiteColor];
|
||||
_blurView.colorTintAlpha = self.maxBlurTintAlpha;
|
||||
_blurView.userInteractionEnabled = NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
|
||||
- (void)setDimState:(DimState)dimState {
|
||||
_dimState = dimState;
|
||||
[self updateAlpha];
|
||||
}
|
||||
|
||||
- (void)setPercent:(CGFloat)percent {
|
||||
_percent = percent;
|
||||
[self updateAlpha];
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (UIView *)backgroundView {
|
||||
if (!_backgroundView) {
|
||||
_backgroundView = [UIView new];
|
||||
_backgroundView.userInteractionEnabled = NO;
|
||||
_backgroundView.alpha = 0;
|
||||
_backgroundView.backgroundColor = [UIColor blackColor];
|
||||
}
|
||||
return _backgroundView;
|
||||
}
|
||||
|
||||
- (HWVisualEffectView *)blurView {
|
||||
if (!_blurView) {
|
||||
_blurView = [HWVisualEffectView new];
|
||||
}
|
||||
return _blurView;
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
|
||||
- (void)setBlurTintColor:(UIColor *)blurTintColor {
|
||||
_blurTintColor = blurTintColor;
|
||||
_blurView.colorTint = _blurTintColor;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
34
Pods/HWPanModal/Sources/View/HWPanContainerView.h
generated
Normal file
34
Pods/HWPanModal/Sources/View/HWPanContainerView.h
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// HWPanContainerView.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWPanContainerView : UIView
|
||||
|
||||
/// the presented view should add to the content view.
|
||||
@property (nonatomic, strong, readonly) UIView *contentView;
|
||||
|
||||
- (instancetype)initWithPresentedView:(UIView *)presentedView frame:(CGRect)frame;
|
||||
|
||||
- (void)updateShadow:(UIColor *)shadowColor
|
||||
shadowRadius:(CGFloat)shadowRadius
|
||||
shadowOffset:(CGSize)shadowOffset
|
||||
shadowOpacity:(float)shadowOpacity;
|
||||
|
||||
- (void)clearShadow;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIView (PanContainer)
|
||||
|
||||
@property (nullable, nonatomic, strong, readonly) HWPanContainerView *panContainerView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
60
Pods/HWPanModal/Sources/View/HWPanContainerView.m
generated
Normal file
60
Pods/HWPanModal/Sources/View/HWPanContainerView.m
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// HWPanContainerView.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/4/26.
|
||||
//
|
||||
|
||||
#import "HWPanContainerView.h"
|
||||
|
||||
@interface HWPanContainerView ()
|
||||
|
||||
@property (nonatomic, strong) UIView *contentView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanContainerView
|
||||
|
||||
- (instancetype)initWithPresentedView:(UIView *)presentedView frame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_contentView = [UIView new];
|
||||
|
||||
_contentView.frame = self.bounds;
|
||||
[self addSubview:_contentView];
|
||||
[_contentView addSubview:presentedView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateShadow:(UIColor *)shadowColor shadowRadius:(CGFloat)shadowRadius shadowOffset:(CGSize)shadowOffset shadowOpacity:(float)shadowOpacity {
|
||||
|
||||
self.layer.shadowColor = shadowColor.CGColor;
|
||||
self.layer.shadowRadius = shadowRadius;
|
||||
self.layer.shadowOffset = shadowOffset;
|
||||
self.layer.shadowOpacity = shadowOpacity;
|
||||
}
|
||||
|
||||
- (void)clearShadow {
|
||||
self.layer.shadowColor = nil;
|
||||
self.layer.shadowRadius = 3.0;
|
||||
self.layer.shadowOffset = CGSizeZero;
|
||||
self.layer.shadowOpacity = 0.0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIView (PanContainer)
|
||||
|
||||
- (HWPanContainerView *)panContainerView {
|
||||
for (UIView *subview in self.subviews) {
|
||||
if ([subview isKindOfClass:HWPanContainerView.class]) {
|
||||
return (HWPanContainerView *) subview;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
19
Pods/HWPanModal/Sources/View/HWPanIndicatorView.h
generated
Normal file
19
Pods/HWPanModal/Sources/View/HWPanIndicatorView.h
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// HWPanIndicatorView.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/5/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalIndicatorProtocol.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWPanIndicatorView : UIView <HWPanModalIndicatorProtocol>
|
||||
|
||||
@property (nonnull, nonatomic, strong) UIColor *indicatorColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
114
Pods/HWPanModal/Sources/View/HWPanIndicatorView.m
generated
Normal file
114
Pods/HWPanModal/Sources/View/HWPanIndicatorView.m
generated
Normal file
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// HWPanIndicatorView.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/5/16.
|
||||
//
|
||||
|
||||
#import "UIView+HW_Frame.h"
|
||||
#import <HWPanModal/HWPanIndicatorView.h>
|
||||
|
||||
@interface HWPanIndicatorView ()
|
||||
|
||||
@property (nonatomic, strong) UIView *leftView;
|
||||
@property (nonatomic, strong) UIView *rightView;
|
||||
|
||||
@property (nonatomic, assign) HWIndicatorState state;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanIndicatorView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.leftView];
|
||||
[self addSubview:self.rightView];
|
||||
self.indicatorColor = [UIColor colorWithRed:0.792 green:0.788 blue:0.812 alpha:1.00];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)animate:(void (^)(void))animations {
|
||||
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:1 initialSpringVelocity:1 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:animations completion:^(BOOL finished) {
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalIndicatorProtocol
|
||||
|
||||
- (void)didChangeToState:(HWIndicatorState)state {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
- (CGSize)indicatorSize {
|
||||
return CGSizeMake(34, 13);
|
||||
}
|
||||
|
||||
- (void)setupSubviews {
|
||||
CGSize size = [self indicatorSize];
|
||||
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, size.width, size.height);
|
||||
CGFloat height = 5;
|
||||
CGFloat correction = height / 2;
|
||||
|
||||
self.leftView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame) / 2 + correction, height);
|
||||
self.leftView.hw_centerY = self.hw_height / 2;
|
||||
self.leftView.layer.cornerRadius = MIN(self.leftView.hw_width, self.leftView.hw_height) / 2;
|
||||
|
||||
self.rightView.frame = CGRectMake(CGRectGetWidth(self.frame) / 2 - correction, 0, CGRectGetWidth(self.frame) / 2 + correction, height);
|
||||
self.rightView.hw_centerY = self.hw_height / 2;
|
||||
self.rightView.layer.cornerRadius = MIN(self.rightView.hw_width, self.rightView.hw_height) / 2;
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (UIView *)leftView {
|
||||
if (!_leftView) {
|
||||
_leftView = [UIView new];
|
||||
}
|
||||
return _leftView;
|
||||
}
|
||||
|
||||
- (UIView *)rightView {
|
||||
if (!_rightView) {
|
||||
_rightView = [UIView new];
|
||||
}
|
||||
return _rightView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setter
|
||||
|
||||
- (void)setIndicatorColor:(UIColor *)indicatorColor {
|
||||
_indicatorColor = indicatorColor;
|
||||
self.leftView.backgroundColor = indicatorColor;
|
||||
self.rightView.backgroundColor = indicatorColor;
|
||||
}
|
||||
|
||||
- (void)setState:(HWIndicatorState)state {
|
||||
|
||||
_state = state;
|
||||
|
||||
switch (state) {
|
||||
case HWIndicatorStateNormal: {
|
||||
CGFloat angle = 20 * M_PI / 180;
|
||||
[self animate:^{
|
||||
self.leftView.transform = CGAffineTransformMakeRotation(angle);
|
||||
self.rightView.transform = CGAffineTransformMakeRotation(-angle);
|
||||
}];
|
||||
}
|
||||
break;
|
||||
case HWIndicatorStatePullDown: {
|
||||
[self animate:^{
|
||||
self.leftView.transform = CGAffineTransformIdentity;
|
||||
self.rightView.transform = CGAffineTransformIdentity;
|
||||
}];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
36
Pods/HWPanModal/Sources/View/HWPanModalIndicatorProtocol.h
generated
Normal file
36
Pods/HWPanModal/Sources/View/HWPanModalIndicatorProtocol.h
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// HWPanModalIndicatorProtocol.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/8/9.
|
||||
//
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HWIndicatorState) {
|
||||
HWIndicatorStateNormal NS_SWIFT_NAME(normal), // origin state
|
||||
HWIndicatorStatePullDown NS_SWIFT_NAME(pull), // drag down state
|
||||
};
|
||||
|
||||
static CGFloat const kIndicatorYOffset = 5;
|
||||
|
||||
@protocol HWPanModalIndicatorProtocol <NSObject>
|
||||
|
||||
/**
|
||||
* When user drags, the state will change.
|
||||
* You can change your UI here.
|
||||
* @param state The state when drag changed.
|
||||
*/
|
||||
- (void)didChangeToState:(HWIndicatorState)state;
|
||||
|
||||
/**
|
||||
* Tell the size of the indicator.
|
||||
*/
|
||||
- (CGSize)indicatorSize;
|
||||
|
||||
/**
|
||||
* You can layout your UI here if you need.
|
||||
* This method called when indicator added to super view
|
||||
*/
|
||||
- (void)setupSubviews;
|
||||
|
||||
@end
|
||||
|
||||
26
Pods/HWPanModal/Sources/View/HWPanModalShadow.h
generated
Normal file
26
Pods/HWPanModal/Sources/View/HWPanModalShadow.h
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// HWPanModalShadow.h
|
||||
// Pods
|
||||
//
|
||||
// Created by hb on 2023/8/3.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWPanModalShadow : NSObject
|
||||
|
||||
@property (nonatomic, strong) UIColor *shadowColor;
|
||||
@property (nonatomic, assign) CGFloat shadowRadius;
|
||||
@property (nonatomic, assign) CGSize shadowOffset;
|
||||
@property (nonatomic, assign) CGFloat shadowOpacity;
|
||||
|
||||
- (instancetype)initWithColor:(UIColor *)shadowColor shadowRadius:(CGFloat)shadowRadius shadowOffset:(CGSize)shadowOffset shadowOpacity:(CGFloat)shadowOpacity;
|
||||
|
||||
+ (instancetype)panModalShadowNil;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
28
Pods/HWPanModal/Sources/View/HWPanModalShadow.m
generated
Normal file
28
Pods/HWPanModal/Sources/View/HWPanModalShadow.m
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// HWPanModalShadow.m
|
||||
// Pods
|
||||
//
|
||||
// Created by hb on 2023/8/3.
|
||||
//
|
||||
|
||||
#import "HWPanModalShadow.h"
|
||||
|
||||
@implementation HWPanModalShadow
|
||||
|
||||
- (instancetype)initWithColor:(UIColor *)shadowColor shadowRadius:(CGFloat)shadowRadius shadowOffset:(CGSize)shadowOffset shadowOpacity:(CGFloat)shadowOpacity {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shadowColor = shadowColor;
|
||||
_shadowRadius = shadowRadius;
|
||||
_shadowOffset = shadowOffset;
|
||||
_shadowOpacity = shadowOpacity;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)panModalShadowNil {
|
||||
return [[HWPanModalShadow alloc] initWithColor:[UIColor clearColor] shadowRadius:0 shadowOffset:CGSizeZero shadowOpacity:0];
|
||||
}
|
||||
|
||||
@end
|
||||
39
Pods/HWPanModal/Sources/View/HWVisualEffectView.h
generated
Normal file
39
Pods/HWPanModal/Sources/View/HWVisualEffectView.h
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// HWVisualEffectView.h
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/6/14.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWVisualEffectView : UIVisualEffectView
|
||||
|
||||
/**
|
||||
* tint color
|
||||
* default is nil
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIColor *colorTint;
|
||||
/**
|
||||
* tint color alpha
|
||||
* default is 0.0
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat colorTintAlpha;
|
||||
/**
|
||||
* blur radius, change it to make blur affect
|
||||
* default is 0.0
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat blurRadius;
|
||||
/**
|
||||
* scale factor.
|
||||
* default is 1.0
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat scale;
|
||||
|
||||
- (void)updateBlurEffect:(UIVisualEffect *)effect;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
111
Pods/HWPanModal/Sources/View/HWVisualEffectView.m
generated
Normal file
111
Pods/HWPanModal/Sources/View/HWVisualEffectView.m
generated
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// HWVisualEffectView.m
|
||||
// HWPanModal
|
||||
//
|
||||
// Created by heath wang on 2019/6/14.
|
||||
//
|
||||
|
||||
#import "HWVisualEffectView.h"
|
||||
|
||||
NSString * const kInternalCustomBlurEffect = @"_UICustomBlurEffect";
|
||||
NSString * const kHWBlurEffectColorTintKey = @"colorTint";
|
||||
NSString * const kHWBlurEffectColorTintAlphaKey = @"colorTintAlpha";
|
||||
NSString * const kHWBlurEffectBlurRadiusKey = @"blurRadius";
|
||||
NSString * const kHWBlurEffectScaleKey = @"scale";
|
||||
|
||||
@interface HWVisualEffectView ()
|
||||
|
||||
@property (nonatomic, strong) UIVisualEffect *blurEffect;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWVisualEffectView
|
||||
|
||||
@synthesize colorTint = _colorTint;
|
||||
@synthesize colorTintAlpha = _colorTintAlpha;
|
||||
@synthesize blurRadius = _blurRadius;
|
||||
@synthesize scale = _scale;
|
||||
|
||||
#pragma mark - init
|
||||
|
||||
- (instancetype)initWithEffect:(UIVisualEffect *)effect {
|
||||
self = [super initWithEffect:effect];
|
||||
if (self) {
|
||||
self.scale = 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public method
|
||||
|
||||
- (void)updateBlurEffect:(UIVisualEffect *)effect {
|
||||
self.blurEffect = effect;
|
||||
self.effect = self.blurEffect;
|
||||
}
|
||||
|
||||
#pragma mark - private method
|
||||
|
||||
- (nullable id)__valueForKey:(NSString *)key {
|
||||
if (![NSStringFromClass(self.blurEffect.class) isEqualToString:kInternalCustomBlurEffect]) {
|
||||
return @(0);
|
||||
}
|
||||
return [self.blurEffect valueForKey:key];
|
||||
}
|
||||
|
||||
- (void)__setValue:(id)value forKey:(NSString *)key {
|
||||
if (![NSStringFromClass(self.blurEffect.class) isEqualToString:kInternalCustomBlurEffect]) {
|
||||
self.effect = self.blurEffect;
|
||||
return;
|
||||
}
|
||||
[self.blurEffect setValue:value forKey:key];
|
||||
self.effect = self.blurEffect;
|
||||
}
|
||||
|
||||
#pragma mark - Getter & Setter
|
||||
|
||||
- (UIVisualEffect *)blurEffect {
|
||||
if (!_blurEffect) {
|
||||
if (NSClassFromString(kInternalCustomBlurEffect)) {
|
||||
_blurEffect = (UIBlurEffect *)[NSClassFromString(@"_UICustomBlurEffect") new];
|
||||
} else {
|
||||
_blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
|
||||
}
|
||||
}
|
||||
|
||||
return _blurEffect;
|
||||
}
|
||||
|
||||
- (UIColor *)colorTint {
|
||||
return [self __valueForKey:kHWBlurEffectColorTintKey];
|
||||
}
|
||||
|
||||
- (void)setColorTint:(UIColor *)colorTint {
|
||||
[self __setValue:colorTint forKey:kHWBlurEffectColorTintKey];
|
||||
}
|
||||
|
||||
- (CGFloat)colorTintAlpha {
|
||||
return ((NSNumber *)[self __valueForKey:kHWBlurEffectColorTintAlphaKey]).floatValue;
|
||||
}
|
||||
|
||||
- (void)setColorTintAlpha:(CGFloat)colorTintAlpha {
|
||||
[self __setValue:@(colorTintAlpha) forKey:kHWBlurEffectColorTintAlphaKey];
|
||||
}
|
||||
|
||||
- (CGFloat)blurRadius {
|
||||
return ((NSNumber *)[self __valueForKey:kHWBlurEffectBlurRadiusKey]).floatValue;
|
||||
}
|
||||
|
||||
- (void)setBlurRadius:(CGFloat)blurRadius {
|
||||
[self __setValue:@(blurRadius) forKey:kHWBlurEffectBlurRadiusKey];
|
||||
}
|
||||
|
||||
- (CGFloat)scale {
|
||||
return ((NSNumber *)[self __valueForKey:kHWBlurEffectScaleKey]).floatValue;
|
||||
}
|
||||
|
||||
- (void)setScale:(CGFloat)scale {
|
||||
[self __setValue:@(scale) forKey:kHWBlurEffectScaleKey];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
39
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContainerView.h
generated
Normal file
39
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContainerView.h
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// HWPanModalContainerView.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/17.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
|
||||
@class HWPanModalContentView;
|
||||
@class HWDimmedView;
|
||||
@class HWPanContainerView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HWPanModalContainerView : UIView
|
||||
|
||||
@property (nonatomic, readonly) HWDimmedView *backgroundView;
|
||||
@property (readonly) HWPanContainerView *panContainerView;
|
||||
@property (nonatomic, readonly) PresentationState currentPresentationState;
|
||||
|
||||
- (instancetype)initWithPresentingView:(UIView *)presentingView contentView:(HWPanModalContentView<HWPanModalPresentable> *)contentView;
|
||||
|
||||
- (void)show;
|
||||
|
||||
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion;
|
||||
|
||||
- (void)setNeedsLayoutUpdate;
|
||||
|
||||
- (void)updateUserHitBehavior;
|
||||
|
||||
- (void)transitionToState:(PresentationState)state animated:(BOOL)animated;
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
546
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContainerView.m
generated
Normal file
546
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContainerView.m
generated
Normal file
@@ -0,0 +1,546 @@
|
||||
//
|
||||
// HWPanModalContainerView.m
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/17.
|
||||
//
|
||||
|
||||
#import "HWPanModalContainerView.h"
|
||||
#import "HWPanModalContentView.h"
|
||||
#import "HWPanModalPresentableHandler.h"
|
||||
#import "HWDimmedView.h"
|
||||
#import "HWPanContainerView.h"
|
||||
#import "UIView+HW_Frame.h"
|
||||
#import "HWPanIndicatorView.h"
|
||||
#import "HWPanModalAnimator.h"
|
||||
|
||||
@interface HWPanModalContainerView () <HWPanModalPresentableHandlerDelegate, HWPanModalPresentableHandlerDataSource>
|
||||
|
||||
@property (nonatomic, strong) HWPanModalContentView<HWPanModalPresentable> *contentView;
|
||||
@property (nonatomic, weak) UIView *presentingView;
|
||||
|
||||
@property (nonatomic, strong) HWPanModalPresentableHandler *handler;
|
||||
|
||||
// 判断弹出的view是否在做动画
|
||||
@property (nonatomic, assign) BOOL isPresentedViewAnimating;
|
||||
@property (nonatomic, assign) PresentationState currentPresentationState;
|
||||
@property (nonatomic, assign) BOOL isPresenting;
|
||||
@property (nonatomic, assign) BOOL isDismissing;
|
||||
|
||||
// view
|
||||
@property (nonatomic, strong) HWDimmedView *backgroundView;
|
||||
@property (nonatomic, strong) HWPanContainerView *panContainerView;
|
||||
@property (nonatomic, strong) UIView<HWPanModalIndicatorProtocol> *dragIndicatorView;
|
||||
|
||||
@property (nonatomic, copy) void(^animationBlock)(void);
|
||||
|
||||
@property (nullable, nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator API_AVAILABLE(ios(10.0));
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanModalContainerView
|
||||
|
||||
- (instancetype)initWithPresentingView:(UIView *)presentingView contentView:(HWPanModalContentView<HWPanModalPresentable> *)contentView {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_presentingView = presentingView;
|
||||
_contentView = contentView;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)show {
|
||||
[self prepare];
|
||||
[self presentAnimationWillBegin];
|
||||
[self beginPresentAnimation];
|
||||
}
|
||||
|
||||
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion {
|
||||
if (flag) {
|
||||
self.animationBlock = completion;
|
||||
[self dismiss:NO mode:PanModalInteractiveModeNone];
|
||||
} else {
|
||||
self.isDismissing = YES;
|
||||
[[self presentable] panModalWillDismiss];
|
||||
[self removeFromSuperview];
|
||||
[[self presentable] panModalDidDismissed];
|
||||
|
||||
completion ? completion() : nil;
|
||||
self.isDismissing = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)prepare {
|
||||
[self.presentingView addSubview:self];
|
||||
self.frame = self.presentingView.bounds;
|
||||
|
||||
_handler = [[HWPanModalPresentableHandler alloc] initWithPresentable:self.contentView];
|
||||
_handler.delegate = self;
|
||||
_handler.dataSource = self;
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
_feedbackGenerator = [UISelectionFeedbackGenerator new];
|
||||
[_feedbackGenerator prepare];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willMoveToSuperview:(UIView *)newSuperview {
|
||||
[super willMoveToSuperview:newSuperview];
|
||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
[self.superview removeObserver:self forKeyPath:@"frame"];
|
||||
[newSuperview addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
if (object == self.presentingView && [keyPath isEqualToString:@"frame"]) {
|
||||
self.frame = self.presentingView.bounds;
|
||||
[self setNeedsLayoutUpdate];
|
||||
[self updateDragIndicatorViewFrame];
|
||||
[self.contentView hw_panModalTransitionTo:self.contentView.hw_presentationState animated:NO];
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentAnimationWillBegin {
|
||||
[[self presentable] panModalTransitionWillBegin];
|
||||
[self layoutBackgroundView];
|
||||
|
||||
if ([[self presentable] originPresentationState] == PresentationStateLong) {
|
||||
self.currentPresentationState = PresentationStateLong;
|
||||
} else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
|
||||
self.currentPresentationState = PresentationStateMedium;
|
||||
}
|
||||
|
||||
[self addSubview:self.panContainerView];
|
||||
[self layoutPresentedView];
|
||||
|
||||
[self.handler configureScrollViewInsets];
|
||||
[[self presentable] presentedViewDidMoveToSuperView];
|
||||
}
|
||||
|
||||
- (void)beginPresentAnimation {
|
||||
self.isPresenting = YES;
|
||||
CGFloat yPos = self.contentView.shortFormYPos;
|
||||
if ([[self presentable] originPresentationState] == PresentationStateLong) {
|
||||
yPos = self.contentView.longFormYPos;
|
||||
} else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
|
||||
yPos = self.contentView.mediumFormYPos;
|
||||
}
|
||||
|
||||
// refresh layout
|
||||
[self configureViewLayout];
|
||||
[self adjustPresentedViewFrame];
|
||||
|
||||
self.panContainerView.hw_top = self.hw_height;
|
||||
|
||||
if ([[self presentable] isHapticFeedbackEnabled]) {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
[self.feedbackGenerator selectionChanged];
|
||||
}
|
||||
}
|
||||
|
||||
[HWPanModalAnimator animate:^{
|
||||
self.panContainerView.hw_top = yPos;
|
||||
self.backgroundView.dimState = DimStateMax;
|
||||
} config:[self presentable] completion:^(BOOL completion) {
|
||||
self.isPresenting = NO;
|
||||
[[self presentable] panModalTransitionDidFinish];
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
self.feedbackGenerator = nil;
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self configureViewLayout];
|
||||
}
|
||||
|
||||
#pragma mark - public method
|
||||
|
||||
- (void)setNeedsLayoutUpdate {
|
||||
|
||||
[self configureViewLayout];
|
||||
[self updateBackgroundColor];
|
||||
[self.handler observeScrollable];
|
||||
[self adjustPresentedViewFrame];
|
||||
[self.handler configureScrollViewInsets];
|
||||
|
||||
[self updateContainerViewShadow];
|
||||
[self updateDragIndicatorView];
|
||||
[self updateRoundedCorners];
|
||||
}
|
||||
|
||||
- (void)updateUserHitBehavior {
|
||||
[self checkBackgroundViewEventPass];
|
||||
[self checkPanGestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)transitionToState:(PresentationState)state animated:(BOOL)animated {
|
||||
|
||||
if (![self.presentable shouldTransitionToState:state]) return;
|
||||
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
[self.presentable willTransitionToState:state];
|
||||
|
||||
switch (state) {
|
||||
case PresentationStateLong: {
|
||||
[self snapToYPos:self.handler.longFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
case PresentationStateMedium: {
|
||||
[self snapToYPos:self.handler.mediumFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
case PresentationStateShort: {
|
||||
[self snapToYPos:self.handler.shortFormYPosition animated:animated];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
self.currentPresentationState = state;
|
||||
[[self presentable] didChangeTransitionToState:state];
|
||||
}
|
||||
|
||||
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
|
||||
[self.handler setScrollableContentOffset:offset animated:animated];
|
||||
}
|
||||
|
||||
#pragma mark - layout
|
||||
|
||||
- (void)adjustPresentedViewFrame {
|
||||
CGRect frame = self.frame;
|
||||
CGSize size = CGSizeMake(CGRectGetWidth(frame), CGRectGetHeight(frame) - self.handler.anchoredYPosition);
|
||||
|
||||
self.panContainerView.hw_size = frame.size;
|
||||
self.panContainerView.contentView.frame = CGRectMake(0, 0, size.width, size.height);
|
||||
self.contentView.frame = self.panContainerView.contentView.bounds;
|
||||
[self.contentView setNeedsLayout];
|
||||
[self.contentView layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)configureViewLayout {
|
||||
|
||||
[self.handler configureViewLayout];
|
||||
self.userInteractionEnabled = [[self presentable] isUserInteractionEnabled];
|
||||
}
|
||||
|
||||
- (void)layoutBackgroundView {
|
||||
[self addSubview:self.backgroundView];
|
||||
[self updateBackgroundColor];
|
||||
self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSArray *hCons = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
|
||||
NSArray *vCons = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
|
||||
[NSLayoutConstraint activateConstraints:hCons];
|
||||
[NSLayoutConstraint activateConstraints:vCons];
|
||||
}
|
||||
|
||||
- (void)updateBackgroundColor {
|
||||
self.backgroundView.blurTintColor = [self.presentable backgroundConfig].blurTintColor;
|
||||
}
|
||||
|
||||
- (void)layoutPresentedView {
|
||||
if (!self.presentable)
|
||||
return;
|
||||
|
||||
self.handler.presentedView = self.panContainerView;
|
||||
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
[self.panContainerView addGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
} else {
|
||||
[self addGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
}
|
||||
|
||||
[self setNeedsLayoutUpdate];
|
||||
[self adjustPanContainerBackgroundColor];
|
||||
}
|
||||
|
||||
- (void)adjustPanContainerBackgroundColor {
|
||||
self.panContainerView.contentView.backgroundColor = self.contentView.backgroundColor ? : [self.presentable panScrollable].backgroundColor;
|
||||
}
|
||||
|
||||
- (void)updateDragIndicatorView {
|
||||
if ([self.presentable showDragIndicator]) {
|
||||
[self addDragIndicatorView];
|
||||
} else {
|
||||
self.dragIndicatorView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addDragIndicatorView {
|
||||
// if has been add, won't update it.
|
||||
self.dragIndicatorView.hidden = NO;
|
||||
|
||||
if (self.dragIndicatorView.superview == self.panContainerView) {
|
||||
[self updateDragIndicatorViewFrame];
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
return;
|
||||
}
|
||||
|
||||
self.handler.dragIndicatorView = self.dragIndicatorView;
|
||||
[self.panContainerView addSubview:self.dragIndicatorView];
|
||||
[self updateDragIndicatorViewFrame];
|
||||
|
||||
[self.dragIndicatorView setupSubviews];
|
||||
[self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
|
||||
}
|
||||
|
||||
- (void)updateDragIndicatorViewFrame {
|
||||
CGSize indicatorSize = [self.dragIndicatorView indicatorSize];
|
||||
self.dragIndicatorView.frame = CGRectMake((self.panContainerView.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
|
||||
}
|
||||
|
||||
- (void)updateContainerViewShadow {
|
||||
HWPanModalShadow *shadow = [[self presentable] contentShadow];
|
||||
if (shadow.shadowColor) {
|
||||
[self.panContainerView updateShadow:shadow.shadowColor shadowRadius:shadow.shadowRadius shadowOffset:shadow.shadowOffset shadowOpacity:shadow.shadowOpacity];
|
||||
} else {
|
||||
[self.panContainerView clearShadow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateRoundedCorners {
|
||||
if ([self.presentable shouldRoundTopCorners]) {
|
||||
[self addRoundedCornersToView:self.panContainerView.contentView];
|
||||
} else {
|
||||
[self resetRoundedCornersToView:self.panContainerView.contentView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addRoundedCornersToView:(UIView *)view {
|
||||
CGFloat radius = [self.presentable cornerRadius];
|
||||
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerTopLeft cornerRadii:CGSizeMake(radius, radius)];
|
||||
|
||||
CAShapeLayer *mask = [CAShapeLayer new];
|
||||
mask.path = bezierPath.CGPath;
|
||||
view.layer.mask = mask;
|
||||
|
||||
// 提高性能
|
||||
view.layer.shouldRasterize = YES;
|
||||
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
|
||||
- (void)resetRoundedCornersToView:(UIView *)view {
|
||||
view.layer.mask = nil;
|
||||
view.layer.shouldRasterize = NO;
|
||||
}
|
||||
|
||||
- (void)snapToYPos:(CGFloat)yPos animated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[HWPanModalAnimator animate:^{
|
||||
self.isPresentedViewAnimating = YES;
|
||||
[self adjustToYPos:yPos];
|
||||
} config:self.presentable completion:^(BOOL completion) {
|
||||
self.isPresentedViewAnimating = NO;
|
||||
}];
|
||||
} else {
|
||||
[self adjustToYPos:yPos];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)adjustToYPos:(CGFloat)yPos {
|
||||
self.panContainerView.hw_top = MAX(yPos, self.handler.anchoredYPosition);
|
||||
|
||||
// change dim background starting from shortFormYPosition.
|
||||
if (self.panContainerView.frame.origin.y >= self.handler.shortFormYPosition) {
|
||||
|
||||
CGFloat yDistanceFromShortForm = self.panContainerView.frame.origin.y - self.handler.shortFormYPosition;
|
||||
CGFloat bottomHeight = self.hw_height - self.handler.shortFormYPosition;
|
||||
CGFloat percent = yDistanceFromShortForm / bottomHeight;
|
||||
self.backgroundView.dimState = DimStatePercent;
|
||||
self.backgroundView.percent = 1 - percent;
|
||||
|
||||
[self.presentable panModalGestureRecognizer:self.handler.panGestureRecognizer dismissPercent:MIN(percent, 1)];
|
||||
|
||||
} else {
|
||||
self.backgroundView.dimState = DimStateMax;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentableHandlerDelegate
|
||||
|
||||
- (void)adjustPresentableYPos:(CGFloat)yPos {
|
||||
[self adjustToYPos:yPos];
|
||||
}
|
||||
|
||||
- (void)presentableTransitionToState:(PresentationState)state {
|
||||
[self transitionToState:state animated:YES];
|
||||
}
|
||||
|
||||
- (PresentationState)getCurrentPresentationState {
|
||||
return self.currentPresentationState;
|
||||
}
|
||||
|
||||
- (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
|
||||
self.handler.panGestureRecognizer.enabled = NO;
|
||||
self.isDismissing = YES;
|
||||
|
||||
[[self presentable] panModalWillDismiss];
|
||||
|
||||
[HWPanModalAnimator animate:^{
|
||||
self.panContainerView.hw_top = CGRectGetHeight(self.bounds);
|
||||
self.backgroundView.dimState = DimStateOff;
|
||||
self.dragIndicatorView.alpha = 0;
|
||||
} config:[self presentable] completion:^(BOOL completion) {
|
||||
[self removeFromSuperview];
|
||||
[[self presentable] panModalDidDismissed];
|
||||
self.animationBlock ? self.animationBlock() : nil;
|
||||
self.isDismissing = NO;
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentableHandlerDataSource
|
||||
|
||||
- (CGSize)containerSize {
|
||||
return self.presentingView.bounds.size;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingDismissed {
|
||||
return self.isDismissing;
|
||||
}
|
||||
|
||||
- (BOOL)isBeingPresented {
|
||||
return self.isPresenting;
|
||||
}
|
||||
|
||||
- (BOOL)isFormPositionAnimating {
|
||||
return self.isPresentedViewAnimating;
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedViewAnchored {
|
||||
|
||||
if (![[self presentable] shouldRespondToPanModalGestureRecognizer:self.handler.panGestureRecognizer]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!self.isPresentedViewAnimating && self.handler.extendsPanScrolling && (CGRectGetMinY(self.panContainerView.frame) <= self.handler.anchoredYPosition || HW_TWO_FLOAT_IS_EQUAL(CGRectGetMinY(self.panContainerView.frame), self.handler.anchoredYPosition))) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - event handle
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
|
||||
if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self pointInside:point withEvent:event]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
BOOL eventThrough = [[self presentable] allowsTouchEventsPassingThroughTransitionView];
|
||||
if (eventThrough) {
|
||||
CGPoint convertedPoint = [self.panContainerView convertPoint:point fromView:self];
|
||||
if (CGRectGetWidth(self.panContainerView.frame) >= convertedPoint.x &&
|
||||
convertedPoint.x > 0 &&
|
||||
CGRectGetHeight(self.panContainerView.frame) >= convertedPoint.y &&
|
||||
convertedPoint.y > 0) {
|
||||
return [super hitTest:point withEvent:event];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkBackgroundViewEventPass {
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
self.backgroundView.userInteractionEnabled = NO;
|
||||
self.backgroundView.tapBlock = nil;
|
||||
} else {
|
||||
self.backgroundView.userInteractionEnabled = YES;
|
||||
__weak typeof(self) wkSelf = self;
|
||||
self.backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
|
||||
if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
|
||||
[wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkPanGestureRecognizer {
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
[self removeGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
[self.panContainerView addGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
} else {
|
||||
[self.panContainerView removeGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
[self addGestureRecognizer:self.handler.panGestureRecognizer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getter
|
||||
|
||||
- (id<HWPanModalPresentable>)presentable {
|
||||
if ([self.contentView conformsToProtocol:@protocol(HWPanModalPresentable)]) {
|
||||
return self.contentView;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HWDimmedView *)backgroundView {
|
||||
if (!_backgroundView) {
|
||||
if (self.presentable) {
|
||||
_backgroundView = [[HWDimmedView alloc] initWithBackgroundConfig:[self.presentable backgroundConfig]];
|
||||
} else {
|
||||
_backgroundView = [[HWDimmedView alloc] init];
|
||||
}
|
||||
|
||||
if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
|
||||
_backgroundView.userInteractionEnabled = NO;
|
||||
} else {
|
||||
__weak typeof(self) wkSelf = self;
|
||||
_backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
|
||||
if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
|
||||
[wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return _backgroundView;
|
||||
}
|
||||
|
||||
- (HWPanContainerView *)panContainerView {
|
||||
if (!_panContainerView) {
|
||||
_panContainerView = [[HWPanContainerView alloc] initWithPresentedView:self.contentView frame:self.bounds];
|
||||
}
|
||||
|
||||
return _panContainerView;
|
||||
}
|
||||
|
||||
- (UIView<HWPanModalIndicatorProtocol> *)dragIndicatorView {
|
||||
|
||||
if (!_dragIndicatorView) {
|
||||
if ([self presentable] &&
|
||||
[[self presentable] respondsToSelector:@selector(customIndicatorView)] &&
|
||||
[[self presentable] customIndicatorView] != nil) {
|
||||
_dragIndicatorView = [[self presentable] customIndicatorView];
|
||||
// set the indicator size first in case `setupSubviews` can Not get the right size.
|
||||
_dragIndicatorView.hw_size = [[[self presentable] customIndicatorView] indicatorSize];
|
||||
} else {
|
||||
_dragIndicatorView = [HWPanIndicatorView new];
|
||||
}
|
||||
}
|
||||
|
||||
return _dragIndicatorView;
|
||||
}
|
||||
|
||||
@end
|
||||
36
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContentView.h
generated
Normal file
36
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContentView.h
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// HWPanModalContentView.h
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/17.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HWPanModal/HWPanModalPresentable.h>
|
||||
#import <HWPanModal/HWPanModalPresentationUpdateProtocol.h>
|
||||
#import <HWPanModal/UIViewController+LayoutHelper.h>
|
||||
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
|
||||
|
||||
@class HWPanModalContainerView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// when use `HWPanModalContentView`, you should take care of the safe area by yourself.
|
||||
@interface HWPanModalContentView : UIView <HWPanModalPresentable, HWPanModalPanGestureDelegate, HWPanModalPresentationUpdateProtocol, HWPanModalPresentableLayoutProtocol>
|
||||
|
||||
/**
|
||||
* present in the target view
|
||||
* @param view The view which present to. If the view is nil, will use UIWindow's keyWindow.
|
||||
*/
|
||||
- (void)presentInView:(nullable UIView *)view;
|
||||
|
||||
/**
|
||||
* call this method to dismiss contentView directly.
|
||||
* @param flag should animate flag
|
||||
* @param completion dismiss completion block
|
||||
*/
|
||||
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
414
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContentView.m
generated
Normal file
414
Pods/HWPanModal/Sources/View/PanModal/HWPanModalContentView.m
generated
Normal file
@@ -0,0 +1,414 @@
|
||||
//
|
||||
// HWPanModalContentView.m
|
||||
// Pods
|
||||
//
|
||||
// Created by heath wang on 2019/10/17.
|
||||
//
|
||||
|
||||
#import "HWPanModalContentView.h"
|
||||
#import "HWPanModalContainerView.h"
|
||||
|
||||
@interface HWPanModalContentView ()
|
||||
|
||||
@property (nonatomic, weak) HWPanModalContainerView *containerView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HWPanModalContentView
|
||||
|
||||
#pragma mark - public method
|
||||
|
||||
- (void)presentInView:(UIView *)view {
|
||||
if (!view) {
|
||||
view = [self findKeyWindow];
|
||||
}
|
||||
HWPanModalContainerView *containerView = [[HWPanModalContainerView alloc] initWithPresentingView:view contentView:self];
|
||||
[containerView show];
|
||||
}
|
||||
|
||||
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion {
|
||||
[self.containerView dismissAnimated:flag completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentationUpdateProtocol
|
||||
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state {
|
||||
[self.containerView transitionToState:state animated:YES];
|
||||
}
|
||||
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset {
|
||||
[self.containerView setScrollableContentOffset:offset animated:YES];
|
||||
}
|
||||
|
||||
- (void)hw_panModalSetNeedsLayoutUpdate {
|
||||
[self.containerView setNeedsLayoutUpdate];
|
||||
}
|
||||
|
||||
- (void)hw_panModalUpdateUserHitBehavior {
|
||||
[self.containerView updateUserHitBehavior];
|
||||
}
|
||||
|
||||
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated {
|
||||
[self.containerView transitionToState:state animated:animated];
|
||||
}
|
||||
|
||||
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated {
|
||||
[self.containerView setScrollableContentOffset:offset animated:animated];
|
||||
}
|
||||
|
||||
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion {
|
||||
[self dismissAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (HWDimmedView *)hw_dimmedView {
|
||||
return self.containerView.backgroundView;
|
||||
}
|
||||
|
||||
- (UIView *)hw_rootContainerView {
|
||||
return self.containerView;
|
||||
}
|
||||
|
||||
- (UIView *)hw_contentView {
|
||||
return (UIView *)self.containerView.panContainerView;
|
||||
}
|
||||
|
||||
- (PresentationState)hw_presentationState {
|
||||
return self.containerView.currentPresentationState;
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentable
|
||||
|
||||
- (UIScrollView *)panScrollable {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)topOffset {
|
||||
return self.topLayoutOffset + 21.f;
|
||||
}
|
||||
|
||||
- (PanModalHeight)shortFormHeight {
|
||||
return [self longFormHeight];
|
||||
}
|
||||
|
||||
- (PanModalHeight)mediumFormHeight {
|
||||
return [self longFormHeight];
|
||||
}
|
||||
|
||||
- (PanModalHeight)longFormHeight {
|
||||
if ([self panScrollable]) {
|
||||
[[self panScrollable] layoutIfNeeded];
|
||||
return PanModalHeightMake(PanModalHeightTypeContent, MAX([self panScrollable].contentSize.height, [self panScrollable].bounds.size.height));
|
||||
} else {
|
||||
return PanModalHeightMake(PanModalHeightTypeMax, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (PresentationState)originPresentationState {
|
||||
return PresentationStateShort;
|
||||
}
|
||||
|
||||
- (CGFloat)springDamping {
|
||||
return 0.8;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)transitionDuration {
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)dismissalDuration {
|
||||
return [self transitionDuration];
|
||||
}
|
||||
|
||||
- (UIViewAnimationOptions)transitionAnimationOptions {
|
||||
return UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState;
|
||||
}
|
||||
|
||||
- (CGFloat)backgroundAlpha {
|
||||
return 0.7;
|
||||
}
|
||||
|
||||
- (CGFloat)backgroundBlurRadius {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (nonnull UIColor *)backgroundBlurColor {
|
||||
return [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (HWBackgroundConfig *)backgroundConfig {
|
||||
return [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)scrollIndicatorInsets {
|
||||
CGFloat top = [self shouldRoundTopCorners] ? [self cornerRadius] : 0;
|
||||
return UIEdgeInsetsMake(top, 0, self.bottomLayoutOffset, 0);
|
||||
}
|
||||
|
||||
- (BOOL)showsScrollableVerticalScrollIndicator {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutoSetPanScrollContentInset {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)anchorModalToLongForm {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsExtendedPanScrolling {
|
||||
if ([self panScrollable]) {
|
||||
UIScrollView *scrollable = [self panScrollable];
|
||||
[scrollable layoutIfNeeded];
|
||||
return scrollable.contentSize.height > (scrollable.frame.size.height - self.bottomLayoutOffset);
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsDragToDismiss {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)minVerticalVelocityToTriggerDismiss {
|
||||
return 300;
|
||||
}
|
||||
|
||||
- (BOOL)allowsTapBackgroundToDismiss {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsPullDownWhenShortState {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowScreenEdgeInteractive {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss {
|
||||
return 500;
|
||||
}
|
||||
|
||||
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle {
|
||||
return PresentingViewControllerAnimationStyleNone;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAnimatePresentingVC {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id <HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isPanScrollEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isUserInteractionEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isHapticFeedbackEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)allowsTouchEventsPassingThroughTransitionView {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldRoundTopCorners {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)cornerRadius {
|
||||
return 8;
|
||||
}
|
||||
|
||||
- (HWPanModalShadow *)contentShadow {
|
||||
return [HWPanModalShadow panModalShadowNil];
|
||||
}
|
||||
|
||||
- (BOOL)showDragIndicator {
|
||||
if ([self allowsTouchEventsPassingThroughTransitionView]) {
|
||||
return NO;
|
||||
}
|
||||
return [self shouldRoundTopCorners];
|
||||
}
|
||||
|
||||
- (nullable UIView <HWPanModalIndicatorProtocol> *)customIndicatorView {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isAutoHandleKeyboardEnabled {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)keyboardOffsetFromInputView {
|
||||
return 5;
|
||||
}
|
||||
|
||||
- (BOOL)shouldRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)willRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (void)didRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (void)didEndRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldTransitionToState:(PresentationState)state {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)willTransitionToState:(PresentationState)state {
|
||||
|
||||
}
|
||||
|
||||
- (void)didChangeTransitionToState:(PresentationState)state {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer dismissPercent:(CGFloat)percent {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalWillDismiss {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalDidDismissed {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalTransitionWillBegin {
|
||||
|
||||
}
|
||||
|
||||
- (void)panModalTransitionDidFinish {
|
||||
|
||||
}
|
||||
|
||||
- (void)presentedViewDidMoveToSuperView {
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)shouldEnableAppearanceTransition {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - HWPanModalPresentableLayoutProtocol
|
||||
|
||||
- (CGFloat)topLayoutOffset {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)bottomLayoutOffset {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)shortFormYPos {
|
||||
CGFloat shortFormYPos = [self topMarginFromPanModalHeight:[self shortFormHeight]] + [self topOffset];
|
||||
return MAX(shortFormYPos, self.longFormYPos);
|
||||
}
|
||||
|
||||
- (CGFloat)mediumFormYPos {
|
||||
CGFloat mediumFormYPos = [self topMarginFromPanModalHeight:[self mediumFormHeight]] + [self topOffset];
|
||||
return MAX(mediumFormYPos, self.longFormYPos);
|
||||
}
|
||||
|
||||
- (CGFloat)longFormYPos {
|
||||
CGFloat longFrom = MAX([self topMarginFromPanModalHeight:[self longFormHeight]], [self topMarginFromPanModalHeight:PanModalHeightMake(PanModalHeightTypeMax, 0)]) + [self topOffset];
|
||||
return longFrom;
|
||||
}
|
||||
|
||||
- (CGFloat)bottomYPos {
|
||||
if (self.containerView) {
|
||||
return self.containerView.bounds.size.height - [self topOffset];
|
||||
}
|
||||
return self.bounds.size.height;
|
||||
}
|
||||
|
||||
- (CGFloat)topMarginFromPanModalHeight:(PanModalHeight)panModalHeight {
|
||||
switch (panModalHeight.heightType) {
|
||||
case PanModalHeightTypeMax:
|
||||
return 0.0f;
|
||||
case PanModalHeightTypeMaxTopInset:
|
||||
return panModalHeight.height;
|
||||
case PanModalHeightTypeContent:
|
||||
return self.bottomYPos - (panModalHeight.height + self.bottomLayoutOffset);
|
||||
case PanModalHeightTypeContentIgnoringSafeArea:
|
||||
return self.bottomYPos - panModalHeight.height;
|
||||
case PanModalHeightTypeIntrinsic: {
|
||||
[self layoutIfNeeded];
|
||||
|
||||
CGSize targetSize = CGSizeMake(self.containerView ? self.containerView.bounds.size.width : [UIScreen mainScreen].bounds.size.width, UILayoutFittingCompressedSize.height);
|
||||
CGFloat intrinsicHeight = [self systemLayoutSizeFittingSize:targetSize].height;
|
||||
return self.bottomYPos - (intrinsicHeight + self.bottomLayoutOffset);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (HWPanModalContainerView *)containerView {
|
||||
// we assume the container view will not change after we got it.
|
||||
if (!_containerView) {
|
||||
UIView *fatherView = self.superview;
|
||||
while (fatherView) {
|
||||
if ([fatherView isKindOfClass:HWPanModalContainerView.class]) {
|
||||
_containerView = (HWPanModalContainerView *) fatherView;
|
||||
break;
|
||||
}
|
||||
fatherView = fatherView.superview;
|
||||
}
|
||||
}
|
||||
|
||||
return _containerView;
|
||||
}
|
||||
|
||||
- (UIView *)findKeyWindow {
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
|
||||
for (UIScene *scene in connectedScenes) {
|
||||
if ([scene isKindOfClass:UIWindowScene.class]) {
|
||||
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||
for (UIWindow *tmpWindow in windowScene.windows) {
|
||||
if ([tmpWindow isKeyWindow]) {
|
||||
return tmpWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
NSArray *windows = [UIApplication sharedApplication].windows;
|
||||
for (UIWindow *window in windows) {
|
||||
if ([window isKeyWindow]) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user