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

Posted

技术标签:

【中文标题】在其中一个页面中包含的 UISwitch 上开始触摸时禁用 UIPageViewController 滚动【英文标题】:Disable UIPageViewController scrolling when touches start on UISwitch contained in one of the pages 【发布时间】:2018-12-16 19:11:03 【问题描述】:

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

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

UIViewControllerUIPageViewController 的一部分时,尝试滑动UISwitch 可能会导致问题,因为滑动开关可以开始滑动UIPageViewController,就好像用户想要更改页面一样。

这种感觉非常破碎和不一致的行为。似乎如果用户触摸开关,但在滑动前短暂犹豫,则触摸由UISwitch 注册,并且它按用户预期工作。但是,如果用户触摸UISwitch 并立即开始滑动,则UIPageViewController 会获得触摸。 UISwitch 接触与否之间似乎有一条非常细的界限(犹豫阈值)。

你会如何解决这个问题?

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


可能的解决方案和问题

我考虑过解决此问题的一种方法是检测 设置 UIViewController 上的任何位置的触摸,如果触摸开始于 UISwitch 的框架中的某处,请阻止 UIPageViewController从滑动。

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

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

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

    print("touchesBegan")

    super.touchesBegan(touches,
                       with: event)

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

我还考虑过为这个应用放弃整个 UIPageViewController 范式,而将 Settings 屏幕改为模式,因为这样可以解决对这种复杂解决方案的需求,但是是应用程序中UIPageViewController 范式的其他优势,所以我想探索是否可以保留。最终,这似乎比奇怪的解决方法更好,但我还是想发布这个,以防其他人遇到同样的问题或任何人有其他可能的解决方案。


更新:解决方案

我花了很多时间根据提交的答案进行实验,同时也学习了很多关于 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")
    

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

你能继承你正在使用的 UISwitches 吗?这听起来更像是该类的工作,覆盖gestureRecognizerShouldBegin() 以返回NO 用于水平移动的滑动手势识别器。该方法的文档提到 UISlider 将该方法用于相同目的; UISwitch 应该也应该这样做。

另一个选项可能是创建一个自定义 UIGestureRecognizer,它通过委托或 UIGestureRecognizerSubclass 方法强制任何其他 UISwipeGestureRecognizer 在它从 UISwitch 启动时失败,并且本身将 cancelsTouchesInView 设置为 false(并且在它“成功”时不做任何事情)。

【讨论】:

是的,我可以继承我正在使用的UISwitch。我尝试在UISwitch 上覆盖gestureRecognizerShouldBegin(),但这永远不会被调用。我能够通过创建UISwipeGestureRecognizer并将其添加到UISwitch,然后将开关设置为UIGestureRecognizerDelegate并实现gestureRecognizer(_ gestureRecognizer:, shouldReceive touch:)来拦截UISwitch上的触摸,但这只能让我检测触摸的时间在开关上开始,而不是在结束时。 您的第二个选项似乎更可行,但我无法完全按照您的建议进行操作。您能否提供任何 Swift 或 Objective-C 中的快速代码示例来说明您建议的解决方案? 嗯。基本上,您希望在通过 UISwitch 启动时禁用 UIPageViewController 的滑动识别器。我认为这就是gestureRecognizerShouldBegin() 应该做的,但我猜不是。如果您使用附加到 UISwitch 的 UISwipeGestureRecognizer 来更改行为,也许可以这样做 - 您可以将其 cancelsTouchesInView 属性设置为 false,以便触摸仍然传递给 UISwitch,然后让它的操作不执行任何操作(或调用什么都不做的方法)?它的存在可能会抵消 UIPageViewController 的识别器,但允许 UISwitch 正常工作。 我尝试设置一个简单的示例,并且 UISwitch 正确地覆盖了 UIPageViewController 手势——每次我触摸它时,只有开关会响应。也许添加 UITableView 会以某种方式阻碍。另一方面,我看到所有 UISwitch 实现都在一个私有子视图中,这意味着主 UISwitch 本身没有收到任何事件(并且不会为覆盖识别器而咨询),这破坏了大多数子类的可能性。子视图具有开关的识别器。【参考方案3】:

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

    UISwitch touchBeganUIPageViewController 的委托和数据源设置为`nil 时,检测UISwitch 上的触摸。

    touchesEnd 上再次设置datasourcedelegateUIPageViewController

通过这种方式,您将能够根据需要与您的UISwitch 进行交互。

【讨论】:

我已经尝试过了,但是touchesBegan 不会在我的UISwitch 子类中被调用,除非我将其所有子视图设置为isUserInteractionEnabled = false(基于我找到的另一个 Stack Overflow 答案)。如果我尝试在调用 touchesBegan 后重新打开子视图上的 isUserInteractionEnabled,则开关不起作用。 另外,当我在子视图上设置isUserInteractionEnabled = false 时,我可以在UIPageViewController 上将delegatedatasource 设置为nil 以防止滚动,但是UISwitch 上的touchesBegan 似乎仍然只是在开关滑动前稍微犹豫后才被调用。似乎当我快速触摸和滑动时,触摸在传递给UISwitch 之前会被其他东西拦截,所以即使我可以让UISwitchtouchesBegan 之后继续工作,它似乎也不是这个解决方案将防止大多数意外滚动。 @gohnjanotis 感谢您尝试此解决方案。我会检查一下,看看我们如何解决这个问题。

以上是关于在其中一个页面中包含的 UISwitch 上开始触摸时禁用 UIPageViewController 滚动的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 上使用 Rtools 中包含的不同 gcc 版本和 Rcpp

C#:如何在 SplitContainer 面板中包含的 PictureBox 中添加两个滚动条

ASP.NET MVC jQuery 自动完成与页面中包含的脚本中的 url.action 助手

另一个文件中包含的 T4 缩进代码

如何从视图控制器中包含的视图调用方法?

使用 QPainter 和 paintEvent 在 PYQT5 中的 QLabel 中包含的 Pixmap 上绘制圆圈