在其中一个页面中包含的 UISwitch 上开始触摸时禁用 UIPageViewController 滚动

Disable UIPageViewController scrolling when touches start on UISwitch contained in one of the pages

我有一个 UIPageViewController,其中一页是 设置 屏幕。在该屏幕上,有一个 UISwitch 用于打开或关闭设置。

虽然我注意到许多人像我一样点击 UISwitch 来切换它,但我观察到一些用户滑动 UISwitch 来切换它。

UISwitch 位于 UIPageViewController 的一部分时,尝试滑动 UISwitch 可能会导致问题,因为滑动开关可以开始滑动 UIPageViewController如果用户想要更改页面。

这种行为让人感觉非常崩溃和不一致。看起来,如果用户触摸开关,但在滑动前短暂犹豫,则触摸由 UISwitch 注册,并且按用户期望的方式工作。但是,如果用户触摸 UISwitch 并立即开始滑动,则 UIPageViewController 会获得触摸。 UISwitch 触及与否之间似乎有一条非常细的线(犹豫阈值)。

你会如何解决这个问题?

这是一个示例,首先是简单的点击来切换 UISwitch,然后展示了一些尝试滑动 UISwitch 的不同方法,这些方法会导致不一致的结果:


可能的解决方案和问题

我考虑过解决这个问题的一种方法是检测 设置 UIViewController 中任何位置的触摸,如果触摸开始于 [=] 框架中的某处13=],防止UIPageViewController滑动.

我担心的是,简单地禁用 UIPageViewController 的滑动并不能保证将触摸传递给 UISwitch。这意味着用户可能会尝试滑动 UISwitchUIPageViewController 不会滑动,但 UISwitch 也不会响应触摸,使其看起来仍然损坏且不一致。

为了开始探索这个可能的解决方案,我尝试通过覆盖 touchesBegan 方法来检测触摸,如下所示:

override func touchesBegan(_ touches: Set<UITouch>,
                           with event: UIEvent?) {

    print("touchesBegan")

    super.touchesBegan(touches,
                       with: event)
}

我已经尝试在特定 设置 页面的 UIPageViewControllerUIViewController 上以这种方式检测触摸,但都没有被调用。我也试过在视图上设置 view.isUserInteractionEnabled = true,在不同的 UIViewController 上设置 truefalse 的不同组合,但似乎仍然无法检测到触摸。

我还考虑过放弃此应用程序的整个 UIPageViewController 范式,并改为将 设置 屏幕设为模态,因为那样可以解决对此的需求一种复杂的解决方案,但应用程序中的 UIPageViewController 范例还有其他优点,所以我想探讨是否可以保留它。最终,这似乎比奇怪的解决方法更好,但我还是想post这样做,以防其他人遇到同样的问题或任何人有其他可能的解决方案。


更新:解决方案

我花了很多时间根据提交的答案进行试验,同时也了解了很多 iOS 的工作原理,所以感谢所有回答的人。

最终,Leon 的回答让我找到了一个使用 UIPanGestureRecognizer 的简单解决方案,这与我基于 Carl 的回答尝试使用 UISwipeGestureRecognizer 的方法非常相似,但最终没有产生相同的结果。

我最终继承了 UISwitch 并添加了一个什么都不做的 UIPanGestureRecognizer。这使得它的行为完全符合我的要求:任何时候用户开始滑动开关时 UIPageViewController 都不会平移。

class NoPanSwitch: UISwitch {

    override init(frame: CGRect) {
        super.init(frame: frame)

        let recognizer = UIPanGestureRecognizer(target: self,
                                                action: nil)
        addGestureRecognizer(recognizer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

您可以子class 您正在使用的 UISwitch 吗?这听起来更像是 class 的工作,将 gestureRecognizerShouldBegin() 重写为 return NO,用于水平移动的滑动手势识别器。该方法的文档提到 UISlider 出于相同目的使用该方法; UISwitch 应该也可以这样做。

另一种选择可能是创建自定义 UIGestureRecognizer,它通过委托或 UIGestureRecognizerSubclass 方法强制任何其他 UISwipeGestureRecognizer 在 UISwitch 上启动时失败,并且它本身通过 cancelsTouchesInView 设置为 false(而不是在 "succeeds" 时做任何事情)。

试试这个,看看它是否如您所愿。

  1. Detect the touches on UISwitch when UISwitch touchBegan set delegate and datasource of UIPageViewController to `nil.

  2. On touchesEnd set again set datasource and delegate for UIPageViewController.

通过这种方式,您将能够随心所欲地与您的 UISwitch 互动。

页面视图控制器具有 gestureRecognizers 属性 用于控制页面内交互的手势识别器。我会尝试将平移手势识别器添加到 UISwitch 并将其添加到页面控制器。我期望的效果是页面控制器的平移手势识别器将要求数组中的手势识别器在识别页面平移之前失败,因此当您轻按开关时不会发生滚动。如果结果不是这样,您可能希望将页面控制器换成带有堆栈视图的滚动视图或包含控制器的集合视图。然后您将完全控制学校视图的 panGEstureRecognizer 以实现上述行为。