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