在前端开发中如果要拥有一个单选或者多选功能十分简单,因为HTML中有现成的标签可以很方便的实现单选或者多选效果,比如这样写上几句代码就能拥有最原始的选择效果。
但是在iOS开发中就没有这么方便的控件了,如果要完成单选或者多选的功能还需要一些逻辑编码,并且可以选择的方案还是有很多的。
在iOS开发中经常用于实现单选或者多选功能的控件是UITableView
,并且,通过这个控件可以完成一个App80%的界面。而微信则是一个全部使用UITableView构建的App。下面将会针对于单选和多选进行一个探究学习,学习SDK中提供有哪些API能够使用,并且结合这些API如何总结出合适的方法完成单选多选功能。
单选其实还是很简单的,在将UITableView添加到控制器中的时候选择任意一个cell都会看到被选择的cell变为灰色,这是系统提供的,不需要开发者进行设置。谈不上好看,但正是由于不是很好看,才留给了开发者很大的自定义的空间。
这个选择样式系统是根据设置的selectionStyle
属性决定的,这是一个枚举类型,默认的是UITableViewCellSelectionStyleBlue
,但是通过选择代理方法获取cell并且打印出来的selectionStyle属性却是UITableViewCellSelectionStyleDefault
!!
|
@property (nonatomic) UITableViewCellSelectionStyle selectionStyle;\
// default is UITableViewCellSelectionStyleBlue.
typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {\
UITableViewCellSelectionStyleNone,\
UITableViewCellSelectionStyleBlue,\
UITableViewCellSelectionStyleGray,\
UITableViewCellSelectionStyleDefault NS_ENUM_AVAILABLE_IOS(7_0)\
};
|
除此之外,UIKit中还为UITableView提供了单选、多选、在编辑状态下的单选、在编辑状态下的多选的布尔值属性,以及其他的选择操作API
|
@property (nonatomic) BOOL allowsSelection;
@property (nonatomic) BOOL allowsSelectionDuringEditing;
@property (nonatomic) BOOL allowsMultipleSelection;
@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing;
@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow
@property (nonatomic, readonly, nullable) NSArray
要达到单选或者多选操作,需要做的是在UITableView的代理方法-tableView:didSelectRowAtIndexPath:
中对选择的cell进行选中标记,以及-tableView:didDeselectRowAtIndexPath
中对取消选中的cell取消选中标记,这里使用UITableViewCell的accessoryType
属性进行选中与否的设置
|
-tableView:didSelectRowAtIndexPath:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}
-tableView:didDeselectRowAtIndexPath
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
}
|
如果这样做了的话,在进行滑动表视图的时候会出现cell重用的现象,可以在要进行重用的时候根据cell的selected
属性进行决定accessoryType
显示样式
|
-tableView:cellForRowAtIndexPath:
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
if (cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}
|
做完了这些,可以在对UITableView进行设置属性的地方决定是要单选allowsSelection
还是多选allowsMultipleSelection
|
_tableView.allowsSelection = YES;\
// or\
_tableView.allowsMultipleSelection = YES;
|
前面说了,如果是多选状态,可以回到一个都没有选中的状态,但是如果是单选状态,一旦选择就回不到原始一个都没有选中的状态。如果要实现一个单选可以回退到原始状态的效果怎么办呢?
这时候需要借助一个标记位,用来标记选中的cell所在的位置,这个标记位使用NSIndexPath
的实例对象来表示,当点击cell的时候对该cell所处的位置和标记位进行比较,如果相同的话就取消掉cell的选中效果,并且将标记位置为nil,同时要对UITableView进行取消选中当前indexPath下的cell操作,这样做的目的是为了在使用indexPathForSelectedRow
属性获取选中cell的时候能够获取到正确的数据;如果和标记位不相同就添加选中状态,并重新赋值标记位。将-tableView:didSelectRowAtIndexPath:
方法使用一下代码进行替换,同时对当前控制器添加一个selectedIndexPath
属性作为标记位
|
-tableView:didSelectRowAtIndexPath:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
if (_selectedIndexPath == indexPath) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
[tableView deselectRowAtIndexPath:_selectedIndexPath animated:YES];\
_selectedIndexPath = nil;\
}else{\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
_selectedIndexPath = indexPath;\
}
|
其他地方还是和单选一样的,这样就可以实现单选回退操作了。
以上的代码
当然也可以使用一个NSIndexPath
的实例对象作为标记位来实现单选操作,在点选每一个cell的时候进行标记的重新赋值,在cellForRow方法中根据标记位来决定哪一个cell显示选中状态。
|
-tableView:cellForRowAtIndexPath:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];\
if (_selectedIndexPath && _selectedIndexPath == indexPath) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}
…
-tableView:didSelectRowAtIndexPath:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
if (_selectedIndexPath) {
// 取消上一次选择的accessoryType\
cell = [tableView cellForRowAtIndexPath:self.selectedIndexPath];\
cell.accessoryType = UITableViewCellAccessoryNone;\
}
// 设置这一次选择的accessoryType\
cell = [tableView cellForRowAtIndexPath:indexPath];\
if (cell.accessoryType) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
} else {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}
// 保存indexPath\
self.selectedIndexPath = indexPath;
[tableView performSelector:@selector(deselectRowAtIndexPath:animated:) withObject:indexPath afterDelay:0.5];
// 或者用短一点的,将该方法中以上写的代码替换成下面这样,但是要考虑一下这样有可能会出现的后果\
_selectedIndexPath = indexPath;
[tableView reloadData];
|
这些做法比较基础,也比较简单。如果是在没有进行自定义Cell,并且对选择样式也没什么要求,一个对号也能行的那种情况下,这样做就行了,没必要多折腾了。但是,往往一个App的局部样式跟整体的样式是相关的,比如微信主色调是一种绿色的,并且设计师由于美观要使用一个带有圆圈的对号来表示选中状态,这样的话一个简单的对号显然已经不能满足需求了。在进行自定义Cell的时候,需要考虑将选中和非选中的样式进行迁移,不要在控制器中进行,换到在Cell中进行。
使用自定义的Cell,需要在实现类里的-setSelected:animated:
方法进行判断,如果不需要有Cell的选中样式可以在-tableView:cellForRowAtIndexPath:
中将选中样式设为none。
|
(void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];\
if (selected) {\
self.checkoutImageView.image = [UIImage imageNamed:@”CheckBox_HL”];\
}else{\
self.checkoutImageView.image = [UIImage imageNamed:@”CheckBox”];\
}\
}
|
效果如图,
这种做法说起来也是比较取巧,在需求不是很复杂的情况下使用还是可以的,如果一旦牵扯到很多的业务逻辑就不要使用这种办法了。
以上的代码
上面说的都是在一个Section下进行单选,如果换个前提条件,是要求在多个Section下进行全部Cell的单选呢?那如果又增加了难度,要求是在多个Section下进行每一个Section内部的单选呢?
首先,这个在多个Section下全局单选功能和单个Section下的单选功能是一样的,重要的是下面这个在每一个Section下进行单选操作。
以上的代码
和单选一样,如果多选任务要求的是多个Section下的全局多选呢?如果是多个Section下的每个Section内部的多选呢?这样的话又要如何实现呢?思考一下。
当认真思考之后你会发现,这样的做法实在是无聊。
在做了这么多的单选和多选操作之后,用一个实实在在的例子进行一下演练,涉及到一个很实际的应用场景:挑选购物车中的物品进行结账。
在你手机中某一家电商类App的购物车中会有同一个商家的多种物品,也会出现所有的商品都不是同一个商家这种情况。当你有钱的时候,你可以全选购物车中的所有物品进行结账;当你不是很有钱的时候,只能够买其中的一部分,你可以选择同一个商家下面的所有商品,同时你又比较纠结,你也可以挑选多个商家的某一些商品进行结账;当你实在是没有钱了,但又想买买买,所有钱只能够买一件的时候,你在购物车中挑选了一件进行结账。
用户的行为就是最直观的项目需求,翻译成编码需求大概是这样:
应该就这些了吧。。。
初始工程已经写好了,这个初始工程使用了plist文件
提供了假数据供表视图使用,同时使用了一些Xib
进行构建局部视图的样式,这在开发中会成为便捷开发的部分,同时也会在进行小功能demo的时候节省大量的时间用来写无聊至极的布局代码。但是,这个初始工程还是有很多问题的,比如,没有很好地解决Cell重用问题
,当点击一个靠上部分或者靠下部分的按钮,然后滑动表格,会发现,其他地方的按钮别选中了!!典型的cell重用问题,这个会在后面进行解决重用问题;还有里面在每一个xib对应的视图中都用到了那个具有特殊状态的按钮,并且在每个类中都写了重复的代码,写三遍的代码就要考虑重构。同时,这个工程仅仅提供一个展示,没有具体的单选以及多选或者全选功能,这些都会在接下来进行实现。
先看看初始工程长什么样子吧,而且最终的工程也是长这个样子的,不过功能部分是看不出来的,体验才能察觉出来。
感觉给自己挖了个大坑!!!!!!
在实际的开发中,一个购物车要考虑的情形比这复杂的多了,这里仅仅是简化到了最简单的购物车功能。