JQWord 02(卡片模块)

JQCardViewController

#import "JQCardController.h"
#import "JQCardSwitchView.h"
#import "JQWordTool.h"
#import "JQCardView.h"
#import "JQEditController.h"
#import "JQNavigationController.h"
#import "JQAddWordController.h"

@interface JQCardController ()
@property (nonatomic,strong)JQCardSwitchView *cardSwitchView;
@property (nonatomic,strong)NSArray *words;
@property (nonatomic,assign)NSInteger editViewtag;//正在编辑view的tag

@end

@implementation JQCardController

#pragma mark 生命周期方法

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setAutomaticallyAdjustsScrollViewInsets:NO];//关闭对其子控件scrollView的默认内边距
#warning 如果navigationBar设置了背景图片,那么这句也没用
    
    self.view.backgroundColor=[UIColor whiteColor];

    //添加右边+号按钮
    self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addClick)];
    
    //添加switchView
    [self addCardSwitchView];
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self.cardSwitchView updateWords];//告诉switchView可以更新数据了
}

#pragma mark 初始方法
/**
 *  添加switchView
 */
- (void)addCardSwitchView {
    CGFloat cardSwitchViewH=420;
    CGFloat cardSwitchViewY=(self.view.height-cardSwitchViewH)*0.5;
    CGRect cardSwitchViewRect=CGRectMake(0, cardSwitchViewY, JQMainScreenSize.width, cardSwitchViewH);
    
    //设置frame
    _cardSwitchView = [[JQCardSwitchView alloc]initWithFrame:cardSwitchViewRect];
    _cardSwitchView.backgroundColor=[UIColor clearColor];
    [self.view addSubview:_cardSwitchView];
}

#pragma mark 点击方法
/**
 *  点击+号按钮
 */
- (void)addClick{
    JQAddWordController *addWordController=[[JQAddWordController alloc]init];
    JQNavigationController *navVC=[[JQNavigationController alloc]init];
    [navVC addChildViewController:addWordController];
    
    [self presentViewController:navVC animated:YES completion:nil];
}

JQCardSwitchView

#import "JQCardSwitchView.h"
#import "JQWord.h"
#import "JQCardView.h"
#import "JQCardController.h"
#import "JQNavigationController.h"
#import "JQEditController.h"

@interface JQCardSwitchView () 
@property (nonatomic,strong)NSArray *words; //创建子控件需要的总数据
@property (nonatomic,strong)NSMutableSet *reuseViewSet;//装当前可重用view
@property (nonatomic,strong)NSMutableArray *visibleViewArr;//装当前正显示的view
@property (nonatomic,assign)CGSize subSize; //一个子控件的size
@end

@implementation JQCardSwitchView{
    JQCardView *_curCardView;
    CGFloat _basicSpace; //基础距离 (计算第一个子控件与左边框之间的距离)
    CGFloat _subSpace; //子控件之间距离
    NSInteger _visibleCount; //初始显示时可显示子控件数
}

#pragma mark 懒加载
- (NSMutableSet *)reuseViewSet{
    if (!_reuseViewSet) {
        _reuseViewSet=[NSMutableSet set];
    }
    return _reuseViewSet;
}

- (NSMutableArray *)visibleViewArr{
    if (!_visibleViewArr){
        _visibleViewArr=[NSMutableArray array];
    }
    return _visibleViewArr;
}

#pragma mark 公开方法
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.delegate=self;
        self.subSize=CGSizeMake(240, self.height);
        #warning 这里是自己定义的subSize
        
        self.showsHorizontalScrollIndicator=NO;
        [self updateWords];
    }
    return self;
}

/**
 *  更新数据:(调用该函数后,会进入setWords重写函数,会重新初始化子控件)
 */
- (void)updateWords{
    self.words=[JQWordTool getAllWords];
}

#pragma mark 重写方法
- (void)setSubSize:(CGSize)subSize{
    _subSize=subSize;
    
    //1.计算visibleCount/subSpace/basicSize
    int count=self.width/subSize.width;//当前屏幕可以摆放子控件的个数
    _basicSpace=self.width-(count*subSize.width);
    if(_basicSpace>0){    //只要有间距,那么其右边默认可以多显示一个子控件
        _visibleCount=count+1;
        _subSpace=_basicSpace/(count+3);//计算出子控件距离
        _basicSpace = _subSpace * 2;
    }else{
        _visibleCount=count;
        _subSpace = 0;
        _basicSpace = 0;
    }
}

/**
 *  拦截数据设置 创建初始view
 */
- (void)setWords:(NSArray *)words{
    _words=words;
    
    //清空操作
    //->移除当前view上的所有子控件
    [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    //->移除可视数组
    [self.visibleViewArr removeAllObjects];
    //->移除重用集合
    [self.reuseViewSet removeAllObjects];
    
    //1.创建初始子控件
    NSInteger count=(self.words.count<_visibleCount?self.words.count:_visibleCount);
    for(int i=0;i设置tag
        cardView.tag=i;
        
        //->通用操作
        [self setupCardView:cardView];
        
        //2.添加到可视数组
        [self.visibleViewArr addObject:cardView];//相当于放到数组最后面
    }
    
    //3.做一次形变
    [self transshape];
    
    //4.计算contentSize
    CGFloat lastX=[self getXWithTag:(self.words.count-1)];
    CGSize size=CGSizeMake(lastX+self.subSize.width+(2*_subSpace), self.height);
    self.contentSize=size;
    
    //5.偏移处理
    [self setupOffset];
}

#pragma mark UIScrollViewDelegate 代理方法
/**
 *  scrollView滚动
 */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    //0.超出范围不做任何操作
    CGFloat offsetMinX=self.contentOffset.x;
    CGFloat offsetMaxX=offsetMinX+self.width;
    if (offsetMinX<=0 || offsetMaxX>=self.contentSize.width) {
        return;
    }

    //1.回收子控件
    [self retrieveView];
    
    //2.创建欲显示子控件
    [self createNewView];
 
    //3.变形
    [self transshape];
}

#pragma mark 逻辑方法
/**
 *  回收子控件
 */
- (void)retrieveView{
    //1.当前scrollView边框(可视边框)X
    CGFloat offsetMinX=self.contentOffset.x;
    CGFloat offsetMaxX=offsetMinX+self.width;
    
    //2.得出第一个和最后一个可视view的X
    //->拿出第一个和最后一个可视view
    UIView *firstView=self.visibleViewArr.firstObject;
    UIView *lastView=self.visibleViewArr.lastObject;

    CGFloat firstViewMaxX=firstView.x+self.subSize.width;;
    CGFloat lastViewX=lastView.x;
    
    //3.判断是否第一个可视view消失在屏幕中
    if (firstViewMaxX<=offsetMinX) {
        //添加到回收容器
        //->还原待回收view的形变属性
        firstView.transform=CGAffineTransformIdentity;
        [self.reuseViewSet addObject:firstView];
        //从当前可视viewArr中移除
        [self.visibleViewArr removeObject:firstView];
        //从当前view中移除
        [firstView removeFromSuperview];
        
    }
    
    //3.判断是否最后一个可视view消失在屏幕中
    if (lastViewX>=offsetMaxX) {
        //添加到回收容器
        lastView.transform=CGAffineTransformIdentity;
        [self.reuseViewSet addObject:lastView];
        //从当前可视viewArr中移除
        [self.visibleViewArr removeObject:lastView];
        //从当前view中移除
        [lastView removeFromSuperview];
        
    }
    
}

/**
 *  创建欲显示子控件
 */
- (void)createNewView{
    //1.当前scrollView边框(可视边框)X
    CGFloat offsetMinX=self.contentOffset.x;
    CGFloat offsetMaxX=offsetMinX+self.width;
    
    //2.计算可能会显示的view的X
    //->计算第一个可视view的左边view的MaxX
    UIView *firstView=self.visibleViewArr.firstObject;
    CGFloat preViewMinX=[self getXWithTag:firstView.tag-1];
    CGFloat preViewMaxX=preViewMinX+self.subSize.width;
    
    //->计算最后一个可视view的右边view的minX
    UIView *lastView=self.visibleViewArr.lastObject;
    CGFloat afterViewX=[self getXWithTag:lastView.tag+1];
    
    //3.添加即将显示的view
    //->判断条件
    BOOL leftJudge=(offsetMinX0);//不能在0之前加入view
    BOOL rightJudge=(offsetMaxX>afterViewX && lastView.tag<(self.words.count-1));
    
    if (leftJudge || rightJudge) {
        
        JQCardView *cardView=[self.reuseViewSet anyObject];
        if (cardView) { //判断是否可以重用
            [self.reuseViewSet removeObject:cardView];
        }else{
            cardView=[[JQCardView alloc]initWithFrame:CGRectMake(0, 0, self.subSize.width, self.subSize.height)];
        }
        
        //->左边添加一个view
        if (leftJudge) {
            cardView.tag=firstView.tag-1;
            [self.visibleViewArr insertObject:cardView atIndex:0];
        }
        //右边添加一个view
        else{
            cardView.tag=lastView.tag+1;
            [self.visibleViewArr addObject:cardView];
        }
        
        //->添加cardView的通用设置
        [self setupCardView:cardView];
    }
}

/**
 *  形变方法 (scrollView在滚动的时候,可能里面的子控件需要变形)
 */
- (void)transshape{
    
    CGFloat offsetMinX=self.contentOffset.x;
    
    for (JQCardView *view  in self.visibleViewArr) {
        CGFloat width = view.width + _subSpace;
        CGFloat y = view.tag * width;
#warning 一定是要用tag哦!只有用自身的tag才能得到自身本来该有的位置
        CGFloat value = (offsetMinX-y)/width;
        CGFloat scale = fabs(cos(fabs(value)*M_PI/5));
        view.transform = CGAffineTransformMakeScale(1.0, scale);
    }
}

#pragma mark 其他方法
/**
 *  根据tag算出x
 */
- (CGFloat)getXWithTag:(NSInteger)tag{
    return _basicSpace+(_subSize.width+_subSpace)*tag;
    /*根据第一第二个子控件的x可以推出来*/
}

/**
 *  设置回到上次偏移
 */
- (void)setupOffset{
    CGPoint offset = self.contentOffset;
    for(int i=0;i<=offset.x;i++){
        [self setContentOffset:CGPointMake(i, 0)];
    }
    /*
     为什么不直接设置偏移量到offset处呢?
     因为每个点这样的偏移会调用若干次 scrollView代理方法
     就可以实时的计算出可回收view和创建新view
     */
}

/**
 *  cardView创建过程中,通用设置
 */
- (void)setupCardView:(JQCardView *)cardView{
    //1.添加向上扫动手势
    UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(cardViewSwipe:)];
    gesture.direction = UISwipeGestureRecognizerDirectionUp;
    [cardView addGestureRecognizer:gesture];
    
    //2.基础配置
    //->重设x
    cardView.x=[self getXWithTag:cardView.tag];//得到x
    //->设置数据
    cardView.word=_words[cardView.tag];
    cardView.editTitle=@"编辑";

    __weak typeof(cardView) tempView=cardView;
    cardView.editAction=^(){
        JQCardController *cardVC=(JQCardController *)tempView.parentViewController;
        
        JQNavigationController *navVC=[[JQNavigationController alloc]init];
        JQEditController *editVC=[[JQEditController alloc]init];
        editVC.word=tempView.word;
        [navVC addChildViewController:editVC];
        
        [cardVC presentViewController:navVC animated:YES completion:nil];
    };
    
    //3.添加
    [self addSubview:cardView];
}

/**
 *  cardView手势触发方法
 */
- (void)cardViewSwipe:(UISwipeGestureRecognizer *)gesture{
    JQCardView *cardView=(JQCardView *)gesture.view;
    
    //弹出提示框;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"确定要删除?" preferredStyle: UIAlertControllerStyleActionSheet];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        
        [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            cardView.y = -(cardView.height);
        } completion:^(BOOL finished) {
            //1.删除数据
            [JQWordTool deleteWord:cardView.word];
            
            //2.回收cardView
            if (self.reuseViewSet.count<=0) {
                [self.reuseViewSet addObject:cardView];
            }
            
            //3.删除cardView
            [self.visibleViewArr removeObject:cardView];
            [cardView removeFromSuperview];
            
            //4.更新当前数据
            [self updateWords];
        }];
        
    }]];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        //点击按钮的响应事件;
    }]];
    
    [cardView.parentViewController presentViewController:alert animated:YES completion:nil];
    
}

@end

JQCardView原理

JQWord 02(卡片模块)_第1张图片
Paste_Image.png
JQWord 02(卡片模块)_第2张图片
Paste_Image.png

1.设置一个卡片的宽高

self.subSize=CGSizeMake(240, self.height);

2.计算出其他参数

/*
     _basicSpace基础距离
     _visibleCount初始显示时,可显示控件个数
     _subSpace子控件之间的距离
     space 屏幕宽度-所有控件宽度和
     */
    //1.计算visibleCount/subSpace/basicSize
    int count=self.width/subSize.width;//当前屏幕可以摆放子控件的个数
    CGFloat space=self.width-(count*subSize.width);
    if(space>0){    //只要有间距,那么其右边默认可以多显示一个子控件
        _visibleCount=count+1;
        _subSpace=space/(count+3);//计算出子控件距离
        _basicSpace = _subSpace * 2;
    }
    //->刚好能摆放若干子控件
    else{
        _visibleCount=count;
        _subSpace = 0;
        _basicSpace = 0;
    }
    /*
     _visibleCount=count+1解释:
     space>0,一个控件左右一定可以摆放其他控件,因为是初始显示
     所以只有右边摆放控件所以count+1
     同理,如果count==2,那么_visibleCount==3
     
     _subSpace=space/(count+3)解释:
     当:space>0,count == 1
     那么该控件的左右两边一定可以显示其他控件
     那么space就要平分为4份 _subSpace = space/4
     同理,如果count == 2
     _subSpace = space/5
     */

所以每个控件的x计算方式可以推出:


viewX = _basicSpace+(_subSize.width+_subSpace)*view.tag;

3.移动时回收和创建
回收:

  • 当第一个可视view的最大X消失在屏幕左边,那么可以回收该view
  • 当最后一个可视view的最小X消失屏幕右边,那么可以回收该view

/**
 *  回收子控件
 */
- (void)retrieveView{
    //1.当前scrollView边框(可视边框)X
    CGFloat offsetMinX=self.contentOffset.x;
    CGFloat offsetMaxX=offsetMinX+self.width;
    
    //2.得出第一个和最后一个可视view的X
    //->拿出第一个和最后一个可视view
    UIView *firstView=self.visibleViewArr.firstObject;
    UIView *lastView=self.visibleViewArr.lastObject;

    CGFloat firstViewMaxX=firstView.x+self.subSize.width;;
    CGFloat lastViewX=lastView.x;
    
    //3.判断是否第一个可视view消失在屏幕中
    if (firstViewMaxX<=offsetMinX) {
        //添加到回收容器
        //->还原待回收view的形变属性
        firstView.transform=CGAffineTransformIdentity;
        [self.reuseViewSet addObject:firstView];
        //从当前可视viewArr中移除
        [self.visibleViewArr removeObject:firstView];
        //从当前view中移除
        [firstView removeFromSuperview];
        
    }
    
    //3.判断是否最后一个可视view消失在屏幕中
    if (lastViewX>=offsetMaxX) {
        //添加到回收容器
        lastView.transform=CGAffineTransformIdentity;
        [self.reuseViewSet addObject:lastView];
        //从当前可视viewArr中移除
        [self.visibleViewArr removeObject:lastView];
        //从当前view中移除
        [lastView removeFromSuperview];
        
    }
    
}

创建:

  • 当第一个可视view的左边view的最大X开始出现在屏幕左边时可以创建该view
  • 当最后一个可视view的右边view的最小X开始出现在屏幕右边时可以创建该view
/**
 *  创建欲显示子控件
 */
- (void)createNewView{
    //1.当前scrollView边框(可视边框)X
    CGFloat offsetMinX=self.contentOffset.x;
    CGFloat offsetMaxX=offsetMinX+self.width;
    
    //2.计算可能会显示的view的X
    //->计算第一个可视view的左边view的MaxX
    UIView *firstView=self.visibleViewArr.firstObject;
    CGFloat preViewMinX=[self getXWithTag:firstView.tag-1];
    CGFloat preViewMaxX=preViewMinX+self.subSize.width;
    
    //->计算最后一个可视view的右边view的minX
    UIView *lastView=self.visibleViewArr.lastObject;
    CGFloat afterViewX=[self getXWithTag:lastView.tag+1];
    
    //3.添加即将显示的view
    //->判断条件
    BOOL leftJudge=(offsetMinX0);//不能在0之前加入view
    BOOL rightJudge=(offsetMaxX>afterViewX && lastView.tag<(self.words.count-1));
    
    if (leftJudge || rightJudge) {
        
        JQCardView *cardView=[self.reuseViewSet anyObject];
        if (cardView) { //判断是否可以重用
            [self.reuseViewSet removeObject:cardView];
        }else{
            cardView=[[JQCardView alloc]initWithFrame:CGRectMake(0, 0, self.subSize.width, self.subSize.height)];
        }
        
        //->左边添加一个view
        if (leftJudge) {
            cardView.tag=firstView.tag-1;
            [self.visibleViewArr insertObject:cardView atIndex:0];
        }
        //右边添加一个view
        else{
            cardView.tag=lastView.tag+1;
            [self.visibleViewArr addObject:cardView];
        }
        
        //->添加cardView的通用设置
        [self setupCardView:cardView];
    }
}
animation.gif

你可能感兴趣的:(JQWord 02(卡片模块))