iOS 高仿 Timi 记账

gvxb2966 8年前
   <p>本人还属于菜鸟级别,代码写得不规范,望见谅!</p>    <h2><strong>项目视频演练 -> <a href="/misc/goto?guid=4959714366929282635" rel="nofollow,noindex">点我</a></strong></h2>    <h2><strong>Demo -> <a href="/misc/goto?guid=4959714367026116135" rel="nofollow,noindex">Timi</a> 不要忘记star支持哟</strong></h2>    <h2><strong>高仿版本:3.6.1</strong></h2>    <h2><strong>使用语言:Objective-C</strong></h2>    <h2><strong>开发工具及调试神器:Xcode 7.3.1,Reveal 1.6.3</strong></h2>    <h2><strong>用到的三方库及扩展库</strong></h2>    <table>     <thead>      <tr>       <th>Name</th>       <th>Explain</th>      </tr>     </thead>     <tbody>      <tr>       <td>Masonry</td>       <td><a href="/misc/goto?guid=4958877303436721101" rel="nofollow,noindex">纯代码Autolayout</a></td>      </tr>      <tr>       <td>MBProgressHUD</td>       <td><a href="/misc/goto?guid=4958870672840018332" rel="nofollow,noindex">未使用,后更改为使用SVProgressHUD</a></td>      </tr>      <tr>       <td>MMDrawerController</td>       <td><a href="/misc/goto?guid=4958870677314802488" rel="nofollow,noindex">抽屉</a></td>      </tr>      <tr>       <td>SVProgressHUD</td>       <td><a href="/misc/goto?guid=4959671431027398613" rel="nofollow,noindex">HUD</a></td>      </tr>      <tr>       <td>YYText</td>       <td><a href="/misc/goto?guid=4958971667887126889" rel="nofollow,noindex">著名库YYKit下的一个富文本</a></td>      </tr>      <tr>       <td>iCarousel</td>       <td><a href="/misc/goto?guid=4958870674500642597" rel="nofollow,noindex">一个类似UIScrollView的控件</a></td>      </tr>      <tr>       <td>ColorCube</td>       <td><a href="/misc/goto?guid=4959714367292881833" rel="nofollow,noindex">图片颜色提取</a></td>      </tr>      <tr>       <td>UITextView_PlaceHolder</td>       <td><a href="/misc/goto?guid=4959654888015231719" rel="nofollow,noindex">给UITextView添加PlaceHolder</a></td>      </tr>      <tr>       <td>SZCalendarPicker</td>       <td><a href="/misc/goto?guid=4959714367415816429" rel="nofollow,noindex">日历</a></td>      </tr>      <tr>       <td>TYPagerController</td>       <td><a href="/misc/goto?guid=4959714367497144013" rel="nofollow,noindex">左右滚动ViewController</a> VTMagic</td>      </tr>      <tr>       <td>Realm</td>       <td><a href="/misc/goto?guid=4959714367594357972" rel="nofollow,noindex">移动端数据库新王者</a></td>      </tr>     </tbody>    </table>    <h2><strong>数据库设计</strong></h2>    <p>TMBill(账单)</p>    <table>     <thead>      <tr>       <th>Key</th>       <th>Identity</th>       <th>Column</th>       <th>Data Type</th>       <th>length</th>       <th>Allowed Null</th>       <th>Default</th>       <th>Description</th>      </tr>     </thead>     <tbody>      <tr>       <td>√</td>       <td>√</td>       <td>billID</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td> </td>       <td>主键</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>dateStr</td>       <td>NSString</td>       <td>10</td>       <td>当前年月日</td>       <td>时间</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>reMarks</td>       <td>NSString</td>       <td>40</td>       <td>nil</td>       <td>备注</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>remarkPhoto</td>       <td>NSData</td>       <td> </td>       <td>√</td>       <td>nil</td>       <td>图片备注</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>isIncome</td>       <td>BOOL</td>       <td>1</td>       <td> </td>       <td>0</td>       <td>类型(收支)</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>money</td>       <td>float</td>       <td>13</td>       <td> </td>       <td>0</td>       <td>金额</td>      </tr>      <tr>       <td>FK</td>       <td> </td>       <td>category</td>       <td>TMCategory</td>       <td> </td>       <td> </td>       <td> </td>       <td>类别</td>      </tr>      <tr>       <td>FK</td>       <td> </td>       <td>book</td>       <td>TMBooks</td>       <td> </td>       <td> </td>       <td> </td>       <td>账本</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/614ae9da78742f0c096ed3f8c20b2483.jpg"></p>    <p style="text-align:center">TMBill(账单).png</p>    <p>TMCategory(类别)</p>    <table>     <thead>      <tr>       <th>Key</th>       <th>Identity</th>       <th>Column</th>       <th>Data Type</th>       <th>length</th>       <th>Allowed Null</th>       <th>Default</th>       <th>Description</th>      </tr>     </thead>     <tbody>      <tr>       <td>√</td>       <td>√</td>       <td>categoryID</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td> </td>       <td>主键</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>categoryImageFileNmae</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td>类别icon文件名</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>categoryTitle</td>       <td>NSString</td>       <td>3</td>       <td> </td>       <td>类别标题</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>isIncome</td>       <td>BOOL</td>       <td>1</td>       <td> </td>       <td> </td>       <td>类型(收支)</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/3ef1d2e7d655b9a25d92a9607cfdb071.jpg"></p>    <p style="text-align:center">TMCategory(类别).png</p>    <p>TMBook(账本)</p>    <table>     <thead>      <tr>       <th>Key</th>       <th>Identity</th>       <th>Column</th>       <th>Data Type</th>       <th>length</th>       <th>Allowed Null</th>       <th>Default</th>       <th>Description</th>      </tr>     </thead>     <tbody>      <tr>       <td>√</td>       <td>√</td>       <td>bookID</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td> </td>       <td>主键</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>bookName</td>       <td>NSString</td>       <td>6</td>       <td> </td>       <td>账本标题</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td> </td>       <td>imageIndex</td>       <td>int</td>       <td>2</td>       <td> </td>       <td>账本对应icon下标</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>bookImageFileName</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td> </td>       <td>类别icon文件名</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/fce5ffe07206dc489a95f76c23a2a355.jpg"></p>    <p style="text-align:center">TMBook(账本).png</p>    <p>TMAddCategory(新增类别)</p>    <table>     <thead>      <tr>       <th>Key</th>       <th>Identity</th>       <th>Column</th>       <th>Data Type</th>       <th>length</th>       <th>Allowed Null</th>       <th>Default</th>       <th>Description</th>      </tr>     </thead>     <tbody>      <tr>       <td>√</td>       <td>√</td>       <td>categoryID</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td> </td>       <td>主键</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>√</td>       <td>categoryImageFileNmae</td>       <td>NSString</td>       <td>64</td>       <td> </td>       <td>类别icon文件名</td>      </tr>      <tr>       <td> </td>       <td> </td>       <td>isIncome</td>       <td>BOOL</td>       <td>1</td>       <td> </td>       <td> </td>       <td>类型(收支)</td>      </tr>     </tbody>    </table>    <p><img src="https://simg.open-open.com/show/1b8050d66035c68e63a2081fe984e4a8.jpg"></p>    <p style="text-align:center">TMAddCategory(新增类别).png</p>    <p>TMCategory(类别),TMAddCategory(新增类别)都是采用plist表的方式先存储。当App每次启动的时候就会先检查数据库对应的表是否为空,为空则从plist表读取数据,存储到本地数据库。</p>    <h2><strong>项目整体结构</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/29b9b2db9d796a52d80c77d8eed69496.png"></p>    <p style="text-align:center">TimiStructure.png</p>    <h2><strong>温馨提醒</strong></h2>    <p>项目里面95%都是使用的纯代码方式布局(Masonry),如果不懂的 Masonry 纯代码布局的请先去了解一下。 <a href="/misc/goto?guid=4959714367684959809" rel="nofollow,noindex">传送门=>串哥的深入讲解 AutoLayout 和 Masonry</a></p>    <h2><strong>时光轴界面(HomePageViewController)</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0953cb9e139d14096dbe34b82c608380.gif"></p>    <p style="text-align:center">2016-07-01 14.58.02.gif</p>    <h2><strong>UI布局之header部分(TMHeaderView)</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5da1ba414f2061479f1dbd98e7943e2a.png"></p>    <p style="text-align:center">Paste_Image.png</p>    <p>其实headerView部分没有什么好说的,那个饼图是用 UIBezierPath 和 CAShapeLayer 绘制而成,我把它单独封装出来了,因为在后面的饼图部分也用到了。关于饼图的加载数据时候的动画我是使用的 CABasicAnimation 具体的操作可以看demo的对应文件( TMPieView )</p>    <h2><strong>UI布局之数据显示部分(HomePageViewController | TMTimeLineCell)</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a87c25edb20a43214b8922bbc7b1263c.png"></p>    <p style="text-align:center">Paste_Image.png</p>    <p>数据的显示全部在一个section里面,并没有分section显示,而且cell也只有一个样式,我是通过收支类型来判断的该那边显示数据。</p>    <p>时间轴上面,相同时间(同一天)时间label和金额label以及时间点不显示出来,我是在模型层加了一个BOOL变量来判断,同时在获取数据之后进行数据的重置,具体的操作可以看 HomePageViewController 的 getDataAndResetBill 函数。</p>    <p>然后在自定义cell( TMTimeLineCell )重写 timeLineBill 属性,通过判断来显示数据。</p>    <p>下图应该清楚的看懂整个cell的布局</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8b6fd881028b0ca1ff7f1a55484ad421.png"></p>    <p style="text-align:center">Paste_Image.png</p>    <p>其实这种做法并不好,一个cell是能完成,但是代码看起来就有点乱糟糟的感觉,正确的做法是应该有两种样式的cell。分别是账单类型为收入,账单类型为支出两种样式。</p>    <p>很多人都应该碰到过,滑动tableView的时候Cell的数据会出现混乱,我是这样解决的,在自定义cell重写 - (void)prepareForReuse 函数,将cell里面的控件元素的属性和对象统统置为nil。</p>    <pre>  <code class="language-objectivec">//* 解决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;      }</code></pre>    <p>细心的人可能看到了我在下滑tableview的时候,中间的时光轴线也跟着变长。当我下滑到一定程度,然后松手就会push到新增账单界面,而且这个push动画不是系统自带的push动画。</p>    <p>下面我一一为大家解答:</p>    <h2><strong>时光轴的线条是怎么变长的?</strong></h2>    <p>第一步、我是新增的一个UIView,默认frame为 (SCREEN_SIZE.width-1)/2,0 , 1, 0) ,将它加到tableview上面。</p>    <pre>  <code class="language-objectivec">self.dropdownLineView = [[UIView alloc] initWithFrame:CGRectMake((SCREEN_SIZE.width-1)/2,0 , 1, 0)];  self.dropdownLineView.backgroundColor = LineColor;  [self.tableView addSubview:self.dropdownLineView];</code></pre>    <p>第二步、在UIScrollViewDelegate的 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 代理函数里面获取滑动的y值。判断其方向并重新设置 dropdownLineView 的frame即可</p>    <pre>  <code class="language-objectivec">- (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];      }  }</code></pre>    <h2><strong>时光轴界面到添加账单(修改账单)界面的转场动画(LYPushTransition,LYPopTransition)</strong></h2>    <p>使用的是自定义的转场动画,具体如何使用请看 <a href="/misc/goto?guid=4959714367769217447" rel="nofollow,noindex">喵神</a> 和 <a href="/misc/goto?guid=4959714367851353499" rel="nofollow,noindex">KittenYang</a> 的blog,推荐 <a href="/misc/goto?guid=4959714367942606759" rel="nofollow,noindex">几句代码快速集成自定义转场效果+全手势驱动</a></p>    <p>1.首先定一个 class ,继承至 NSObject ,遵守 UIViewControllerAnimatedTransitioning 协议。</p>    <p>2.需要实现两个方法</p>    <pre>  <code class="language-objectivec">/** 动画时间 */  - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext    /** 转场动画内容(怎么转) */  - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext</code></pre>    <h3><strong>Push代码细节讲解(是一个反向prensent转场动画)</strong></h3>    <pre>  <code class="language-objectivec">/** 动画时间 */  - (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];      }];  }</code></pre>    <h3><strong>Pop做Push的相反操作即可</strong></h3>    <p>3. ViewController如何使用自定义转场动画</p>    <ul>     <li> <p>pushViewController</p> <p>在push的控制器设置 navigationController 的 delegate 为 self</p> <pre>  <code class="language-objectivec">self.navigationController.delegate = self;</code></pre> <p>实现协议方法</p> <pre>  <code class="language-objectivec">- (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;    }  }</code></pre> <p>通过 operation 判断是 push 操作还是 pop 操作,然后然后对于的动画即可</p> <p>pop 控制器不需要做任何操作</p> <p>如果使用 push ,则会发现 NavigationBar 没有变化,会一直处于那个地方,很丑...</p> <p>然而使用 present 就可以避免这种现象</p> </li>     <li> <p>presentViewController</p> 设置 presentViewController 的 ViewController 的 transitioningDelegate 为 self <p>注意,如果是present的 UINavigationController ,则需要设置 NavigationController 的 transitioningDelegate 为 self</p> <pre>  <code class="language-objectivec">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];</code></pre> <p>实现 transitioningDelegate 协议方法</p> </li>    </ul>    <pre>  <code class="language-objectivec">/** 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;  }</code></pre>    <pre>  <code class="language-objectivec">dismiss`控制器则需要写一个代理,告诉`present`的那个控制器`dismiss`即可</code></pre>    <p>NavigationItemTitleView按钮的边框&点击切换时候的颜色动画</p>    <pre>  <code class="language-objectivec">/** 设置边框宽度 */   titleBtn.layer.borderWidth = 1.5;  //* 设置Btn的边框颜色 */  titleBtn.layer.borderColor = [UIColor whiteColor].CGColor;</code></pre>    <p><strong>关于点击按钮切换时候的动画我是使用的两个UIView的动画</strong></p>    <pre>  <code class="language-objectivec">//* 改变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) {            }];      }];</code></pre>    <p>点击类别按钮弹出菜单(TMTimeLineMenuView)</p>    <p>我不是在每个cell下面都添加了deleteBtn,updateBtn,因为这样会使性能大大降低。</p>    <p>我是自定义的一个UIView( TMTimeLineMenuView ),这里面有三个控件,分别是 deleteBtn , updateBtn , categoryBtn 。</p>    <p>这个categoryBtn是放在deleteBtn,updateBtn上面的。因为在deleteBtn和updateBtn弹出的时候我把 TMTimeLineMenuView 放到了最顶层</p>    <pre>  <code class="language-objectivec">//* 置顶 */  [weakSelf.superview bringSubviewToFront:weakSelf];</code></pre>    <p>也就意味着tableView是在TMTimeLineMenuView的下面。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/90b4ed800969b616e5cf2d9806b92e63.png"></p>    <p style="text-align:center">Paste_Image.png</p>    <p>如果没有categoryBtn,弹出deleteBtn和updateBtn就感觉是直接在tableViewCell上面做的动画,会很丑。所以添加一个categoryBtn放在updateBtn和deleteBtn上面,就感觉deleteBtn和updateBtn是放在tableViewCell下面的。给用户很好的用户体验。</p>    <p><strong>如何将TMTimeLineMenuView中的控件显示到对应的位置?(HomePageViewController->didClickCategoryBtnWithIndexPath:)</strong></p>    <p>第一步:获取到点击的cell对应的indexPath</p>    <p>第二步:获取对应cell在tableview中的rect</p>    <p>第三步:将获取到的rect转换成在self.view中的rect</p>    <pre>  <code class="language-objectivec">/** 获取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 ];</code></pre>    <h2>创建账单界面(TMCreateBillViewController)</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1adbbca57c40bc0917ec057f38154d26.gif"></p>    <p style="text-align:center">TimiAddBillController.gif</p>    <h2><strong>选择类别动画之类别图片动画(应该使用UI Dynamics)</strong></h2>    <p><strong>第一步:</strong></p>    <p>在创建账单界面添加一个 UIImageView 控件,大小跟collectionViewCell里面的 categoryImageView 一样,放在屏幕外。并设置圆角。</p>    <pre>  <code class="language-objectivec">- (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;  }</code></pre>    <p><strong>第二步: 获取点击的位置</strong></p>    <p>1.拿到对应cell</p>    <pre>  <code class="language-objectivec">cell = (TMCategotyCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];</code></pre>    <p>2.将cell对应的类别图片赋值给 _selectCategoryImageView<br> 然后获取到cell的 center ,这个 centter的y 仅仅是它在collectionView的位置,所以还需要修改y值,然后使用UIView的block动画移动到headerView上面对应的点。在动画完成之后将它放到最底层</p>    <pre>  <code class="language-objectivec">/** 选择类别之后的类别图片动画 */  - (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];  }</code></pre>    <h2><strong>选择类别动画之HeaderView颜色动画</strong></h2>    <p><strong>第一步:提取颜色</strong></p>    <p>我使用的是一个三方库, <a href="https://github.com/search?utf8=%E2%9C%93&q=ColorExtraction" rel="nofollow,noindex">ColorExtraction</a></p>    <pre>  <code class="language-objectivec">//* 颜色提取 */  CCColorCube *imageColor = [[CCColorCube alloc] init];  NSArray *colors = [imageColor extractColorsFromImage:category.categoryImage flags:CCAvoidBlack count:1];</code></pre>    <p><strong>第二步:动画</strong></p>    <p>我是使用UIBezierPath和CAShapeLayer结合CABasicAnimation做的动画。</p>    <p>UIBezierPath的path如何而来?</p>    <p>path就是一条线,path的 moveToPoint 点就是 self.bounds.origin 点即左上点</p>    <p>addLineToPoint 点就是 self.bounds.origin.x 点和 self.bounds.size.height 点即左下点</p>    <p>然后通过CABasicAnimation改变 lineWidth</p>    <pre>  <code class="language-objectivec">- (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];  }</code></pre>    <h2>饼图(TMPiewViewController)</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3ddd2c1edbb6d7e7992c1f65bf982d12.gif"></p>    <p style="text-align:center">TMPie.gif</p>    <h2><strong>饼图HeaderView部分</strong></h2>    <p>控件是使用三方库 iCarousel <a href="/misc/goto?guid=4958870674500642597" rel="nofollow,noindex">链接</a></p>    <p><strong>数据源如何而来?</strong></p>    <p>1.先把每个月中文对应的英文缩写保存到一个数组中</p>    <pre>  <code class="language-objectivec">- (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;  }</code></pre>    <p>疑问:为什么数据每个元素,中间有个 \n</p>    <p>答:我是使用的是一个UILabel \n 用于换行</p>    <p>2.拿到筛选过后的数据,是一个NSDictionary。额...说一下,这个筛选过后的数据的一个结构,因为同一天我们可能会记多笔账,所以把同一天的 dateStr 作为 key ,然后把所有属于这一天的账单数据当作一个 value ,目前为止只是过滤掉同一天的时间字符串。</p>    <p>然后下一步我们要做的就是过滤掉同一年的相同月份</p>    <pre>  <code class="language-objectivec">/** 过滤掉同年相同月份 */  - (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;  }</code></pre>    <h2><strong>获取layer的位置</strong></h2>    <pre>  <code class="language-objectivec">- (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;  }</code></pre>    <p>拿到所有的sublayer,取出layer的path,通过 CGPathContainsPoint 判断触摸的点是否在这个path里面</p>    <p>类别详细界面(TMPiewCategoryDetailViewController)</p>    <p>解决cell重用导致数据 年月日label 显示混乱,在模型定义两个 BOOL 变量 same,partSame<br> 拿到数据之后将数据进行“重置”</p>    <pre>  <code class="language-objectivec">(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;          }      }  }</code></pre>    <h2><strong>侧滑控制器,使用的是MMDrawerController库</strong></h2>    <p>本来 MMDrawerController 是支持在屏幕向右滑就能出现左边的菜单栏,由于使用了 TYPagerController 出现了手势之间的冲突</p>    <p>解决和 TYPagerController 手势冲突的问题</p>    <pre>  <code class="language-objectivec">UIScreenEdgePanGestureRecognizer *screenEdgeGR = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(clickMenuBtn:)];      screenEdgeGR.edges = UIRectEdgeLeft;      [self.view addGestureRecognizer:screenEdgeGR];</code></pre>    <pre>  <code class="language-objectivec">- (void)clickMenuBtn:(UIButton *)sender {      [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];  }</code></pre>    <p>如果直接是这样的话则会出现下面的情况</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c0ed7d1508f86d9a5140a2f2aee2c895.gif"></p>    <p style="text-align:center">2016-06-25 22.50.25.gif</p>    <p>因为 UIScreenEdgePanGestureRecognizer 是一个持续响应事件,也就是说你的手指没离开屏幕则会一直响应这个函数,因为 toggleDrawerSide 在内部会判断菜单栏是打开还是关闭,打开则关闭,关闭则会打开,所以也就会出现上面这种情况了。</p>    <p>解决办法</p>    <pre>  <code class="language-objectivec">if (self.mm_drawerController.openSide == MMDrawerSideNone) {          [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];      }</code></pre>    <h2>账本控制器(TMSideViewController)</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9a0a2efc7fb633c5750e10a8a6bc7903.gif"></p>    <p style="text-align:center">books.gif</p>    <p>如何抖动?在cell上添加一个 UILongPressGestureRecognizer 长按手势</p>    <pre>  <code class="language-objectivec">//* 长按手势 */  UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGR:)];  longGR.minimumPressDuration = 1.0;  longGR.numberOfTouchesRequired = 1;  longGR.allowableMovement = 10;  [self addGestureRecognizer:longGR];</code></pre>    <p>给cell添加一个代理</p>    <pre>  <code class="language-objectivec">@protocol TMSideCellDelegate <NSObject>  @required  @optional  - (void)TMSideCellWithIndexPath:(NSIndexPath *)indexPath withLongPress:(UILongPressGestureRecognizer *)longPress;  @end</code></pre>    <p>当控制器接收到响应事件的时候只需要做三件事</p>    <pre>  <code class="language-objectivec">self.editSelectedIndexPath = indexPath;     //1  self.edit = YES;                            //2  [self.collectionView reloadData];           //3</code></pre>    <p>在 - (UICollectionViewCell *)collectionView: cellForItemAtIndexPath: 添加判断代码</p>    <pre>  <code class="language-objectivec">//* 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;      }</code></pre>    <pre>  <code class="language-objectivec">/** 抖动动画 */  - (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];      }];  }</code></pre>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/d3dbf8dba11a</p>    <p> </p>