iOS 高仿Timi記賬

寫(xiě)在最前面:

其實(shí)本文應(yīng)該早在兩個(gè)月之前就該寫(xiě)完的,由于當(dāng)時(shí)找工作,種種原因擱置到現(xiàn)在才寫(xiě)完。
本文沒(méi)有把每一個(gè)點(diǎn)都能寫(xiě)到。再說(shuō)時(shí)隔兩個(gè)多月沒(méi)碰這個(gè)項(xiàng)目了,多多少少都忘了一些。我盡可能把當(dāng)時(shí)在寫(xiě)這個(gè)項(xiàng)目遇到的各種坑寫(xiě)詳細(xì)。如果在看本文或者demo的時(shí)候有不明白的地方可以在Github上面提issue或者簡(jiǎn)書(shū)簡(jiǎn)信我也可以。
溫馨提示:看文章的時(shí)候結(jié)合代碼一起看,效果會(huì)更佳喲。
目前完成進(jìn)度70%,由于時(shí)間的關(guān)系(臨近期末,各種事情的原因...)
項(xiàng)目采用MVC設(shè)計(jì)模式
本人還屬于菜鳥(niǎo)級(jí)別,代碼寫(xiě)得不規(guī)范,望見(jiàn)諒!
如果項(xiàng)目中同樣的問(wèn)題,你有更好的辦法解決請(qǐng)告訴我,讓我們一起學(xué)習(xí)。

廢話說(shuō)了一大堆,開(kāi)始進(jìn)入正題?。。?/p>

項(xiàng)目視頻演練 -> 點(diǎn)我

Demo ->Timi 不要忘記star支持喲

高仿版本:3.6.1

使用語(yǔ)言:Objective-C

開(kāi)發(fā)工具及調(diào)試神器:Xcode 7.3.1,Reveal 1.6.3

用到的三方庫(kù)及擴(kuò)展庫(kù)

Name Explain
Masonry 純代碼Autolayout
MBProgressHUD 未使用,后更改為使用SVProgressHUD
MMDrawerController 抽屜
SVProgressHUD HUD
YYText 著名庫(kù)YYKit下的一個(gè)富文本
iCarousel 一個(gè)類(lèi)似UIScrollView的控件
ColorCube 圖片顏色提取
UITextView_PlaceHolder 給UITextView添加PlaceHolder
SZCalendarPicker 日歷
TYPagerController 左右滾動(dòng)ViewController VTMagic
Realm 移動(dòng)端數(shù)據(jù)庫(kù)新王者

數(shù)據(jù)庫(kù)設(shè)計(jì)

TMBill(賬單)

Key Identity Column Data Type length Allowed Null Default Description
billID NSString 64 主鍵
dateStr NSString 10 當(dāng)前年月日 時(shí)間
reMarks NSString 40 nil 備注
remarkPhoto NSData nil 圖片備注
isIncome BOOL 1 0 類(lèi)型(收支)
money float 13 0 金額
FK category TMCategory 類(lèi)別
FK book TMBooks 賬本
TMBill(賬單).png

TMCategory(類(lèi)別)

Key Identity Column Data Type length Allowed Null Default Description
categoryID NSString 64 主鍵
categoryImageFileNmae NSString 64 類(lèi)別icon文件名
categoryTitle NSString 3 類(lèi)別標(biāo)題
isIncome BOOL 1 類(lèi)型(收支)
TMCategory(類(lèi)別).png

TMBook(賬本)

Key Identity Column Data Type length Allowed Null Default Description
bookID NSString 64 主鍵
bookName NSString 6 賬本標(biāo)題
imageIndex int 2 賬本對(duì)應(yīng)icon下標(biāo)
bookImageFileName NSString 64 類(lèi)別icon文件名
TMBook(賬本).png

TMAddCategory(新增類(lèi)別)

Key Identity Column Data Type length Allowed Null Default Description
categoryID NSString 64 主鍵
categoryImageFileNmae NSString 64 類(lèi)別icon文件名
isIncome BOOL 1 類(lèi)型(收支)
TMAddCategory(新增類(lèi)別).png

TMCategory(類(lèi)別),TMAddCategory(新增類(lèi)別)都是采用plist表的方式先存儲(chǔ)。當(dāng)App每次啟動(dòng)的時(shí)候就會(huì)先檢查數(shù)據(jù)庫(kù)對(duì)應(yīng)的表是否為空,為空則從plist表讀取數(shù)據(jù),存儲(chǔ)到本地?cái)?shù)據(jù)庫(kù)。

項(xiàng)目整體結(jié)構(gòu)

TimiStructure.png

溫馨提醒

項(xiàng)目里面95%都是使用的純代碼方式布局(Masonry),如果不懂的Masonry純代碼布局的請(qǐng)先去了解一下。傳送門(mén)=>串哥的深入講解 AutoLayout 和 Masonry

時(shí)光軸界面(HomePageViewController)

2016-07-01 14.58.02.gif

UI布局之header部分(TMHeaderView)

Paste_Image.png

其實(shí)headerView部分沒(méi)有什么好說(shuō)的,那個(gè)餅圖是用UIBezierPathCAShapeLayer繪制而成,我把它單獨(dú)封裝出來(lái)了,因?yàn)樵诤竺娴娘瀳D部分也用到了。關(guān)于餅圖的加載數(shù)據(jù)時(shí)候的動(dòng)畫(huà)我是使用的CABasicAnimation具體的操作可以看demo的對(duì)應(yīng)文件(TMPieView)

UI布局之?dāng)?shù)據(jù)顯示部分(HomePageViewController | TMTimeLineCell)

Paste_Image.png

數(shù)據(jù)的顯示全部在一個(gè)section里面,并沒(méi)有分section顯示,而且cell也只有一個(gè)樣式,我是通過(guò)收支類(lèi)型來(lái)判斷的該那邊顯示數(shù)據(jù)。
時(shí)間軸上面,相同時(shí)間(同一天)時(shí)間label和金額label以及時(shí)間點(diǎn)不顯示出來(lái),我是在模型層加了一個(gè)BOOL變量來(lái)判斷,同時(shí)在獲取數(shù)據(jù)之后進(jìn)行數(shù)據(jù)的重置,具體的操作可以看HomePageViewControllergetDataAndResetBill函數(shù)。
然后在自定義cell(TMTimeLineCell)重寫(xiě)timeLineBill屬性,通過(guò)判斷來(lái)顯示數(shù)據(jù)。
下圖應(yīng)該清楚的看懂整個(gè)cell的布局
Paste_Image.png

其實(shí)這種做法并不好,一個(gè)cell是能完成,但是代碼看起來(lái)就有點(diǎn)亂糟糟的感覺(jué),正確的做法是應(yīng)該有兩種樣式的cell。分別是賬單類(lèi)型為收入,賬單類(lèi)型為支出兩種樣式。

很多人都應(yīng)該碰到過(guò),滑動(dòng)tableView的時(shí)候Cell的數(shù)據(jù)會(huì)出現(xiàn)混亂,我是這樣解決的,在自定義cell重寫(xiě)- (void)prepareForReuse函數(shù),將cell里面的控件元素的屬性和對(duì)象統(tǒng)統(tǒng)置為nil。

//* 解決tableView滾動(dòng)導(dǎo)致數(shù)據(jù)混亂
     準(zhǔn)備重用,防止?jié)L動(dòng)出現(xiàn)數(shù)據(jù)錯(cuò)亂 */
- (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;
    }

細(xì)心的人可能看到了我在下滑tableview的時(shí)候,中間的時(shí)光軸線也跟著變長(zhǎng)。當(dāng)我下滑到一定程度,然后松手就會(huì)push到新增賬單界面,而且這個(gè)push動(dòng)畫(huà)不是系統(tǒng)自帶的push動(dòng)畫(huà)。
下面我一一為大家解答:

時(shí)光軸的線條是怎么變長(zhǎng)的?

第一步、我是新增的一個(gè)UIView,默認(rèn)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 代理函數(shù)里面獲取滑動(dòng)的y值。判斷其方向并重新設(shè)置dropdownLineView的frame即可

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    /** 當(dāng)下拉的時(shí)候才有動(dòng)畫(huà)  y>0下拉,y<0上劃*/
    CGFloat  y = [scrollView.panGestureRecognizer translationInView:self.tableView].y;
//    NSLog(@"%s--%d---y = %f",__func__,__LINE__,y);
    if (y>0) {
        /**
         *  疑問(wèn):為什么是`y`是`-y`不是`0`,因?yàn)閌dropdownLineView`是添加到`tableView`的,所以當(dāng)`tabelView`拉下的時(shí)候`dropdownLineView`也會(huì)跟著向下移動(dòng)。
         *  當(dāng)`y`是`-y`的時(shí)候`dropdownLineView`會(huì)向上移動(dòng)`y`個(gè)單位,才會(huì)達(dá)到我們理想的效果
         */
        self.dropdownLineView.frame = CGRectMake((SCREEN_SIZE.width-1)/2, -y, 1, y);
        [self.tableView bringSubviewToFront:self.dropdownLineView];
         /** 餅圖+號(hào)按鈕動(dòng)畫(huà)*/
        [self.headerView animationWithCreateBtnDuration:1.0f angle:y];
    }
}

時(shí)光軸界面到添加賬單(修改賬單)界面的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(LYPushTransition,LYPopTransition)

使用的是自定義的轉(zhuǎn)場(chǎng)動(dòng)畫(huà),具體如何使用請(qǐng)看喵神KittenYang 的blog,推薦幾句代碼快速集成自定義轉(zhuǎn)場(chǎng)效果+全手勢(shì)驅(qū)動(dòng)
1.首先定一個(gè)class,繼承至NSObject,遵守UIViewControllerAnimatedTransitioning協(xié)議。
2.需要實(shí)現(xiàn)兩個(gè)方法

/** 動(dòng)畫(huà)時(shí)間 */
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext

/** 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)內(nèi)容(怎么轉(zhuǎn)) */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
Push代碼細(xì)節(jié)講解(是一個(gè)反向prensent轉(zhuǎn)場(chǎng)動(dòng)畫(huà))
/** 動(dòng)畫(huà)時(shí)間 */
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.5f;
}
/** 動(dòng)畫(huà)內(nèi)容(如何轉(zhuǎn)場(chǎng)) */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    /**
     *
    1.transitionContext 過(guò)渡內(nèi)容上下文,可以通過(guò)它調(diào)用`viewControllerForKey:`拿到對(duì)應(yīng)的過(guò)渡控制器
        key:UITransitionContextToViewControllerKey 目的控制器
            UITransitionContextFromViewControllerKey 開(kāi)始控制器
    2.拿到對(duì)應(yīng)的過(guò)渡控制器之后需要設(shè)置view的frame
        `finalFrameForViewController:` 可以拿到最后的frame,最后即完成動(dòng)畫(huà)后的frame
        `initialFrameForViewController:` 拿到初始化的frame,開(kāi)始動(dòng)畫(huà)之前的frame
    3.然后添加到`transitionContext的containerView`
    
    4.設(shè)置動(dòng)畫(huà)的其他附帶屬性動(dòng)畫(huà)
     
    5.做動(dòng)畫(huà)... `UIView的block動(dòng)畫(huà)`
     
    6.在動(dòng)畫(huà)結(jié)束后我們必須向context報(bào)告VC切換完成,是否成功。系統(tǒng)在接收到這個(gè)消息后,將對(duì)VC狀態(tài)進(jìn)行維護(hù)。
     *
     */
    
    //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如何使用自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
  • pushViewController
    在push的控制器設(shè)置navigationControllerdelegateself

    self.navigationController.delegate = self;
    

    實(shí)現(xiàn)協(xié)議方法

    - (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;
    }
    

}
```
通過(guò)operation判斷是push操作還是pop操作,然后然后對(duì)于的動(dòng)畫(huà)即可
pop控制器不需要做任何操作
如果使用push,則會(huì)發(fā)現(xiàn)NavigationBar沒(méi)有變化,會(huì)一直處于那個(gè)地方,很丑...
然而使用present就可以避免這種現(xiàn)象

  • presentViewController
    設(shè)置presentViewControllerViewControllertransitioningDelegateself
    注意,如果是present的UINavigationController,則需要設(shè)置NavigationControllertransitioningDelegateself

    UIStoryboard *storyboard = [UIStoryboard    storyboardWithName:@"Main" bundle:nil];
    SecondViewController *secondVC = [storyboard instantiateViewControllerWithIdentifier:@"second"];
    secondVC.delegate = self;
    //* present */
    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:secondVC];
    

//* 如果present的NavigationController則需要設(shè)置NavigationController的transitioningDelegate為self */
navi.transitioningDelegate = self;
[self presentViewController:navi animated:YES completion:nil];

實(shí)現(xiàn)`transitioningDelegate`協(xié)議方法

/** 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`控制器則需要寫(xiě)一個(gè)代理,告訴`present`的那個(gè)控制器`dismiss`即可
      
      
      
    

NavigationItemTitleView按鈕的邊框&點(diǎn)擊切換時(shí)候的顏色動(dòng)畫(huà)

/** 設(shè)置邊框?qū)挾?*/
 titleBtn.layer.borderWidth = 1.5;
//* 設(shè)置Btn的邊框顏色 */
titleBtn.layer.borderColor = [UIColor whiteColor].CGColor;

關(guān)于點(diǎn)擊按鈕切換時(shí)候的動(dòng)畫(huà)我是使用的兩個(gè)UIView的動(dòng)畫(huà)

 //* 改變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) {
            
        }];
    }];

點(diǎn)擊類(lèi)別按鈕彈出菜單(TMTimeLineMenuView)

我不是在每個(gè)cell下面都添加了deleteBtn,updateBtn,因?yàn)檫@樣會(huì)使性能大大降低。
我是自定義的一個(gè)UIView(TMTimeLineMenuView),這里面有三個(gè)控件,分別是deleteBtn,updateBtn,categoryBtn。
這個(gè)categoryBtn是放在deleteBtn,updateBtn上面的。因?yàn)樵赿eleteBtn和updateBtn彈出的時(shí)候我把TMTimeLineMenuView放到了最頂層

//* 置頂 */
[weakSelf.superview bringSubviewToFront:weakSelf];

也就意味著tableView是在TMTimeLineMenuView的下面。


Paste_Image.png

如果沒(méi)有categoryBtn,彈出deleteBtn和updateBtn就感覺(jué)是直接在tableViewCell上面做的動(dòng)畫(huà),會(huì)很丑。所以添加一個(gè)categoryBtn放在updateBtn和deleteBtn上面,就感覺(jué)deleteBtn和updateBtn是放在tableViewCell下面的。給用戶很好的用戶體驗(yàn)。

如何將TMTimeLineMenuView中的控件顯示到對(duì)應(yīng)的位置?(HomePageViewController->didClickCategoryBtnWithIndexPath:)

第一步:獲取到點(diǎn)擊的cell對(duì)應(yīng)的indexPath
第二步:獲取對(duì)應(yīng)cell在tableview中的rect
第三步:將獲取到的rect轉(zhuǎn)換成在self.view中的rect

/** 獲取cell在tableView中的位置 */
CGRect rect = [self.tableView rectForRowAtIndexPath:indexPath];
//* 轉(zhuǎn)換成在self.view中的位置 */
CGRect rectInSuperview = [self.tableView convertRect:rect toView:[self.tableView superview]];
self.timeLineMenuView.currentImage = self.timeLineCell.categoryImageBtn.currentImage;
[self.timeLineMenuView showTimeLineMenuViewWithRect:rectInSuperview ];

創(chuàng)建賬單界面(TMCreateBillViewController)

TimiAddBillController.gif

選擇類(lèi)別動(dòng)畫(huà)之類(lèi)別圖片動(dòng)畫(huà)(應(yīng)該使用UI Dynamics)

第一步:

在創(chuàng)建賬單界面添加一個(gè)UIImageView控件,大小跟collectionViewCell里面的categoryImageView一樣,放在屏幕外。并設(shè)置圓角。

- (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;
}
第二步: 獲取點(diǎn)擊的位置

1.拿到對(duì)應(yīng)cell

cell = (TMCategotyCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];

2.將cell對(duì)應(yīng)的類(lèi)別圖片賦值給_selectCategoryImageView
然后獲取到cell的center,這個(gè)centter的y僅僅是它在collectionView的位置,所以還需要修改y值,然后使用UIView的block動(dòng)畫(huà)移動(dòng)到headerView上面對(duì)應(yīng)的點(diǎn)。在動(dòng)畫(huà)完成之后將它放到最底層

/** 選擇類(lèi)別之后的類(lèi)別圖片動(dòng)畫(huà) */
- (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];
}

選擇類(lèi)別動(dòng)畫(huà)之HeaderView顏色動(dòng)畫(huà)

第一步:提取顏色

我使用的是一個(gè)三方庫(kù),ColorExtraction

//* 顏色提取 */
CCColorCube *imageColor = [[CCColorCube alloc] init];
NSArray *colors = [imageColor extractColorsFromImage:category.categoryImage flags:CCAvoidBlack count:1];
第二步:動(dòng)畫(huà)

我是使用UIBezierPath和CAShapeLayer結(jié)合CABasicAnimation做的動(dòng)畫(huà)。
UIBezierPath的path如何而來(lái)?
path就是一條線,path的moveToPoint點(diǎn)就是self.bounds.origin點(diǎn)即左上點(diǎn)
addLineToPoint點(diǎn)就是self.bounds.origin.x點(diǎn)和self.bounds.size.height點(diǎn)即左下點(diǎn)
然后通過(guò)CABasicAnimation改變lineWidth

- (void)animationWithBgColor:(UIColor *)color {
    //* 如果選擇的類(lèi)別圖片的顏色和上次選擇的一樣  直接return */
    if ([color isEqual: self.previousSelectColor]) return;
    //* 修改背景顏色為上一次選擇的顏色,不然就會(huì)是最開(kāi)始默認(rèn)的顏色,動(dòng)畫(huà)會(huì)很丑,給用戶的體驗(yàn)很不好 */
    self.backgroundColor = self.previousSelectColor;
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"lineWidth"];
    animation.fromValue = @0.0;
    animation.toValue = @(self.bounds.size.width * 2);
    animation.duration = 0.3f;
    //* 設(shè)置填充色 */
    self.bgColorlayer.fillColor = color.CGColor;
    //* 設(shè)置邊框色 */
    self.bgColorlayer.strokeColor = color.CGColor;
    
    self.previousSelectColor = color;
    //* 保持動(dòng)畫(huà) */
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    
    [self.bgColorlayer addAnimation:animation forKey:@"bgColorAnimation"];

    //* 將子控件放在最上面,不然layer會(huì)覆蓋 */
    [self bringSubviewToFront:self.categoryImageView];
    [self bringSubviewToFront:self.moneyLabel];
    [self bringSubviewToFront:self.categoryNameBtn];
}

餅圖(TMPiewViewController)

TMPie.gif

餅圖HeaderView部分

控件是使用三方庫(kù)iCarousel 鏈接

數(shù)據(jù)源如何而來(lái)?

1.先把每個(gè)月中文對(duì)應(yīng)的英文縮寫(xiě)保存到一個(gè)數(shù)組中

- (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;
}

疑問(wèn):為什么數(shù)據(jù)每個(gè)元素,中間有個(gè)\n
答:我是使用的是一個(gè)UILabel\n用于換行
2.拿到篩選過(guò)后的數(shù)據(jù),是一個(gè)NSDictionary。額...說(shuō)一下,這個(gè)篩選過(guò)后的數(shù)據(jù)的一個(gè)結(jié)構(gòu),因?yàn)橥惶煳覀兛赡軙?huì)記多筆賬,所以把同一天的dateStr作為key,然后把所有屬于這一天的賬單數(shù)據(jù)當(dāng)作一個(gè)value,目前為止只是過(guò)濾掉同一天的時(shí)間字符串。
然后下一步我們要做的就是過(guò)濾掉同一年的相同月份

/** 過(guò)濾掉同年相同月份 */
- (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];
}
/** 把時(shí)間字符串轉(zhuǎn)換成月份 */
- (NSString *)conversionDateStringIntoMonth:(NSString *)dateString {
    NSRange range = NSMakeRange(5, 2);
    NSString *month = [dateString substringWithRange:range];
    return self.items[month.integerValue - 1];
}
/** 判斷字典里面是否已經(jīng)包含這個(gè)對(duì)象 */
- (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,通過(guò)CGPathContainsPoint判斷觸摸的點(diǎn)是否在這個(gè)path里面

類(lèi)別詳細(xì)界面(TMPiewCategoryDetailViewController)

解決cell重用導(dǎo)致數(shù)據(jù)年月日l(shuí)abel顯示混亂,在模型定義兩個(gè)BOOL變量same,partSame
拿到數(shù)據(jù)之后將數(shù)據(jù)進(jìn)行“重置”

 (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) {//第一個(gè)數(shù)據(jù)永遠(yuǎn)是不相同的
            [self.bills addObject:bill];
            previous = bill.dateStr;
            continue;
        } else {
            TMBill *theBill = [TMBill new];
            if ([previous isEqualToString:bill.dateStr]) {//完全相同,時(shí)間日期
                theBill = bill;
                theBill.same = YES;
                [self.bills addObject:theBill];
            } else if ([[previous substringToIndex:7] isEqualToString:[bill.dateStr substringToIndex:7]]) {//部分相同,年月份相同,具體時(shí)間不同
                theBill = bill;
                theBill.partSame = YES;
                [self.bills addObject:theBill];
            } else {//不同
                [self.bills addObject:bill];
            }
            previous = bill.dateStr;
        }
    }
}

側(cè)滑控制器,使用的是MMDrawerController庫(kù)

本來(lái)MMDrawerController是支持在屏幕向右滑就能出現(xiàn)左邊的菜單欄,由于使用了TYPagerController出現(xiàn)了手勢(shì)之間的沖突
解決和TYPagerController手勢(shì)沖突的問(wèn)題

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];
}

如果直接是這樣的話則會(huì)出現(xiàn)下面的情況

2016-06-25 22.50.25.gif

因?yàn)?code>UIScreenEdgePanGestureRecognizer是一個(gè)持續(xù)響應(yīng)事件,也就是說(shuō)你的手指沒(méi)離開(kāi)屏幕則會(huì)一直響應(yīng)這個(gè)函數(shù),因?yàn)?code>toggleDrawerSide在內(nèi)部會(huì)判斷菜單欄是打開(kāi)還是關(guān)閉,打開(kāi)則關(guān)閉,關(guān)閉則會(huì)打開(kāi),所以也就會(huì)出現(xiàn)上面這種情況了。
解決辦法

if (self.mm_drawerController.openSide == MMDrawerSideNone) {
        [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
    }

賬本控制器(TMSideViewController)

books.gif

如何抖動(dòng)?在cell上添加一個(gè)UILongPressGestureRecognizer長(zhǎng)按手勢(shì)

 //* 長(zhǎng)按手勢(shì) */
UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGR:)];
longGR.minimumPressDuration = 1.0;
longGR.numberOfTouchesRequired = 1;
longGR.allowableMovement = 10;
[self addGestureRecognizer:longGR];

給cell添加一個(gè)代理

@protocol TMSideCellDelegate <NSObject>
@required
@optional
- (void)TMSideCellWithIndexPath:(NSIndexPath *)indexPath withLongPress:(UILongPressGestureRecognizer *)longPress;
@end

當(dāng)控制器接收到響應(yīng)事件的時(shí)候只需要做三件事

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;
    }
/** 抖動(dòng)動(dòng)畫(huà) */
- (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];
    }];
}

好了,暫時(shí)就寫(xiě)這么多吧。有疑問(wèn)可以在Github上面提issue或者簡(jiǎn)書(shū)簡(jiǎn)信我也可以。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容