UIView 侧滑事件的封装

一、前言

iOS开发中,我们经常使用UITableViewCell的侧滑效果及事件。比如微信的主页面,都是可以侧滑的,然后里面是删除等功能。这是iOS设计规范里很通用的一种快捷操作方案,基本上每一款App都有这样的设计。那么问题来了,UIView有没有同样的处理方案呢?毕竟很多时候,UIView才是最常用的。

二、需求简要

视觉效果就是UITableViewCell的侧滑一样,功能也是那样。其实,实际开发中,UITableViewCell的侧滑用的也是第三方的控件,比如笔者现在用的就是MGSwipeTableCell,其效果与微信的一模一样(微信用的应该也是这个吧~)

三、应用场景

UITableView更多的用于列表展示页,与视图有频繁交互的页面还是要以UIView和相关控件为主,比如基本的表单页,需要承载用户输入的大量的数据,以及编辑等操作,这时,侧滑加入一下删除等操作就很常见了。

四、实现方案分析

在实现的过程中,小编在方案上纠结了一会儿,到底是用继承来实现还是Category呢?我第一想法是用继承,但最终我选择了Category。这里简单的说一下原因,小编我是从使用的角度来考虑的,如果使用的是继承,那么要使用的话,就意味着必须继承于这个封装的侧滑View,这样他们之间就绑定在了一起,而我很讨厌这种感觉,就是这种强依赖关系,况且,侧滑只是一种附带的小功能,能不能以“贴标签”的形式给target view 贴上swipe的功能呢?那么,我们很容易就想到了Cagegory。其实从实现的角度来考虑,继承的方案会简单很多,主要也是Category方案在实现中遇到了一些坑!

五、代码实现

UIView+Swipe.h

#import <UIKit/UIKit.h>

@interface SwipeButton : UIButton

+ (instancetype)buttonWithTitle:(NSString *)title
                backgroundColor:(UIColor *)backgroundColor
                       callback:(MPNoParameterBlock)handler;

@end

@interface UIView (Swipe)

/** 配置左侧的按钮 */
- (void)configLeftButtons:(NSArray<SwipeButton *> *)buttons swipe:(MPNoParameterBlock)swipeHandler;
/** 配置右侧的按钮 */
- (void)configRightButtons:(NSArray<SwipeButton *> *)buttons swipe:(MPNoParameterBlock)swipeHandler;
/** 从侧滑状态复位 */
- (void)recover;

@end

UIView+Swipe.m

#import "UIView+Swipe.h"
#import "Masonry.h"

@interface SwipeButton ()

@property (nonatomic, copy) MPNoParameterBlock useForRecoverHandler;

@property (nonatomic, copy) MPNoParameterBlock clickHandler;

@end

@implementation SwipeButton

+ (instancetype)buttonWithTitle:(NSString *)title
                backgroundColor:(UIColor *)backgroundColor
                       callback:(MPNoParameterBlock)handler {

    return [self buttonWithTitle:title icon:nil backgroundColor:backgroundColor callback:handler];
}

+ (instancetype)buttonWithTitle:(NSString *)title
                           icon:(UIImage*)icon
                backgroundColor:(UIColor *)backgroundColor
                       callback:(MPNoParameterBlock)callback {
    
    SwipeButton *button = [self buttonWithType:UIButtonTypeCustom];
    button.backgroundColor = backgroundColor;
    button.titleLabel.font = [UIFont systemFontOfSize:15];
    button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
    button.titleLabel.textAlignment = NSTextAlignmentCenter;
    [button setTitle:title forState:UIControlStateNormal];
    [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [button setImage:icon forState:UIControlStateNormal];
    button.clickHandler = callback;
    [button sizeToFit];
    [button addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
    return button;
}

+ (void)onClick:(SwipeButton *)sender {
    if (sender.clickHandler) {
        sender.clickHandler();
    }
    if (sender.useForRecoverHandler) {
        sender.useForRecoverHandler();
    }
}

@end

typedef NS_ENUM(NSInteger, ViewSwipePosition) {
    ViewSwipePositionNormal = 0,    // 正常位置
    ViewSwipePositionLeft = 1,      // 左侧按钮出现
    ViewSwipePositionRight = 2,     // 右侧按钮出现
};

@interface UIView ()

@property (nonatomic, weak) UIImageView *mainShotImageView;

@property (nonatomic, weak) UIView *leftView;

@property (nonatomic, weak) UIView *rightView;

@property (nonatomic, assign) ViewSwipePosition position;

@property (nonatomic, assign) CGFloat leftSwipeDistance;

@property (nonatomic, assign) CGFloat rightSwipeDistance;

@property (nonatomic, copy) MPNoParameterBlock leftSwipeHandler;

@property (nonatomic, copy) MPNoParameterBlock rightSwipeHandler;

@end

#define widthPerItem 78

NSString *const leftViewKey = @"leftViewKey";
NSString *const rightViewKey = @"rightViewKey";
NSString *const positionKey = @"positionKey";
NSString *const leftSwipeDistanceKey = @"leftSwipeDistanceKey";
NSString *const rightSwipeDistanceKey = @"rightSwipeDistanceKey";
NSString *const mainShotImageViewKey = @"mainShotImageViewKey";
NSString *const rightSwipeHandlerKey = @"rightSwipeHandlerKey";
NSString *const leftSwipeHandlerKey = @"leftSwipeHandlerKey";


@implementation UIView (Swipe)

- (void)setLeftView:(UIView *)leftView {
    objc_setAssociatedObject(self, &leftViewKey, leftView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)leftView {
    return objc_getAssociatedObject(self, &leftViewKey);
}

- (void)setRightView:(UIView *)rightView {
    objc_setAssociatedObject(self, &rightViewKey, rightView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)rightView {
    return objc_getAssociatedObject(self, &rightViewKey);
}

- (void)setMainShotImageView:(UIImageView *)mainShotImageView {
    objc_setAssociatedObject(self, &mainShotImageViewKey, mainShotImageView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIImageView *)mainShotImageView {
    return objc_getAssociatedObject(self, &mainShotImageViewKey);
}

- (void)setPosition:(ViewSwipePosition)position {
    NSNumber *positionNum = [NSNumber numberWithInteger:position];
    objc_setAssociatedObject(self, &positionKey, positionNum, OBJC_ASSOCIATION_ASSIGN);
}

- (ViewSwipePosition)position {
    NSNumber *isRightNum = objc_getAssociatedObject(self, &positionKey);
    return isRightNum.theIntegerValue;
}

- (void)setLeftSwipeDistance:(CGFloat)leftSwipeDistance {
    NSNumber *swipeDistanceNum = [NSNumber numberWithFloat:leftSwipeDistance];
    objc_setAssociatedObject(self, &leftSwipeDistanceKey , swipeDistanceNum, OBJC_ASSOCIATION_ASSIGN);
}

- (CGFloat)leftSwipeDistance {
    NSNumber *swipeDistanceNum = objc_getAssociatedObject(self, &leftSwipeDistanceKey);
    return swipeDistanceNum.theFloatValue;
}

- (void)setRightSwipeDistance:(CGFloat)rightSwipeDistance {
    NSNumber *swipeDistanceNum = [NSNumber numberWithFloat:rightSwipeDistance];
    objc_setAssociatedObject(self, &rightSwipeDistanceKey, swipeDistanceNum, OBJC_ASSOCIATION_ASSIGN);
}

- (CGFloat)rightSwipeDistance {
    NSNumber *swipeDistanceNum = objc_getAssociatedObject(self, &rightSwipeDistanceKey);
    return swipeDistanceNum.theFloatValue;
}

- (MPNoParameterBlock)leftSwipeHandler {
    MPNoParameterBlock block = objc_getAssociatedObject(self, &leftSwipeHandlerKey);
    return block;
}

- (void)setLeftSwipeHandler:(MPNoParameterBlock)leftSwipeHandler {
    objc_setAssociatedObject(self, &leftSwipeHandlerKey, leftSwipeHandler, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (MPNoParameterBlock)rightSwipeHandler {
    MPNoParameterBlock block = objc_getAssociatedObject(self, &rightSwipeHandlerKey);
    return block;
}

- (void)setRightSwipeHandler:(MPNoParameterBlock)rightSwipeHandler {
    objc_setAssociatedObject(self, &rightSwipeHandlerKey, rightSwipeHandler, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)configMainImageView {
    
    if (self.mainShotImageView) {
        return;
    }
    UIImageView *mainImageView = [[UIImageView alloc] init];
    mainImageView.hidden = YES;
    mainImageView.userInteractionEnabled = YES;
    [self addSubview:mainImageView];
    self.mainShotImageView = mainImageView;
    [mainImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(0);
        make.top.bottom.mas_equalTo(self);
        make.right.mas_equalTo(0);
    }];
}

- (void)configLeftButtons:(NSArray<SwipeButton *> *)buttons
                    swipe:(MPNoParameterBlock)swipeHandler {

    if (buttons.count == 0) {
        return;
    }
    if (swipeHandler) {
        self.leftSwipeHandler = swipeHandler;
    }
    UIView *leftView = [[UIView alloc] init];
    leftView.backgroundColor = [UIColor whiteColor];
    [self addSubview:leftView];
    self.leftView = leftView;
    
    [self configMainImageView];
    
    self.leftSwipeDistance = widthPerItem * buttons.count;
    [leftView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.mas_equalTo(self.mainShotImageView.mas_left);
        make.top.bottom.mas_equalTo(self);
        make.width.mas_equalTo(0);
    }];
    
    [self configLeftSlipGesture];
    
    SwipeButton *currentBtn;
    __weak typeof(self)weakSelf = self;
    for (SwipeButton *btnItem in buttons) {
        [self.leftView addSubview:btnItem];
        [btnItem mas_makeConstraints:^(MASConstraintMaker *make) {
            make.right.mas_equalTo(currentBtn ? currentBtn.mas_left : self.leftView.mas_right);
            make.top.bottom.mas_equalTo(self.leftView);
            if (currentBtn) {
                make.width.mas_equalTo(currentBtn);
            }
        }];
        currentBtn = btnItem;
        currentBtn.useForRecoverHandler = ^{
            [weakSelf recoverSwipeState:UISwipeGestureRecognizerDirectionLeft];
        };
    }
    if (currentBtn) {
        [currentBtn mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.mas_equalTo(self.leftView);
        }];
    }
}

- (void)configRightButtons:(NSArray<SwipeButton *> *)buttons
                     swipe:(MPNoParameterBlock)swipeHandler {
    
    if (buttons.count == 0) {
        return;
    }
    if (swipeHandler) {
        self.rightSwipeHandler = swipeHandler;
    }
    UIView *rightView = [[UIView alloc] init];
    rightView.backgroundColor = [UIColor whiteColor];
    [self addSubview:rightView];
    self.rightView = rightView;
    
    [self configMainImageView];
    
    self.rightSwipeDistance = widthPerItem * buttons.count;
    [rightView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(self.mainShotImageView.mas_right);
        make.top.bottom.mas_equalTo(self);
        make.width.mas_equalTo(0);
    }];
    
    [self configLeftSlipGesture];
    
    SwipeButton *currentBtn;
    __weak typeof(self)weakSelf = self;
    for (SwipeButton *btnItem in buttons) {
        [self.rightView addSubview:btnItem];
        [btnItem mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.mas_equalTo(currentBtn ? currentBtn.mas_right : self.rightView.mas_left);
            make.top.bottom.mas_equalTo(self.rightView);
            if (currentBtn) {
                make.width.mas_equalTo(currentBtn);
            }
        }];
        currentBtn = btnItem;
        currentBtn.useForRecoverHandler = ^{
            [weakSelf recoverSwipeState:UISwipeGestureRecognizerDirectionRight];
        };
    }
    if (currentBtn) {
        [currentBtn mas_makeConstraints:^(MASConstraintMaker *make) {
            make.right.mas_equalTo(self.rightView);
        }];
    }
}

- (void)recover {
    
    if (self.position == ViewSwipePositionLeft) {
        [self recoverSwipeState:UISwipeGestureRecognizerDirectionLeft];
    } else if (self.position == ViewSwipePositionRight) {
        [self recoverSwipeState:UISwipeGestureRecognizerDirectionRight];
    }
}

- (void)recoverSwipeState:(UISwipeGestureRecognizerDirection)direction {
    
    UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] init];
    gesture.direction = direction;
    [self handleSwipeFrom:gesture];
}

- (void)configLeftSlipGesture {
    
    for (UIGestureRecognizer *gesture in self.gestureRecognizers) {
        [self removeGestureRecognizer:gesture];
    }
    for (UIGestureRecognizer *gesture in self.mainShotImageView.gestureRecognizers) {
        [self.mainShotImageView removeGestureRecognizer:gesture];
    }
    
    UISwipeGestureRecognizer *recognizerLeft = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipeFrom:)];
    [recognizerLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
    [self addGestureRecognizer:recognizerLeft];
    
    UISwipeGestureRecognizer *mainShotRecognizerLeft = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipeFrom:)];
    [mainShotRecognizerLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
    [self.mainShotImageView addGestureRecognizer:mainShotRecognizerLeft];

    UISwipeGestureRecognizer *recognizerRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipeFrom:)];
    [recognizerRight setDirection:UISwipeGestureRecognizerDirectionRight];
    [self addGestureRecognizer:recognizerRight];
    
    UISwipeGestureRecognizer *mainShotRecognizerRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipeFrom:)];
    [mainShotRecognizerRight setDirection:UISwipeGestureRecognizerDirectionRight];
    [self.mainShotImageView addGestureRecognizer:mainShotRecognizerRight];
}



- (void)handleSwipeFrom:(UISwipeGestureRecognizer *)sender {
    
    if (self.position == ViewSwipePositionNormal) {
        if (sender.direction == UISwipeGestureRecognizerDirectionLeft && self.rightView) {
            self.mainShotImageView.image = [self screenShotView:self];
            self.mainShotImageView.hidden = NO;
            [self updateSelfPostionTo:ViewSwipePositionRight];
            if (self.rightSwipeHandler) {
                self.rightSwipeHandler();
            }
        } else if (sender.direction == UISwipeGestureRecognizerDirectionRight && self.leftView) {
            self.mainShotImageView.image = [self screenShotView:self];
            self.mainShotImageView.hidden = NO;
            [self updateSelfPostionTo:ViewSwipePositionLeft];
            if (self.leftSwipeHandler) {
                self.leftSwipeHandler();
            }
        }
    } else if (self.position == ViewSwipePositionLeft) {
        if (sender.direction == UISwipeGestureRecognizerDirectionLeft) {
            [self updateSelfPostionTo:ViewSwipePositionNormal];
        }
    } else if (self.position == ViewSwipePositionRight) {
        if (sender.direction == UISwipeGestureRecognizerDirectionRight) {
            [self updateSelfPostionTo:ViewSwipePositionNormal];
        }
    }
}

- (void)updateSelfPostionTo:(ViewSwipePosition)position {
    
    CGFloat offset = 0;
    if (position == ViewSwipePositionRight) {
        offset = self.rightSwipeDistance;
    } else if (position == ViewSwipePositionLeft) {
        offset = -self.leftSwipeDistance;
    }
    
    [self.mainShotImageView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(-offset);
        make.right.mas_equalTo(self.mas_right).offset(-offset);
    }];
    
    if (position == ViewSwipePositionLeft) {
        [self.leftView mas_updateConstraints:^(MASConstraintMaker *make) {
            make.width.mas_equalTo(-offset);
        }];
    } else if (position == ViewSwipePositionRight) {
        [self.rightView mas_updateConstraints:^(MASConstraintMaker *make) {
            make.width.mas_equalTo(offset);
        }];
    }
    
    
    [self configAndActionAnimationIfNeed];
    
    self.position = position;

    if (self.position == ViewSwipePositionNormal) {
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            self.mainShotImageView.hidden = YES;
        });
    }
}

// 对指定视图进行截图
- (UIImage *)screenShotView:(UIView *)view {
    
    UIImage *imageRet = nil;
    if (view) {
        UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0);
        
        //获取图像
        [view.layer renderInContext:UIGraphicsGetCurrentContext()];
        imageRet = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    } else {
        
    }
    return imageRet;
}

@end

Usage

SwipeButton *deleteBtn = [SwipeButton buttonWithTitle:@"删除" backgroundColor:RedColor callback:^{
    // Click Handler
}];
[view configRightButtons:@[deleteBtn] swipe:nil];

Tips:实现方案中用到了截图的方案,在侧滑状态中,其实用的是一块截图

六、写在最后

对于以上的实现方案有更好建议的,欢迎指正,对于当前的效果,其实还有一些被不太完善,主要是动画那里,目前没有实现视图的滑动实时跟随手指移动,后续对于手势那部分可能还是要优化一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明AI阅读 16,135评论 3 119
  • “叮咚,叮咚……” “来了,来了。”门铃响起第三遍时,一个急匆匆的声音从门缝里传了出来。 “梅梅!你怎么来了?也没...
    无色口红阅读 817评论 0 6
  • 题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 解法一: 这种题...
    qming_c阅读 274评论 0 0
  • 记得从前在某本笔记本的底角记下过一句话“心湖蓄满了,该从笔尖溢出了”,现在的我不正是这种状态吗?总会不由自主地想把...
    170415阅读 699评论 1 6
  • 每个人都有一个坚硬的盔甲,怕受伤害,用一个硬壳把自己包裹起来。
    若兰词话阅读 350评论 0 1

友情链接更多精彩内容