美文网首页iOSer 的自我修养iOS开发iOS学习笔记
iOS开发实战-时光记账Demo 本地数据库版

iOS开发实战-时光记账Demo 本地数据库版

作者: gwk_iOS | 来源:发表于2017-01-09 12:31 被阅读1686次

    写在前面
    弄了下个人站...防止内容再次被锁定...所有东西都在这里面
    welcome~
    个人博客

    现在记账APP也是用途比较广泛
    自己写了个简单的demo 欢迎指正

    效果

    效果.gif

    分析

    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

    相关文章

      网友评论

      • 一三六二:如果要转账的话怎么处理呢?
      • bddf0272dd7e:一运行就崩溃 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'TallyDate''根本没发玩啊?????
      • JsonXu:想问下思维导图是用的哪个软件
        gwk_iOS:@丶ITang mindNode
      • Helong:demo缺东西,是不是分类都缺失了?
        gwk_iOS:@Helong 这是coredata的bug 多clean 几次 大退xcode 就行了
        Helong:@gwk_ios #import "TimeTallyDemo+CoreDataModel.h"找不到,Tally没有
        gwk_iOS:github上是完整的,没有什么缺失,你是碰到什么错误了?
      • 60343a0ad510:试试 Realm 吧。
        gwk_iOS:恩 感谢推荐。Reamlm也有了解过,CoreData的语法实在是太烦了。本文也主要是用来熟悉coreData
      • ZhangCc_:代码很规范,清晰易懂,注释详细,一看就是个老司机,非常好~
      • ZhangCc_:不错不错
      • 042a0e1be73f:谢谢分享!

      本文标题:iOS开发实战-时光记账Demo 本地数据库版

      本文链接:https://www.haomeiwen.com/subject/rowcbttx.html