remove old module
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"platforms": ["ios"],
|
|
||||||
"ios": {
|
|
||||||
"modules": ["ExpoScrollForwarderModule"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export {ExpoScrollForwarderView} from './src/ExpoScrollForwarderView'
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
Pod::Spec.new do |s|
|
|
||||||
s.name = 'ExpoScrollForwarder'
|
|
||||||
s.version = '1.0.0'
|
|
||||||
s.summary = 'Forward scroll gesture from UIView to UIScrollView'
|
|
||||||
s.description = 'Forward scroll gesture from UIView to UIScrollView'
|
|
||||||
s.author = 'bluesky-social'
|
|
||||||
s.homepage = 'https://github.com/bluesky-social/social-app'
|
|
||||||
s.platforms = { :ios => '13.4', :tvos => '13.4' }
|
|
||||||
s.source = { git: '' }
|
|
||||||
s.static_framework = true
|
|
||||||
|
|
||||||
s.dependency 'ExpoModulesCore'
|
|
||||||
|
|
||||||
# Swift/Objective-C compatibility
|
|
||||||
s.pod_target_xcconfig = {
|
|
||||||
'DEFINES_MODULE' => 'YES',
|
|
||||||
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
|
||||||
}
|
|
||||||
|
|
||||||
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
||||||
end
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import ExpoModulesCore
|
|
||||||
|
|
||||||
public class ExpoScrollForwarderModule: Module {
|
|
||||||
public func definition() -> ModuleDefinition {
|
|
||||||
Name("ExpoScrollForwarder")
|
|
||||||
|
|
||||||
View(ExpoScrollForwarderView.self) {
|
|
||||||
Prop("scrollViewTag") { (view: ExpoScrollForwarderView, prop: Int) in
|
|
||||||
view.scrollViewTag = prop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
import ExpoModulesCore
|
|
||||||
|
|
||||||
// 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 scrollView: UIScrollView?
|
|
||||||
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<UITouch>, 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 sv = self.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.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
|
|
||||||
}
|
|
||||||
|
|
||||||
private func findScrollView(in view: UIView, foundCount: Int) -> UIScrollView? {
|
|
||||||
var foundCount = foundCount
|
|
||||||
if let sv = view as? UIScrollView { return sv }
|
|
||||||
for child in view.subviews {
|
|
||||||
if let found = findScrollView(in: child, foundCount: foundCount) {
|
|
||||||
if foundCount == 1 {
|
|
||||||
print("found sv: \(found)")
|
|
||||||
// return found
|
|
||||||
} else {
|
|
||||||
print("found sv: \(found)")
|
|
||||||
foundCount += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryFindScrollView() {
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
guard let sv = self.findScrollView(in: self.superview!.superview!.superview!, foundCount: 0) else {
|
|
||||||
print("⚠️ ExpoScrollForwarder: couldn’t find UIScrollView under tag \(tag)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scrollView = sv
|
|
||||||
self.rctRefreshCtrl = sv.refreshControl as? RCTRefreshControl
|
|
||||||
|
|
||||||
self.addCancelGestureRecognizers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addCancelGestureRecognizers() {
|
|
||||||
self.cancelGestureRecognizers?.forEach { r in
|
|
||||||
self.scrollView?.addGestureRecognizer(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeCancelGestureRecognizers() {
|
|
||||||
self.cancelGestureRecognizers?.forEach { r in
|
|
||||||
self.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.scrollView?.scrollRectToVisible(CGRect(x: 0, y: offset, width: 0, height: 0), animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopTimer() {
|
|
||||||
self.disableCancelGestureRecognizers()
|
|
||||||
self.animTimer?.invalidate()
|
|
||||||
self.animTimer = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface ExpoScrollForwarderViewProps {
|
|
||||||
scrollViewTag: number | null
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import {requireNativeViewManager} from 'expo-modules-core'
|
|
||||||
|
|
||||||
import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
|
||||||
|
|
||||||
const NativeView: React.ComponentType<ExpoScrollForwarderViewProps> =
|
|
||||||
requireNativeViewManager('ExpoScrollForwarder')
|
|
||||||
|
|
||||||
export function ExpoScrollForwarderView({
|
|
||||||
children,
|
|
||||||
...rest
|
|
||||||
}: ExpoScrollForwarderViewProps) {
|
|
||||||
return <NativeView {...rest}>{children}</NativeView>
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types'
|
|
||||||
|
|
||||||
export function ExpoScrollForwarderView({
|
|
||||||
children,
|
|
||||||
}: React.PropsWithChildren<ExpoScrollForwarderViewProps>) {
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
@@ -77,6 +77,7 @@ static const CGFloat kMinimumVelocity = 5.0;
|
|||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[self stopAnimation];
|
[self stopAnimation];
|
||||||
|
[self removeCancelGestureRecognizers];
|
||||||
_svcv = nil;
|
_svcv = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ static const CGFloat kMinimumVelocity = 5.0;
|
|||||||
{
|
{
|
||||||
[super prepareForRecycle];
|
[super prepareForRecycle];
|
||||||
[self stopAnimation];
|
[self stopAnimation];
|
||||||
|
[self removeCancelGestureRecognizers];
|
||||||
_svcv = nil;
|
_svcv = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,10 +211,6 @@ static const CGFloat kMinimumVelocity = 5.0;
|
|||||||
|
|
||||||
if (sv.contentOffset.y <= -kPullThreshold) {
|
if (sv.contentOffset.y <= -kPullThreshold) {
|
||||||
[self refresh];
|
[self refresh];
|
||||||
// if ([_svcv.scrollView.refreshControl isKindOfClass:[RCTRefreshControl class]]) {
|
|
||||||
// RCTRefreshControl *refreshCtrl = (RCTRefreshControl *) _svcv.scrollView.refreshControl;
|
|
||||||
// [refreshCtrl forwarderBeginRefreshing];
|
|
||||||
// }
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,12 +351,9 @@ static const CGFloat kMinimumVelocity = 5.0;
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) refresh
|
- (void)refresh
|
||||||
{
|
{
|
||||||
// if ([_svcv.scrollView.refreshControl isKindOfClass:[RCTPullToRefreshViewComponentView class]]) {
|
// TODO: implement
|
||||||
// RCTPullToRefreshViewComponentView *refreshView = (RCTPullToRefreshViewComponentView *) _svcv.scrollView.refreshControl;
|
|
||||||
// [refreshView beginRefreshingProgrammatically];
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<RCTComponentViewProtocol> ScrollForwarderViewCls(void)
|
Class<RCTComponentViewProtocol> ScrollForwarderViewCls(void)
|
||||||
@@ -367,35 +362,3 @@ Class<RCTComponentViewProtocol> ScrollForwarderViewCls(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
//- (void)forwarderBeginRefreshing
|
|
||||||
//{
|
|
||||||
// _refreshingProgrammatically = NO;
|
|
||||||
//
|
|
||||||
// [self sizeToFit];
|
|
||||||
//
|
|
||||||
// if (!self.scrollView) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// UIScrollView *scrollView = (UIScrollView *)self.scrollView;
|
|
||||||
//
|
|
||||||
// [UIView animateWithDuration:0.3
|
|
||||||
// delay:0
|
|
||||||
// options:UIViewAnimationOptionBeginFromCurrentState
|
|
||||||
// animations:^(void) {
|
|
||||||
// // Whenever we call this method, the scrollview will always be at a position of
|
|
||||||
// // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
|
|
||||||
// [scrollView setContentOffset:CGPointMake(0, -65)];
|
|
||||||
// }
|
|
||||||
// completion:^(__unused BOOL finished) {
|
|
||||||
// [super beginRefreshing];
|
|
||||||
// [self setCurrentRefreshingState:super.refreshing];
|
|
||||||
//
|
|
||||||
// if (self->_onRefresh) {
|
|
||||||
// self->_onRefresh(nil);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ];
|
|
||||||
//}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user