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 () <UIScrollViewDelegate>
@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<count;i++){
JQCardView *cardView=[[JQCardView alloc]initWithFrame:CGRectMake(0, 0, self.subSize.width, self.subSize.height)];
//->设置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=(offsetMinX<preViewMaxX && firstView.tag>0);//不能在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原理
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=(offsetMinX<preViewMaxX && firstView.tag>0);//不能在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];
}
}