jsdw_ios/QuickLocation/UIKit/Pop/DLViewTransition.m

1229 lines
47 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

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

//
// FWViewTransition.m
// DLSDK
//
// Created by osell on 2022/8/23.
//
#import "DLViewTransition.h"
#import <objc/runtime.h>
#pragma mark - DLAnimatedTransition
@interface DLAnimatedTransition ()
@property (nonatomic, assign) BOOL isSystem;
@property (nonatomic, weak) id<UIViewControllerContextTransitioning> transitionContext;
@property (nonatomic, copy) void(^interactBegan)(void);
@end
@implementation DLAnimatedTransition
@synthesize gestureRecognizer = _gestureRecognizer;
#pragma mark - Lifecycle
+ (instancetype)systemTransition
{
static DLAnimatedTransition *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[DLAnimatedTransition alloc] init];
instance.isSystem = YES;
});
return instance;
}
+ (instancetype)transitionWithBlock:(void (^)(DLAnimatedTransition *))block
{
DLAnimatedTransition *transition = [[self alloc] init];
transition.transitionBlock = block;
return transition;
}
- (instancetype)init
{
self = [super init];
if (self) {
_transitionDuration = 0.35;
self.completionSpeed = 0.35;
}
return self;
}
#pragma mark - Private
- (void)setInteractEnabled:(BOOL)enabled
{
_interactEnabled = enabled;
_gestureRecognizer.enabled = enabled;
}
- (void)interactWith:(UIViewController *)viewController
{
if (!viewController.view) return;
for (UIGestureRecognizer *gestureRecognizer in viewController.view.gestureRecognizers) {
if (gestureRecognizer == self.gestureRecognizer) return;
}
[viewController.view addGestureRecognizer:self.gestureRecognizer];
}
- (__kindof UIPanGestureRecognizer *)gestureRecognizer
{
if (!_gestureRecognizer) {
if (self.interactScreenEdge) {
UIScreenEdgePanGestureRecognizer *gestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureRecognizerAction:)];
gestureRecognizer.edges = UIRectEdgeLeft;
_gestureRecognizer = gestureRecognizer;
} else {
_gestureRecognizer = [[DLPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureRecognizerAction:)];
}
}
return _gestureRecognizer;
}
- (void)setGestureRecognizer:(__kindof UIPanGestureRecognizer *)gestureRecognizer
{
_gestureRecognizer = gestureRecognizer;
[gestureRecognizer addTarget:self action:@selector(gestureRecognizerAction:)];
}
- (void)gestureRecognizerAction:(UIPanGestureRecognizer *)gestureRecognizer
{
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan: {
_isInteractive = YES;
BOOL interactBegan = self.interactBlock ? self.interactBlock(gestureRecognizer) : YES;
if (interactBegan && self.interactBegan) {
self.interactBegan();
}
break;
}
case UIGestureRecognizerStateChanged: {
BOOL interactChanged = self.interactBlock ? self.interactBlock(gestureRecognizer) : YES;
if (interactChanged) {
CGFloat percent;
if ([gestureRecognizer isKindOfClass:[DLPanGestureRecognizer class]]) {
percent = [(DLPanGestureRecognizer *)gestureRecognizer swipePercent];
} else {
CGPoint transition = [gestureRecognizer translationInView:gestureRecognizer.view];
percent = MAX(0, MIN(1, transition.x / gestureRecognizer.view.bounds.size.width));
}
[self updateInteractiveTransition:percent];
}
break;
}
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateEnded: {
_isInteractive = NO;
BOOL interactEnded = self.interactBlock ? self.interactBlock(gestureRecognizer) : YES;
if (interactEnded) {
BOOL finished = NO;
if (gestureRecognizer.state == UIGestureRecognizerStateCancelled ||
gestureRecognizer.state == UIGestureRecognizerStateFailed) {
finished = NO;
} else if (self.percentComplete >= 0.5) {
finished = YES;
} else if ([gestureRecognizer isKindOfClass:[DLPanGestureRecognizer class]]) {
CGPoint velocity = [gestureRecognizer velocityInView:gestureRecognizer.view];
CGPoint transition = [gestureRecognizer translationInView:gestureRecognizer.view];
switch (((DLPanGestureRecognizer *)gestureRecognizer).direction) {
case UISwipeGestureRecognizerDirectionUp:
if (velocity.y <= -100 && fabs(transition.x) < fabs(transition.y)) finished = YES;
break;
case UISwipeGestureRecognizerDirectionLeft:
if (velocity.x <= -100 && fabs(transition.x) > fabs(transition.y)) finished = YES;
break;
case UISwipeGestureRecognizerDirectionDown:
if (velocity.y >= 100 && fabs(transition.x) < fabs(transition.y)) finished = YES;
break;
case UISwipeGestureRecognizerDirectionRight:
if (velocity.x >= 100 && fabs(transition.x) > fabs(transition.y)) finished = YES;
break;
default:
break;
}
} else {
CGPoint velocity = [gestureRecognizer velocityInView:gestureRecognizer.view];
CGPoint transition = [gestureRecognizer translationInView:gestureRecognizer.view];
if (velocity.x >= 100 && fabs(transition.x) > fabs(transition.y)) finished = YES;
}
if (finished) {
[self finishInteractiveTransition];
} else {
[self cancelInteractiveTransition];
}
}
break;
}
default:
break;
}
}
- (BOOL)presentationEnabled
{
return self.presentationBlock != nil;
}
- (void)setPresentationEnabled:(BOOL)enabled
{
if (enabled == self.presentationEnabled) return;
if (enabled) {
self.presentationBlock = ^UIPresentationController *(UIViewController *presented, UIViewController *presenting) {
return [[DLPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
};
} else {
self.presentationBlock = nil;
}
}
- (id<UIViewControllerInteractiveTransitioning>)interactiveTransitionForTransition:(id<UIViewControllerAnimatedTransitioning>)transition
{
if (self.transitionType == FWAnimatedTransitionTypeDismiss || self.transitionType == FWAnimatedTransitionTypePop) {
if (!self.isSystem && self.interactEnabled && self.isInteractive) {
return self;
}
}
return nil;
}
#pragma mark - UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
{
self.transitionType = FWAnimatedTransitionTypePresent;
// 自动设置和绑定dismiss交互转场在dismiss前设置生效
if (!self.isSystem && self.interactEnabled && !self.interactBlock) {
__weak UIViewController *weakPresented = presented;
self.interactBegan = ^{
[weakPresented dismissViewControllerAnimated:YES completion:nil];
};
[self interactWith:presented];
}
return !self.isSystem ? self : nil;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
self.transitionType = FWAnimatedTransitionTypeDismiss;
return !self.isSystem ? self : nil;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator
{
return [self interactiveTransitionForTransition:animator];
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
return [self interactiveTransitionForTransition:animator];
}
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
{
if (self.presentationBlock) {
return self.presentationBlock(presented, presenting);
}
return nil;
}
#pragma mark - UINavigationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
// push时检查toVC的转场代理
DLAnimatedTransition *transition = toVC.dl_viewTransition ?: self;
transition.transitionType = FWAnimatedTransitionTypePush;
// 自动设置和绑定pop交互转场在pop前设置生效
if (!transition.isSystem && transition.interactEnabled && !transition.interactBlock) {
transition.interactBegan = ^{
[navigationController popViewControllerAnimated:YES];
};
[transition interactWith:toVC];
}
return !transition.isSystem ? transition : nil;
} else if (operation == UINavigationControllerOperationPop) {
// pop时检查fromVC的转场代理
DLAnimatedTransition *transition = fromVC.dl_viewTransition ?: self;
transition.transitionType = FWAnimatedTransitionTypePop;
return !transition.isSystem ? transition : nil;
}
return nil;
}
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController
{
return [self interactiveTransitionForTransition:animationController];
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return transitionContext.isAnimated ? self.transitionDuration : 0.f;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
self.transitionContext = transitionContext;
if (self.transitionBlock) {
self.transitionBlock(self);
} else {
[self animate];
}
}
#pragma mark - Animate
- (DLAnimatedTransitionType)transitionType
{
// 如果自定义type优先使用之
if (_transitionType != DLAnimatedTransitionTypeNone) {
return _transitionType;
}
// 自动根据上下文获取type
if (!self.transitionContext) {
return DLAnimatedTransitionTypeNone;
}
UIViewController *fromViewController = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 导航栏为同一个时为push|pop
if (fromViewController.navigationController && toViewController.navigationController &&
fromViewController.navigationController == toViewController.navigationController) {
NSInteger toIndex = [toViewController.navigationController.viewControllers indexOfObject:toViewController];
NSInteger fromIndex = [fromViewController.navigationController.viewControllers indexOfObject:fromViewController];
if (toIndex > fromIndex) {
return FWAnimatedTransitionTypePush;
} else {
return FWAnimatedTransitionTypePop;
}
} else {
if (toViewController.presentingViewController == fromViewController) {
return FWAnimatedTransitionTypePresent;
} else {
return FWAnimatedTransitionTypeDismiss;
}
}
}
- (void)start
{
UIView *fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
switch (self.transitionType) {
// push时fromView在下toView在上
case FWAnimatedTransitionTypePush: {
[self.transitionContext.containerView addSubview:fromView];
[self.transitionContext.containerView addSubview:toView];
break;
}
// pop时fromView在上toView在下
case FWAnimatedTransitionTypePop: {
// 此处后添加fromView方便做pop动画可自行移动toView到上面
[self.transitionContext.containerView addSubview:toView];
[self.transitionContext.containerView addSubview:fromView];
break;
}
// present时使用toView做动画
case FWAnimatedTransitionTypePresent: {
[self.transitionContext.containerView addSubview:toView];
break;
}
// dismiss时使用fromView做动画
case FWAnimatedTransitionTypeDismiss: {
[self.transitionContext.containerView addSubview:fromView];
break;
}
default: {
break;
}
}
}
- (void)animate
{
// 子类可重写默认alpha动画
DLAnimatedTransitionType transitionType = [self transitionType];
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
UIView *transitionView = transitionIn ? [self.transitionContext viewForKey:UITransitionContextToViewKey] : [self.transitionContext viewForKey:UITransitionContextFromViewKey];
[self start];
if (transitionIn) transitionView.alpha = 0;
[UIView animateWithDuration:[self transitionDuration:self.transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
transitionView.alpha = transitionIn ? 1 : 0;
} completion:^(BOOL finished) {
[self complete];
}];
}
- (void)complete
{
DLAnimatedTransitionType transitionType = [self transitionType];
BOOL didComplete = ![self.transitionContext transitionWasCancelled];
[self.transitionContext completeTransition:didComplete];
if (didComplete && self.dismissCompletion &&
transitionType == FWAnimatedTransitionTypeDismiss) {
self.dismissCompletion();
}
}
@end
#pragma mark - DLSwipeAnimatedTransition
@implementation DLSwipeAnimatedTransition
+ (instancetype)transitionWithInDirection:(UISwipeGestureRecognizerDirection)inDirection
outDirection:(UISwipeGestureRecognizerDirection)outDirection
{
DLSwipeAnimatedTransition *transition = [[self alloc] init];
transition.inDirection = inDirection;
transition.outDirection = outDirection;
return transition;
}
- (instancetype)init
{
self = [super init];
if (self) {
_inDirection = UISwipeGestureRecognizerDirectionUp;
_outDirection = UISwipeGestureRecognizerDirectionDown;
}
return self;
}
- (void)setOutDirection:(UISwipeGestureRecognizerDirection)outDirection
{
_outDirection = outDirection;
if ([self.gestureRecognizer isKindOfClass:[DLPanGestureRecognizer class]]) {
((DLPanGestureRecognizer *)self.gestureRecognizer).direction = outDirection;
}
}
- (void)animate
{
DLAnimatedTransitionType transitionType = [self transitionType];
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
UISwipeGestureRecognizerDirection direction = transitionIn ? self.inDirection : self.outDirection;
CGVector offset;
switch (direction) {
case UISwipeGestureRecognizerDirectionLeft: {
offset = CGVectorMake(-1.f, 0.f);
break;
}
case UISwipeGestureRecognizerDirectionRight: {
offset = CGVectorMake(1.f, 0.f);
break;
}
case UISwipeGestureRecognizerDirectionUp: {
offset = CGVectorMake(0.f, -1.f);
break;
}
case UISwipeGestureRecognizerDirectionDown:
default: {
offset = CGVectorMake(0.f, 1.f);
break;
}
}
CGRect fromFrame = [self.transitionContext initialFrameForViewController:[self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]];
CGRect toFrame = [self.transitionContext finalFrameForViewController:[self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]];
UIView *fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
if (transitionIn) {
[self.transitionContext.containerView addSubview:toView];
toView.frame = [self animateFrameWithFrame:toFrame offset:offset initial:YES show:transitionIn];
fromView.frame = fromFrame;
} else {
[self.transitionContext.containerView insertSubview:toView belowSubview:fromView];
fromView.frame = [self animateFrameWithFrame:fromFrame offset:offset initial:YES show:transitionIn];
toView.frame = toFrame;
}
[UIView animateWithDuration:[self transitionDuration:self.transitionContext] animations:^{
if (transitionIn) {
toView.frame = [self animateFrameWithFrame:toFrame offset:offset initial:NO show:transitionIn];
} else {
fromView.frame = [self animateFrameWithFrame:fromFrame offset:offset initial:NO show:transitionIn];
}
} completion:^(BOOL finished) {
if ([self.transitionContext transitionWasCancelled]) {
[toView removeFromSuperview];
}
[self complete];
}];
}
- (CGRect)animateFrameWithFrame:(CGRect)frame offset:(CGVector)offset initial:(BOOL)initial show:(BOOL)show
{
NSInteger vectorValue = offset.dx == 0 ? offset.dy : offset.dx;
NSInteger flag = 0;
if (initial) {
vectorValue = vectorValue > 0 ? -vectorValue : vectorValue;
flag = show ? vectorValue : 0;
} else {
vectorValue = vectorValue > 0 ? vectorValue : -vectorValue;
flag = show ? 0 : vectorValue;
}
CGFloat offsetX = frame.size.width * offset.dx * flag;
CGFloat offsetY = frame.size.height * offset.dy * flag;
return CGRectOffset(frame, offsetX, offsetY);
}
@end
#pragma mark - DLTransformAnimatedTransition
@implementation DLTransformAnimatedTransition
+ (instancetype)transitionWithInTransform:(CGAffineTransform)inTransform
outTransform:(CGAffineTransform)outTransform
{
DLTransformAnimatedTransition *transition = [[self alloc] init];
transition.inTransform = inTransform;
transition.outTransform = outTransform;
return transition;
}
- (instancetype)init
{
self = [super init];
if (self) {
_inTransform = CGAffineTransformMakeScale(0.01, 0.01);
_outTransform = CGAffineTransformMakeScale(0.01, 0.01);
}
return self;
}
- (void)animate
{
DLAnimatedTransitionType transitionType = [self transitionType];
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
UIView *transitionView = transitionIn ? [self.transitionContext viewForKey:UITransitionContextToViewKey] : [self.transitionContext viewForKey:UITransitionContextFromViewKey];
[self start];
if (transitionIn) {
transitionView.transform = self.inTransform;
transitionView.alpha = 0;
}
[UIView animateWithDuration:[self transitionDuration:self.transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
transitionView.transform = transitionIn ? CGAffineTransformIdentity : self.outTransform;
transitionView.alpha = transitionIn ? 1 : 0;
} completion:^(BOOL finished) {
[self complete];
}];
}
@end
#pragma mark - DLPresentationController
@interface DLPresentationController ()
@property (nonatomic, strong) UIView *dimmingView;
@end
@implementation DLPresentationController
#pragma mark - Lifecycle
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
{
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
if (self) {
_showDimming = YES;
_dimmingClick = YES;
_dimmingAnimated = YES;
_dimmingColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
_rectCorner = UIRectCornerTopLeft | UIRectCornerTopRight;
_cornerRadius = 0;
_presentedFrame = CGRectZero;
_presentedSize = CGSizeZero;
_verticalInset = 0;
}
return self;
}
#pragma mark - Accessor
- (UIView *)dimmingView
{
if (!_dimmingView) {
_dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
_dimmingView.backgroundColor = self.dimmingColor;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapAction:)];
[_dimmingView addGestureRecognizer:tapGesture];
}
return _dimmingView;
}
- (void)setShowDimming:(BOOL)showDimming
{
_showDimming = showDimming;
self.dimmingView.hidden = !showDimming;
}
- (void)setDimmingClick:(BOOL)dimmingClick
{
_dimmingClick = dimmingClick;
self.dimmingView.userInteractionEnabled = dimmingClick;
}
- (void)onTapAction:(id)sender
{
[self.presentedViewController dismissViewControllerAnimated:YES completion:self.dismissCompletion];
}
#pragma mark - Protected
- (void)presentationTransitionWillBegin
{
[super presentationTransitionWillBegin];
self.presentedView.frame = [self frameOfPresentedViewInContainerView];
if (self.cornerRadius > 0) {
self.presentedView.layer.masksToBounds = YES;
if ((self.rectCorner & UIRectCornerAllCorners) == UIRectCornerAllCorners) {
self.presentedView.layer.cornerRadius = self.cornerRadius;
} else {
CAShapeLayer *cornerLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.presentedView.bounds byRoundingCorners:self.rectCorner cornerRadii:CGSizeMake(self.cornerRadius, self.cornerRadius)];
cornerLayer.frame = self.presentedView.bounds;
cornerLayer.path = path.CGPath;
self.presentedView.layer.mask = cornerLayer;
}
}
self.dimmingView.frame = self.containerView.bounds;
[self.containerView insertSubview:self.dimmingView atIndex:0];
if (self.dimmingAnimated) {
self.dimmingView.alpha = 0;
[self.presentingViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 1.0;
} completion:nil];
}
}
- (void)dismissalTransitionWillBegin
{
[super dismissalTransitionWillBegin];
if (self.dimmingAnimated) {
[self.presentingViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 0;
} completion:nil];
}
}
- (void)dismissalTransitionDidEnd:(BOOL)completed
{
[super dismissalTransitionDidEnd:completed];
if (completed) {
[self.dimmingView removeFromSuperview];
}
}
- (CGRect)frameOfPresentedViewInContainerView
{
if (self.frameBlock) {
return self.frameBlock(self);
} else if (!CGRectEqualToRect(self.presentedFrame, CGRectZero)) {
return self.presentedFrame;
} else if (!CGSizeEqualToSize(self.presentedSize, CGSizeZero)) {
CGRect presentedFrame = CGRectMake(0, 0, self.presentedSize.width, self.presentedSize.height);
presentedFrame.origin.x = (self.containerView.bounds.size.width - self.presentedSize.width) / 2;
presentedFrame.origin.y = (self.containerView.bounds.size.height - self.presentedSize.height) / 2;
return presentedFrame;
} else if (self.verticalInset != 0) {
CGRect presentedFrame = self.containerView.bounds;
presentedFrame.origin.y = self.verticalInset;
presentedFrame.size.height -= self.verticalInset;
return presentedFrame;
} else {
return self.containerView.bounds;
}
}
@end
#pragma mark - DLPanGestureRecognizer
@interface DLPanGestureRecognizer () <UIGestureRecognizerDelegate>
@property (nonatomic, strong) NSNumber *isFailed;
@end
@implementation DLPanGestureRecognizer
- (instancetype)init
{
self = [super init];
if (self) {
_direction = UISwipeGestureRecognizerDirectionDown;
_autoDetected = YES;
self.delegate = self;
}
return self;
}
- (instancetype)initWithTarget:(id)target action:(SEL)action
{
self = [super initWithTarget:target action:action];
if (self) {
_direction = UISwipeGestureRecognizerDirectionDown;
_autoDetected = YES;
self.delegate = self;
}
return self;
}
- (CGFloat)swipePercent
{
CGFloat percent = 0;
CGPoint transition = [self translationInView:self.view];
switch (self.direction) {
case UISwipeGestureRecognizerDirectionLeft:
percent = -transition.x / self.view.bounds.size.width;
break;
case UISwipeGestureRecognizerDirectionRight:
percent = transition.x / self.view.bounds.size.width;
break;
case UISwipeGestureRecognizerDirectionUp:
percent = -transition.y / self.view.bounds.size.height;
break;
case UISwipeGestureRecognizerDirectionDown:
default:
percent = transition.y / self.view.bounds.size.height;
break;
}
return MAX(0, MIN(1, percent));
}
- (void)reset
{
[super reset];
self.isFailed = nil;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.instantBegan && self.state == UIGestureRecognizerStateBegan) return;
[super touchesBegan:touches withEvent:event];
if (self.instantBegan) self.state = UIGestureRecognizerStateBegan;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (!self.scrollView || !self.scrollView.scrollEnabled) return;
if (self.direction == UISwipeGestureRecognizerDirectionUp || self.direction == UISwipeGestureRecognizerDirectionDown) {
if (![self canScrollVertical:self.scrollView]) return;
} else {
if (![self canScrollHorizontal:self.scrollView]) return;
}
if (self.state == UIGestureRecognizerStateFailed) return;
if (self.isFailed) {
if (self.isFailed.boolValue) {
self.state = UIGestureRecognizerStateFailed;
}
return;
}
CGPoint velocity = [self velocityInView:self.view];
CGPoint location = [touches.anyObject locationInView:self.view];
CGPoint prevLocation = [touches.anyObject previousLocationInView:self.view];
if (CGPointEqualToPoint(velocity, CGPointZero) && CGPointEqualToPoint(location, prevLocation)) return;
BOOL isFailed = NO;
switch (self.direction) {
case UISwipeGestureRecognizerDirectionDown: {
CGFloat edgeOffset = [self contentOffsetOfEdge:UIRectEdgeTop scrollView:self.scrollView].y;
if ((fabs(velocity.x) < fabs(velocity.y)) && (location.y > prevLocation.y) && (self.scrollView.contentOffset.y <= edgeOffset)) {
isFailed = NO;
} else if (self.scrollView.contentOffset.y >= edgeOffset) {
isFailed = YES;
}
break;
}
case UISwipeGestureRecognizerDirectionUp: {
CGFloat edgeOffset = [self contentOffsetOfEdge:UIRectEdgeBottom scrollView:self.scrollView].y;
if ((fabs(velocity.x) < fabs(velocity.y)) && (location.y < prevLocation.y) && (self.scrollView.contentOffset.y >= edgeOffset)) {
isFailed = NO;
} else if (self.scrollView.contentOffset.y <= edgeOffset) {
isFailed = YES;
}
break;
}
case UISwipeGestureRecognizerDirectionRight: {
CGFloat edgeOffset = [self contentOffsetOfEdge:UIRectEdgeLeft scrollView:self.scrollView].x;
if ((fabs(velocity.y) < fabs(velocity.x)) && (location.x > prevLocation.x) && (self.scrollView.contentOffset.x <= edgeOffset)) {
isFailed = NO;
} else if (self.scrollView.contentOffset.x >= edgeOffset) {
isFailed = YES;
}
break;
}
case UISwipeGestureRecognizerDirectionLeft: {
CGFloat edgeOffset = [self contentOffsetOfEdge:UIRectEdgeRight scrollView:self.scrollView].x;
if ((fabs(velocity.y) < fabs(velocity.x)) && (location.x < prevLocation.x) && (self.scrollView.contentOffset.x >= edgeOffset)) {
isFailed = NO;
} else if (self.scrollView.contentOffset.x <= edgeOffset) {
isFailed = YES;
}
break;
}
default:
break;
}
if (isFailed && self.shouldFailed) {
isFailed = self.shouldFailed(self);
}
if (isFailed) {
self.state = UIGestureRecognizerStateFailed;
self.isFailed = @YES;
} else {
self.isFailed = @NO;
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (self.shouldBegin) return self.shouldBegin(self);
if (self.maximumDistance <= 0) return YES;
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
switch (self.direction) {
case UISwipeGestureRecognizerDirectionLeft:
return gestureRecognizer.view.bounds.size.width - location.x <= self.maximumDistance;
case UISwipeGestureRecognizerDirectionRight:
return location.x <= self.maximumDistance;
case UISwipeGestureRecognizerDirectionUp:
return gestureRecognizer.view.bounds.size.height - location.y <= self.maximumDistance;
case UISwipeGestureRecognizerDirectionDown:
default:
return location.y <= self.maximumDistance;
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
[otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) {
if (self.autoDetected) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if (self.direction == UISwipeGestureRecognizerDirectionUp || self.direction == UISwipeGestureRecognizerDirectionDown) {
if ([self canScrollHorizontal:scrollView]) return NO;
} else {
if ([self canScrollVertical:scrollView]) return NO;
}
if (scrollView != self.scrollView) self.scrollView = scrollView;
return YES;
} else {
if (self.scrollView && self.scrollView == otherGestureRecognizer.view) {
return YES;
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
[otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) {
if (self.autoDetected) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if (self.direction == UISwipeGestureRecognizerDirectionUp || self.direction == UISwipeGestureRecognizerDirectionDown) {
if ([self canScrollHorizontal:scrollView]) return NO;
} else {
if ([self canScrollVertical:scrollView]) return NO;
}
if (scrollView != self.scrollView) self.scrollView = scrollView;
return self.shouldBeRequiredToFail ? self.shouldBeRequiredToFail(otherGestureRecognizer) : YES;
} else {
if (self.scrollView && self.scrollView == otherGestureRecognizer.view) {
return self.shouldBeRequiredToFail ? self.shouldBeRequiredToFail(otherGestureRecognizer) : YES;
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (self.shouldRequireFailure) {
return self.shouldRequireFailure(otherGestureRecognizer);
}
return NO;
}
- (BOOL)canScrollHorizontal: (UIScrollView *)scrollView {
BOOL canHorizontal = NO;
if (scrollView.bounds.size.height > 0) {
canHorizontal = scrollView.contentSize.height + scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom > CGRectGetHeight(scrollView.bounds);
}
return canHorizontal;
}
- (BOOL)canScrollVertical: (UIScrollView *)scrollView {
BOOL canVertical = NO;
if (scrollView.bounds.size.height > 0) {
canVertical = scrollView.contentSize.height + scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom > CGRectGetHeight(scrollView.bounds);
}
return canVertical;
}
- (CGPoint)contentOffsetOfEdge:(UIRectEdge)edge scrollView: (UIScrollView *)scrollView {
CGPoint contentOffset = scrollView.contentOffset;
switch (edge) {
case UIRectEdgeTop:
contentOffset.y = -scrollView.adjustedContentInset.top;
break;
case UIRectEdgeLeft:
contentOffset.x = -scrollView.adjustedContentInset.left;
break;
case UIRectEdgeBottom:
contentOffset.y = scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.adjustedContentInset.bottom;
break;
case UIRectEdgeRight:
contentOffset.x = scrollView.contentSize.width - scrollView.bounds.size.width + scrollView.adjustedContentInset.right;
break;
default:
break;
}
return contentOffset;
}
@end
#pragma mark - FWPresentationTarget
@interface DLPresentationTarget : NSObject <UIPopoverPresentationControllerDelegate>
@property (nonatomic, assign) BOOL isPopover;
@property (nonatomic, assign) BOOL shouldDismiss;
@end
@implementation DLPresentationTarget
- (instancetype)init
{
self = [super init];
if (self) {
_shouldDismiss = YES;
}
return self;
}
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
return self.isPopover ? UIModalPresentationNone : controller.presentationStyle;
}
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
return self.shouldDismiss;
}
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
{
return self.shouldDismiss;
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
if (presentationController.presentedViewController.dl_presentationDidDismiss) {
presentationController.presentedViewController.dl_presentationDidDismiss();
}
}
@end
#pragma mark - UIViewController+DLTransition
@implementation UIViewController (DLTransition)
- (DLAnimatedTransition *)dl_modalTransition
{
return objc_getAssociatedObject(self, @selector(dl_modalTransition));
}
// 注意App退出后台时如果弹出页面整个present动画不会执行。如果需要设置遮罩层等需要在viewDidAppear中处理兼容
- (void)setDl_modalTransition:(DLAnimatedTransition *)modalTransition
{
// 设置delegation动画nil时清除delegate动画
self.transitioningDelegate = modalTransition;
// 强引用防止被自动释放nil时释放引用
objc_setAssociatedObject(self, @selector(dl_modalTransition), modalTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (DLAnimatedTransition *)dl_viewTransition
{
return objc_getAssociatedObject(self, @selector(dl_viewTransition));
}
- (void)setDl_viewTransition:(DLAnimatedTransition *)viewTransition
{
objc_setAssociatedObject(self, @selector(dl_viewTransition), viewTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (DLAnimatedTransition *)dl_setPresentTransition:(void (^)(DLPresentationController *))presentationBlock
{
DLSwipeAnimatedTransition *modalTransition = [[DLSwipeAnimatedTransition alloc] init];
modalTransition.presentationBlock = ^UIPresentationController *(UIViewController *presented, UIViewController *presenting) {
DLPresentationController *presentationController = [[DLPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
if (presentationBlock) presentationBlock(presentationController);
return presentationController;
};
self.modalPresentationStyle = UIModalPresentationCustom;
self.dl_modalTransition = modalTransition;
return modalTransition;
}
- (DLAnimatedTransition *)dl_setAlertTransition:(void (^)(DLPresentationController *))presentationBlock
{
DLTransformAnimatedTransition *modalTransition = [DLTransformAnimatedTransition transitionWithInTransform:CGAffineTransformMakeScale(1.1, 1.1) outTransform:CGAffineTransformIdentity];
modalTransition.presentationBlock = ^UIPresentationController *(UIViewController *presented, UIViewController *presenting) {
DLPresentationController *presentationController = [[DLPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
if (presentationBlock) presentationBlock(presentationController);
return presentationController;
};
self.modalPresentationStyle = UIModalPresentationCustom;
self.dl_modalTransition = modalTransition;
return modalTransition;
}
- (DLAnimatedTransition *)dl_setFadeTransition:(void (^)(DLPresentationController *))presentationBlock
{
DLAnimatedTransition *modalTransition = [[DLAnimatedTransition alloc] init];
modalTransition.presentationBlock = ^UIPresentationController *(UIViewController *presented, UIViewController *presenting) {
DLPresentationController *presentationController = [[DLPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
if (presentationBlock) presentationBlock(presentationController);
return presentationController;
};
self.modalPresentationStyle = UIModalPresentationCustom;
self.dl_modalTransition = modalTransition;
return modalTransition;
}
- (void (^)(void))dl_presentationDidDismiss
{
return objc_getAssociatedObject(self, @selector(dl_presentationDidDismiss));
}
- (void)setDl_presentationDidDismiss:(void (^)(void))presentationDidDismiss
{
objc_setAssociatedObject(self, @selector(dl_presentationDidDismiss), presentationDidDismiss, OBJC_ASSOCIATION_COPY_NONATOMIC);
if (@available(iOS 13.0, *)) {
self.presentationController.delegate = [self dl_presentationTarget];
}
}
- (void)dl_setPopoverPresentation:(void (NS_NOESCAPE ^)(UIPopoverPresentationController *))presentationBlock shouldDismiss:(BOOL)shouldDismiss
{
self.modalPresentationStyle = UIModalPresentationPopover;
[self dl_presentationTarget].isPopover = YES;
[self dl_presentationTarget].shouldDismiss = shouldDismiss;
self.popoverPresentationController.delegate = [self dl_presentationTarget];
if (self.popoverPresentationController && presentationBlock) {
presentationBlock(self.popoverPresentationController);
}
}
- (DLPresentationTarget *)dl_presentationTarget
{
DLPresentationTarget *target = objc_getAssociatedObject(self, _cmd);
if (!target) {
target = [[DLPresentationTarget alloc] init];
objc_setAssociatedObject(self, _cmd, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return target;
}
@end
#pragma mark - UIView+DLTransition
@implementation UIView (DLTransition)
- (UIView *)dl_transitionToController:(UIViewController *)viewController pinEdges:(BOOL)pinEdges
{
UIView *ancestorView = [self ancestorViewWith:viewController];
[ancestorView addSubview:self];
if (pinEdges) {
self.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
NSArray *constraints = @[topConstraint, bottomConstraint, leftConstraint, rightConstraint];
[constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj setActive:YES];
}];
[self.superview addConstraints:constraints];
[ancestorView setNeedsLayout];
[ancestorView layoutIfNeeded];
}
return ancestorView;
}
- (UIViewController *)dl_wrappedTransitionController:(BOOL)pinEdges
{
UIViewController *viewController = [[UIViewController alloc] init];
[viewController.view addSubview:self];
if (pinEdges) {
self.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
NSArray *constraints = @[topConstraint, bottomConstraint, leftConstraint, rightConstraint];
[constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj setActive:YES];
}];
[self.superview addConstraints:constraints];
[viewController.view setNeedsLayout];
[viewController.view layoutIfNeeded];
}
return viewController;
}
- (void)dl_setPresentTransition:(DLAnimatedTransitionType)transitionType
contentView:(UIView *)contentView
completion:(void (^)(BOOL))completion
{
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
if (transitionIn) {
self.alpha = 0;
contentView.transform = CGAffineTransformMakeTranslation(0, contentView.frame.size.height);
[UIView animateWithDuration:0.25 animations:^{
contentView.transform = CGAffineTransformIdentity;
self.alpha = 1;
} completion:^(BOOL finished) {
if (completion) completion(finished);
}];
} else {
[UIView animateWithDuration:0.25 animations:^{
contentView.transform = CGAffineTransformMakeTranslation(0, contentView.frame.size.height);
self.alpha = 0;
} completion:^(BOOL finished) {
contentView.transform = CGAffineTransformIdentity;
[self removeFromSuperview];
if (completion) completion(finished);
}];
}
}
- (void)dl_setAlertTransition:(DLAnimatedTransitionType)transitionType
completion:(void (^)(BOOL))completion
{
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
if (transitionIn) {
self.alpha = 0;
self.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView animateWithDuration:0.25 animations:^{
self.transform = CGAffineTransformIdentity;
self.alpha = 1;
} completion:^(BOOL finished) {
if (completion) completion(finished);
}];
} else {
[UIView animateWithDuration:0.25 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
if (completion) completion(finished);
}];
}
}
- (void)dl_setFadeTransition:(DLAnimatedTransitionType)transitionType
completion:(void (^)(BOOL))completion
{
BOOL transitionIn = (transitionType == FWAnimatedTransitionTypePush || transitionType == FWAnimatedTransitionTypePresent);
if (transitionIn) {
self.alpha = 0;
[UIView animateWithDuration:0.25 animations:^{
self.alpha = 1;
} completion:^(BOOL finished) {
if (completion) completion(finished);
}];
} else {
[UIView animateWithDuration:0.25 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
if (completion) completion(finished);
}];
}
}
- (UIView*)ancestorViewWith: (UIViewController *)viewController {
if (viewController.tabBarController.navigationController) {
return viewController.tabBarController.navigationController.view;
} else if (viewController.tabBarController) {
return viewController.tabBarController.view;
} else if (viewController.navigationController) {
return viewController.view;
} else {
return viewController.view;
}
}
@end
#pragma mark - UINavigationController+DLTransition
@implementation UINavigationController (DLTransition)
- (DLAnimatedTransition *)dl_navigationTransition
{
return objc_getAssociatedObject(self, @selector(dl_navigationTransition));
}
- (void)setDl_navigationTransition:(DLAnimatedTransition *)navigationTransition
{
// 设置delegate动画nil时清理delegate动画无需清理CA动画
self.delegate = navigationTransition;
// 强引用防止被自动释放nil时释放引用
objc_setAssociatedObject(self, @selector(dl_navigationTransition), navigationTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end