在iOS视图上点击检测,无需重复。

这篇文章介绍了一种处理UIView实例上的轻击检测的方法。 它显示了如何在新实体中提取重复行为并将其与UIView的实例组成,而不是扩展UIView类,如何提高简单性,可重用性和可扩展性。

如今的问题

用户点击UIView时触发动作就像将UITapGestureRecognizer添加到视图并使用目标/动作机制执行动作一样简单。

如果我们想检测更多UIView的点击,我们会发现自己在重复:

  1. 手势的实例化
  2. 在视图中添加手势
  3. 选择器方法的实现,以及
  4. 方法实现内部的手势状态检查。

但是唯一不同的是:

  1. 哪个视图变得可以轻敲,以及
  2. 点击事件发生时执行的操作。

因此,在一个更好的世界中,做这样的事情就足够了:

或这个:

我们怎样才能做到这一点?

拟议的解决方案

我们将定义一个新的Tap类型,该类型将Tap-on-Action行为添加到视图:此类型的对象接收视图以及在Tap事件发生并连接它们时执行的动作。 基本的Tap类是:

手势动作的目标是这个新的Tap对象,因此另一个对象需要对其进行强有力的引用。 否则,它将被释放,手势事件发生时目标将为nil ,并且将不执行任何操作。

解决此问题的第一种方法是将Tap对象保留在视图上下文中(通常是UIViewController)。 这种方法不切实际,因为我们将需要为每个使我们可以Tap的视图保持每个Tap的引用,并且我们仍将重复没有意义的样板代码。 您可以在这个操场上看到这种方法。

保持引用与我们的点击操作无关,这是一个技术细节,一种更好的方法是让Tap类型处理引用保持,以保持界面整洁和易于使用。 怎么样? 使用创建Tap实例并将其保存在集合中的类方法。 像这样:

您可以在这个操场上看看这种方法。

Tap类将保留其实例,但应在释放其视图时释放它们的每个实例。 我们如何检测到这一点? 好吧,我认为一种非常简单的方法正在Tap和他的视图之间的引用,因此稍后,当我们找到带有nil视图的Tap对象时,可以释放它。 我们可以定期或每次创建新的Tap时触发此“清理”操作。

最终解决方案的完整代码可在此处获得,我们可以像这样使用它:

其他解决方案

使用继承:我们可以创建一个继承自UIViewTappableView类型。 该子类添加了onTapDo:方法。 添加手势target-action时,我们还将有一个私有方法用作选择器。 以这种方式进行操作,我们不是使UIView轻敲,而是创建一种新型的可轻敲的UIView :任何想要轻敲的视图都应继承自TappableView而不是原始UIView 。 如果我们要检测UILabelUIView子类)上的轻UIView ,则不能。 另外,我们使用继承来避免复制,这不是正确使用继承。 您可以在这个操场上看看这种方法。

使用协议扩展:我们可以定义一个协议可Tappable的协议,并使用UIView的默认实现的协议扩展,但是由于协议扩展不支持存储的属性,因此我们无法存储执行轻Tappable时要执行的块。 无论如何,一种解决方法是使用关联的对象在运行时向该对象添加新变量。 这种方法的缺点是,除了要用obj-c运行时接触UIView对象的内脏之外,当我们要使任何 UIView都可以被轻敲时,我们要使所有 UIView轻敲,而不是全部 。 我们可以扩展自定义视图类型的协议,而不是普通的旧UIView ,但是我们再次创建新的类型,而不是使任何UIView都可轻敲。 此方法在此操场上实现。

结论

本文中提出的解决方案将重点放在我们需要的内容上(在轻击视图时触发操作),并用代码清晰,简单地表达它,避免重复并隐藏实现细节。 此外,视图对Tap一无所知,它完全是分离的。 我们将行为添加到任何UIView对象,而不是类型,而不是新类型。

对点击动作行为进行建模并将其封装在Tap类型中,可以实现以下解决方案:

  • 灵活,我们可以检测任何UIView实例上的轻击;
  • 可扩展的,我们可以围绕它扩展功能(我们可以添加功能以启用/禁用点击,在检测到点击事件时添加动画等); 和
  • 易于使用,我们只需要可点击的视图以及检测到点击时要执行的操作即可。

我看到这种行为建模机制以及将其与现有对象关联而不是将其直接添加到现有类型上的巨大潜力,在Codika,我们目前正在研究一种基于这种方法在运行时更改应用程序语言的解决方案。 在以后的文章中,我将对此进行介绍。

注意: 此处 包含完整代码的仓库,用法示例和游乐场