UITableViewRowAction和UIContextualAction的通用解决方案



许多人在向左移动时遇到了为UITableViewCell实现漂亮的按钮映射的问题。 一些使用“开箱即用”的标准功能,其他使用自己的实现而麻烦,而其他使用Unicode字符进行管理。 在本文中,我将告诉您如何实现UITableViewRowAction的最大自定义。

首先,有一点理论: UITableViewRowAction是一个类,允许用户在将单元格向左移动(例如删除,更改等)时对该单元格执行某些操作。 该类的每个实例代表一个要执行的动作,包括文本,样式和单击处理程序。 iOS SDK允许我们自定义UITableViewRowAction,如下所示:

public convenience init(style: UITableViewRowActionStyle, title: String?, handler: @escaping (UITableViewRowAction, IndexPath) -> Swift.Void) 

您还可以设置backgroundColor。

现有方法。


考虑现有的实现示例,或者我在Internet上找到的示例。
方法编号1 。 使用“开箱即用”功能。 这足以使用带有文本和背景的普通按钮。 看起来太简单又无聊:


方法编号2 。 使用UIColor(patternImage:UIImage)设置backgroundColor。 这将实现美观,但会剥夺多功能性,增加许多限制和困难。 由于UITableViewRowAction的宽度是根据文本的大小计算的,因此您需要将标题指定为一组具有一定宽度的空白字符。 我们将无法对具有动态高度的像元使用此方法。 如果单元格的高度增加,它将看起来像这样:



方法编号3 。 使用Unicode字符。 无需计算带有空格的文本的大小,该方法适用于具有动态高度的单元格。 看起来不错:



但是,如果要在图标下显示文本,则此方法不适合我们。

方法编号4 。 使用CoreGraphics呈现UI组件。 想法是手动绘制具有所需元素排列的图像(文本和图像)。 在这种情况下,您需要计算每个元素位置的大小和坐标。 使用这种方法,我们可以制作文字和图片,并根据需要进行排列。 但是这种方法很难实现。 我们想要更简单的东西。

其他实现 。 还有一些现成的解决方案。 他们使用自己的单元格实现以及ScrollView和其他逻辑。 例如, SwipeCellKitMGSwipeTableCell 。 但是这些解决方案不允许实现标准的iOS行为;它们仅覆盖一个单元,这与在表委托中创建的UITableViewRowAction不同。 例如,使用其中一种解决方案,如果您为多个单元格左移打开了动作,则必须手动关闭每个动作中的这些动作,这非常不方便。 为此,您必须添加自己的逻辑以控制滑动上的打开操作。 我们想要创建一个不需要额外费用的简单解决方案,就好像我们使用的是UITableViewRowAction的标准实现一样。

让我们开始吧


让我们尝试创建一个通用的解决方案,使您可以使用我们需要的任何设计制作UITableViewRowAction。

从UITableViewRowAction创建我们的后代类,并定义主要实例创建方法:

 typealias RowActionTapHandler = (RowAction, IndexPath) -> Void class RowAction: UITableViewRowAction { static func rowAction(with actionHandler: @escaping RowActionTapHandler) -> Self { let rowAction = self.init(style: .default, title: nil) { action, indexPath in guard let action = action as? RowAction else { return } actionHandler(action, indexPath) } return rowAction } } 

由于UITableViewRowAction没有主要的初始值设定项,为方便起见,我们可以创建一个静态方法,在其中内部,我们以标准方式创建UITableViewRowAction并将一个actionHandler放入其中以处理单击。

现在,让我们尝试实现UITableViewRowAction按钮的通用自定义。 这个想法是从完成的UIView(可以在常规xib中配置)中获得按钮外观的。 因此,我们可以根据个人喜好(文本的任何字体,元素的大小和排列等)制作任何类型的按钮。

我们可以在UITableViewRowAction中更改两件事: backgroundColortitle 。 目前,更改UITableViewRowAction按钮外观的唯一方法是更改​​backgroundColor属性。 因此,我们的目标是转换:UIView-> UIImage-> UIColor。 我们可以使用图像获取颜色,如下所示: UIColor(patternImage:UIImage) 。 我们可以通过使用标准CoreGraphics框架渲染UIView中的图像。

但是首先,我们将决定如何设置按钮的大小:“开箱即用”没有此功能,UITableViewRowAction中的大小由标题字段文本的大小计算得出。

经过实验(测量按钮的计算宽度和实际宽度),我发现了一个空白的Unicode字符,其最小的非零宽度: U + 200A

我们将使用它并创建一个常量:

 private enum Constants { static let placeholderSymbol = "\u{200A}" } 

我们将编写一个函数以获取与所需大小相对应的一串空格字符:

 private static let placeholderSymbolWidth: CGFloat = { let flt_max = CGFloat.greatestFiniteMagnitude let maxSize = CGSize(width: flt_max, height: flt_max) let attributes = [NSFontAttributeName : UIFont.systemFont(ofSize: UIFont.systemFontSize)] let boundingRect = Constants.placeholderSymbol.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) return boundingRect.width }() private func emptyTitle(for size: CGSize) -> String { var usefulWidth = size.width - Constants.minimalActionWidth usefulWidth = usefulWidth < 0 ? 0 : usefulWidth let countOfSymbols = Int(floor(usefulWidth * Constants.shiftFactor / RowAction.placeholderSymbolWidth)) return String(repeating: Constants.placeholderSymbol, count: countOfSymbols) } 

placeholderSymbolWidth变量获取我们的Unicode字符的宽度, emptyTitle(对于大小:CGSize)-> String函数计算与大小大小相对应的空白字符的数目,并返回这些字符的字符串。 上面的公式使用shiftFactor常数。 这是宽度与标称值的偏差系数。 事实是,随着UITableViewRowAction的宽度增加,会有很小的误差增加(即,按钮的实际宽度与通常公式所计算出的宽度不同)。 为了避免这种情况,我们使用实验得出的常数。

另外,UITableViewRowAction的最小宽度为30(例如,如果您设置title =“”)。 此宽度是标题左右缩进所需的最小宽度。
更新我们的常量:

 private enum Constants { static let placeholderSymbol = "\u{200A}" static let minimalActionWidth: CGFloat = 30 static let shiftFactor: CGFloat = 1.1 } 

现在让我们从UIView中获取图像。 我们将编写以下函数:

 private func image(from view: UIView) -> UIImage? { UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, UIScreen.main.scale) guard let context = UIGraphicsGetCurrentContext() else { assertionFailure("Something wrong with CoreGraphics image context"); return nil } context.setFillColor(UIColor.white.cgColor) context.fill(CGRect(origin: .zero, size: view.bounds.size)) view.layer.render(in: context) let img = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return img } 

此功能将我们的视图转换为图片。 在这种情况下,图像的大小对应于view的大小。

最后一步仍然是,我们实现了使用UIView设置UITableViewRowAction的方法:

 func setupForCell(with view: UIView) { guard let img = image(from: view) else { return } self.title = emptyTitle(for: img.size) self.backgroundColor = UIColor(patternImage: img) } 

在这里,我们使用前面编写的函数来配置按钮,并使用生成的图像设置backgroundColor

仅此而已。 我们为UITableViewRowAction提供了通用解决方案,现在我们可以使用UIView以任何方式对其进行自定义。



从iOS 11开始,Apple为表中单元格的滑动操作添加了新类-UIContextualAction。 它允许您立即指定要在滑动操作上显示的图像(这是使用UITableViewRowAction多年来一直缺少的图像)。 他本来应该使我们的生活更轻松,但是除了新的机会之外,他还带来了其他问题。 此类对图片的大小施加限制,不允许同时使用文本和图片,并且图片必须具有特殊格式(模板图像)。 因此,我最终确定了支持iOS 11中滑动操作的解决方案。现在,为了创建UITableViewRowAction或UIContextualAction,您首先需要创建RowActionFactory工厂,并使用我们的视图对其进行配置(就像我们在setupForCell(带有view:UIView)方法中所做的那样并返回我们需要的实体:UITableViewRowAction或UIContextualAction(分别使用rowAction()contextualAction(对于indexPath:IndexPath)方法 )。 以下是使用RowActionFactory的示例:

 @available(iOS 11.0, *) func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { var contextualActions: [UIContextualAction] = [] for actionType in rowActionTypes { // actionType -     let rowActionFactory = RowActionFactory { (indexPath) in tableView.setEditing(false, animated: true) } let rowActionView = <...> //  UIView     rowActionFactory.setupForCell(with: rowActionView) if let contextualAction = rowActionFactory.contextualAction(for: indexPath) { contextualActions.append(contextualAction) } } let swipeActionsConfiguration = UISwipeActionsConfiguration(actions: contextualActions) swipeActionsConfiguration.performsFirstActionWithFullSwipe = false return swipeActionsConfiguration } 

完整的解决方案可以在这里找到: TCSCus​​tomRowActionFactory(github.com)

Source: https://habr.com/ru/post/zh-CN414019/


All Articles