开启VoiceOver后UIScrollView设置contentOffset触发两次didScroll问题
UIScrollView开启VoiceOver后二次滚动偏移归零问题解决方案
问题原因
- 该行为是iOS VoiceOver的可访问性适配逻辑导致:当你主动给UIScrollView赋值contentOffset后,VoiceOver会自动触发校验逻辑,尝试将当前聚焦的可访问元素滚动到可视区域内。如果你滚动到的目标位置没有可被VoiceOver识别的可访问元素,或者当前VoiceOver焦点仍停留在x=0位置的元素上,系统会自动触发滚动将焦点元素展示出来,进而触发第二次
scrollViewDidScroll回调,contentOffset.x被重置为0。 - 另一种可能的触发场景是你计算的目标偏移值超出了scrollView的合法滚动范围:普通模式下系统不会主动修正超出contentSize范围的偏移,但VoiceOver模式下会强制将偏移修正到合法区间,若此时contentSize宽度不足也可能出现偏移被重置的问题。
修复方案
方案1:主动同步VoiceOver焦点(推荐)
不要直接赋值contentOffset,改用系统提供的setContentOffset(_:animated:)方法,滚动完成后主动通知VoiceOver切换焦点到目标位置的元素,避免系统自动调整偏移:
func scrollToView(atIndex index: Int) { let targetX = scrollView.frame.size.width * CGFloat(index) scrollView.setContentOffset(CGPoint(x: targetX, y: scrollView.contentOffset.y), animated: false) // 主动将VoiceOver焦点切换到目标位置的对应视图 guard let targetView = scrollView.subviews.first(where: { $0.frame.minX == targetX && $0.isAccessibilityElement }) else { return } UIAccessibility.post(notification: .layoutChanged, argument: targetView) }
方案2:校验contentSize合法性
调用滚动方法前先确认contentSize设置正确,保证目标偏移值在合法滚动范围内:
func scrollToView(atIndex index: Int) { let targetX = scrollView.frame.size.width * CGFloat(index) // 校验目标偏移不超过最大可滚动值 let maxValidX = scrollView.contentSize.width - scrollView.bounds.width guard targetX <= maxValidX else { return } scrollView.contentOffset = CGPoint(x: targetX, y: scrollView.contentOffset.y) }
内容的提问来源于stack exchange,提问作者Jakub Vodak
相关产品推荐
相关产品推荐

