先看效果
今天一个同学让我帮他做cell编辑功能,效果大致如下:
效果.gif
点击顶部编辑按钮,所有cell变为可编辑状态,按钮文字变为“完成”,cell编辑后点击按钮,所有cell进入不可编辑状态。
计算所选中物品的总价格
缺点,要是想把编辑的内容,传递到控制器的话,好像没办法做到,因为他依赖于tableView的刷新,当你点击了右上角“完成“的按钮,他会先用self.dataArr 进行刷新,把值通过model赋值给cell, 这样的话,处在编辑状态的时候的texfield的改变,又被原来的数据源覆盖了,所以没办法,把textField 改变的值,传递回去
2.加,减,选中按钮, 功能实现
关键点说明
model是数据的载体,cell展示的内容也有model决定,所以model里不仅有3个文本的内容,还有一个标识是否是可编辑状态的属性。
#import <Foundation/Foundation.h>@interface EditableCellModel : NSObject@property (nonatomic,copy) NSString *name;@property (nonatomic,copy) NSString *range;@property (nonatomic,copy) NSString *value;/** 是否是编辑状态 */@property (nonatomic,assign) BOOL isEditState;@end
用代理,将这个cell和textField一起传过去(传cell是为了获取cell所在的indexPath,传textField是为了获取textField的文本,改变数据源这两个缺一不可):
@class EditableCell;@protocol EditableCellDelegate <NSObject>- editableCell:(EditableCell *)editableCell valueTextFieldTextDidChange:(UITextField *)sender;@end
在上述代理方法里进行处理:
#pragma mark - cell的textField文本改变时回调- editableCell:(EditableCell *)editableCell valueTextFieldTextDidChange:(UITextField *)sender{ // 获取cell所在行数 NSInteger row = [self.tableView indexPathForCell:editableCell].row; // 获取cell对应的model EditableCellModel *cellModel = self.dataArray[row]; // 修改model cellModel.value = sender.text;}
/** 编辑按钮点击 */- editButtonClicked:(UIButton *)sender{ // 改变数据源 for (EditableCellModel *cellModel in self.dataArray) { // 是否进入编辑状态取反 cellModel.isEditState = !cellModel.isEditState; } // 刷新tableView [self.tableView reloadData]; // 更新按钮文字 NSString *editButtonTitle = [sender.titleLabel.text isEqualToString:@"编辑"] ? @"完成" : @"编辑"; [sender setTitle:editButtonTitle forState:UIControlStateNormal];}
编辑商品信息
按钮 : 控制cell中的控件,可否编辑
方法一 : 使用通知--->可控制状态,可传值
点击了按钮,就发送通知,在cell中接收通知,执行控制可否编辑的方法,当点了”完成“,
再使用block/代理把值传递到控制器
先看效果图
沉迷王者荣耀,简书许久没更新,罪过。。。
购物车逻辑及实现总结
逻辑整理:当我们把有购买意向的物品加到购物车后,我们在购物车中调用接口获取购物车中的物品信息。数据源格式大概是(感觉不怎么对,但是能理解就行)
[
{@“店铺信息”:[@{物品信息},@{物品信息},@{物品信息}]}, -------》组一
{@"店铺信息":[@{物品信息}]}, -------》组二
{@”店铺信息“:[@{物品信息},@{物品信息}]} -------》组三
]
把数据源用model装起来,把数据填充到tableview中去。
1.单个商品的选择、单个店铺内所有商品的选择、结账栏下的全选
如果做得很简单的话,可以直接用系统的单选和全选方法。
最重要的两句 !!!!
TableDemo.editing=YES; 编辑状态
TableDemo.allowsMultipleSelectionDuringEditing=YES; 编辑的时候多选
cell.tintColor= [UIColorredColor]; 选中后的颜色
选中和取消选中
-tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath 选中
-tableView:(UITableView*)tableView didDeselectRowAtIndexPath:(NSIndexPath*)indexPath 取消选中
方法二 : model中添加一个bool变量 --->可控制状态,不可传值
要想点了按钮,就让cell知道按钮的话,那就得在点击方法中,刷新tableView才能让走给cell赋值的方法,才能走cell中的setModel
方法, 进而控制cell中控件 是否可编辑
5.选中收藏,把数据存到本地数据库
这里强调一下,购物车数据还是应该存到后台,因为要考虑切换设备的情况,这里存到本地仅仅是为了练习数据库存图片
思路
cell展示的内容来自数据源,所以,对cell展示内容进行修改,实际上就是对数据源的修改。
还是原来的强势
但是 ; 要是仅仅想要控制控件 是否可编辑/ cell上控件的隐藏/cell的按钮的选中未选中,model中整个bool值还是能实现的
demo下载地址:
https://git.oschina.net/wwwzz/ShoppingCartDemo
小demo一份:
demo
效果图
先来分析下,要实现的功能:
1.页面内容展现
这里因为找不到合适数据,文字内容是从网易新闻获取的,图片是本地的,在加一些按钮.之所以获取网易的文字展现是为了练习解决下滑动内容混乱的问题
总结及引申
编辑cell,主要就两步:
- 第一步:改变数据源
- 第二步:reloadData
有些cell的编辑虽然看起复杂,但原理也是这样的,比如我们公司的商城APP的购物车页面:
购物车页面
数量加减、选不选中、删除商品什么的,其实都是对数据源的修改。
注释:这里的删除 及 修改 都是要对数据源进行修改在刷新的
第四个功能:
主要需要注意价格计算,有数量的不需要再加需要记录下来,没有数量的加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>
头视图的代理方法
第六部分
可以确定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>
其实都不难,一个功能一个功能的实现就好,有什么问题请留言
4.全选按钮实现,没有选中的加1选中的不变,再次点击全部不选
3.总价格展示
//中划线
NSDictionary *attribtDic = @{NSStrikethroughStyleAttributeName: [NSNumber numberWithInteger:NSUnderlineStyleSingle]};
NSMutableAttributedString *attribtStr = [[NSMutableAttributedString alloc]initWithString:info[@"GoodsOldPrice"] attributes:attribtDic];
// 赋值
_Goods_OldPrice.attributedText = attribtStr;
第五部分
主要是使用数据库用到了FMDB,先创建一个单例
.h
Paste_Image.png
.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>
demo中的数据源我没有放到model中去处理,其实原理都一样,我把判断各类按钮的判断字段加到数据源中去了,如果用model模型的话,自己加上相对应的字段,并设置初始值。当进行model数据源的修改时,直接进行修改。
第一个比较简单就不讲解了,具体看demo吧
定制段头的View 把section的全选按钮、点击商品、编辑的三个按钮全部装起来,里面点击的方法用代理的方法。
-tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section;
这是加载自定义段头的代理方法。
可以照着model看,思路清楚一点
这是一种model处理方式,还有一种就是用JsonModel来处理,一层层的写下来,原理一样。
第二个功能:
先在 cell 设置代理
Paste_Image.png
.m 传递 tag 用以区分是哪个cell 上的控件
Paste_Image.png
Paste_Image.png
看代理实现前先写model
Paste_Image.png
.m 图片是本地的所以给了固定值,价格是为了好算
Paste_Image.png
</br>
控制器的宏定义 属性 和代理
Paste_Image.png
</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>
建议使用Masonry进行cell适配
第三个功能:
把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>
澳门新濠3559,以店铺为section:商店下的商品为row和店铺名称组成一个 section
6.其他功能
创建两种cell,一个是正常的物品显示cell,另一个cell是编辑后的cell。
Paste_Image.png
正常的cell:只说下label中划线的实现
商品数量用的是第三方PPNumberButton,点击时改变model中该商品的实际数量;点击商品种类进行重新选择(方法未实现,原理一样);点击删除进行cell的删除及数据源的删除。
cell的创建就是和我们平常的一样,把要展示的样式代码编写或者xib都可以。再把数据源填充到我们所创建好的cell中和段头上。
编辑后的cell:
主要由三部分组成。商品数量 商品种类 删除
demo纯代码编写的,只隔离了部分模块,因为我也是拿来练练手,所以如果有需要,后续我会把购物车模块化。如果内容有不妥和臃肿的地方,大家可以提出来,我及时学习并修改。如果大家有意见的可以@我1804094055qq.com。如果觉得可以请大家不吝star。
整个界面就是TableView 底部结账栏View组成
如果不用系统的话,则利用model来进行单选和全选的操作,选中和取消选中对model中的判断字段进行修改,刷新当前cell或者section。
//一个section刷新
NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:section];
[tableview reloadSections:indexSet
withRowAnimation:UITableViewRowAnimationAutomatic];
//一个cell刷新
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:row inSection:section];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];
这里需要注意的是,每次单选和全选的时候,需要对底部结账栏进行数据的刷新,并且单选完整组后,需要对section上的按钮进行选中状态变化,当选中section时,同样需要对section下的row进行选中状态,如果全部商品选中,还得需要改变结账栏的状态。(这里其实不难,无非就是对model中各类字段进行改变,再刷新,同时判断选中数量的多少来进行按钮的状态变换)
2.删除单个商品
删除的话就相对容易了,直接对数据源删除对应的row,当只剩一个后,删除该数据后,记得删除该组section不然报错。删除后及时刷新底部结账栏金额显示。
3.编辑section
点击编辑按钮,修改model,展示编辑cell。编辑按钮上放了数量计数器、商品的信息、删除。
计数器:用到的是好友的一个库PPNumberButton 喜欢的大家可以去玩玩。点击后用代理方法把数量的变化跟新model。
商品信息:点击对商品的信息进行重新选择,同样修改数据源。
删除:代理出来进行model的修改。
因为这里用的是假数据,所以进行的都是对数据源的修改,正常情况下,原理都一样,可以在次基础上,如果接口成功,就对本地数据进行修改,最后提交的信息会和后台匹配一次的,如果有问题,可以自己修改一下。
有小伙伴提出demo中没有下拉刷新,其实下拉刷新不影响该demo。不过加上效果更好。
谢谢“爱在巴黎梦醒时”该小伙伴。
demo的bug注释:
因为demo中判断section的全选和编辑的按钮都是放在每个section的第一个row中的,所以删除section的第一个row后,会有全选和编辑的固定bug出来。特此声明,该bug不影响主体逻辑,如次bug影响小伙伴对逻辑思路的学习,那我后面再重新组织数据源。
1.在刷新底部结账栏的总金额时,同时跟新全选状态。(及底部结账栏的UI都要统一刷新一次)
model
创建好一个View添加在TableView的下方。View上写上全选及总金额等UI。每次我们选定的物品的增减都要调用该View赋值的方法,刷新金额等字段显示。
使用masonry进行适配会省去我们要考虑的label换行及各类UI适配问题,切记在写约束时不要写高度。
编辑:编程 本文来源:cell的创建就是和我们平常的一样,先来分析下
关键词: 澳门新濠3559