iOS-实现安卓左滑返回上一页面效果

闲来无事,整点活儿干干!
不整别的,先来巴拉巴拉这玩意儿怎么玩儿。

开局一张图,内容全靠骗

image

开整

首先需要滑动,那肯定得要来一个滑动的手势,既然滑动手势有了,加哪?既然手势作为全局生效,那肯定不能加在单个的view上,所以直接加在window上,这样不就都有了嘛。


UIScreenEdgePanGestureRecognizer *gesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:**@selector(screenEdgePanGestureExcuteEvent:)];
gesture.edges = UIRectEdgeRight;
[Gesture_KeyWindow addGestureRecognizer:gesture];

既然手势加上了,那不得来个响应事件。

如何去实现呢,拖动的时候要像脸上的痘痘一样,冒出个头,直接加个view,然后做动画😓,你来,我整不动,别问我为什么,问就是我这么干过,结果把路走死了。
那就想想其他办法,那不得有个玩意儿叫layer嘛。那我添加个画布多刺激,想加啥就加啥。
说干就干。

第一步:

既然要拖,那就得先拿到位置,对吧,这个没啥问题吧(有问题的,请提出你的问题,我不回答,我怕我打死你)。


CGPoint changePoint = [gesture locationInView:Gesture_KeyWindow];

先忽略细节,开始画猪,咱得先画个猪脑袋吧,至少知道你画的是个啥玩意儿,先整个效果都!

点拿到了,那就要开始拖了。那我们是不是得要知道两个状态啊,我到底是刚摁下去呢,还是在拖动,对吧,所以就有俩货,UIGestureRecognizerStateBegan、UIGestureRecognizerStateChanged,一个是咱刚摁下去的状态,一个是在滑的状态,好了,这两个状态知道了,在UIGestureRecognizerStateBegan状态是记录开始的点,在UIGestureRecognizerStateChanged状态的时候记录变化点。

开始动用你的脑瓜子YY一下,咱拖的时候不是有一个痘痘一样凸起的东西吗?那咱先画那个玩意儿。想想那个S一样的曲线,脑瓜一热,这不就来了吗,UIBezierPath这货,只要你牛逼,啥画不出来,看看它的方法


- (void)moveToPoint:(CGPoint)point;

- (void)addLineToPoint:(CGPoint)point;

- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;

- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwiseAPI_AVAILABLE(ios(4.0));

- (void)closePath;

第一个移到一个点,这不难理解,既然要画那不得知道从那开始,对吧!
第二个添加一条线,还是直的,想想那个曲线,明明是弯的好吧,所以pass掉
第三个画的是弯的线,大致就是本来是直的线段,现在来了俩点,然后按照切线的方向把线往一边扯,然后弯了
第四个和第三个一样,不过是两个点变成了一个
第五个,不管了,第六个把最后的起点和终点链接起来

方法的意思知道了,那就开始描线了
首先起点位置选择,我们拿到拖动的点,那这个点的y肯定是线的中间位置(别问为什么,问就是只可意会不可言传),线的中间点知道了,那就可以定一个起点和终点的y值,跨度多少呢,经过不断尝试,大概200的跨度差不多,所以起点的y值就是changePoint.y - 100,终点就是changePoint + y。然后开始的x,既然从边缘开始,那不就是屏幕的宽度嘛,so easy,所以起点的位置就是{Screen_Width, changePoint.y - 100},终点就是{Screen_Width, changePoint.y + 100}
所以

第二步

移动到一个点 (想啥呢,layerUIBezierPath都还没创建呢)


shapeLayer = [CAShapeLayer layer];
shapeLayer.backgroundColor = [UIColor clearColor].CGColor;
shapeLayer.frame = CGRectMake(0, 0, Gesture_SCREEN_WIDTH, Gesture_SCREEN_HEIGHT);
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;

创建UIBezierPath,开始画线


UIBezierPath *path = [[UIBezierPath alloc] init];
path.lineWidth=1;
path.usesEvenOddFillRule = YES;
[path moveToPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y - 100)];

然后想想那个曲线,分解一下,第一段像是一个丿,第二段像是一个(,第三段像一个la(不好意思没找到),那不就来了嘛,小老弟,直接上代码(想屁吃呢,点还没确定呢)

首先确定第一段终点位置,要丿到一个地方,首先y的位置肯定不能是拖动点的y的位置,那玩意儿是中心点,肯定比它要高,那就高个20吧(别问,问就是大概加估计)。
再开始确定x的位置,首先比拖动点的x要大(为什么大,从左到右,0~屏幕宽度,你说为啥大),然后大多少,凭借我这几百度的近视眼估计了一下,大概是拖动长度的四分之一就够了,那不就来了嘛,x就是tempPoint.x + 10* (Gesture_SCREEN_WIDTH - tempPoint.x) / 40,(有没有不知道的,不知道算了),第一段终点位置就确定了{tempPoint.x + 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y - 20}tempPoint也就是拖动点,不过做了一些处理)

终点确定了,那就开始确定往哪个点弯,经过不断试错我来确定了一个(Gesture_SCREEN_WIDTH, tempPoint.y - 70),不多解释,解释就是凭经验(错误的经验),所以代码就是


[path addQuadCurveToPoint:CGPointMake(tempPoint.x+10* (Gesture_SCREEN_WIDTH- tempPoint.x) /40, tempPoint.y-20) controlPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y-70)];

第二段就简单了,对称一下,就拿到终点{tempPoint.x + 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y + 20}x不变,起点y20,终点就加20,控制点根据第一个点的方法判断x就是tempPoint.x - 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40y自然就是中心点了,控制点就是{tempPoint.x - 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y},第三段依葫芦画瓢,和第一段一样,三段画完,关门


[path addQuadCurveToPoint:CGPointMake(tempPoint.x + 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y + 20) controlPoint:CGPointMake(tempPoint.x - 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y)];
[path addQuadCurveToPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y + 100) controlPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y + 70)];
[path closePath];

然后给layer填充颜色,曲线添加到layer上,perfect!


shapeLayer.fillColor = [_config.backGroundColor colorWithAlphaComponent:_config.backGroundAlpha].CGColor;
shapeLayer.path = path.CGPath;
[Gesture_KeyWindow.layer addSublayer:shapeLayer];

第三步:

背景画好了,那不得再来个图片点缀一下?
再来个图片的layer,添加到刚刚的layer
至于位置嘛,不就跟痘痘里的东西一样嘛,对吧,所以直接就是{tempPoint.x + 10, tempPoint.y - 15, 30, 30}
话不多说,直接上代码。看不懂算求


CALayer *subLayer = [CALayer layer];
subLayer.backgroundColor = [UIColor clearColor].CGColor;
subLayer.contents = (__bridge id _Nullable)([self changeImage:Gesture_ImageWithName(_config.returnImageName) Color:_config.imageColor].CGImage);
subLayer.frame = CGRectMake(tempPoint.x+10, tempPoint.y - 15, 30, 30);

至此,背景大功告成

第四步:

开始添砖加瓦
拖动的时候,不能无限拖呗
所以得限制一下,如果拖动到某个边界的时候,继续拖就不要改变x值了,我设置了最大位移距离为40


CGPoint tempPoint = CGPointZero;
if (progressPoint.x < Gesture_SCREEN_WIDTH - 40) {
    tempPoint = CGPointMake(Gesture_SCREEN_WIDTH - 40, progressPoint.y);
}else{ 
    tempPoint = progressPoint;
}

既然x设置限制,y有没有呢,当然也有。当我们在屏幕顶部或底部的时候,起点和终点是不是就可能会超出屏幕了,所以限制一下y的值,最小不得小于100(为什么是100,看上面),最大不能超过屏幕高度 - 100


if (progressPoint.y <= 100) {
    tempPoint = CGPointMake(tempPoint.x,100)
}else if (progressPoint.y >= Gesture_SCREEN_HEIGHT - 100) {
    tempPoint = CGPointMake(tempPoint.x,Gesture_SCREEN_HEIGHT - 100);
 }

既然那个拖动展示的曲线要根据我当前的位置,那我是不是只要我接触点改变,就得重新绘制一次
另外就是直接返回首页,当我拖到一定位置时,几秒中不动就应该触发返回首页的操作,但是!!!不动?,屏幕感知那么灵敏,呼吸一下都有细微差距,所以不动,指的是宏观上的不动,不要在意细小的差别,所以给个左右精度就好了,我这儿设置的是左右不超过3就算为不动
所以思路有,那就简单了,上才艺,上代码


CGPoint changePoint = [gesture locationInView:Gesture_KeyWindow];
if (gesture.state == UIGestureRecognizerStateBegan) {
     farPoint= changePoint;
}
if (gesture.state == UIGestureRecognizerStateChanged) {        
     if (changePoint.x < farPoint.x)
         farPoint= changePoint;
     }
     if (_config.isCanPopToRootViewController) {
        if (homeLeftPoint.x > changePoint.x + 3 || homeLeftPoint.x < changePoint.x -3 ) {
             [self createLayer:changePoint];
             isPopToRootController = NO;
             [NSObject cancelPreviousPerformRequestsWithTarget:self];
             [self performSelector:@selector(changeReturnType:) withObject:@(changePoint.x) afterDelay:_config.returnHomeTime];
        }
     }else{
         [self createLayer:changePoint];
     }
}


/**
 改变返回执行类型以及图标
 */
- (void)changeReturnType:(NSNumber *)pointX {
    homeLeftPoint = CGPointMake([pointX floatValue],0);
    isPopToRootController = YES;
    CALayer *layer = [shapeLayer.sublayers firstObject];
    layer.contents = (__bridge id _Nullable)([self changeImage:Gesture_ImageWithName(_config.returnHomeImageName) Color:_config.homeImageColor].CGImage);
}

最后就是松手的时候了,当手势状态为UIGestureRecognizerStateEnded,即是松手了。
那不得简简单单,直接调用返回不就完了,想屁吃呢,那个痘痘不是还展示在屏幕吗?不打算给摁下去?
所以下一步操作来了,让这个痘痘圆润的滚回去
所以咱怎么拖出来的,怎么给收回去不就好了嘛!
但是!!!!!我不想搞了,你们玩儿吧,我直接简单粗暴一点,给个动画,直接他移出去,虽然效果没有重新绘制好,但是谁叫我懒呢。上代码


if (gesture.state == UIGestureRecognizerStateEnded) {
        CABasicAnimation *anim = [CABasicAnimation animation];
        anim.keyPath=@"position";
        anim.duration=0.5;
        anim.fromValue = [NSValue valueWithCGPoint:shapeLayer.position];
        anim.toValue = [NSValue valueWithCGPoint:CGPointMake(shapeLayer.position.x + 55, shapeLayer.position.y)];
        anim.fillMode = kCAFillModeForwards;
        anim.removedOnCompletion = NO;
        [shapeLayer addAnimation:anim forKey:nil];
        if (changePoint.x < farPoint.x + 20)
            if  (!isPopToRootController) {
                if (self.delegate && [self.delegate respondsToSelector:@selector(CM_GestureRecognizerPopLastController:currentController:)]) {
                    UIViewController*lastController =nil;
                    NSInteger controllerCount =_config.navigationController.viewControllers.count;
                    if (controllerCount >= 2) {
                        lastController =_config.navigationController.viewControllers[controllerCount -2];
                    }
                    if ([self.delegate CM_GestureRecognizerPopLastController:lastController currentController:_config.navigationController.topViewController]) {
                        [_config.navigationController popViewControllerAnimated:_config.isShowPopAnimated];
                    }
                }else{
                    [_config.navigationController popViewControllerAnimated:_config.isShowPopAnimated];
                }
            }else{
                if (self.delegate && [self.delegate respondsToSelector:@selector(CM_GestureRecognizerBackHomeController:)]) {
                    if ([self.delegate CM_GestureRecognizerBackHomeController:_config.navigationController.viewControllers.firstObject]) {
                        [_config.navigationController popToRootViewControllerAnimated:_config.isShowPopAnimated];
                    }
                }else{
                    [_config.navigationController popToRootViewControllerAnimated:_config.isShowPopAnimated];
                }
            }
        }
    }

用法

- (void)initScreenPoP {
    PopGestureRecognizerManager *manager = [PopGestureRecognizerManager shareManager];
    //设置返回图片
    manager.config.returnImageName = @"icon_pop_jt";
    //设置返回代理,可不设置,不设置默认返回
    //代理的返回值控制是否自动调用返回
    manager.delegate = self;
    //设置返回首页图片
    manager.config.returnHomeImageName = @"icon_pop_home";
    //config传nil,就使用默认的配置,也可以自各儿创建
    //页面可单独修改manager.config的各个配置
    //根控制器默认不触发返回效果
    [manager registerManagerWithConfig:nil completeBlock:^(BOOL isSuccess, NSString *failString) {
          NSLog(@"%@",failString);
    }];
}

附所有代码
PopGestureRecognizerManager.h

//
//  PopGestureRecognizerManager.h
//  ScreenPoP
//
//  Created by wr on 2019/7/3.
//  Copyright © 2019年 wanmengchao. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "PopGestureRecognizerManagerConfiger.h"
#import "PopGestureRecognizerDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@interface PopGestureRecognizerManager : NSObject

/// 配置
@property (nonatomic, strong, nonnull) PopGestureRecognizerManagerConfiger *config;

/// 返回代理
@property (nonatomic, weak) id<PopGestureRecognizerDelegate> delegate;

/// 单例
+ (instancetype)shareManager;

/// 注册
/// @param config 配置信息
/// @param block 注册回调
- (void)registerManagerWithConfig:(PopGestureRecognizerManagerConfiger * __nullable)config completeBlock:(void(^ __nullable)(BOOL isSuccess, NSString *failString))block;
@end

NS_ASSUME_NONNULL_END

PopGestureRecognizerManager.m

//
//  PopGestureRecognizerManager.m
//  ScreenPoP
//
//  Created by wr on 2019/7/3.
//  Copyright © 2019年 wanmengchao. All rights reserved.
//

#import "PopGestureRecognizerManager.h"

//屏幕大小
#define Gesture_SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define Gesture_SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
//颜色宏定义
#define Gesture_COLOR_HEX(hex) Gesture_COLOR_HEXA(hex,1.0f)
#define Gesture_COLOR_HEXA(rgbValue,a) [UIColor colorWithRed:((float)(((rgbValue) & 0xFF0000) >> 16))/255.0 green:((float)(((rgbValue) & 0xFF00)>>8))/255.0 blue: ((float)((rgbValue) & 0xFF))/255.0 alpha:(a)]
#define Gesture_ImageWithName(imgName) [UIImage imageNamed:imgName]
#define Gesture_KeyWindow [[UIApplication sharedApplication] delegate].window
#define PPLog(format, ...) printf("%s",[[NSString stringWithFormat:(format), ##__VA_ARGS__] UTF8String])

static PopGestureRecognizerManager *manager = nil;

@implementation PopGestureRecognizerManager{
    CAShapeLayer *shapeLayer;
    CGPoint startPoint;
    CGPoint farPoint;
    CGPoint lastPoint;
    CGPoint homeLeftPoint;
    BOOL isPopToRootController;
    NSTimer *timer;
}

+ (instancetype)shareManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[PopGestureRecognizerManager alloc] init];
    });
    return manager;
}


/// 重写单例对象的alloc方法, 防止单例对象被重复创建
+ (instancetype)alloc {
    if (manager) {
        // 如果单例对象存在则抛出异常
        NSException *exception = [NSException exceptionWithName:@"重复创建单例对象异常" reason:@"单例被重复创建" userInfo:nil];
        [exception raise];
    }
    return [super alloc];
}

- (instancetype)init {
    if (self = [super init]) {
        _config = [[PopGestureRecognizerManagerConfiger alloc] init];
    }
    return self;
}

/**
 添加手势到window上
 */
- (void)addScreenEdgePanGestureToWindow {
    UIScreenEdgePanGestureRecognizer *gesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(screenEdgePanGestureExcuteEvent:)];
    gesture.edges = UIRectEdgeRight;
    [Gesture_KeyWindow addGestureRecognizer:gesture];
}

- (void)registerManagerWithConfig:(PopGestureRecognizerManagerConfiger * __nullable)config completeBlock:(void(^ __nullable)(BOOL isSuccess, NSString *failString))block {
    if (config) {
        self.config = config;
    }
    [self addScreenEdgePanGestureToWindow];
    if (self.config.returnImageName.length == 0) {
        if (block) {
            block(YES, @"拖动时返回的图片未设置(returnImageName未设置)");
        }
    }if (self.config.isCanPopToRootViewController && self.config.returnHomeImageName.length == 0) {
        if (block) {
            block(YES, @"可以返回主页时(isCanPopToRootViewController = YES),拖动展示的图片未设置(returnHomeImageName未设置)");
        }
    }else{
        if (block) {
            block(YES, @"当前controller为导航控制器的根控制器时,拖动无效果");
        }
    }
    
#if DEBUG
    PPLog(@"==========Manager注册信息==========\n");
    PPLog(@"NavigationController:%@\n",self.config.navigationController);
    PPLog(@"拖动时的背景颜色R:%g G:%g B:%g \n",CGColorGetComponents(self.config.backGroundColor.CGColor)[0] * 255,CGColorGetComponents(self.config.backGroundColor.CGColor)[1] * 255,CGColorGetComponents(self.config.backGroundColor.CGColor)[2] * 255);
    PPLog(@"背景颜色Alpha值:%g\n",self.config.backGroundAlpha);
    PPLog(@"拖动时展示的图片:%@\n",self.config.returnImageName);
    if (self.config.imageColor) {
        PPLog(@"拖动时图片的颜色:R:%g G:%g B:%g \n",CGColorGetComponents(self.config.imageColor.CGColor)[0] * 255,CGColorGetComponents(self.config.imageColor.CGColor)[1] * 255,CGColorGetComponents(self.config.imageColor.CGColor)[2] * 255);
    }else{
        PPLog(@"不改变拖动时图片的颜色\n");
    }
    PPLog(@"是否跟随手势位置移动:%@\n",self.config.isFollowGesturePosition ? @"是" : @"否");
    PPLog(@"是否可以返回首页:%@\n",self.config.isCanPopToRootViewController ? @"是" : @"否");
    PPLog(@"返回首页拖动时展示的图片:%@\n",self.config.returnImageName);
    PPLog(@"返回首页拖动触发时间:%g\n",self.config.returnHomeTime);
    PPLog(@"是否展示返回动画:%@\n",self.config.isShowPopAnimated ? @"是" : @"否");
#endif
}

/**
 屏幕边界手势执行事件
 
 @param gesture 手势
 */
- (void)screenEdgePanGestureExcuteEvent:(UIScreenEdgePanGestureRecognizer *)gesture {
    if (_config.navigationController.viewControllers.count == 1) {
        return;
    }
    CGPoint changePoint = [gesture locationInView:Gesture_KeyWindow];
    if (gesture.state == UIGestureRecognizerStateBegan) {
        farPoint = changePoint;
    }
    if (gesture.state == UIGestureRecognizerStateChanged) {
        if (changePoint.x < farPoint.x) {
            farPoint = changePoint;
        }
        if (_config.isCanPopToRootViewController) {
            if (homeLeftPoint.x > changePoint.x + 3 || homeLeftPoint.x < changePoint.x - 3) {
                [self createLayer:changePoint];
                isPopToRootController = NO;
                [NSObject cancelPreviousPerformRequestsWithTarget:self];
                [self performSelector:@selector(changeReturnType:) withObject:@(changePoint.x) afterDelay:_config.returnHomeTime];
            }
        }else{
            [self createLayer:changePoint];
        }
    }
    if (gesture.state == UIGestureRecognizerStateEnded) {
        CABasicAnimation *anim = [CABasicAnimation animation];
        anim.keyPath = @"position";
        anim.duration = 0.5;
        anim.fromValue = [NSValue valueWithCGPoint:shapeLayer.position];
        anim.toValue = [NSValue valueWithCGPoint:CGPointMake(shapeLayer.position.x + 55, shapeLayer.position.y)];
        anim.fillMode = kCAFillModeForwards;
        anim.removedOnCompletion = NO;
        [shapeLayer addAnimation:anim forKey:nil];
        if (changePoint.x < farPoint.x + 20) {
            if (!isPopToRootController) {
                if (self.delegate && [self.delegate respondsToSelector:@selector(CM_GestureRecognizerPopLastController:currentController:)]) {
                    UIViewController *lastController = nil;
                    NSInteger controllerCount = _config.navigationController.viewControllers.count;
                    if (controllerCount >= 2) {
                        lastController = _config.navigationController.viewControllers[controllerCount - 2];
                    }
                    if ([self.delegate CM_GestureRecognizerPopLastController:lastController currentController:_config.navigationController.topViewController]) {
                        [_config.navigationController popViewControllerAnimated:_config.isShowPopAnimated];
                    }
                }else{
                    [_config.navigationController popViewControllerAnimated:_config.isShowPopAnimated];
                }
            }else{
                if (self.delegate && [self.delegate respondsToSelector:@selector(CM_GestureRecognizerBackHomeController:)]) {
                    if ([self.delegate CM_GestureRecognizerBackHomeController:_config.navigationController.viewControllers.firstObject]) {
                        [_config.navigationController popToRootViewControllerAnimated:_config.isShowPopAnimated];
                    }
                }else{
                    [_config.navigationController popToRootViewControllerAnimated:_config.isShowPopAnimated];
                }
            }
        }
    }
}

/**
 创建背景layer
 
 @param progressPoint 滑动点
 */
- (void)createLayer:(CGPoint)progressPoint {
    CGPoint tempPoint = CGPointZero;
    if (progressPoint.x < Gesture_SCREEN_WIDTH - 40) {
        tempPoint = CGPointMake(Gesture_SCREEN_WIDTH - 40, progressPoint.y);
    }else{
        tempPoint = progressPoint;
    }
    if (_config.isFollowGesturePosition) {
        if (progressPoint.y <= 100) {
            tempPoint = CGPointMake(tempPoint.x, 100);
        }else if (progressPoint.y >= Gesture_SCREEN_HEIGHT - 100) {
            tempPoint = CGPointMake(tempPoint.x, Gesture_SCREEN_HEIGHT - 100);
        }
    }else{
        tempPoint = CGPointMake(tempPoint.x, Gesture_SCREEN_HEIGHT / 2);
    }
    [shapeLayer removeFromSuperlayer];
    shapeLayer = [CAShapeLayer layer];
    shapeLayer.backgroundColor = [UIColor clearColor].CGColor;
    shapeLayer.frame = CGRectMake(0, 0, Gesture_SCREEN_WIDTH, Gesture_SCREEN_HEIGHT);
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;
    CALayer *subLayer = [CALayer layer];
    subLayer.backgroundColor = [UIColor clearColor].CGColor;
    subLayer.contents = (__bridge id _Nullable)([self changeImage:Gesture_ImageWithName(_config.returnImageName) Color:_config.imageColor].CGImage);
    subLayer.frame = CGRectMake(tempPoint.x + 10, tempPoint.y - 15, 30, 30);
    UIBezierPath *path = [[UIBezierPath alloc] init];
    path.lineWidth = 1;
    path.usesEvenOddFillRule = YES;
    [path moveToPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y - 100)];
    [path addQuadCurveToPoint:CGPointMake(tempPoint.x + 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y - 20) controlPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y - 70)];
    [path addQuadCurveToPoint:CGPointMake(tempPoint.x + 10 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y + 20) controlPoint:CGPointMake(tempPoint.x - 0 * (Gesture_SCREEN_WIDTH - tempPoint.x) / 40, tempPoint.y)];
    [path addQuadCurveToPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y + 100) controlPoint:CGPointMake(Gesture_SCREEN_WIDTH, tempPoint.y + 70)];
    [path closePath];
    shapeLayer.fillColor = [_config.backGroundColor colorWithAlphaComponent:_config.backGroundAlpha].CGColor;
    shapeLayer.path = path.CGPath;
    [shapeLayer addSublayer:subLayer];
    [Gesture_KeyWindow.layer addSublayer:shapeLayer];
}

/**
 改变返回执行类型以及图标
 */
- (void)changeReturnType:(NSNumber *)pointX {
    homeLeftPoint = CGPointMake([pointX floatValue], 0);
    isPopToRootController = YES;
    CALayer *layer = [shapeLayer.sublayers firstObject];
    layer.contents = (__bridge id _Nullable)([self changeImage:Gesture_ImageWithName(_config.returnHomeImageName) Color:_config.homeImageColor].CGImage);
}

/**
 修改图片颜色
 
 @param image 要改变的图片
 @param color 设置的颜色
 @return 返回图片
 */
- (UIImage *)changeImage:(UIImage *)image Color:(UIColor * __nullable)color {
    if (color) {
        UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(context, 0, image.size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextSetBlendMode(context, kCGBlendModeNormal);
        CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
        CGContextClipToMask(context, rect, image.CGImage);
        [color setFill];
        CGContextFillRect(context, rect);
        UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return newImage;
    }else{
        return image;
    }
}

@end


PopGestureRecognizerManagerConfiger.h

//
//  PopGestureRecognizerManagerConfiger.h
//  ScreenPoP
//
//  Created by wr on 2019/7/3.
//  Copyright © 2019年 wanmengchao. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface PopGestureRecognizerManagerConfiger : NSObject

/**
 导航控制器
 默认为当前window的导航控制器
 */
@property (nonatomic, strong, nullable) UINavigationController *navigationController;

/**
 拖动时,背景颜色
 默认为0x999999
 */
@property (nonatomic, strong, nullable) UIColor *backGroundColor;

/**
 背景颜色alpha
 默认为0.5
 */
@property (nonatomic, assign) CGFloat backGroundAlpha;

/**
 拖动时展示的图片
 必填,否则无图片
 */
@property (nonatomic, copy, nonnull) NSString *returnImageName;

/**
 用于修改拖动时展示的图片的颜色
 默认为不变色即nil
 */
@property (nonatomic, retain, nullable) UIColor *imageColor;

/**
 用于修改拖动时展示返回首页的图片的颜色
 默认为不变色即nil
 */
@property (nonatomic, retain, nullable) UIColor *homeImageColor;

/**
 是否跟随手势位置移动
 默认为YES
 */
@property (nonatomic, assign) BOOL isFollowGesturePosition;

/**
 是否可以返回首页
 默认为YES
 */
@property (nonatomic, assign) BOOL isCanPopToRootViewController;


/**
 返回首页拖动时展示的图片
 isCanPopToRootViewController为YES时,必填
 */
@property (nonatomic, copy, nullable) NSString *returnHomeImageName;

/**
 返回首页用时
 默认为1秒
 */
@property (nonatomic, assign) CGFloat returnHomeTime;

/**
 是否执行返回动画
 默认为:YES
 */
@property (nonatomic, assign) BOOL isShowPopAnimated;

@end

NS_ASSUME_NONNULL_END

PopGestureRecognizerManagerConfiger.m

//
//  PopGestureRecognizerManagerConfiger.m
//  ScreenPoP
//
//  Created by wr on 2019/7/3.
//  Copyright © 2019年 wanmengchao. All rights reserved.
//

#import "PopGestureRecognizerManagerConfiger.h"
#define Gesture_COLOR_HEX(hex) Gesture_COLOR_HEXA(hex,1.0f)
#define Gesture_COLOR_HEXA(rgbValue,a) [UIColor colorWithRed:((float)(((rgbValue) & 0xFF0000) >> 16))/255.0 green:((float)(((rgbValue) & 0xFF00)>>8))/255.0 blue: ((float)((rgbValue) & 0xFF))/255.0 alpha:(a)]

@implementation PopGestureRecognizerManagerConfiger

- (instancetype)init {
    if (self = [super init]) {
        _backGroundColor = Gesture_COLOR_HEX(0x999999);
        _isFollowGesturePosition = YES;
        _isCanPopToRootViewController = YES;
        _isShowPopAnimated = YES;
        _imageColor = nil;
        _returnHomeTime = 1.0f;
        _backGroundAlpha = 0.3f;
    }
    return self;
}

- (UINavigationController *)navigationController {
    if (!_navigationController) {
        _navigationController = (UINavigationController *)[UIApplication sharedApplication].keyWindow.rootViewController;
    }
    return _navigationController;
}


@end

PopGestureRecognizerDelegate.h

//
//  PopGestureRecognizerDelegate.h
//  ScreenPoP
//
//  Created by 万孟超 on 2022/1/19.
//  Copyright © 2022 wanmengchao. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol PopGestureRecognizerDelegate <NSObject>

@optional
/// 返回时代理
/// 重写返回按钮时,可实现此方法处理数据
/// @param lastController 上一个controller
/// @param currentController 当前controller
/// @return 是否自动返回上一个controller
- (BOOL)CM_GestureRecognizerPopLastController:(UIViewController *)lastController currentController:(UIViewController *)currentController;

/// 返回首页代理
/// @param currentController 当前controller
/// @return 是否自动返回主页
- (BOOL)CM_GestureRecognizerBackHomeController:(UIViewController *)currentController;

@end

NS_ASSUME_NONNULL_END

至此,搞定收工

附demo地址:ScreenPop: 仿Android左滑返回
Swift版的就不搞了,实现思路一样的!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335