iOS 高仿 Timi 記賬
本人還屬于菜鳥級別,代碼寫得不規范,望見諒!
項目視頻演練 -> 點我
Demo -> Timi 不要忘記star支持喲
高仿版本:3.6.1
使用語言:Objective-C
開發工具及調試神器:Xcode 7.3.1,Reveal 1.6.3
用到的三方庫及擴展庫
Name | Explain |
---|---|
Masonry | 純代碼Autolayout |
MBProgressHUD | 未使用,后更改為使用SVProgressHUD |
MMDrawerController | 抽屜 |
SVProgressHUD | HUD |
YYText | 著名庫YYKit下的一個富文本 |
iCarousel | 一個類似UIScrollView的控件 |
ColorCube | 圖片顏色提取 |
UITextView_PlaceHolder | 給UITextView添加PlaceHolder |
SZCalendarPicker | 日歷 |
TYPagerController | 左右滾動ViewController VTMagic |
Realm | 移動端數據庫新王者 |
數據庫設計
TMBill(賬單)
Key | Identity | Column | Data Type | length | Allowed Null | Default | Description |
---|---|---|---|---|---|---|---|
√ | √ | billID | NSString | 64 | 主鍵 | ||
dateStr | NSString | 10 | 當前年月日 | 時間 | |||
reMarks | NSString | 40 | nil | 備注 | |||
remarkPhoto | NSData | √ | nil | 圖片備注 | |||
isIncome | BOOL | 1 | 0 | 類型(收支) | |||
money | float | 13 | 0 | 金額 | |||
FK | category | TMCategory | 類別 | ||||
FK | book | TMBooks | 賬本 |
TMBill(賬單).png
TMCategory(類別)
Key | Identity | Column | Data Type | length | Allowed Null | Default | Description |
---|---|---|---|---|---|---|---|
√ | √ | categoryID | NSString | 64 | 主鍵 | ||
categoryImageFileNmae | NSString | 64 | 類別icon文件名 | ||||
categoryTitle | NSString | 3 | 類別標題 | ||||
isIncome | BOOL | 1 | 類型(收支) |
TMCategory(類別).png
TMBook(賬本)
Key | Identity | Column | Data Type | length | Allowed Null | Default | Description |
---|---|---|---|---|---|---|---|
√ | √ | bookID | NSString | 64 | 主鍵 | ||
bookName | NSString | 6 | 賬本標題 | ||||
imageIndex | int | 2 | 賬本對應icon下標 | ||||
bookImageFileName | NSString | 64 | 類別icon文件名 |
TMBook(賬本).png
TMAddCategory(新增類別)
Key | Identity | Column | Data Type | length | Allowed Null | Default | Description |
---|---|---|---|---|---|---|---|
√ | √ | categoryID | NSString | 64 | 主鍵 | ||
√ | categoryImageFileNmae | NSString | 64 | 類別icon文件名 | |||
isIncome | BOOL | 1 | 類型(收支) |
TMAddCategory(新增類別).png
TMCategory(類別),TMAddCategory(新增類別)都是采用plist表的方式先存儲。當App每次啟動的時候就會先檢查數據庫對應的表是否為空,為空則從plist表讀取數據,存儲到本地數據庫。
項目整體結構
TimiStructure.png
溫馨提醒
項目里面95%都是使用的純代碼方式布局(Masonry),如果不懂的 Masonry 純代碼布局的請先去了解一下。 傳送門=>串哥的深入講解 AutoLayout 和 Masonry
時光軸界面(HomePageViewController)
2016-07-01 14.58.02.gif
UI布局之header部分(TMHeaderView)
Paste_Image.png
其實headerView部分沒有什么好說的,那個餅圖是用 UIBezierPath 和 CAShapeLayer 繪制而成,我把它單獨封裝出來了,因為在后面的餅圖部分也用到了。關于餅圖的加載數據時候的動畫我是使用的 CABasicAnimation 具體的操作可以看demo的對應文件( TMPieView )
UI布局之數據顯示部分(HomePageViewController | TMTimeLineCell)
Paste_Image.png
數據的顯示全部在一個section里面,并沒有分section顯示,而且cell也只有一個樣式,我是通過收支類型來判斷的該那邊顯示數據。
時間軸上面,相同時間(同一天)時間label和金額label以及時間點不顯示出來,我是在模型層加了一個BOOL變量來判斷,同時在獲取數據之后進行數據的重置,具體的操作可以看 HomePageViewController 的 getDataAndResetBill 函數。
然后在自定義cell( TMTimeLineCell )重寫 timeLineBill 屬性,通過判斷來顯示數據。
下圖應該清楚的看懂整個cell的布局
Paste_Image.png
其實這種做法并不好,一個cell是能完成,但是代碼看起來就有點亂糟糟的感覺,正確的做法是應該有兩種樣式的cell。分別是賬單類型為收入,賬單類型為支出兩種樣式。
很多人都應該碰到過,滑動tableView的時候Cell的數據會出現混亂,我是這樣解決的,在自定義cell重寫 - (void)prepareForReuse 函數,將cell里面的控件元素的屬性和對象統統置為nil。
//* 解決tableView滾動導致數據混亂
準備重用,防止滾動出現數據錯亂 */
- (void)prepareForReuse {
[super prepareForReuse];
self.timeLineBill = nil;
self.categoryImageBtn.imageView.image = nil;
self.leftCategoryNameLabel.text = nil;
self.leftMoneyLabel.text = nil;
self.leftRemarkLabel.text = nil;
self.rightCategoryNameLabel.text = nil;
self.rightMoneyLabel.text = nil;
self.rightRemarkLabel.text = nil;
self.lastBill = NO;
}
細心的人可能看到了我在下滑tableview的時候,中間的時光軸線也跟著變長。當我下滑到一定程度,然后松手就會push到新增賬單界面,而且這個push動畫不是系統自帶的push動畫。
下面我一一為大家解答:
時光軸的線條是怎么變長的?
第一步、我是新增的一個UIView,默認frame為 (SCREEN_SIZE.width-1)/2,0 , 1, 0) ,將它加到tableview上面。
self.dropdownLineView = [[UIView alloc] initWithFrame:CGRectMake((SCREEN_SIZE.width-1)/2,0 , 1, 0)];
self.dropdownLineView.backgroundColor = LineColor;
[self.tableView addSubview:self.dropdownLineView];
第二步、在UIScrollViewDelegate的 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 代理函數里面獲取滑動的y值。判斷其方向并重新設置 dropdownLineView 的frame即可
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
/** 當下拉的時候才有動畫 y>0下拉,y<0上劃*/
CGFloat y = [scrollView.panGestureRecognizer translationInView:self.tableView].y;
// NSLog(@"%s--%d---y = %f",__func__,__LINE__,y);
if (y>0) {
/**
* 疑問:為什么是`y`是`-y`不是`0`,因為`dropdownLineView`是添加到`tableView`的,所以當`tabelView`拉下的時候`dropdownLineView`也會跟著向下移動。
* 當`y`是`-y`的時候`dropdownLineView`會向上移動`y`個單位,才會達到我們理想的效果
*/
self.dropdownLineView.frame = CGRectMake((SCREEN_SIZE.width-1)/2, -y, 1, y);
[self.tableView bringSubviewToFront:self.dropdownLineView];
/** 餅圖+號按鈕動畫*/
[self.headerView animationWithCreateBtnDuration:1.0f angle:y];
}
}
時光軸界面到添加賬單(修改賬單)界面的轉場動畫(LYPushTransition,LYPopTransition)
使用的是自定義的轉場動畫,具體如何使用請看 喵神 和 KittenYang 的blog,推薦 幾句代碼快速集成自定義轉場效果+全手勢驅動
1.首先定一個 class ,繼承至 NSObject ,遵守 UIViewControllerAnimatedTransitioning 協議。
2.需要實現兩個方法
/** 動畫時間 */
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
/** 轉場動畫內容(怎么轉) */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
Push代碼細節講解(是一個反向prensent轉場動畫)
/** 動畫時間 */
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5f;
}
/** 動畫內容(如何轉場) */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
/**
*
1.transitionContext 過渡內容上下文,可以通過它調用`viewControllerForKey:`拿到對應的過渡控制器
key:UITransitionContextToViewControllerKey 目的控制器
UITransitionContextFromViewControllerKey 開始控制器
2.拿到對應的過渡控制器之后需要設置view的frame
`finalFrameForViewController:` 可以拿到最后的frame,最后即完成動畫后的frame
`initialFrameForViewController:` 拿到初始化的frame,開始動畫之前的frame
3.然后添加到`transitionContext的containerView`
4.設置動畫的其他附帶屬性動畫
5.做動畫... `UIView的block動畫`
6.在動畫結束后我們必須向context報告VC切換完成,是否成功。系統在接收到這個消息后,將對VC狀態進行維護。
*
*/
//1...
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = toVC.view;
//2...
CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
//(dx, dy) eg:dx偏移多少
toView.frame = CGRectOffset(finalFrame, 0, -SCREEN_SIZE.height);
//3....
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toView];
//4...
//5...
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
toView.frame = finalFrame;
} completion:^(BOOL finished) {
//6...
[transitionContext completeTransition:YES];
}];
}
Pop做Push的相反操作即可
3. ViewController如何使用自定義轉場動畫
-
pushViewController
在push的控制器設置 navigationController 的 delegate 為 self
self.navigationController.delegate = self;
實現協議方法
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { if (operation == UINavigationControllerOperationPush) { LYPushTransition *push = [LYPushTransition new]; return push; } else if (operation == UINavigationControllerOperationPop) { LYPopTransition *pop = [LYPopTransition new]; return pop; }else { return nil; } }
通過 operation 判斷是 push 操作還是 pop 操作,然后然后對于的動畫即可
pop 控制器不需要做任何操作
如果使用 push ,則會發現 NavigationBar 沒有變化,會一直處于那個地方,很丑...
然而使用 present 就可以避免這種現象
-
presentViewController
設置 presentViewController 的 ViewController 的 transitioningDelegate 為 self注意,如果是present的 UINavigationController ,則需要設置 NavigationController 的 transitioningDelegate 為 self
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; SecondViewController *secondVC = [storyboard instantiateViewControllerWithIdentifier:@"second"]; secondVC.delegate = self; //* present */ UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:secondVC]; //* 如果present的NavigationController則需要設置NavigationController的transitioningDelegate為self */ navi.transitioningDelegate = self; [self presentViewController:navi animated:YES completion:nil];
實現 transitioningDelegate 協議方法
/** prensent */
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self.push;
}
/** dismiss */
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return self.pop;
}
dismiss`控制器則需要寫一個代理,告訴`present`的那個控制器`dismiss`即可
NavigationItemTitleView按鈕的邊框&點擊切換時候的顏色動畫
/** 設置邊框寬度 */
titleBtn.layer.borderWidth = 1.5;
//* 設置Btn的邊框顏色 */
titleBtn.layer.borderColor = [UIColor whiteColor].CGColor;
關于點擊按鈕切換時候的動畫我是使用的兩個UIView的動畫
//* 改變NavigationTitleBtn的顏色 */
[UIView animateWithDuration:0.3f delay:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{
[weakSelf.navigationTitleBtn setBackgroundColor:[UIColor colorWithRed:1.000 green:0.812 blue:0.124 alpha:1.000]];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
[weakSelf.navigationTitleBtn setBackgroundColor:[UIColor colorWithWhite:0.278 alpha:0.500]];
} completion:^(BOOL finished) {
}];
}];
點擊類別按鈕彈出菜單(TMTimeLineMenuView)
我不是在每個cell下面都添加了deleteBtn,updateBtn,因為這樣會使性能大大降低。
我是自定義的一個UIView( TMTimeLineMenuView ),這里面有三個控件,分別是 deleteBtn , updateBtn , categoryBtn 。
這個categoryBtn是放在deleteBtn,updateBtn上面的。因為在deleteBtn和updateBtn彈出的時候我把 TMTimeLineMenuView 放到了最頂層
//* 置頂 */
[weakSelf.superview bringSubviewToFront:weakSelf];
也就意味著tableView是在TMTimeLineMenuView的下面。
Paste_Image.png
如果沒有categoryBtn,彈出deleteBtn和updateBtn就感覺是直接在tableViewCell上面做的動畫,會很丑。所以添加一個categoryBtn放在updateBtn和deleteBtn上面,就感覺deleteBtn和updateBtn是放在tableViewCell下面的。給用戶很好的用戶體驗。
如何將TMTimeLineMenuView中的控件顯示到對應的位置?(HomePageViewController->didClickCategoryBtnWithIndexPath:)
第一步:獲取到點擊的cell對應的indexPath
第二步:獲取對應cell在tableview中的rect
第三步:將獲取到的rect轉換成在self.view中的rect
/** 獲取cell在tableView中的位置 */
CGRect rect = [self.tableView rectForRowAtIndexPath:indexPath];
//* 轉換成在self.view中的位置 */
CGRect rectInSuperview = [self.tableView convertRect:rect toView:[self.tableView superview]];
self.timeLineMenuView.currentImage = self.timeLineCell.categoryImageBtn.currentImage;
[self.timeLineMenuView showTimeLineMenuViewWithRect:rectInSuperview ];
創建賬單界面(TMCreateBillViewController)
TimiAddBillController.gif
選擇類別動畫之類別圖片動畫(應該使用UI Dynamics)
第一步:
在創建賬單界面添加一個 UIImageView 控件,大小跟collectionViewCell里面的 categoryImageView 一樣,放在屏幕外。并設置圓角。
- (UIImageView *)selectCategoryImageView
{
if (!_selectCategoryImageView) {
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, -30, kCollectionCellWidth-20, kCollectionCellWidth-20)];
imageView.layer.cornerRadius = (kCollectionCellWidth - 20)/2;
imageView.layer.masksToBounds = YES;
imageView.contentMode = UIViewContentModeScaleAspectFill;
_selectCategoryImageView = imageView;
}
return _selectCategoryImageView;
}
第二步: 獲取點擊的位置
1.拿到對應cell
cell = (TMCategotyCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
2.將cell對應的類別圖片賦值給 _selectCategoryImageView
然后獲取到cell的 center ,這個 centter的y 僅僅是它在collectionView的位置,所以還需要修改y值,然后使用UIView的block動畫移動到headerView上面對應的點。在動畫完成之后將它放到最底層
/** 選擇類別之后的類別圖片動畫 */
- (void)animationWithCell:(TMCategotyCollectionViewCell *)cell {
self.selectCategoryImageView.image = cell.categoryImageView.image;
CGPoint center = cell.center;
/** 在collectionView中的y */
CGFloat y = CGRectGetMaxY(cell.frame);
center.y = kMaxNBY + y + 10;
self.selectCategoryImageView.center = center;
WEAKSELF
[UIView animateWithDuration:0.05 animations:^{
weakSelf.selectCategoryImageView.center = kHeaderCategoryImageCenter;
} completion:^(BOOL finished) {
[weakSelf.view sendSubviewToBack:weakSelf.selectCategoryImageView];
}];
[self.view bringSubviewToFront:self.selectCategoryImageView];
}
選擇類別動畫之HeaderView顏色動畫
第一步:提取顏色
我使用的是一個三方庫, ColorExtraction
//* 顏色提取 */
CCColorCube *imageColor = [[CCColorCube alloc] init];
NSArray *colors = [imageColor extractColorsFromImage:category.categoryImage flags:CCAvoidBlack count:1];
第二步:動畫
我是使用UIBezierPath和CAShapeLayer結合CABasicAnimation做的動畫。
UIBezierPath的path如何而來?
path就是一條線,path的 moveToPoint 點就是 self.bounds.origin 點即左上點
addLineToPoint 點就是 self.bounds.origin.x 點和 self.bounds.size.height 點即左下點
然后通過CABasicAnimation改變 lineWidth
- (void)animationWithBgColor:(UIColor *)color {
//* 如果選擇的類別圖片的顏色和上次選擇的一樣 直接return */
if ([color isEqual: self.previousSelectColor]) return;
//* 修改背景顏色為上一次選擇的顏色,不然就會是最開始默認的顏色,動畫會很丑,給用戶的體驗很不好 */
self.backgroundColor = self.previousSelectColor;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"lineWidth"];
animation.fromValue = @0.0;
animation.toValue = @(self.bounds.size.width * 2);
animation.duration = 0.3f;
//* 設置填充色 */
self.bgColorlayer.fillColor = color.CGColor;
//* 設置邊框色 */
self.bgColorlayer.strokeColor = color.CGColor;
self.previousSelectColor = color;
//* 保持動畫 */
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.bgColorlayer addAnimation:animation forKey:@"bgColorAnimation"];
//* 將子控件放在最上面,不然layer會覆蓋 */
[self bringSubviewToFront:self.categoryImageView];
[self bringSubviewToFront:self.moneyLabel];
[self bringSubviewToFront:self.categoryNameBtn];
}
餅圖(TMPiewViewController)
TMPie.gif
餅圖HeaderView部分
控件是使用三方庫 iCarousel 鏈接
數據源如何而來?
1.先把每個月中文對應的英文縮寫保存到一個數組中
- (NSArray *)items {
if (!_items) {
_items = @[@"JAN\n1月",@"FEB\n2月",@"MAR\n3月",@"APR\n4月",@"MAY\n5月",@"JUN\n6月",@"JUL\n7月",@"AUG\n8月",@"SEP\n9月",@"OCT\n10月",@"NOV\n11月",@"DEC\n12月",@"ALL\n全部"];
}
return _items;
}
疑問:為什么數據每個元素,中間有個 \n
答:我是使用的是一個UILabel \n 用于換行
2.拿到篩選過后的數據,是一個NSDictionary。額...說一下,這個篩選過后的數據的一個結構,因為同一天我們可能會記多筆賬,所以把同一天的 dateStr 作為 key ,然后把所有屬于這一天的賬單數據當作一個 value ,目前為止只是過濾掉同一天的時間字符串。
然后下一步我們要做的就是過濾掉同一年的相同月份
/** 過濾掉同年相同月份 */
- (void)filterMonthWithDateArray:(NSArray *)array {
for (NSString *dateStr in array) {
NSString *yearAndMonth = [dateStr substringToIndex:7];
BOOL contains = [self containsMonth:yearAndMonth];
if (!contains) {
NSString *month = [self conversionDateStringIntoMonth:dateStr];
[self.dic setValue:month forKey:dateStr];
}
}
[self.dic setValue:self.items.lastObject forKey:@"ALL"];
self.sortDicKeys = [self sortArray:self.dic.allKeys ascending:YES];
[self.iCar reloadData];
}
/** 把時間字符串轉換成月份 */
- (NSString *)conversionDateStringIntoMonth:(NSString *)dateString {
NSRange range = NSMakeRange(5, 2);
NSString *month = [dateString substringWithRange:range];
return self.items[month.integerValue - 1];
}
/** 判斷字典里面是否已經包含這個對象 */
- (BOOL)containsMonth:(NSString *)yearAndMonth {
if (self.dic.allKeys.count==0) {
return NO;
} else {
for (NSInteger i=0; i<self.dic.allKeys.count ; i++) {
if ([[self.dic.allKeys[i] substringToIndex:7] isEqualToString:yearAndMonth]) {
return YES;
}
}
}
return NO;
}
獲取layer的位置
- (NSInteger)getLayerIndexWithPoint:(CGPoint)point {
for (NSInteger i=0; i<[self.containerLayer sublayers].count; i++) {
CAShapeLayer *layer = (CAShapeLayer *)[self.containerLayer sublayers][i];
CGPathRef path = [layer path];
if (CGPathContainsPoint(path, NULL, point, 0)) {
return i;
}
}
return -1;
}
拿到所有的sublayer,取出layer的path,通過 CGPathContainsPoint 判斷觸摸的點是否在這個path里面
類別詳細界面(TMPiewCategoryDetailViewController)
解決cell重用導致數據 年月日label 顯示混亂,在模型定義兩個 BOOL 變量 same,partSame
拿到數據之后將數據進行“重置”
(void)resetBill {
self.bills = [NSMutableArray array];
NSString *previous;
for (NSInteger i=0; i<self.results.count; i++) {
TMBill *bill = self.results[i];
if (i==0) {//第一個數據永遠是不相同的
[self.bills addObject:bill];
previous = bill.dateStr;
continue;
} else {
TMBill *theBill = [TMBill new];
if ([previous isEqualToString:bill.dateStr]) {//完全相同,時間日期
theBill = bill;
theBill.same = YES;
[self.bills addObject:theBill];
} else if ([[previous substringToIndex:7] isEqualToString:[bill.dateStr substringToIndex:7]]) {//部分相同,年月份相同,具體時間不同
theBill = bill;
theBill.partSame = YES;
[self.bills addObject:theBill];
} else {//不同
[self.bills addObject:bill];
}
previous = bill.dateStr;
}
}
}
側滑控制器,使用的是MMDrawerController庫
本來 MMDrawerController 是支持在屏幕向右滑就能出現左邊的菜單欄,由于使用了 TYPagerController 出現了手勢之間的沖突
解決和 TYPagerController 手勢沖突的問題
UIScreenEdgePanGestureRecognizer *screenEdgeGR = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(clickMenuBtn:)];
screenEdgeGR.edges = UIRectEdgeLeft;
[self.view addGestureRecognizer:screenEdgeGR];
- (void)clickMenuBtn:(UIButton *)sender {
[self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
}
如果直接是這樣的話則會出現下面的情況
2016-06-25 22.50.25.gif
因為 UIScreenEdgePanGestureRecognizer 是一個持續響應事件,也就是說你的手指沒離開屏幕則會一直響應這個函數,因為 toggleDrawerSide 在內部會判斷菜單欄是打開還是關閉,打開則關閉,關閉則會打開,所以也就會出現上面這種情況了。
解決辦法
if (self.mm_drawerController.openSide == MMDrawerSideNone) {
[self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
}
賬本控制器(TMSideViewController)
books.gif
如何抖動?在cell上添加一個 UILongPressGestureRecognizer 長按手勢
//* 長按手勢 */
UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGR:)];
longGR.minimumPressDuration = 1.0;
longGR.numberOfTouchesRequired = 1;
longGR.allowableMovement = 10;
[self addGestureRecognizer:longGR];
給cell添加一個代理
@protocol TMSideCellDelegate <NSObject>
@required
@optional
- (void)TMSideCellWithIndexPath:(NSIndexPath *)indexPath withLongPress:(UILongPressGestureRecognizer *)longPress;
@end
當控制器接收到響應事件的時候只需要做三件事
self.editSelectedIndexPath = indexPath; //1
self.edit = YES; //2
[self.collectionView reloadData]; //3
在 - (UICollectionViewCell *)collectionView: cellForItemAtIndexPath: 添加判斷代碼
//* edit mode on shake ->ture*/
if (self.isEdit) {
if ([indexPath isEqual:self.editSelectedIndexPath]) {
cell.editSelectedItemImageView.hidden = NO;
[self shakeCell:cell];
} else {
cell.editSelectedItemImageView.hidden = YES;
}
} else {
cell.editSelectedItemImageView.hidden = YES;
cell.transform = CGAffineTransformIdentity;
}
/** 抖動動畫 */
- (void)shakeCell:(TMSideCell *)cell {
[UIView animateWithDuration:0.1 delay:0 options:0 animations:^{
cell.transform=CGAffineTransformMakeRotation(-0.02);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction animations:^{
cell.transform=CGAffineTransformMakeRotation(0.02);
} completion:nil];
}];
}
來自:http://www.jianshu.com/p/d3dbf8dba11a