swift协议和关联类型实现底部sheet弹窗滑动

114 阅读1分钟

控制器中懒加载的 containerView

class QianDaoCalendarSheet: UIViewController {
    lazy var containerView: SpringView = {
        let view = SpringView()
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        initialUI()
        makePanGesture()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
/// 遵守协议 `PanGestureViewProtocol`
extension QianDaoCalendarSheet: PanGestureViewProtocol {

    typealias View = SpringView
    
    var targetView: SpringView {
        self.containerView
    }
    var targetViewHeight: CGFloat {
        490
    }
    var autoDismissHeight: CGFloat? {
        300
    }
    var duration: CGFloat? {
        0.2
    }
    
    func downAnimationCompleted() {
        // dismiss viewController
        self.dismiss(animated: true)
    }
    func makePanGesture() {
        let panGes = UIPanGestureRecognizer.init()
        containerView.addGestureRecognizer(panGes)
        panGes.addTarget(self, action: #selector(handlerPanGesture(sender:)))
    }
    @objc func handlerPanGesture(sender: UIPanGestureRecognizer) {
        panGestureAction(pan: sender)
    }
}

PanGestureViewProtocol 代码

protocol PanGestureViewProtocol {

    associatedtype View: UIView
    
    /// 滑动的view
    var targetView: View { get }
    /// view高度
    var targetViewHeight: CGFloat { get }
    /// view显示的高度,小于此高度,view会自动收起
    var autoDismissHeight: CGFloat? { get }
    /// 动画时间
    var duration: CGFloat? { get }
    
    /// targetView 动画完成后调用
    func downAnimationCompleted()
}

extension PanGestureViewProtocol {
    public func panGestureAction(pan: UIPanGestureRecognizer) {
        let translation = pan.translation(in: self.targetView)
        let height = targetViewHeight - translation.y
        if translation.y < 0 {
            return
        } else {
            let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
            let SCREEN_WIDTH = UIScreen.main.bounds.size.width
            self.targetView.frame = CGRect(x: 0, y: SCREEN_HEIGHT-height, width: SCREEN_WIDTH, height: targetViewHeight)
            switch pan.state {
            case .began:
                break
            case .ended:
                if height < autoDismissHeight ?? targetViewHeight*0.5, height > 0 {
                    moveToDown(targetView)
                } else if height >= autoDismissHeight ?? targetViewHeight*0.5, height <= targetViewHeight {
                    moveToUp(targetView)
                } else if height <= 0 {
                    targetView.frame = CGRect(x: 0, y: SCREEN_HEIGHT, width: SCREEN_WIDTH, height: targetViewHeight)
                } else {
                    targetView.frame = CGRect(x: 0, y: SCREEN_HEIGHT-targetViewHeight, width: SCREEN_WIDTH, height: targetViewHeight)
                }
            default:
                break
            }
        }
    }
    private func moveToUp(_ targetView: View) {
        let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
        let SCREEN_WIDTH = UIScreen.main.bounds.size.width
        UIView.animate(withDuration: duration ?? 0.2, delay: 0, options: .curveLinear) {
            targetView.frame = CGRect(x: 0, y: SCREEN_HEIGHT - targetViewHeight, width: SCREEN_WIDTH, height: targetViewHeight)
        } completion: { _ in }
    }
    private func moveToDown(_ targetView: View) {
        let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
        let SCREEN_WIDTH = UIScreen.main.bounds.size.width
        UIView.animate(withDuration: duration ?? 0.2, delay: 0, options: .curveLinear) {
            targetView.frame = CGRect(x: 0, y: SCREEN_HEIGHT, width: SCREEN_WIDTH, height: targetViewHeight)
        } completion: { finished in
            if finished {
                downAnimationCompleted()
            } else { }
        }
    }
}

效果实现在 智能教辅 v3.1.2以后版本可查看,(首页->签到->点击日历)