【支持iOS11】UITableView左滑删除自定义 - 实现多选项并使用自定义图片
【支持iOS11】UITableView左滑删除自定义 - 实现多选项并使用自定义图片
本文介绍关于cell侧滑在iOS8-10 的写法,以及iOS11.0以后的新型处理方式。
本文介绍两种UITableView左滑菜单的实现方法,1. 默认, 2. 自定义。效果如下:
1. 系统默认效果
swipe-default.PNG
2. 自定义图标效果 (类似“邮件”应用)
swipe-customize-1.PNG
目录:
- 系统默认图标实现方法
- 自定义图标实现方法
a. 自定义多个左滑菜单选项
b. 自定义左滑选项外观
UITableView视图层级(iOS8-10, 11)
具体实现方法 (支持iOS8-10, 11) - TableCell上有其他按钮的处理方法
1. 系统默认图标实现方法
如果只需要使用默认图标,只需要在对应的TableViewController里实现数据源方法tableView:commitEditingStyle:forRowAtIndexPath就行了:
向左滑动table cell,该cell会自动进入编辑模式(cell.isEditing = 1),并在右边出现删除按钮,红底白字,按钮上的文字会根据系统语言自动改变;点击该按钮则触发commitEditingStyle执行相应的动作。
如果不进行自定义,默认的左滑菜单只会有一个按钮,不过按钮上的文字可以用随意进行更改,按钮的宽度会根据文字标题长度自动调整,需要自己支持多语言:
效果如下:
change-title.PNG
PS:如果除了文字内容外,还想调整其它,比如文字颜色,背景颜色,选项的宽高等,则可以拿到对应的UIButton以后直接修改,具体方法参照本文2) b部分。
2. 自定义图标实现方法
我认为自定义又分为两个层次: a. 自定义多个左滑菜单选项 和 b. 自定义左滑选项外观 .
a. 自定义多个左滑菜单选项
如果需要超过一个左滑选项,需要实现代理方法tableView:editActionsForRowAtIndexPath,在里面创建多个UITableViewRowAction:
可以看到这里我们创建了delete和read两个action。这是因为实现了该方法以后,1) 中用的commitEditingStyle:forRowAtIndexPath就不会被触发了,所以删除按钮也需要自己定义。
[tableView setEditing:NO animated:YES]; 这一行代码很重要,它的效果是在点击之后退出编辑模式,关闭左滑菜单。如果忘了加这一句的话,即使点击了按钮cell也不会还原。在1) 中使用默认模式的时候,系统会自动帮我们调用这一句,现在则需要手动调用。
对创建并返回的每个action,apple library会自动帮我们生成一个对应按钮,配置好基本的交互,并添加到左滑菜单中。
效果如下:
two-actions-2.PNG
上图我们对两个action都指定了UITableViewRowActionStyleNormal(灰底白字),不过其实有几种不同的预设外观 (不要问我为啥有两个都是红底白字。。。)
UITableViewRowActionStyleNormal:灰底白字
UITableViewRowActionStyleDefault:红底白字
UITableViewRowActionStyleDestructive:红底白字
我们还可以更改action button的背景色,在创建action的时候添加一行代码即可:
效果如下:
change-background-2.PNG
2b. 自定义左滑选项外观
自定义左滑选项外观的资料很少,我做的时候找得相当辛苦。不过后来理解了UITableView的视图层级,一切就变得很简单了。
UITableView视图层级(iOS8-10, 11)
和iOS8-10相比,iOS11的左滑选项的视图层级有了较大改变。最显著的改变是从是UITableViewCell的子视图变成了UITableView的子视图。总结一下就是:
iOS 8-10: UITableView -> UITableViewCell -> UITableViewCellDeleteConfirmationView -> _UITableViewCellActionButton
iOS 11 (Xcode 8编译): UITableView -> UITableViewWrapperView -> UISwipeActionPullView -> UISwipeActionStandardButton
iOS 11 (Xcode 9编译): UITableView -> UISwipeActionPullView -> UISwipeActionStandardButton
不想看原理的看到这里就可以直接跳到下面去看具体的实现方法了, 有兴趣看每一层具体有些什么的同学可以继续。
iOS8-10下的层级:
在tableView代理方法里设置断点打印发现,正常状态下cell上只有两个subview:
而在左滑进入editing mode之后,就变成了3个,多出来一个 叫做UITableViewCellDeleteConfirmationView的子视图:
再进一步查看这个多出来的UITableViewCellDeleteConfirmationView的子视图,发现两个UIButton:
最后再打印一下这两个UIButton的title,发现分别是“Read”和“Delete”。
也就是说,这两个UIButton,分别对应我们在a部分创建的两个UITableViewRowAction。所以我们只要遍历UITableViewCell的子视图,拿到对应UIButton的reference,什么修改高度,添加图片,修改字体,都是手到擒来。
iOS11下的层级 (用Xcode 8编译)
依然在tableView代理方法里设置断点打印发现,UITableViewCell下面没有UITableViewCellDeleteConfirmationView子视图了,不过在UITableViewWrapperView下面,多了一个UISwipeActionPullView。
再看一下这个UISwipeActionPullView子视图,发现了我们要找的选项按钮:
这两个button的title和action都和我们之前所创建的左滑选项相对应,所以我们可以用类似的方法遍历UITableView的子视图,拿到对应UIButton的reference进行修改。
iOS11下的层级 (用Xcode 9编译)
Xcode 9 默认使用iOS11 SDK来编译,添加打印后发现Xcode 9 编译出来的没有UITableViewWrapperView这一层,UISwipeActionPullView的子视图直接附属于UITableViewCell。
除了少了一层UITableViewWrapperView以外,其他和Xcode 8编译出来的一样。
放一下Xcode 8、9编译的对比图:
懒人请直接看这里:p
具体实现方法 (支持iOS8-10, 11)
为了同时支持iOS8-10和iOS11, 我把操作选项外观的代码统一放在UITableView的ViewController的- (void)viewDidLayoutSubviews实现。
这样做的原因有两个:
- 原本因为iOS8-10中,左滑选项是UITableViewCell的子视图,而在iOS11中,左滑选项变成了UITableView的子视图。虽然可以用tabelCell.superview来获取tableView,不过我认为最好从高层级去操作低层级。所以统一在UITableView层处理。
- iOS8-10的UITableViewCellDeleteConfirmationView子视图出现得较晚。在代理方法willBeginEditingRowAtIndexPath中还没有出现,而在viewDidLayoutSubviews则可以保证子视图出现。
首先我们遍历UITableView的子视图拿到选项按钮(UIButton)的reference,对iOS8-10和iOS11做不同处理:
Xcode 8 编译版本:(如果你使用的是Xcode 9,参见下面)
Xcode 9 编译版本:(比Xcode 8编译出来少一层)
注意一下这里我们用到了一个变量self.editingIndexPath,这代表着当前左滑的cell的index,方便我们获取iOS8-10上面的tableCell的reference。分别在控制进入和退出编辑模式的代理方法中设置的:
[self.view setNeedsLayout]; 这一句非常重要,它的作用强制UITableView重新绘图。只有添加了这一句,- (void)viewDidLayoutSubviews才会被调用,才能使我们的自定义外观生效。
好了,我们已经拿到了按钮(UIButton)的reference,然后就可以给按钮添加了图片,并且设置文本的字体和颜色了。
如果没有[self centerImageAndTextOnButton:readButton],则效果如下:
after-layoutsubviews-1.PNG
可以看到图标在左,文字在右,还互相重合。这就是我们熟悉的UIButton的外观处理,需要分别修改UILabel和UIImageView的frame:
调整过后就可以做到文章开头展示的效果了:
swipe-customize-2.PNG
PS:假如仅支持iOS8-10的话,我个人是倾向于创建一个custom class,继承UITableViewCell,然后在该custom class中-(void)layoutSubviews来实现的。那样代码更干净,不需要特意去调用[self.view setNeedsLayout]; 不过为了支持新版本总是要有所牺牲的。
3. TableCell上有其它按钮的处理方法
我自己做的时候遇到了一种特殊情况,UITableViewCell上面带有比较显著的button,类似下图的这种情况:
swipe-button-1.PNG
这种情况比较尴尬的就是当你左滑的时候如果刚好碰到了YES或者NO button, 在进入左滑选项的同时会触发按钮选项,相当容易引发误操作。为了解决这个问题,我在TableViewController中实现了下面两个代理方法:
tableView:willBeginEditingRowAtIndexPath是在cell进入editing mode之前调用的,在这里将contentView下面的所有按钮的交互设置为disabled。
tableView:didEndEditingRowAtIndexPath是在cell即将退出editing mode时调用的,在这里将之前被disable的所有button的交互重新设置为enabled。
这样就可以保证在左滑菜单出现的时候,原本cell上的那些按钮都处于不能点按的状态,也就不会触发误操作了。
PS: 试过直接disable 整个contentView不起作用,必须直接disable对应的UIButton才行,推测跟apple自己处理event的有限次序有关。