iOS開發(fā)實戰(zhàn)-時光記賬Demo 本地數(shù)據(jù)庫版

寫在前面
弄了下個人站...防止內(nèi)容再次被鎖定...所有東西都在這里面
welcome~
個人博客

現(xiàn)在記賬APP也是用途比較廣泛
自己寫了個簡單的demo 歡迎指正

效果

效果.gif

分析

1.思維推導(dǎo)

首先簡單的做了下思維推導(dǎo)


思維推導(dǎo)

2.文件結(jié)構(gòu)

大致框架想好后就可以著手開始準(zhǔn)備了

  • 數(shù)據(jù)庫管理:coreData
  • 視圖管理:navigationcontroller
    暫時沒有使用cocoapods導(dǎo)入第三方的數(shù)據(jù)庫管理框架
    簡單的coreData完全可以勝任
    說白了就兩個頁面 主界面 和 記賬界面

這是完成時的文件結(jié)構(gòu)


文件結(jié)構(gòu)

3.數(shù)據(jù)庫設(shè)計

  • Tally 賬單表
  • identity :String 唯一標(biāo)識
  • expenses :double 支出
  • income :double 收入
  • timestamp :date 時間戳
  • 關(guān)系
    • 與TallyDate 日期表:1V1
    • 與TallyType 類型表:1V1


      賬單表
  • TallyDate 日期表
  • date :string 日期
  • 關(guān)系
    -與Tally 賬單表:1VN


    日期表
  • TallyType 類型表

  • typename :string 類型名
  • typeicon :string 類型圖片標(biāo)
  • 關(guān)系
    -與Tally 賬單表:1VN


    類型表

4.頁面編寫

增加賬單頁面

由于主頁只是一個展示的時光軸界面,UIScrollView加幾個按鈕就能完成,需要讀取數(shù)據(jù)庫內(nèi)容画舌,所以我們先把內(nèi)頁-增加賬單 完成滤钱。

  • view
  • UICollectionView展示賬單類型
  • 自定義View計算器界面計算存儲結(jié)果
  • model
    • UICollectionViewCell模型 使用了plist和KVC轉(zhuǎn)字典
  • controller
    • 負(fù)責(zé)添加 兩個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;
//回調(diào)image在整個view中的位置
@property(nonatomic,copy)PositionInViewBlock positionInViewBlock;
//修改賬單界面進(jìn)入時傳入?yún)?shù)
- (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;                 //當(dāng)前輸入值
@property (nonatomic,copy)NSString *resutlStr;              //結(jié)果值
@property (nonatomic,strong)UILabel *resultLab;             //結(jié)果欄
@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];
    //回調(diào)實際位置
    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;
}

//結(jié)果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;
}

//普通數(shù)字btn
- (void)loadBtn {
    // 1 - 9 btn
    ...
    [btn addTarget:self action:@selector(clickNumber:) forControlEvents:UIControlEventTouchUpInside];
    
    //0 btn
    ...
    [zeroBtn addTarget:self action:@selector(clickNumber:) forControlEvents:UIControlEventTouchUpInside];
    
    //小數(shù)點 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];

}

//點擊數(shù)字按鍵
- (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];
  
    
    //保留小數(shù)點后兩位
    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)];
        }
        
        //總位數(shù)不超過9 處理小數(shù)部分
        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 {
        //總位數(shù)不超過9 整數(shù)部分
        if (self.nValue.length > kMaxCalCount) {
            self.nValue = [NSString stringWithFormat:@"%0.2f",[self.nValue doubleValue]];
            self.nValue = [self.nValue substringToIndex:kMaxCalCount];
        }
    }

    //顯示數(shù)字
    self.resultLab.text = [NSString stringWithFormat:@"¥ %.2f",[self.nValue doubleValue]];
    self.addBtn.selected = NO;
    
    NSLog(@"new = %@",self.nValue);
}

//單擊加號
- (void)clickAdd {
    //顯示結(jié)果  點擊后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];
        //存數(shù)據(jù)
        NSManagedObjectContext *managedObjectContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;

        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
        [dateFormatter setDateFormat:@"yyyy-MM-dd"];
        NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
        //查詢有無對應(yīng)的date 有則使用無則創(chuàng)建
        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;
        }
        //配置數(shù)據(jù)
        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];
        //給關(guān)系賦值
        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;
    //設(shè)置賬單類型
    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];
    //找出當(dāng)前賬單
    NSFetchRequest *fetchRequest = [Tally fetchRequest];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"identity = %@", identity];
    [fetchRequest setPredicate:predicate];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
    //配置當(dāng)前賬單
    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];
    }
}


//修改界面?zhèn)髦?- (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>
//選擇對應(yīng)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數(shù)據(jù)
- (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 {
    
    //將類型名字和圖片信息寫入數(shù)據(jù)庫
    NSManagedObjectContext *managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
    for (TallyListCellModel *model in self.tallyListArray) {
        
        //查詢有無對應(yīng)的type 有則使用無則創(chuàng)建
        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);
    //回調(diào)
    if ([self.customDelegate respondsToSelector:@selector(didSelectItem:andTitle:withRectInCollection:)]){
        [self.customDelegate didSelectItem:cell.imageView.image andTitle:cell.imageLab.text withRectInCollection:imgRect];
    }
  
}

//外部調(diào)用 用于修改賬單傳值
- (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
  • 用于傳遞給賬單界面的數(shù)據(jù)模型
  • 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擴(kuò)展 屬性
@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) {
        //讀取字典對應(yīng)key的數(shù)組
        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);
            }
        }
        
        //當(dāng)前時間線總長
        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];
    }
    //控制板出現(xiàn)
    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出現(xiàn)時動畫
- (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{
    //兩邊收回
}

//刪除當(dāng)前賬單  回調(diào)給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];
    }
}

//修改當(dāng)前賬單  回調(diào)給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];
}

//出現(xiàn)時 刷新整個時間線
- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"will");
    [self showTimeLineView];
 
}

- (void)showTimeLineView {
    //先讀取數(shù)據(jù)庫中內(nèi)容 封裝成字典
    [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];
    
    //設(shè)置時間線視圖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];
    
    
    
}


//讀取數(shù)據(jù)庫中的數(shù)據(jù)  以字典的形式 key:@"日期" object:[賬單信息]
- (void)readSqliteData{
    NSManagedObjectContext *managedObjectContext = ((AppDelegate*)[UIApplication sharedApplication].delegate).persistentContainer.viewContext;
    self.timeLineModelsDict = nil;
    
    //先查詢?nèi)掌?遍歷日期表
    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表 將表中的每個結(jié)果保存下來
        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;
}

//刪除前的確認(rèn)
- (void)willDeleteCurrentTallyWithIdentity:(NSString*)identity {
    
    UIAlertController *alertVC =[UIAlertController alertControllerWithTitle:@"提示" message:@"確認(rèn)刪除" preferredStyle:UIAlertControllerStyleAlert ];
    [alertVC addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        //從數(shù)據(jù)庫中刪除 Tally表中對應(yīng)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 修改前的準(zhǔn)備
- (void)willModifyCurrentTallyWithIdentity:(NSString*)identity {
    AddTallyViewController *addVC = [[AddTallyViewController alloc] init];
    [addVC selectTallyWithIdentity:identity];
    [self.navigationController pushViewController:addVC animated:YES];

}
@end

5.結(jié)束

由于coredata增刪改查時的代碼量實在是太大略号,我們可以優(yōu)化一下官觅,將數(shù)據(jù)庫操作全部放到一個類中纵菌,這樣代碼邏輯會更清晰一點,可讀性更強(qiáng)休涤。
也是也到這里才想到數(shù)據(jù)庫封裝咱圆。所以剛剛?cè)ジ牧讼隆?br> 所以上面的代碼都包括冗長的coreData操作

創(chuàng)建一個 數(shù)據(jù)庫操作的單例

#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;
//從數(shù)據(jù)庫中刪除 Tally表中對應(yīng)identity字段行
- (void)deleteTally:(Tally*)object;

//保存
- (void)saveTally;

//讀取對應(yīng)字段
- (Tally*)getTallyWithIdentity:(NSString *)identity;

//獲取對應(yīng)類型
- (TallyType*)getTallyTypeWithTypeName:(NSString*)typeName;

//讀取數(shù)據(jù)庫中的數(shù)據(jù)  以字典的形式 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;
}

//從數(shù)據(jù)庫中刪除 Tally表中某一數(shù)據(jù)
- (void)deleteTally:(Tally*)object {
    [self.managedObjectContext deleteObject:object];
}

//保存
- (void)saveTally {
    [self.managedObjectContext save:nil];
}

//讀取對應(yīng)字段
- (Tally*)getTallyWithIdentity:(NSString *)identity {
    //返回對應(yīng)賬單
}

//獲取對應(yīng)類型
- (TallyType*)getTallyTypeWithTypeName:(NSString*)typeName {
    //返回對應(yīng)賬單類型
}

//讀取數(shù)據(jù)庫中的數(shù)據(jù)  以字典的形式 key:@"日期" object:[賬單信息]
- (NSDictionary*)getAllDataWithDict{
        //遍歷查詢
    return dict;
}

@end

Demo地址

歡迎加星關(guān)注
https://github.com/gongxiaokai/TimeTallyDemo
簡書地址
http://www.reibang.com/u/7897b0bd4a55

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笛辟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子序苏,更是在濱河造成了極大的恐慌手幢,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忱详,死亡現(xiàn)場離奇詭異围来,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匈睁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門监透,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人航唆,你說我怎么就攤上這事胀蛮。” “怎么了糯钙?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵醇滥,是天一觀的道長。 經(jīng)常有香客問我超营,道長,這世上最難降的妖魔是什么阅虫? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任演闭,我火速辦了婚禮,結(jié)果婚禮上颓帝,老公的妹妹穿的比我還像新娘米碰。我一直安慰自己,他們只是感情好购城,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布吕座。 她就那樣靜靜地躺著,像睡著了一般瘪板。 火紅的嫁衣襯著肌膚如雪吴趴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天侮攀,我揣著相機(jī)與錄音锣枝,去河邊找鬼。 笑死兰英,一個胖子當(dāng)著我的面吹牛撇叁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畦贸,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼陨闹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趋厉,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寨闹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后觅廓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鼻忠,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年杈绸,在試婚紗的時候發(fā)現(xiàn)自己被綠了帖蔓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞳脓,死狀恐怖塑娇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劫侧,我是刑警寧澤埋酬,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站烧栋,受9級特大地震影響写妥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜审姓,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一珍特、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魔吐,春花似錦扎筒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辞色,卻和暖如春骨宠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背相满。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工诱篷, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雳灵。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓棕所,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悯辙。 傳聞我的和親對象是個殘疾皇子琳省,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容