iOS Demo 详解(二) 购物车
demo下载地址:
https://git.oschina.net/wwwzz/ShoppingCartDemo
可以照着model看,思路清楚一点
先看效果图

先来分析下,要实现的功能:
1.页面内容展现
这里因为找不到合适数据,文字内容是从网易新闻获取的,图片是本地的,在加一些按钮.之所以获取网易的文字展现是为了练习解决下滑动内容混乱的问题
2.加,减,选中按钮, 功能实现
3.总价格展示
4.全选按钮实现,没有选中的加1选中的不变,再次点击全部不选
5.选中收藏,把数据存到本地数据库
这里强调一下,购物车数据还是应该存到后台,因为要考虑切换设备的情况,这里存到本地仅仅是为了练习数据库存图片
6.其他功能
第一个比较简单就不讲解了,具体看demo吧
第二个功能:
先在 cell 设置代理

.m 传递 tag 用以区分是哪个cell 上的控件


看代理实现前先写model

.m 图片是本地的所以给了固定值,价格是为了好算

</br>
控制器的宏定义 属性 和代理

</br>
数据源中设置代理,并把indexPath.row 当做tag值传递,区分cell,根据model中的BOOL值判断选择状态, 使用KVC修改占位符颜色
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
LXTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"111"];
if(cell == nil){
cell = [[LXTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"111"];
}
cell.delegate = self;
[cell.numberTF addTarget:self action:@selector(textFieldDidChange:) forControlEvents:(UIControlEventEditingChanged)];
// cell 上 传递 tag
cell.selectBtn.tag = indexPath.row;
cell.rightButton.tag = indexPath.row;
cell.leftButton.tag = indexPath.row;
LXModel *model = self.MyArray[indexPath.row];
// 获取 网易新闻字符串
[cell setCellModel:model];
cell.selectBtn.selected = model.isBtnSelect;
cell.PictureImage.image = [UIImage imageWithData:model.picture];
if (cell.selectBtn.selected == YES){
[cell.selectBtn setImage:[UIImage imageNamed:@"选中"] forState:(UIControlStateNormal)];
}else if(cell.selectBtn.selected == NO){
[cell.selectBtn setImage:[UIImage imageNamed:@"未选中"] forState:(UIControlStateNormal)];
}
if (model.number == nil || model.number <= 0) {
cell.numberTF.text = @"";
cell.numberTF.placeholder = @"0";
}else if(model.number != nil){
cell.numberTF.text = model.number;
}
// 价格
cell.priceLabel.text = [NSString stringWithFormat:@"%ld",model.DanGeJiaGe];
cell.numberTF.delegate = self;
// 使用KVC 修改占位符 颜色
[cell.numberTF setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];
return cell;
}
</br>
</br>
cell 选中代理方法
self.MyArray 里面装的是请求下来的网易新闻字符串
两个model是用来互相替换的,这样可以解决滑动时数据不对称
// 选中 代理
- (void)selectAtIndex:(NSInteger)row{
LXModel *model = self.MyArray[row];
LXModel *tempModel = [[LXModel alloc]init];
tempModel.alias = model.alias;
tempModel.picture = model.picture;
if (model.isBtnSelect == YES){
NSInteger tempNumber = [tempModel.number integerValue];
// 取消选中状态 直接清0
if (tempNumber == 0){
tempModel.number = @"";
}
tempModel.isBtnSelect = NO;
} else if (model.isBtnSelect == NO){
tempModel.isBtnSelect = YES;
NSInteger tempNumber = [tempModel.number integerValue];
// 选中加一
tempNumber++;
tempModel.number = [NSString stringWithFormat:@"%ld",tempNumber];
}
//替换
[self.MyArray replaceObjectAtIndex:row withObject:tempModel];
// 计算总和
_zongHeLabel.text = [self ZongHeJiSuan:self.MyArray];
[self.mainTableView reloadData];
}
</br>
加减 一样 减号注意别小于0
// 添加的 btn 代理
- (void)addButtonDelegate:(NSInteger)row{
LXModel *model = self.MyArray[row];
LXModel *tempModel = [[LXModel alloc]init];
tempModel.alias = model.alias;
tempModel.number = model.number;
//选中按钮 选中状态
tempModel.isBtnSelect = YES;
NSInteger tempNumber = [tempModel.number integerValue];
tempNumber++;
tempModel.number = [NSString stringWithFormat:@"%ld",tempNumber];
// 替换
[self.MyArray replaceObjectAtIndex:row withObject:tempModel];
//计算总价格
_zongHeLabel.text = [self ZongHeJiSuan:self.MyArray];
[self.mainTableView reloadData];
}
// 减号 代理 方法
- (void)jianButtonDelegate:(NSInteger)row{
LXModel *model = self.MyArray[row];
LXModel *tempModel = [[LXModel alloc]init];
tempModel.alias = model.alias;
tempModel.number = model.number;
NSInteger reduce = [tempModel.number integerValue];
reduce--;
tempModel.isBtnSelect = YES;
if (reduce <= 0 ) {
reduce = 0 ;
tempModel.isBtnSelect = NO;
}
tempModel.number = [NSString stringWithFormat:@"%ld",reduce];
//替换
[self.MyArray replaceObjectAtIndex:row withObject:tempModel];
// 计算总价格
_zongHeLabel.text = [self ZongHeJiSuan:self.MyArray];
[self.mainTableView reloadData];
}
</br>
第三个功能:
把cell 上的数字加到一个数组中,然后用valueForKeyPath 计算数组和
这里价格是写死的,不一样的时候 先让数量 乘以 价格 得出 单独商品价格,再把单独商品价格 加到数组中,计算总和
#pragma mark ===== 总和计算
- (NSString *)ZongHeJiSuan:(NSMutableArray *)arrary{
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < arrary.count; i++) {
LXModel *model = arrary[i];
[arr addObject:[NSString stringWithFormat:@"%ld",model.DanGeJiaGe * [model.number integerValue]]];
}
// 计算 数组之和
NSNumber *sum = [arr valueForKeyPath:@"@sum.self"];
return [NSString stringWithFormat:@"%@",sum];
}
</br>
第四个功能:
主要需要注意价格计算,有数量的不需要再加需要记录下来,没有数量的加1
我的解决办法是 分成两部分,一部分加不为0的,一部分加为0的,最后加到一起乘以价格得出总价格
// 全选方法
- (void)allSelectBtnAction:(UIButton *)btn{
// 之前有数值的
NSInteger a = 0;
// 之前没有数值的
NSInteger b = 0;
for (int i = 0 ; i < self.MyArray.count ; i++){
LXModel *model = self.MyArray[i];
LXModel *tempModel = [[LXModel alloc]init];
tempModel.alias = model.alias;
if (btn.selected == YES){
tempModel.isBtnSelect = YES;
NSInteger tempNumber = [model.number integerValue];
if (tempNumber == 0){
b++ ;
tempModel.number = [NSString stringWithFormat:@"1"];
} else {
tempModel.number = model.number;
a = a + tempNumber;
}
tempNumber = b + a;
_zongHeLabel.text = [NSString stringWithFormat:@"%ld",(b + a) * tempModel.DanGeJiaGe];
} else {
// 取消 全选
tempModel.isBtnSelect = NO;
NSInteger tempNumber = 0;
tempModel.number = [NSString stringWithFormat:@"%ld",tempNumber];
_zongHeLabel.text = @"0";
}
//替换
[self.MyArray replaceObjectAtIndex:i withObject:tempModel];
}
[self.mainTableView reloadData];
// 点击 切换 button 状态
if (btn.selected == YES){
btn.selected = NO;
} else {
btn.selected = YES;
}
}
</br>
第五部分
主要是使用数据库用到了FMDB,先创建一个单例
.h

.m
创建单例
+ (instancetype)ShareDataBase{
static dispatch_once_t onceToKen;
dispatch_once(&onceToKen,^{
if (ShareDB == nil) {
ShareDB = [[DataBase alloc]init];
[ShareDB initDataBase];
}
});
return ShareDB;
}
// 重写 allocWithZone 保证单例 唯一性
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
if(ShareDB == nil){
ShareDB = [super allocWithZone:zone];
}
return ShareDB;
}
- (id)copyWithZone:(NSZone *)zone{
return self;
}
创建数据库 图片转成二进制 blob 类型 取出时为 data 类型
- (void)initDataBase{
// 获得Documents目录路径
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 文件路径 数据库 名
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"GWCmodel.sqlite"];
// 实例化FMDataBase对象
// 得到数据库
self.db = [FMDatabase databaseWithPath:filePath];
//打开数据库
if ([_db open]){
// 创建数据库 id 主键 自增 网易新闻字符串 商品选中数量 价格 图片
BOOL result = [self.db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_shopping (id integer PRIMARY KEY AUTOINCREMENT,WYString text NOT NULL,Numer text NO NULL , Price integer NOT NULL , Picture blob NO NULL);"];
if(result){
NSLog(@"创表成功");
//一定要使用缓存,减少时间复杂度
[_db shouldCacheStatements];
}else{
NSLog(@"创表失败");
}
}
}
插入数据
谓词查询 http://www.jianshu.com/p/f63107b3c177
- (void)addDataBase:(LXModel *)model{
// 打开数据库
[_db open];
// 根据 字符串 判断数据库中是否存在
FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM t_shopping where WYString = ?",model.alias];
//2 遍历结果
if([resultSet next]){
// SET 更新内容 WHERE 判断条件 如果model.number 即数量不一样 就更新
[_db executeUpdate:@"UPDATE t_shopping SET Numer = ? WHERE WYString = ? ",model.number,model.alias];
}else {
// 不存在 插入新数据
BOOL bResult = [_db executeUpdateWithFormat:@"INSERT INTO t_shopping (WYString,Numer,Price,Picture) VALUES (%@ ,%@ ,%ld,%@);",model.alias,model.number,model.DanGeJiaGe,model.picture];
if(bResult) {
NSLog(@"数据插入成功!");
}
else {
NSLog(@"数据插入失败!");
}
}
}
删除数据
- (void)deleteDataBase:(LXModel *)model{
[_db open];
// 根据 网易字符串 判断
[_db executeUpdate:@"DELETE FROM t_shopping WHERE WYString = ?",model.alias];
}
获取所有数据
- (NSMutableArray *)getAllData{
[_db open];
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
FMResultSet *res = [_db executeQuery:@"SELECT * FROM t_shopping"];
while ([res next]) {
LXModel *model = [[LXModel alloc] init];
model.alias = [res stringForColumn:@"WYString"];
model.DanGeJiaGe = [[res stringForColumn:@"Price"]integerValue];
model.number = [res stringForColumn:@"Numer"] ;
model.picture = [res dataForColumn:@"Picture"];
[dataArray addObject:model];
}
return dataArray;
}
</br>
第六部分
可以确定cell 高度时 这么写可以 不需要代理 提高执行效率
_mainTableView.rowHeight = 230;
</br>
监听 tf 变化 正则表达式
- (void)textFieldDidChange:(UITextField *)tf{
if ([self checkForNumber:tf.text] ){
} else {
// 防止崩溃
if (tf.text.length > 0){
// 不匹配删掉 字符串最后一位
tf.text = [tf.text substringToIndex:tf.text.length - 1];
}
}
}
// 只能输入数字 开头不能为0
- (BOOL)checkForNumber:(NSString *)number{
//转意符 \d \\d
NSString *regEx = @"^[1-9]\\d*$";
return [self baseCheckForRegEx:regEx data:number];
}
// 检测 正则表达式
- (BOOL)baseCheckForRegEx:(NSString *)regEx data:(NSString *)data{
//谓词查询
NSPredicate *card = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regEx];
// 判断是否匹配
if (([card evaluateWithObject:data])) {
return YES;
}
return NO;
}
收藏按钮点击事件,根据是否选中判断加入数据库
- (void)reckonButtonAction{
// 打开数据库
[DataBase ShareDataBase];
ShouCangVController *sc = [[ShouCangVController alloc]init];
for (int i = 0 ; i < self.MyArray.count ; i++){
LXModel *model = self.MyArray[i];
if (model.isBtnSelect == YES){
[[DataBase ShareDataBase]addDataBase:model];
}
}
[self.navigationController pushViewController:sc animated:YES];
}
</br>
其实都不难,一个功能一个功能的实现就好,有什么问题请留言