iOS 高仿Timi记账

写在最前面:

其实本文应该早在两个月之前就该写完的,由于当时找工作,种种原因搁置到现在才写完。
本文不可能把每一个点都能写到,如果真要一个点一个点的写,可能得写1w+字吧。再说时隔两个多月没碰这个项目了,多多少少都忘了一些。我尽可能把当时在写这个项目遇到的各种坑写详细。如果在看本文或者demo的时候有不明白的地方可以在Github上面提issue或者简书简信我也可以。
温馨提示:看文章的时候结合代码一起看,效果会更佳哟。
目前完成进度70%,由于时间的关系(临近期末,各种事情的原因…),剩下的30%未能完成,后面抽空完成吧。
项目采用MVC设计模式
本人还属于菜鸟级别,代码写得不规范,望见谅!
如果项目中同样的问题,你有更好的办法解决请告诉我,让我们一起学习。

废话说了一大堆,开始进入正题!!!

项目视频演练 -> 点我

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 账本
11959078-853d783b9c189b58

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 类型(收支)
12959078-eb1a791ce022a422

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文件名
13959078-21309f82d3353baf

TMBook(账本).png

TMAddCategory(新增类别)

KEY IDENTITY COLUMN DATA TYPE LENGTH ALLOWED NULL DEFAULT DESCRIPTION
categoryID NSString 64 主键
categoryImageFileNmae NSString 64 类别icon文件名
isIncome BOOL 1 类型(收支)
14959078-3e24d45c96f1a226

TMAddCategory(新增类别).png

TMCategory(类别),TMAddCategory(新增类别)都是采用plist表的方式先存储。当App每次启动的时候就会先检查数据库对应的表是否为空,为空则从plist表读取数据,存储到本地数据库。

项目整体结构

15959078-5bf4eb18f7c839c9

TimiStructure.png

温馨提醒

项目里面95%都是使用的纯代码方式布局(Masonry),如果不懂的Masonry纯代码布局的请先去了解一下。传送门=>串哥的深入讲解 AutoLayout 和 Masonry

时光轴界面(HomePageViewController)

UI布局之header部分(TMHeaderView)

17959078-26524816d97344b9

Paste_Image.png

其实headerView部分没有什么好说的,那个饼图是用UIBezierPathCAShapeLayer绘制而成,我把它单独封装出来了,因为在后面的饼图部分也用到了。关于饼图的加载数据时候的动画我是使用的CABasicAnimation具体的操作可以看demo的对应文件(TMPieView)

UI布局之数据显示部分(HomePageViewController | TMTimeLineCell)

18959078-ae001ee1ecaea501

Paste_Image.png

数据的显示全部在一个section里面,并没有分section显示,而且cell也只有一个样式,我是通过收支类型来判断的该那边显示数据。
时间轴上面,相同时间(同一天)时间label和金额label以及时间点不显示出来,我是在模型层加了一个BOOL变量来判断,同时在获取数据之后进行数据的重置,具体的操作可以看HomePageViewControllergetDataAndResetBill函数。
然后在自定义cell(TMTimeLineCell)重写timeLineBill属性,通过判断来显示数据。
下图应该清楚的看懂整个cell的布局

19959078-17722f19aa068eb9

Paste_Image.png

其实这种做法并不好,一个cell是能完成,但是代码看起来就有点乱糟糟的感觉,正确的做法是应该有两种样式的cell。分别是账单类型为收入,账单类型为支出两种样式。

很多人都应该碰到过,滑动tableView的时候Cell的数据会出现混乱,我是这样解决的,在自定义cell重写- (void)prepareForReuse函数,将cell里面的控件元素的属性和对象统统置为nil。

细心的人可能看到了我在下滑tableview的时候,中间的时光轴线也跟着变长。当我下滑到一定程度,然后松手就会push到新增账单界面,而且这个push动画不是系统自带的push动画。
下面我一一为大家解答:

时光轴的线条是怎么变长的?

第一步、我是新增的一个UIView,默认frame为(SCREEN_SIZE.width-1)/2,0 , 1, 0),将它加到tableview上面。

第二步、在UIScrollViewDelegate的 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 代理函数里面获取滑动的y值。判断其方向并重新设置dropdownLineView的frame即可

时光轴界面到添加账单(修改账单)界面的转场动画(LYPushTransition,LYPopTransition)

使用的是自定义的转场动画,具体如何使用请看喵神KittenYang 的blog,推荐几句代码快速集成自定义转场效果+全手势驱动
1.首先定一个class,继承至NSObject,遵守UIViewControllerAnimatedTransitioning协议。
2.需要实现两个方法

Push代码细节讲解(是一个反向prensent转场动画)

Pop做Push的相反操作即可

3. ViewController如何使用自定义转场动画
  • pushViewController
    在push的控制器设置navigationControllerdelegateself

    实现协议方法

    通过operation判断是push操作还是pop操作,然后然后对于的动画即可
    pop控制器不需要做任何操作
    如果使用push,则会发现NavigationBar没有变化,会一直处于那个地方,很丑…
    然而使用present就可以避免这种现象
  • presentViewController
    设置presentViewControllerViewControllertransitioningDelegateself
    注意,如果是present的UINavigationController,则需要设置NavigationControllertransitioningDelegateself

    实现transitioningDelegate协议方法

NavigationItemTitleView按钮的边框&点击切换时候的颜色动画

关于点击按钮切换时候的动画我是使用的两个UIView的动画

点击类别按钮弹出菜单(TMTimeLineMenuView)

我不是在每个cell下面都添加了deleteBtn,updateBtn,因为这样会使性能大大降低。
我是自定义的一个UIView(TMTimeLineMenuView),这里面有三个控件,分别是deleteBtn,updateBtn,categoryBtn
这个categoryBtn是放在deleteBtn,updateBtn上面的。因为在deleteBtn和updateBtn弹出的时候我把TMTimeLineMenuView放到了最顶层

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

20959078-7cfbee09c7f7e6af

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

创建账单界面(TMCreateBillViewController)

TimiAddBillController.gif

选择类别动画之类别图片动画(应该使用UI Dynamics)

第一步:

在创建账单界面添加一个UIImageView控件,大小跟collectionViewCell里面的categoryImageView一样,放在屏幕外。并设置圆角。

第二步: 获取点击的位置

1.拿到对应cell

2.将cell对应的类别图片赋值给_selectCategoryImageView
然后获取到cell的center,这个centter的y仅仅是它在collectionView的位置,所以还需要修改y值,然后使用UIView的block动画移动到headerView上面对应的点。在动画完成之后将它放到最底层

选择类别动画之HeaderView颜色动画

第一步:提取颜色

我使用的是一个三方库,ColorExtraction

第二步:动画

我是使用UIBezierPath和CAShapeLayer结合CABasicAnimation做的动画。
UIBezierPath的path如何而来?
path就是一条线,path的moveToPoint点就是self.bounds.origin点即左上点
addLineToPoint点就是self.bounds.origin.x点和self.bounds.size.height点即左下点
然后通过CABasicAnimation改变lineWidth

饼图(TMPiewViewController)

TMPie.gif

饼图HeaderView部分

控件是使用三方库iCarousel 链接

数据源如何而来?

1.先把每个月中文对应的英文缩写保存到一个数组中

疑问:为什么数据每个元素,中间有个\n
答:我是使用的是一个UILabel\n用于换行
2.拿到筛选过后的数据,是一个NSDictionary。额…说一下,这个筛选过后的数据的一个结构,因为同一天我们可能会记多笔账,所以把同一天的dateStr作为key,然后把所有属于这一天的账单数据当作一个value,目前为止只是过滤掉同一天的时间字符串。
然后下一步我们要做的就是过滤掉同一年的相同月份

获取layer的位置

拿到所有的sublayer,取出layer的path,通过CGPathContainsPoint判断触摸的点是否在这个path里面

类别详细界面(TMPiewCategoryDetailViewController)

解决cell重用导致数据年月日label显示混乱,在模型定义两个BOOL变量same,partSame
拿到数据之后将数据进行“重置”

侧滑控制器,使用的是MMDrawerController库

本来MMDrawerController是支持在屏幕向右滑就能出现左边的菜单栏,由于使用了TYPagerController出现了手势之间的冲突
解决和TYPagerController手势冲突的问题

如果直接是这样的话则会出现下面的情况

2016-06-25 22.50.25.gif

因为UIScreenEdgePanGestureRecognizer是一个持续响应事件,也就是说你的手指没离开屏幕则会一直响应这个函数,因为toggleDrawerSide在内部会判断菜单栏是打开还是关闭,打开则关闭,关闭则会打开,所以也就会出现上面这种情况了。
解决办法

账本控制器(TMSideViewController)

books.gif

如何抖动?在cell上添加一个UILongPressGestureRecognizer长按手势

给cell添加一个代理

当控制器接收到响应事件的时候只需要做三件事

- (UICollectionViewCell *)collectionView: cellForItemAtIndexPath:添加判断代码

好了,暂时就写这么多吧。有疑问可以在Github上面提issue或者简书简信我也可以。谢谢!

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部