现在记账APP也是用途比较广泛

自己写了个简单的demo 欢迎指正

效果

分析

1.思维推导

首先简单的做了下思维推导

2.文件结构

大致框架想好后就可以着手开始准备了

  • 数据库管理:coreData
  • 视图管理:navigationcontroller

    暂时没有使用cocoapods导入第三方的数据库管理框架

    简单的coreData完全可以胜任

    说白了就两个页面 主界面 和 记账界面

这是完成时的文件结构

3.数据库设计

  • Tally 账单表
  • identity :String 唯一标识
  • expenses :double 支出

  • income :double 收入

  • timestamp :date 时间戳

  • 关系

    • 与TallyDate 日期表:1V1
    • 与TallyType 类型表:1V1

  • TallyDate 日期表

  • date :string 日期
  • 关系

    -与Tally 账单表:1VN

  • TallyType 类型表

  • typename :string 类型名
  • typeicon :string 类型图片标
  • 关系

    -与Tally 账单表:1VN

4.页面编写

增加账单页面

由于主页只是一个展示的时光轴界面,UIScrollView加几个按钮就能完成,需要读取数据库内容,所以我们先把内页-增加账单 完成。

  • view
  • UICollectionView展示账单类型
  • 自定义View计算器界面计算存储结果
  • model
    • UICollectionViewCell模型 使用了plist和KVC转字典
  • controller
    • 负责添加 两个view 及处理两个view的代理

增加账单部分代码

  • model
#import <Foundation/Foundation.h>

@interface TallyListCellModel : NSObject
@property (nonatomic,copy)NSString *tallyCellImage;
@property (nonatomic,copy)NSString *tallyCellName; - (instancetype)initWithDict:(NSDictionary*)dict;
+ (instancetype)tallyListCellModelWithDict:(NSDictionary*)dict; @end
#import "TallyListCellModel.h"
@implementation TallyListCellModel
- (instancetype)initWithDict:(NSDictionary*)dict {
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)tallyListCellModelWithDict:(NSDictionary*)dict {
return [[TallyListCellModel alloc] initWithDict:dict];
}
@end
  • 计算界面
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <CoreData/CoreData.h>
#import "TimeTallyDemo+CoreDataModel.h"
typedef void(^PositionInViewBlock)(CGPoint point); @protocol CalculatorViewDelegate <NSObject> //保存成功
- (void)tallySaveCompleted; //保存失败
- (void)tallySaveFailed; //回到原来的位置
- (void)backPositionWithAnimation; @end @interface CalculatorView : UIView
//类型图片的size
@property(nonatomic,assign)CGSize imageViewSize;
//类型图
@property(nonatomic,strong)UIImage *image;
//类型名
@property(nonatomic,strong)NSString *typeName;
//账单是否存在 判断 是修改还是新增
@property(nonatomic,assign)BOOL isTallyExist;
@property(nonatomic,strong)id<CalculatorViewDelegate> delegate;
//回调image在整个view中的位置
@property(nonatomic,copy)PositionInViewBlock positionInViewBlock;
//修改账单界面进入时传入参数
- (void)modifyTallyWithIdentity:(NSString *)identity;
@end

#import "CalculatorView.h" @interface CalculatorView()
@property (nonatomic,assign)CGFloat btnWidth; //btn宽
@property (nonatomic,assign)CGFloat btnHeight; //btn高
@property (nonatomic,copy)NSString *nValue; //当前输入值
@property (nonatomic,copy)NSString *resutlStr; //结果值
@property (nonatomic,strong)UILabel *resultLab; //结果栏
@property (nonatomic,strong)UIButton *addBtn; //+
@property (nonatomic,strong)UIColor *btnColor; //按钮颜色
@property (nonatomic,assign)CGColorRef boardLineColor; //边框线条颜色
@property (nonatomic,strong)UIImageView *imageView; //tally类型图
@property(nonatomic,strong)UILabel *typeLab;
@property(nonatomic,copy)NSString *tallyIdentity;
@end
static CGFloat const kBoardWidth = 1;
static CGFloat const kMaxCalCount = 9;
@implementation CalculatorView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.btnWidth = (self.frame.size.width-kBoardWidth/2)/4;
self.btnHeight = self.frame.size.height/5;
self.nValue = @"";
self.resutlStr = @"";
self.typeLab.text = @"";
self.btnColor = [UIColor grayColor];
self.boardLineColor = [UIColor lightGrayColor].CGColor;
self.layer.borderColor = self.boardLineColor;
self.layer.borderWidth = kBoardWidth;
self.imageView = [[UIImageView alloc] init];
[self addSubview:self.imageView];
[self loadBtn];
}
return self;
} - (void)setImage:(UIImage *)image {
_image = image; [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
_imageView.image = image;
}]; } - (void)setTypeName:(NSString *)typeName {
_typeName = typeName;
self.typeLab.text = typeName;
} - (void)setImageViewSize:(CGSize)imageViewSize {
_imageViewSize = imageViewSize;
_imageView.frame = CGRectMake(10, (self.btnHeight-imageViewSize.height)/2, imageViewSize.width, imageViewSize.height);
_imageView.backgroundColor = [UIColor clearColor];
//回调实际位置
if (_positionInViewBlock) {
CGPoint point = CGPointMake(10+imageViewSize.width/2, self.frame.origin.y + _imageView.frame.origin.y + imageViewSize.height/2) ;
_positionInViewBlock(point);
}
self.typeLab.frame = CGRectMake(self.imageView.frame.origin.x + self.imageView.frame.size.width + 10, self.imageView.frame.origin.y, imageViewSize.width * 2, imageViewSize.height);
} //类型名称lab
- (UILabel *)typeLab {
if (!_typeLab) {
_typeLab = [[UILabel alloc] init];
_typeLab.font = [UIFont systemFontOfSize:14];
[self addSubview:_typeLab];
}
return _typeLab;
} //结果lab
- (UILabel *)resultLab {
if (!_resultLab) {
_resultLab = [[UILabel alloc] initWithFrame:CGRectMake(self.frame.size.width * 0.3, 0, self.frame.size.width * 0.7-10, self.btnHeight)];
_resultLab.text = @"¥ 0.00";
_resultLab.textAlignment = NSTextAlignmentRight;
_resultLab.adjustsFontSizeToFitWidth = YES;
_resultLab.font = [UIFont systemFontOfSize:25];
_resultLab.userInteractionEnabled = YES;
UITapGestureRecognizer *tag = [[UITapGestureRecognizer alloc] init];
tag.numberOfTapsRequired = 1;
[tag addTarget:self action:@selector(clickResultLab)];
[_resultLab addGestureRecognizer:tag];
}
return _resultLab;
} - (void)clickResultLab {
if ([self.delegate respondsToSelector:@selector(backPositionWithAnimation)]) {
[self.delegate backPositionWithAnimation];
}
}
//加号
- (UIButton *)addBtn {
if (!_addBtn) {
...
[_addBtn addTarget:self action:@selector(clickAdd) forControlEvents:UIControlEventTouchUpInside]; }
return _addBtn;
} //普通数字btn
- (void)loadBtn {
// 1 - 9 btn
...
[btn addTarget:self action:@selector(clickNumber:) forControlEvents:UIControlEventTouchUpInside]; //0 btn
...
[zeroBtn addTarget:self action:@selector(clickNumber:) forControlEvents:UIControlEventTouchUpInside]; //小数点 btn
...
pointBtn.tag = 99;
[pointBtn addTarget:self action:@selector(clickNumber:) //重置 btn
...
[resetBtn addTarget:self action:@selector(resetZero) forControlEvents:UIControlEventTouchUpInside]; //DEL btn
...
[delBtn addTarget:self action:@selector(clickDel) forControlEvents:UIControlEventTouchUpInside]; //ok btn
...
[okBtn addTarget:self action:@selector(clickOk) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.addBtn];
[self addSubview:self.resultLab]; } //点击数字按键
- (void)clickNumber:(UIButton*)btn { if(self.addBtn.isSelected){
self.nValue = @"";
}
NSString *currentValue = @"";
if (btn.tag == 99) {
//有 . 就不加 .
if ([self.nValue rangeOfString:@"."].location == NSNotFound) {
currentValue = @".";
}
}else {
currentValue = [NSString stringWithFormat:@"%ld",(long)btn.tag];
} self.nValue = [self.nValue stringByAppendingString:currentValue]; //保留小数点后两位
NSRange pointRange = [self.nValue rangeOfString:@"."];
if (pointRange.location != NSNotFound) {
if ([self.nValue substringFromIndex:pointRange.location+1].length > 2) {
self.nValue = [self.nValue substringWithRange:NSMakeRange(0, pointRange.location + 3)];
} //总位数不超过9 处理小数部分
if ([self.nValue substringToIndex:pointRange.location].length > kMaxCalCount) {
self.nValue = [NSString stringWithFormat:@"%0.2f",[self.nValue doubleValue]];
self.nValue = [self.nValue substringToIndex:kMaxCalCount+3];
} } else {
//总位数不超过9 整数部分
if (self.nValue.length > kMaxCalCount) {
self.nValue = [NSString stringWithFormat:@"%0.2f",[self.nValue doubleValue]];
self.nValue = [self.nValue substringToIndex:kMaxCalCount];
}
} //显示数字
self.resultLab.text = [NSString stringWithFormat:@"¥ %.2f",[self.nValue doubleValue]];
self.addBtn.selected = NO; NSLog(@"new = %@",self.nValue);
} //单击加号
- (void)clickAdd {
//显示结果 点击后nValue清零
if (!self.addBtn.isSelected) {
self.addBtn.selected = YES;
double result = [self.resutlStr doubleValue] + [self.nValue doubleValue] ;
self.resutlStr = [NSString stringWithFormat:@"%.2f",result];
self.resultLab.text = [NSString stringWithFormat:@"¥ %.2f",[self.resutlStr doubleValue]];
NSLog(@"resutl = %@",self.resutlStr);
} } //重置0
- (void)resetZero {
self.resutlStr = @"";
self.nValue = @"";
self.resultLab.text = @"¥ 0.00";
} //退格
- (void)clickDel {
if (self.nValue.length > 0) {
self.nValue = [self.nValue substringWithRange:NSMakeRange(0, self.nValue.length-1)];
}
NSLog(@"-----%@",self.nValue);
self.resultLab.text = [NSString stringWithFormat:@"¥ %.2f",[self.nValue doubleValue]];
}
//完成
- (void)clickOk {
//存在 使用修改保存方式 不存在 使用新增保存方式
if (self.isTallyExist) {
[self modifyTallySavedWithIdentity:self.tallyIdentity];
} else {
[self addTallySave];
}
} //增加账单保存
- (void)addTallySave {
if ( ![self.typeLab.text isEqualToString:@""] && [self.nValue doubleValue] != 0) {
[self clickAdd];
//存数据
NSManagedObjectContext *managedObjectContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.viewContext; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
//查询有无对应的date 有则使用无则创建
NSFetchRequest *fdate = [TallyDate fetchRequest];
NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]];
fdate.sortDescriptors = sortDescriptors;
NSPredicate *p = [NSPredicate predicateWithFormat:@"date = %@",dateString];
fdate.predicate = p;
NSArray<TallyDate *> *ss = [managedObjectContext executeFetchRequest:fdate error:nil];
TallyDate *date;
if (ss.count > 0) {
date = ss[0];
} else {
date = [[TallyDate alloc] initWithContext:managedObjectContext];
date.date = dateString;
}
//配置数据
Tally *model = [[Tally alloc] initWithContext:managedObjectContext];
NSFetchRequest *ftype = [TallyType fetchRequest];
NSPredicate *ptype = [NSPredicate predicateWithFormat:@"typename = %@",self.typeLab.text];
ftype.predicate = ptype;
NSArray<TallyType *> *sstype = [managedObjectContext executeFetchRequest:ftype error:nil];
TallyType *type = [sstype firstObject];
//给关系赋值
model.typeship = type;
model.dateship = date;
model.identity = [NSString stringWithFormat:@"%@", [model objectID]];
model.timestamp = [NSDate date];
if ([self.typeLab.text isEqualToString:@"工资"]) {
model.income = [self.resutlStr doubleValue];
model.expenses = 0;
} else {
model.expenses = [self.resutlStr doubleValue];
model.income = 0;
}
//存
[managedObjectContext save:nil];
if ([self.delegate respondsToSelector:@selector(tallySaveCompleted)]) {
[self.delegate tallySaveCompleted];
}
} else {
if ([self.delegate respondsToSelector:@selector(tallySaveFailed)]) {
[self.delegate tallySaveFailed];
}
NSLog(@"不存");
}
} //修改账单保存
- (void)modifyTallySavedWithIdentity:(NSString *)identity {
[self clickAdd];
if ([self.resutlStr doubleValue] == 0) {
if ([self.delegate respondsToSelector:@selector(tallySaveFailed)]) {
[self.delegate tallySaveFailed];
}
NSLog(@"不存");
return;
}
self.addBtn.selected = NO;
NSManagedObjectContext *managedObjectContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
//设置账单类型
NSFetchRequest *ftype = [TallyType fetchRequest];
NSPredicate *ptype = [NSPredicate predicateWithFormat:@"typename = %@",self.typeLab.text];
ftype.predicate = ptype;
NSArray<TallyType *> *sstype = [managedObjectContext executeFetchRequest:ftype error:nil];
TallyType *type = [sstype firstObject];
//找出当前账单
NSFetchRequest *fetchRequest = [Tally fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identity = %@", identity];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
//配置当前账单
Tally *tally = [fetchedObjects firstObject];
tally.typeship = type;
if ([self.typeLab.text isEqualToString:@"工资"]) {
tally.income = [self.resutlStr doubleValue];
tally.expenses = 0;
} else {
tally.expenses = [self.resutlStr doubleValue];
tally.income = 0;
}
[managedObjectContext save:nil];
if ([self.delegate respondsToSelector:@selector(tallySaveCompleted)]) {
[self.delegate tallySaveCompleted];
}
} //修改界面传值
- (void)modifyTallyWithIdentity:(NSString *)identity {
self.tallyIdentity = identity;
NSManagedObjectContext *managedObjectContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
NSFetchRequest *fetchRequest = [Tally fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identity = %@", identity];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
Tally *tally = [fetchedObjects firstObject];
self.imageView.image = [UIImage imageNamed:tally.typeship.typeicon];
self.typeLab.text = tally.typeship.typename;
self.nValue = tally.income > 0?[NSString stringWithFormat:@"%@",@(tally.income)]:[NSString stringWithFormat:@"%@",@(tally.expenses)];
self.resultLab.text = [NSString stringWithFormat:@"¥ %.2f",[self.nValue doubleValue]];
self.isTallyExist = YES;
} @end
  • 类型选择界面 自定义cell就不贴出来了 就一个图片和label
#import <UIKit/UIKit.h>
#import "TallyListCell.h"
#import <CoreData/CoreData.h>
#import "TimeTallyDemo+CoreDataModel.h"
#import "AppDelegate.h" @protocol TallyListViewDelegate <NSObject>
//选择对应cell 传递 image title cell的实际位置
- (void)didSelectItem:(UIImage*)cellImage andTitle:(NSString*)title withRectInCollection:(CGRect)itemRect;
//滚动到底
- (void)listScrollToBottom;
@end @interface TallyListView : UICollectionView
@property(nonatomic,strong)id<TallyListViewDelegate> customDelegate;
@end
#import "TallyListView.h"
@interface TallyListView()<UICollectionViewDelegate,UICollectionViewDataSource> @property (nonatomic, strong) NSArray *tallyListArray;
@property (nonatomic, assign) CGFloat offsety; @end
static NSString *cellId = @"tallyListCellID";
@implementation TallyListView //读取plist数据
- (NSArray *)tallyListArray {
if (!_tallyListArray) {
NSMutableArray *res = [NSMutableArray array];
NSString *path = [[NSBundle mainBundle] pathForResource:@"TallyList" ofType:@"plist"];
NSArray *list = [NSArray arrayWithContentsOfFile:path];
for (NSDictionary *dict in list) {
TallyListCellModel *model = [TallyListCellModel tallyListCellModelWithDict:dict];
[res addObject:model];
}
_tallyListArray = [NSArray arrayWithArray:res];
[self writeToSqlite];
}
return _tallyListArray;
} - (void)writeToSqlite { //将类型名字和图片信息写入数据库
NSManagedObjectContext *managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
for (TallyListCellModel *model in self.tallyListArray) { //查询有无对应的type 有则使用无则创建
NSFetchRequest *fdate = [TallyType fetchRequest];
NSPredicate *p = [NSPredicate predicateWithFormat:@"typename = %@",model.tallyCellName];
fdate.predicate = p;
NSArray<TallyType *> *ss = [managedObjectContext executeFetchRequest:fdate error:nil];
if (ss.count == 0) {
TallyType *type = [[TallyType alloc] initWithContext:managedObjectContext];
type.typename = model.tallyCellName;
type.typeicon = model.tallyCellImage;
[managedObjectContext save:nil];
} }
} //初始化
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout {
self = [super initWithFrame:frame collectionViewLayout:layout];
if (self) {
self.delegate = self;
self.dataSource = self;
self.backgroundColor = [UIColor clearColor];
[self registerNib:[UINib nibWithNibName:@"TallyListCell" bundle:nil] forCellWithReuseIdentifier:cellId];
}
return self;
} #pragma mark - UICollectionViewDelegate & UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.tallyListArray.count;
} - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
TallyListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellId forIndexPath:indexPath];
cell.cellModel = self.tallyListArray[indexPath.item];
return cell;
} - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TallyListCell *cell = (TallyListCell*)[collectionView cellForItemAtIndexPath:indexPath];
//cell在collectionView的位置
CGRect cellRect = [collectionView convertRect:cell.frame fromView:collectionView];
//image在cell中的位置
CGRect imgInCellRect = cell.imageView.frame;
CGFloat x = cellRect.origin.x + imgInCellRect.origin.x;
CGFloat y = cellRect.origin.y + imgInCellRect.origin.y + 64 - self.offsety;
//图片在collectionView的位置
CGRect imgRect = CGRectMake(x, y, imgInCellRect.size.width, imgInCellRect.size.height);
//回调
if ([self.customDelegate respondsToSelector:@selector(didSelectItem:andTitle:withRectInCollection:)]){
[self.customDelegate didSelectItem:cell.imageView.image andTitle:cell.imageLab.text withRectInCollection:imgRect];
} } //外部调用 用于修改账单传值
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
self.offsety = scrollView.contentOffset.y;
} - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
CGFloat bottomY = self.contentSize.height - self.frame.size.height;
if (scrollView.contentOffset.y >= bottomY) {
NSLog(@"计算器下去");
if ([self.customDelegate respondsToSelector:@selector(listScrollToBottom)]) {
[self.customDelegate listScrollToBottom];
}
}
}
@end

主页面界面

  • model
  • 用于传递给账单界面的数据模型
  • view
    • 时间线绘图
  • controller
    • 处理时间线视图的删改查

主界面部分代码

  • model
#import <Foundation/Foundation.h>

typedef enum : NSUInteger {
TallyMoneyTypeIn = 0,
TallyMoneyTypeOut, } TallyMoneyType; @interface TimeLineModel : NSObject
@property(nonatomic,copy)NSString *tallyIconName;
@property(nonatomic,assign)double tallyMoney;
@property(nonatomic,assign)TallyMoneyType tallyMoneyType;
@property(nonatomic,copy)NSString *tallyDate;
@property(nonatomic,copy)NSString *tallyType;
@property(nonatomic,copy)NSString *identity;
@property(nonatomic,assign)double income;
@property(nonatomic,assign)double expense; @end
  • 时间线视图

    这里用runtime方法为uibutton分类给时间线上的btn添加了两个属性

    keyWithBtn :用于存储日期

    panelBtnType :用于存储按钮是修改还是删除
typedef enum : NSUInteger {
PanelViewBtnTypeLeft = 0,
PanelViewBtnTypeRight,
} PanelViewBtnType;
//使用runtime 给uibutton扩展 属性
@interface UIButton (BtnWithKey)
@property(nonatomic,copy)NSString *keyWithBtn;
@property(nonatomic,assign)PanelViewBtnType panelBtnType;
@end
static const void *kKeyWithBtn = @"keyWithBtn";
static const void *kPanelBtnType = @"panelBtnType";
@implementation UIButton (BtnWithKey) - (NSString *)keyWithBtn {
return objc_getAssociatedObject(self, kKeyWithBtn); }
- (void)setKeyWithBtn:(NSString *)keyWithBtn {
objc_setAssociatedObject(self, kKeyWithBtn, keyWithBtn, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} - (PanelViewBtnType)panelBtnType {
return [objc_getAssociatedObject(self, kPanelBtnType) intValue];
} - (void)setPanelBtnType:(PanelViewBtnType)panelBtnType {
objc_setAssociatedObject(self, kPanelBtnType, [NSNumber numberWithUnsignedInteger:panelBtnType], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#import "TimeLineView.h"

@interface TimeLineView()
@property(nonatomic,strong)UIView *panelView; //删除、修改 面板
@end @implementation TimeLineView - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
}
return self;
} - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext();
int keyIndex = 0;
CGFloat aDateAllLine = 0;
for (NSString *key in self.timeLineModelsDict.allKeys) {
//读取字典对应key的数组
NSArray<TimeLineModel*> *modelArray = self.timeLineModelsDict[key];
//画线画日期
CGRect dateRect = CGRectMake(self.center.x-kDateWidth/2, aDateAllLine, kDateWidth, kDateWidth);
CGContextAddEllipseInRect(context, dateRect);
CGContextSetFillColorWithColor(context, [UIColor grayColor].CGColor);
CGContextFillPath(context);
CGContextStrokePath(context);
CGRect dateLabRect = CGRectMake(0, aDateAllLine, self.frame.size.width/2-kBtnWidth, kBtnWidth/2);
//日期lab
UILabel *dateLab = [[UILabel alloc] initWithFrame:dateLabRect];
dateLab.textAlignment = NSTextAlignmentRight;
dateLab.text = key;
[dateLab setTextColor:[UIColor blueColor]];
[self addSubview:dateLab]; for (int i = 0 ; i < modelArray.count; i++) {
//画竖线
CGFloat start = aDateAllLine + kDateWidth + i * (kLineHeight+kBtnWidth);
CGFloat end = aDateAllLine + kDateWidth+kLineHeight + i * (kLineHeight+kBtnWidth);
CGContextMoveToPoint(context, self.center.x, start);
CGContextAddLineToPoint(context, self.center.x, end);
CGContextSetLineWidth(context, kLineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
CGContextStrokePath(context); //收支类型btn
CGRect btnRect = CGRectMake(self.center.x-kBtnWidth/2, end, kBtnWidth, kBtnWidth);
UIButton *btn = [[UIButton alloc] initWithFrame:btnRect];
btn.tag = i;
btn.keyWithBtn = key;
[btn setImage:[UIImage imageNamed:modelArray[i].tallyIconName] forState:UIControlStateNormal];
btn.layer.masksToBounds = YES;
[btn addTarget:self action:@selector(clickTallyTypeBtn:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn]; //收支情况
CGFloat labX = modelArray[i].tallyMoneyType == TallyMoneyTypeIn ? 0:self.center.x + kBtnWidth;
CGFloat labY = btnRect.origin.y;
CGFloat labWidth = self.frame.size.width/2 - kBtnWidth ;
CGFloat labHeight = btnRect.size.height;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(labX, labY, labWidth, labHeight)];
label.textAlignment = modelArray[i].tallyMoneyType == TallyMoneyTypeIn ? NSTextAlignmentRight:NSTextAlignmentLeft;
label.text = [NSString stringWithFormat:@"%@ %0.2f",modelArray[i].tallyType,modelArray[i].tallyMoney];
[self addSubview:label]; //最后一条线 最后一条账单不画此线
if (keyIndex < self.timeLineModelsDict.allKeys.count) {
CGFloat lastStart = aDateAllLine;
CGFloat lastEnd = kLineHeight;
CGContextMoveToPoint(context, self.center.x, lastStart);
CGContextAddLineToPoint(context, self.center.x, lastEnd);
CGContextSetLineWidth(context, kLineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
CGContextStrokePath(context);
}
} //当前时间线总长
aDateAllLine = aDateAllLine + kDateWidth + (kBtnWidth+kLineHeight)*modelArray.count + kLineHeight;
keyIndex++; } } //刷新view时清空UI
- (void)setNeedsDisplay {
[super setNeedsDisplay];
for (UIView *view in self.subviews) {
[view removeFromSuperview];
}
} //点击账单类型按钮
- (void)clickTallyTypeBtn:(UIButton *)btn {
NSLog(@"%@",btn.keyWithBtn);
//清空控制板
if (self.panelView) {
[self.panelView removeFromSuperview];
}
//控制板出现
self.panelView = [[UIView alloc] initWithFrame:CGRectMake(0, btn.frame.origin.y, self.frame.size.width, btn.frame.size.height)];
self.panelView.backgroundColor = [UIColor whiteColor];
self.panelView.userInteractionEnabled = YES;
[self addSubview:self.panelView];
//左按钮 删除
...
leftBtn.panelBtnType = PanelViewBtnTypeLeft;
leftBtn.tag = btn.tag;
leftBtn.keyWithBtn = btn.keyWithBtn;
[leftBtn addTarget:self action:@selector(deleteCurrentTally:) forControlEvents:UIControlEventTouchUpInside]; //右按钮 修改
...
rightBtn.panelBtnType = PanelViewBtnTypeRight;
rightBtn.tag = btn.tag;
rightBtn.keyWithBtn = btn.keyWithBtn;
[rightBtn addTarget:self action:@selector(modifyCurrentTally:) forControlEvents:UIControlEventTouchUpInside];
//中间按钮 收回panelview
...
[middleBtn addTarget:self action:@selector(clickMiddleBtn) forControlEvents:UIControlEventTouchUpInside];
} //btn出现时动画
- (void)btnShowAnimation:(UIButton*)btn{
//layer动画 左右分开
} //点击中间按钮 控制板收回
- (void)clickMiddleBtn{
for (UIButton *btn in self.panelView.subviews) {
[self btnDismissAnimation:btn];
}
[NSTimer scheduledTimerWithTimeInterval:0.5 repeats:NO block:^(NSTimer * _Nonnull timer) {
[self.panelView removeFromSuperview];
}]; } //btn消失时动画
- (void)btnDismissAnimation:(UIButton*)btn{
//两边收回
} //删除当前账单 回调给controller处理
- (void)deleteCurrentTally:(UIButton*)btn {
if ([self.delegate respondsToSelector:@selector(willChangeValueForKey:)]) {
NSArray<TimeLineModel*>*array = self.timeLineModelsDict[btn.keyWithBtn];
[self.delegate willDeleteCurrentTallyWithIdentity:array[btn.tag].identity];
}
} //修改当前账单 回调给controller处理
- (void)modifyCurrentTally:(UIButton*)btn {
if ([self.delegate respondsToSelector:@selector(willModifyCurrentTallyWithIdentity:)]) {
NSArray<TimeLineModel*>*array = self.timeLineModelsDict[btn.keyWithBtn];
[self.delegate willModifyCurrentTallyWithIdentity:array[btn.tag].identity];
}
}
@end
  • controller
#import "ViewController.h"
#import "AddTallyViewController.h"
#import "TimeLineView.h"
@interface ViewController ()<TimeLineViewDelegate>
@property(nonatomic,strong)UIScrollView *scrollView;
@property(nonatomic,strong)NSDictionary *timeLineModelsDict;
@property(nonatomic,assign)CGFloat allDateAllLine;
@property (weak, nonatomic) IBOutlet UILabel *incomLab;
@property (weak, nonatomic) IBOutlet UILabel *expenseLab; @end
@implementation ViewController - (UIScrollView *)scrollView {
if (!_scrollView) {
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 64+80, self.view.frame.size.width, self.view.frame.size.height-64-80)];
_scrollView.backgroundColor = [UIColor whiteColor];
}
return _scrollView;
} - (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",NSHomeDirectory());
self.title = @"我的账本";
[self.view addSubview:self.scrollView];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} //增加一条账单
- (IBAction)clickAddTally:(id)sender {
AddTallyViewController *addVC = [[AddTallyViewController alloc] init];
[self.navigationController pushViewController:addVC animated:YES];
} //出现时 刷新整个时间线
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"will");
[self showTimeLineView]; } - (void)showTimeLineView {
//先读取数据库中内容 封装成字典
[self readSqliteData]; //移除上一个timelineView
for (UIView *view in self.scrollView.subviews) {
if (view.tag == 1990) {
[view removeFromSuperview];
}
} //计算总收入 和 总支出
double incomeTotal = 0;
double expenseTotal = 0;
self.allDateAllLine = 0;
//计算时间线长度
for (NSString *key in self.timeLineModelsDict.allKeys) {
NSArray<TimeLineModel*> *modelArray = self.timeLineModelsDict[key];
//一天的时间线总长
self.allDateAllLine = self.allDateAllLine + kDateWidth + (kBtnWidth+kLineHeight)*modelArray.count + kLineHeight;
for (TimeLineModel *model in modelArray) {
incomeTotal = incomeTotal + model.income;
expenseTotal = expenseTotal + model.expense;
}
} self.incomLab.text = [NSString stringWithFormat:@"%.2f",incomeTotal];
self.expenseLab.text = [NSString stringWithFormat:@"%.2f",expenseTotal]; //设置时间线视图timelineview
CGRect rect = CGRectMake(0, 0, self.view.frame.size.width, self.allDateAllLine);
TimeLineView *view = [[TimeLineView alloc] initWithFrame:rect];
view.tag = 1990;
view.delegate = self;
view.backgroundColor = [UIColor whiteColor];
view.timeLineModelsDict = self.timeLineModelsDict;
[self.scrollView addSubview:view];
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, self.allDateAllLine);
//滚动到顶端
[self.scrollView setContentOffset:CGPointZero animated:YES]; } //读取数据库中的数据 以字典的形式 key:@"日期" object:[账单信息]
- (void)readSqliteData{
NSManagedObjectContext *managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
self.timeLineModelsDict = nil; //先查询日期 遍历日期表
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"TallyDate" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray<TallyDate*> *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//再查询该日期下的tally表
for (TallyDate *date in fetchedObjects) {
NSString *key = date.date;
NSFetchRequest *fetchRequest2 = [[NSFetchRequest alloc] init];
NSEntityDescription *entity2 = [NSEntityDescription entityForName:@"Tally" inManagedObjectContext:managedObjectContext];
[fetchRequest2 setEntity:entity2];
//在tally表中 筛选 为该日期的tally 并逆序排列
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"dateship.date = %@",key];
[fetchRequest2 setPredicate:predicate];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:NO];
[fetchRequest2 setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor2, nil]];
NSError *error = nil;
NSArray<Tally*> *fetchedObjects2 = [managedObjectContext executeFetchRequest:fetchRequest2 error:&error];
NSMutableArray *array = [NSMutableArray array];
//遍历 tally表 将表中的每个结果保存下来
for (Tally *tally in fetchedObjects2) {
TimeLineModel *model = [[TimeLineModel alloc] init];
model.tallyDate = tally.dateship.date;
model.tallyIconName = tally.typeship.typeicon;
model.tallyMoney = tally.income > 0 ? tally.income:tally.expenses;
model.tallyMoneyType = tally.income > 0 ? TallyMoneyTypeIn:TallyMoneyTypeOut;
model.tallyType = tally.typeship.typename;
model.identity = tally.identity;
model.income = tally.income;
model.expense = tally.expenses;
[array addObject:model];
}
[dict setObject:array forKey:key];
}
self.timeLineModelsDict = dict;
} //删除前的确认
- (void)willDeleteCurrentTallyWithIdentity:(NSString*)identity { UIAlertController *alertVC =[UIAlertController alertControllerWithTitle:@"提示" message:@"确认删除" preferredStyle:UIAlertControllerStyleAlert ];
[alertVC addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//从数据库中删除 Tally表中对应identity字段行
NSManagedObjectContext *managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
NSFetchRequest *fetchRequest = [Tally fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identity = %@", identity];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[managedObjectContext deleteObject:[fetchedObjects firstObject]];
[managedObjectContext save:&error];
//删除完成后 刷新视图
[self showTimeLineView]; }]];
[alertVC addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertVC animated:YES completion:nil]; } //TimeLineViewDelegate delegate 修改前的准备
- (void)willModifyCurrentTallyWithIdentity:(NSString*)identity {
AddTallyViewController *addVC = [[AddTallyViewController alloc] init];
[addVC selectTallyWithIdentity:identity];
[self.navigationController pushViewController:addVC animated:YES]; }
@end

5.结束

由于coredata增删改查时的代码量实在是太大,我们可以优化一下,将数据库操作全部放到一个类中,这样代码逻辑会更清晰一点,可读性更强。

也是也到这里才想到数据库封装。所以刚刚去改了下。

所以上面的代码都包括冗长的coreData操作

创建一个 数据库操作的单例

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "TimeTallyDemo+CoreDataModel.h"
#import "AppDelegate.h"
#import "TimeLineModel.h"
@interface CoreDataOperations : NSObject
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
//单例
+ (instancetype)sharedInstance;
//从数据库中删除 Tally表中对应identity字段行
- (void)deleteTally:(Tally*)object; //保存
- (void)saveTally; //读取对应字段
- (Tally*)getTallyWithIdentity:(NSString *)identity; //获取对应类型
- (TallyType*)getTallyTypeWithTypeName:(NSString*)typeName; //读取数据库中的数据 以字典的形式 key:@"日期" object:[账单信息]
- (NSDictionary*)getAllDataWithDict;
@end
#import "CoreDataOperations.h"
@interface CoreDataOperations()
@end @implementation CoreDataOperations static CoreDataOperations *instance = nil; + (instancetype)sharedInstance
{
return [[CoreDataOperations alloc] init];
} + (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
} - (instancetype)init
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super init];
if (instance) {
instance.managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
}
});
return instance;
} //从数据库中删除 Tally表中某一数据
- (void)deleteTally:(Tally*)object {
[self.managedObjectContext deleteObject:object];
} //保存
- (void)saveTally {
[self.managedObjectContext save:nil];
} //读取对应字段
- (Tally*)getTallyWithIdentity:(NSString *)identity {
//返回对应账单
} //获取对应类型
- (TallyType*)getTallyTypeWithTypeName:(NSString*)typeName {
//返回对应账单类型
} //读取数据库中的数据 以字典的形式 key:@"日期" object:[账单信息]
- (NSDictionary*)getAllDataWithDict{
//遍历查询
return dict;
} @end

Demo地址

欢迎加星关注

https://github.com/gongxiaokai/TimeTallyDemo

简书地址

http://www.jianshu.com/u/7897b0bd4a55

iOS开发实战-时光记账Demo 本地数据库版的更多相关文章

  1. iOS开发实战-时光记账Demo 网络版

    之前写了一个本地数据库版本 戳这里 现在这个就是增加了后台 登录注册页面以及web的上传记录展示页面 含有少量php有兴趣可以看下 另外demo中包括数据库操作.json.网络请求等都没有用到第三方库 ...

  2. .Net Core 跨平台开发实战-服务器缓存:本地缓存、分布式缓存、自定义缓存

    .Net Core 跨平台开发实战-服务器缓存:本地缓存.分布式缓存.自定义缓存 1.概述 系统性能优化的第一步就是使用缓存!什么是缓存?缓存是一种效果,就是把数据结果存在某个介质中,下次直接重用.根 ...

  3. iOS开发——实战OC篇&环境搭建之Xib(玩转UINavigationController与UITabBarController)

    iOS开发——实战OC篇&环境搭建之Xib(玩转UINavigationController与UITabBarController)   前面我们介绍了StoryBoard这个新技术,和纯技术 ...

  4. iOS开发——实战OC篇&环境搭建之纯代码(玩转UINavigationController与UITabBarController)

    iOS开发——实战OC篇&环境搭建之纯代码(玩转UINavigationController与UITabBarController)   这里我们就直接上实例: 一:新建一个项目singleV ...

  5. IOS开发-UI学习-sqlite数据库的操作

    IOS开发-UI学习-sqlite数据库的操作 sqlite是一个轻量级的数据库,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了,而且它的处理速度比Mysql.PostgreSQL这 ...

  6. Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序

    Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序 C#原本是用来编写Windows以及Windows Phone的应用程序.自从Xamarin问世后.C#的作用就发生了非常大的变化 ...

  7. 《iOS开发实战 从入门到上架App Store(第2版)》书籍目录

    第1章 开发准备 1.1 iOS 10新特性简述 1.1.1 新增触觉反馈编程接口 1.1.2 SiriKit框架的开放 1.1.3 引入Messages App 1.1.4 通知框架的整合与扩展 1 ...

  8. iOS开发实战-上架AppStore 通过内购和广告获得收益

    写在前面 由于一些原因需要离职,准备重回大上海 忽然发现手头上也没什么独立App,那就随便写个放到AppStore上吧,凑个数吧.哈哈哈. 这个App是无聊找配色的时候看到的一套图 正好春节在家没什么 ...

  9. iOS开发——实战OC篇&环境搭建之StoryBoard(玩转UINavigationController与UITabBarController)

      环境搭建之StoryBoard(玩转UINavigationController与UITabBarController)   研究了这么就IOS开发,都没有所处一个像样或者自己忙一点的项目.最近自 ...

随机推荐

  1. 整理一些提高C#编程性能的技巧

    1.使用StringBuilder代替使用string 连接符 "+" 说明:String类对象是不可变的(只读),一旦创建该对象,就不能修改该对象的值. 对象String对象的重 ...

  2. sleep()方法和wait()方法之间有什么差异?

    sleep()方法用被用于让程序暂停指定的时间,而wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者nofifyAl()方法 主要的区别是,wait()释放 ...

  3. SQL Server各个版本的区别

    SQLEXPR_x64_CHS.exe,标准SQL Server Express edition,只有数据库引擎,甚至连图形管理界面都没有.SQLEXPRWT_x64_CHS.exe,多了一个图形管理 ...

  4. 中学之Vim实践课程

    今天转发娄老师的一篇VIM编辑器的文章,很赞哦!(值得收藏)文后的参考资料记得看一看,也很棒!                               原文地址:http://www.cnblog ...

  5. 前端UI组件复用工具

    "懒"是第一生产力. 代码复用总是程序员喜闻乐见的,前端组件化的最终目的就是复用,今天我们就将深入探讨如何实现UI组件的复用. 通常我们所说的组件往往是包含业务逻辑的前端组件,而这 ...

  6. hibernate 返回对象指定属性,需要返回的列,可以直接返回 对象属性

    // hibernate 返回对象指定属性,需要返回的列,可以直接返回 对象属性 @Override public TeamPlan getTeamPlanByBaoMingId(String bao ...

  7. Tomcat中部署web应用 ---- Dubbo服务消费者Web应用war包的部署

    样例视频:http://www.roncoo.com/course/view/f614343765bc4aac8597c6d8b38f06fd IP: 192.168.2.61 部署容器:apache ...

  8. Hibernate缓存和懒加载的坑你知道多少?这5个简单问题回答不上来就不敢说会用hibernate

    问题1:session.flush()调用之后,懒加载还生效吗? 如果不生效,那是抛异常还是没有任何反应,或者直接返回null? 答案:生效.可以理解为在同一个session当中,懒加载只会执行一次. ...

  9. 禁用自动映射的 Exchange 邮箱

    客户最近询问他们无法从用户的Outlook配置文件中删除邮箱.在这种情况下,它们是共享邮箱,并出现在Outlook的左窗格中.但原因和解决方法同样适用于用户邮箱.并且 无论用户邮箱在本地 Exchan ...

  10. MaintainableCSS 《可维护性 CSS》 --- ID 篇

    ID 从语法上讲,当只有一个实例时,我们应该使用一个ID.当有多个时,我们应该使用一个 class. 但是,ID 作用的优先级高于 class ,在我们想覆盖一个样式的时候,这就会导致问题. 为了演示 ...