iOS 嵌套UIScrollView的滑动冲突解决方法

2018-11-02  本文已影响0人  枭龙gogogo

基本结构

主要难点及解决方法

mainScrollView和subScrollView在滚动的时候会产生冲突

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
    }

滚动过程中对contentOffset的处理

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard mainScrollView == scrollView else {
            return
        }
        if (subScrollView != nil && subScrollView!.contentOffset.y > 0) || scrollView.contentOffset.y > topCellHeight - fixHeight {
            mainScrollView.setContentOffset(CGPoint(x: 0, y: topCellHeight - fixHeight), animated: false)
        } else if scrollView.contentOffset.y < topCellHeight - fixHeight {
            for subController in self.childViewControllers {
                guard let vc = subController as? BaseViewController else {
                    return
                }
                vc.tableView.setContentOffset(.zero, animated: false)
            }
        }
    }
// 在subScrollView的scrollViewDidScroll方法里调用该方法
func subViewDidScroll(_ scrollView: UIScrollView) {
        subScrollView = scrollView
        if mainScrollView.contentOffset.y < topCellHeight - fixHeight {
            subScrollView.setContentOffset(.zero, animated: false)
        }
    }

惯性效果无法在内外层传递

DynamicItem的实例可以看作是一个质点, 在垂直方向上, 它的位置(center)可以用来计算两帧动画之间scrollView移动的距离, , 它的 transform 属性可以不用考虑.

// 遵循UIDynamicItem协议的质点(有位置速度,无尺寸)
class MyDynamicItem: NSObject, UIDynamicItem {
    var center: CGPoint = .zero
    var bounds: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
    var transform: CGAffineTransform
    override init() {
        transform = CGAffineTransform()
        super.init()
    }
}
// 禁止SubScrollView原有的线性减速逻辑
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        DispatchQueue.main.async {
            scrollView.setContentOffset(scrollView.contentOffset, animated: false)
        }
        guard let delegate = delegate else { return }
        delegate.subViewWillEndDragging(scrollView, velocity: velocity.y * 500)
    }
var dynamicItem = MyDynamicItem()
var animator: UIDynamicAnimator?
var lastCenter: CGPoint = .zero

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    self.animator?.removeAllBehaviors()
}

// 在subScrollView的scrollViewWillEndDragging方法里调用该方法
func subViewWillEndDragging(_ subScrollView: UIScrollView, velocity: CGFloat) {
        if (velocity < 0 && subScrollView.contentOffset.y > 0) || (velocity > 0 && mainTableView.contentOffset.y < self.topCellHeight - self.fixHeight) {
            DispatchQueue.main.async {
                subScrollView.setContentOffset(subScrollView.contentOffset, animated: false)
            }
            dynamicItem.center = CGPoint(x: 0, y: mainTableView.contentOffset.y)
            lastCenter = dynamicItem.center
            let behavior = UIDynamicItemBehavior(items: [dynamicItem])
            behavior.addLinearVelocity(CGPoint(x: 0, y: velocity), for: dynamicItem)
            behavior.resistance = 2
            behavior.action = { [weak self] in
                guard let `self` = self else { return }
       
                if velocity < 0 { // 向下滑
                    let mainOffset = self.mainTableView.contentOffset.y
                    let subOffset = subScrollView.contentOffset.y
                    let scrollDistance = (self.lastCenter.y - self.dynamicItem.center.y)
                    if subOffset - scrollDistance <= 0 { // subScrollView滑动到顶部,需要把惯性传递给mainScrollView
                        subScrollView.contentOffset.y = 0
                        self.mainTableView.contentOffset.y = mainOffset - (scrollDistance - subOffset)
                    } else if self.bounceBehavior != nil { // 在回弹过程中,scrollDistance为负数,保证mainScrollView的offset不超过0
                        subScrollView.contentOffset.y = 0
                        self.mainTableView.contentOffset.y = min(mainOffset - (scrollDistance - subOffset), 0)
                    } else { // subScrollView未滑动到顶部,正常减速
                        subScrollView.contentOffset.y = subOffset - scrollDistance
                        self.mainTableView.contentOffset.y = self.topCellHeight - self.fixHeight
                    }
                } else if velocity > 0 { // 向上滑
                    let mainOffset = self.mainTableView.contentOffset.y
                    let subOffset = subScrollView.contentOffset.y
                    let scrollDistance = (self.dynamicItem.center.y - self.lastCenter.y)
                    if mainOffset + scrollDistance >= self.topCellHeight - self.fixHeight { // mainScrollView滑动到极限值,需要把惯性传递给subScrollView
                        self.mainTableView.contentOffset.y = self.topCellHeight - self.fixHeight
                        subScrollView.contentOffset.y = min(subOffset + mainOffset + scrollDistance - (self.topCellHeight - self.fixHeight), subScrollView.contentSize.height - subScrollView.frame.height)
                    } else { // mainScrollView滑动未到极限值,正常减速
                        self.mainTableView.contentOffset.y = mainOffset + scrollDistance
                        subScrollView.contentOffset.y = 0
                    }
                }
               
                self.lastCenter = self.dynamicItem.center
                
                self.bounceAnimate()
            }
            
            decelerateBehavior = behavior
            animator?.addBehavior(behavior)
        }
    }

弹簧效果

private func bounceAnimate() {
        let outsideFrame = mainTableView.contentOffset.y < 0
        
        if outsideFrame, let animator = self.animator, let _ = decelerateBehavior, bounceBehavior == nil {
            var target: CGPoint = .zero
            if mainTableView.contentOffset.y < 0 {
                dynamicItem.center = mainTableView.contentOffset
                target = .zero
                mainTableView.bounces = false
                let behavior = UIAttachmentBehavior(item: dynamicItem, attachedToAnchor: target)
                behavior.length = 0
                behavior.damping = 1
                behavior.frequency = 2
                
                self.bounceBehavior = behavior
                animator.addBehavior(behavior)
            }
        }
    }
上一篇 下一篇

猜你喜欢

热点阅读