diff --git a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift deleted file mode 100644 index 738c8b5cda..0000000000 --- a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift +++ /dev/null @@ -1,214 +0,0 @@ -import ExpoModulesCore -import React - -// This view will be used as a native component. Make sure to inherit from `ExpoView` -// to apply the proper styling (e.g. border radius and shadows). -class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { - var scrollViewTag: Int? { - didSet { - self.tryFindScrollView() - } - } - - private var rctScrollView: RCTScrollView? - private var rctRefreshCtrl: RCTRefreshControl? - private var cancelGestureRecognizers: [UIGestureRecognizer]? - private var animTimer: Timer? - private var initialOffset: CGFloat = 0.0 - private var didImpact: Bool = false - - required init(appContext: AppContext? = nil) { - super.init(appContext: appContext) - - let pg = UIPanGestureRecognizer(target: self, action: #selector(callOnPan(_:))) - pg.delegate = self - self.addGestureRecognizer(pg) - - let tg = UITapGestureRecognizer(target: self, action: #selector(callOnPress(_:))) - tg.isEnabled = false - tg.delegate = self - - let lpg = UILongPressGestureRecognizer(target: self, action: #selector(callOnPress(_:))) - lpg.minimumPressDuration = 0.01 - lpg.isEnabled = false - lpg.delegate = self - - self.cancelGestureRecognizers = [lpg, tg] - } - - // We don't want to recognize the scroll pan gesture and the swipe back gesture together - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer is UIPanGestureRecognizer, otherGestureRecognizer is UIPanGestureRecognizer { - return false - } - - return true - } - - // We only want the "scroll" gesture to happen whenever the pan is vertical, otherwise it will - // interfere with the native swipe back gesture. - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { - return true - } - - let velocity = gestureRecognizer.velocity(in: self) - return abs(velocity.y) > abs(velocity.x) - } - - // This will be used to cancel the scroll animation whenever we tap inside of the header. We don't need another - // recognizer for this one. - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - self.stopTimer() - } - - // This will be used to cancel the animation whenever we press inside of the scroll view. We don't want to change - // the scroll view gesture's delegate, so we add an additional recognizer to detect this. - @IBAction func callOnPress(_ sender: UITapGestureRecognizer) { - self.stopTimer() - } - - @IBAction func callOnPan(_ sender: UIPanGestureRecognizer) { - guard let rctsv = self.rctScrollView, let sv = rctsv.scrollView else { - return - } - - let translation = sender.translation(in: self).y - - if sender.state == .began { - if sv.contentOffset.y < 0 { - sv.contentOffset.y = 0 - } - - self.initialOffset = sv.contentOffset.y - } - - if sender.state == .changed { - sv.contentOffset.y = self.dampenOffset(-translation + self.initialOffset) - - if sv.contentOffset.y <= -130, !didImpact { - let generator = UIImpactFeedbackGenerator(style: .light) - generator.impactOccurred() - - self.didImpact = true - } - } - - if sender.state == .ended { - let velocity = sender.velocity(in: self).y - self.didImpact = false - - if sv.contentOffset.y <= -130 { - self.rctRefreshCtrl?.forwarderBeginRefreshing() - return - } - - // A check for a velocity under 250 prevents animations from occurring when they wouldn't in a normal - // scroll view - if abs(velocity) < 250, sv.contentOffset.y >= 0 { - return - } - - self.startDecayAnimation(translation, velocity) - } - } - - func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) { - guard let sv = self.rctScrollView?.scrollView else { - return - } - - var velocity = velocity - - self.enableCancelGestureRecognizers() - - if velocity > 0 { - velocity = min(velocity, 5000) - } else { - velocity = max(velocity, -5000) - } - - var animTranslation = -translation - self.animTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120, repeats: true) { _ in - velocity *= 0.9875 - animTranslation = (-velocity / 120) + animTranslation - - let nextOffset = self.dampenOffset(animTranslation + self.initialOffset) - - if nextOffset <= 0 { - if self.initialOffset <= 1 { - self.scrollToOffset(0) - } else { - sv.contentOffset.y = 0 - } - - self.stopTimer() - return - } else { - sv.contentOffset.y = nextOffset - } - - if abs(velocity) < 5 { - self.stopTimer() - } - } - } - - func dampenOffset(_ offset: CGFloat) -> CGFloat { - if offset < 0 { - return offset - (offset * 0.55) - } - - return offset - } - - func tryFindScrollView() { - guard let scrollViewTag = scrollViewTag else { - return - } - - // Before we switch to a different scrollview, we always want to remove the cancel gesture recognizer. - // Otherwise we might end up with duplicates when we switch back to that scrollview. - self.removeCancelGestureRecognizers() - - self.rctScrollView = self.appContext? - .findView(withTag: scrollViewTag, ofType: RCTScrollView.self) - self.rctRefreshCtrl = self.rctScrollView?.scrollView.refreshControl as? RCTRefreshControl - - self.addCancelGestureRecognizers() - } - - func addCancelGestureRecognizers() { - self.cancelGestureRecognizers?.forEach { r in - self.rctScrollView?.scrollView?.addGestureRecognizer(r) - } - } - - func removeCancelGestureRecognizers() { - self.cancelGestureRecognizers?.forEach { r in - self.rctScrollView?.scrollView?.removeGestureRecognizer(r) - } - } - - func enableCancelGestureRecognizers() { - self.cancelGestureRecognizers?.forEach { r in - r.isEnabled = true - } - } - - func disableCancelGestureRecognizers() { - self.cancelGestureRecognizers?.forEach { r in - r.isEnabled = false - } - } - - func scrollToOffset(_ offset: Int, animated: Bool = true) { - self.rctScrollView?.scroll(toOffset: CGPoint(x: 0, y: offset), animated: animated) - } - - func stopTimer() { - self.disableCancelGestureRecognizers() - self.animTimer?.invalidate() - self.animTimer = nil - } -}