作者 周泳恩

Init

*.pbxproj -text
\ No newline at end of file
... ...
# OSX
#
.DS_Store
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
\ No newline at end of file
... ...
# react-native-vode-mj-refresh
## Getting started
`$ npm install react-native-vode-mj-refresh --save`
### Mostly automatic installation
`$ react-native link react-native-vode-mj-refresh`
### Manual installation
#### iOS
1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]`
2. Go to `node_modules` ➜ `react-native-vode-mj-refresh` and add `RNVodeMjRefresh.xcodeproj`
3. In XCode, in the project navigator, select your project. Add `libRNVodeMjRefresh.a` to your project's `Build Phases` ➜ `Link Binary With Libraries`
4. Run your project (`Cmd+R`)<
## Usage
```javascript
import RNVodeMjRefresh from 'react-native-vode-mj-refresh';
// TODO: What to do with the module?
RNVodeMjRefresh;
```
\ No newline at end of file
... ...
import { NativeModules } from 'react-native';
const { RNVodeMjRefresh } = NativeModules;
export default RNVodeMjRefresh;
... ...
//
// RCTMJRefreshHeader.h
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/6/25.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MJRefresh.h"
@interface RCTMJRefreshHeader : MJRefreshHeader
/** 普通闲置状态 */
//MJRefreshStateIdle = 1,
/** 松开就可以进行刷新的状态 */
//MJRefreshStatePulling,
/** 正在刷新中的状态 */
//MJRefreshStateRefreshing,
/** 即将刷新的状态 */
//MJRefreshStateWillRefresh,
@property (nonatomic, copy) RCTBubblingEventBlock onMJRefreshIdle;
@property (nonatomic, copy) RCTBubblingEventBlock onMJWillRefresh;
@property (nonatomic, copy) RCTBubblingEventBlock onMJRefresh;
@property (nonatomic, copy) RCTBubblingEventBlock onMJReleaseToRefresh;
@property (nonatomic, copy) RCTBubblingEventBlock onMJPulling;
@end
... ...
//
// RCTMJRefreshHeader.m
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/6/25.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import "RCTMJRefreshHeader.h"
@implementation RCTMJRefreshHeader
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState;
switch (state) {
case MJRefreshStateIdle:
if(self.reactTag)self.onMJRefreshIdle(@{@"target": self.reactTag});
break;
case MJRefreshStatePulling:
if(self.reactTag)self.onMJReleaseToRefresh(@{@"target":self.reactTag});
break;
case MJRefreshStateRefreshing:
if(self.reactTag)self.onMJRefresh(@{@"target":self.reactTag});
break;
default:
break;
}
}
- (void)setPullingPercent:(CGFloat)pullingPercent
{
[super setPullingPercent:pullingPercent];
if(self.reactTag)self.onMJPulling(@{@"target":self.reactTag,@"percent":[NSNumber numberWithFloat:pullingPercent]});
}
@end
... ...
//
// RCTMJRefreshViewManager.m
// React
//
// Created by Macbook on 2018/6/21.
// Copyright © 2018年 Facebook. All rights reserved.
//
#import "MJRefresh.h"
#import <React/RCTViewManager.h>
#import <Foundation/Foundation.h>
#import "RCTMJRefreshHeader.h"
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/UIView+React.h>
@interface RCTMJRefreshViewManager:RCTViewManager
@end
@implementation RCTMJRefreshViewManager
{
RCTMJRefreshHeader *header;
}
RCT_EXPORT_MODULE()
-(UIView *)view
{
header=[RCTMJRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];
return header;
}
- (NSArray *)customDirectEventTypes
{
return @[
@"onMJRefresh",
@"onMJReleaseToRefresh",
@"onMJRefreshIdle",
@"onMJPulling"
];
}
RCT_EXPORT_VIEW_PROPERTY(onMJRefresh, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onMJRefreshIdle, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onMJReleaseToRefresh, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onMJPulling, RCTBubblingEventBlock);
RCT_EXPORT_METHOD(finishRefresh:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMJRefreshHeader *> *viewRegistry) {
RCTMJRefreshHeader *view = viewRegistry[reactTag];
if (![view isKindOfClass:[RCTMJRefreshHeader class]]) {
RCTLogError(@"Invalid view returned from registry, expecting RCTBarrage, got: %@", view);
} else {
[view endRefreshing];
}
}];
}
RCT_EXPORT_METHOD(beginRefresh:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMJRefreshHeader *> *viewRegistry) {
RCTMJRefreshHeader *view = viewRegistry[reactTag];
if (![view isKindOfClass:[RCTMJRefreshHeader class]]) {
RCTLogError(@"Invalid view returned from registry, expecting RCTBarrage, got: %@", view);
} else {
[view beginRefreshing];
}
}];
}
-(void)loadNewData
{
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
@end
... ...
//
// RCTMJScrollContentShadowView.h
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/7/6.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <React/RCTShadowView.h>
@interface RCTMJScrollContentShadowView : RCTShadowView
@end
... ...
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMJScrollContentShadowView.h"
#import <yoga/Yoga.h>
#import <React/RCTUtils.h>
@implementation RCTMJScrollContentShadowView
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
layoutContext:(RCTLayoutContext)layoutContext
{
if (layoutMetrics.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
// Motivation:
// Yoga place `contentView` on the right side of `scrollView` when RTL layout is enfoced.
// That breaks everything; it is completely pointless to (re)position `contentView`
// because it is `contentView`'s job. So, we work around it here.
layoutContext.absolutePosition.x += layoutMetrics.frame.size.width;
layoutMetrics.frame.origin.x = 0;
}
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
}
@end
... ...
//
// RCTMJScrollContentView.h
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/7/6.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import <React/RCTView.h>
@interface RCTMJScrollContentView : RCTView
@end
... ...
//
// RCTMJScrollContentView.m
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/7/6.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <React/RCTView.h>
#import <React/RCTAssert.h>
#import <React/UIView+React.h>
#import "RCTMJScrollView.h"
#import "RCTMJScrollContentView.h"
@implementation RCTMJScrollContentView
- (void)reactSetFrame:(CGRect)frame
{
[super reactSetFrame:frame];
RCTMJScrollView *scrollView = (RCTMJScrollView *)self.superview.superview;
if (!scrollView) {
return;
}
RCTAssert([scrollView isKindOfClass:[RCTMJScrollView class]],
@"Unexpected view hierarchy of RCTScrollView component.");
[scrollView updateContentOffsetIfNeeded];
}
@end
... ...
//
// RCTMJScrollContentViewMananger.m
// RCTMJRefreshHeader
//
// Created by Macbook on 2018/7/6.
// Copyright © 2018年 Macbook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
#import "RCTMJScrollContentShadowView.h"
#import "RCTMJScrollContentView.h"
@interface RCTMJScrollContentViewManager : RCTViewManager
@end
@implementation RCTMJScrollContentViewManager
RCT_EXPORT_MODULE()
- (RCTMJScrollContentView *)view
{
return [RCTMJScrollContentView new];
}
- (RCTShadowView *)shadowView
{
return [RCTMJScrollContentShadowView new];
}
@end
... ...
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIScrollView.h>
#import <React/RCTAutoInsetsProtocol.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTScrollableProtocol.h>
#import <React/RCTView.h>
@protocol UIScrollViewDelegate;
@interface RCTMJScrollView : RCTView <UIScrollViewDelegate, RCTScrollableProtocol, RCTAutoInsetsProtocol>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* The `RCTScrollView` may have at most one single subview. This will ensure
* that the scroll view's `contentSize` will be efficiently set to the size of
* the single subview's frame. That frame size will be determined somewhat
* efficiently since it will have already been computed by the off-main-thread
* layout system.
*/
@property (nonatomic, readonly) UIView *contentView;
/**
* If the `contentSize` is not specified (or is specified as {0, 0}, then the
* `contentSize` will automatically be determined by the size of the subview.
*/
@property (nonatomic, assign) CGSize contentSize;
/**
* The underlying scrollView (TODO: can we remove this?)
*/
@property (nonatomic, readonly) UIScrollView *scrollView;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames;
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition;
@property (nonatomic, assign) int snapToInterval;
@property (nonatomic, copy) NSString *snapToAlignment;
// NOTE: currently these event props are only declared so we can export the
// event names to JS - we don't call the blocks directly because scroll events
// need to be coalesced before sending, for performance reasons.
@property (nonatomic, copy) RCTDirectEventBlock onScrollBeginDrag;
@property (nonatomic, copy) RCTDirectEventBlock onScroll;
@property (nonatomic, copy) RCTDirectEventBlock onScrollEndDrag;
@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollBegin;
@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd;
@end
@interface RCTMJScrollView (Internal)
- (void)updateContentOffsetIfNeeded;
@end
@interface RCTEventDispatcher (RCTMJScrollView)
/**
* Send a fake scroll event.
*/
- (void)sendFakeScrollEvent:(NSNumber *)reactTag;
@end
... ...
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMJScrollView.h"
#import <UIKit/UIKit.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTUtils.h>
#import "UIView+Private1.h"
#import <React/UIView+React.h>
#import "MJRefresh.h"
#if !TARGET_OS_TV
#import <React/RCTRefreshControl.h>
#endif
@interface RCTMJScrollEvent : NSObject <RCTEvent>
- (instancetype)initWithEventName:(NSString *)eventName
reactTag:(NSNumber *)reactTag
scrollViewContentOffset:(CGPoint)scrollViewContentOffset
scrollViewContentInset:(UIEdgeInsets)scrollViewContentInset
scrollViewContentSize:(CGSize)scrollViewContentSize
scrollViewFrame:(CGRect)scrollViewFrame
scrollViewZoomScale:(CGFloat)scrollViewZoomScale
userData:(NSDictionary *)userData
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;
@end
@implementation RCTMJScrollEvent
{
CGPoint _scrollViewContentOffset;
UIEdgeInsets _scrollViewContentInset;
CGSize _scrollViewContentSize;
CGRect _scrollViewFrame;
CGFloat _scrollViewZoomScale;
NSDictionary *_userData;
uint16_t _coalescingKey;
}
@synthesize viewTag = _viewTag;
@synthesize eventName = _eventName;
- (instancetype)initWithEventName:(NSString *)eventName
reactTag:(NSNumber *)reactTag
scrollViewContentOffset:(CGPoint)scrollViewContentOffset
scrollViewContentInset:(UIEdgeInsets)scrollViewContentInset
scrollViewContentSize:(CGSize)scrollViewContentSize
scrollViewFrame:(CGRect)scrollViewFrame
scrollViewZoomScale:(CGFloat)scrollViewZoomScale
userData:(NSDictionary *)userData
coalescingKey:(uint16_t)coalescingKey
{
RCTAssertParam(reactTag);
if ((self = [super init])) {
_eventName = [eventName copy];
_viewTag = reactTag;
_scrollViewContentOffset = scrollViewContentOffset;
_scrollViewContentInset = scrollViewContentInset;
_scrollViewContentSize = scrollViewContentSize;
_scrollViewFrame = scrollViewFrame;
_scrollViewZoomScale = scrollViewZoomScale;
_userData = userData;
_coalescingKey = coalescingKey;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (uint16_t)coalescingKey
{
return _coalescingKey;
}
- (NSDictionary *)body
{
NSDictionary *body = @{
@"contentOffset": @{
@"x": @(_scrollViewContentOffset.x),
@"y": @(_scrollViewContentOffset.y)
},
@"contentInset": @{
@"top": @(_scrollViewContentInset.top),
@"left": @(_scrollViewContentInset.left),
@"bottom": @(_scrollViewContentInset.bottom),
@"right": @(_scrollViewContentInset.right)
},
@"contentSize": @{
@"width": @(_scrollViewContentSize.width),
@"height": @(_scrollViewContentSize.height)
},
@"layoutMeasurement": @{
@"width": @(_scrollViewFrame.size.width),
@"height": @(_scrollViewFrame.size.height)
},
@"zoomScale": @(_scrollViewZoomScale ?: 1),
};
if (_userData) {
NSMutableDictionary *mutableBody = [body mutableCopy];
[mutableBody addEntriesFromDictionary:_userData];
body = mutableBody;
}
return body;
}
- (BOOL)canCoalesce
{
return YES;
}
- (RCTMJScrollEvent *)coalesceWithEvent:(RCTMJScrollEvent *)newEvent
{
NSArray<NSDictionary *> *updatedChildFrames = [_userData[@"updatedChildFrames"] arrayByAddingObjectsFromArray:newEvent->_userData[@"updatedChildFrames"]];
if (updatedChildFrames) {
NSMutableDictionary *userData = [newEvent->_userData mutableCopy];
userData[@"updatedChildFrames"] = updatedChildFrames;
newEvent->_userData = userData;
}
return newEvent;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}
- (NSArray *)arguments
{
return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), [self body]];
}
@end
/**
* Include a custom scroll view subclass because we want to limit certain
* default UIKit behaviors such as textFields automatically scrolling
* scroll views that contain them.
*/
@interface RCTMJCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
@property (nonatomic, assign) BOOL centerContent;
#if !TARGET_OS_TV
@property (nonatomic, strong) RCTRefreshControl *rctRefreshControl;
@property (nonatomic, assign) BOOL pinchGestureEnabled;
#endif
@end
@implementation RCTMJCustomScrollView
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
[self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)];
if ([self respondsToSelector:@selector(setSemanticContentAttribute:)]) {
// We intentionaly force `UIScrollView`s `semanticContentAttribute` to `LTR` here
// because this attribute affects a position of vertical scrollbar; we don't want this
// scrollbar flip because we also flip it with whole `UIScrollView` flip.
self.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
}
#if !TARGET_OS_TV
_pinchGestureEnabled = YES;
#endif
}
return self;
}
- (UIView *)contentView
{
return ((RCTMJScrollView *)self.superview).contentView;
}
/**
* @return Whether or not the scroll view interaction should be blocked because
* JS was found to be the responder.
*/
- (BOOL)_shouldDisableScrollInteraction
{
// Since this may be called on every pan, we need to make sure to only climb
// the hierarchy on rare occasions.
UIView *JSResponder = [RCTUIManager JSResponder];
if (JSResponder && JSResponder != self.superview) {
BOOL superviewHasResponder = [self isDescendantOfView:JSResponder];
return superviewHasResponder;
}
return NO;
}
- (void)handleCustomPan:(__unused UIPanGestureRecognizer *)sender
{
if ([self _shouldDisableScrollInteraction] && ![[RCTUIManager JSResponder] isKindOfClass:[RCTMJScrollView class]]) {
self.panGestureRecognizer.enabled = NO;
self.panGestureRecognizer.enabled = YES;
// TODO: If mid bounce, animate the scroll view to a non-bounced position
// while disabling (but only if `stopScrollInteractionIfJSHasResponder` was
// called *during* a `pan`). Currently, it will just snap into place which
// is not so bad either.
// Another approach:
// self.scrollEnabled = NO;
// self.scrollEnabled = YES;
}
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
// Limiting scroll area to an area where we actually have content.
CGSize contentSize = self.contentSize;
UIEdgeInsets contentInset = self.contentInset;
CGSize fullSize = CGSizeMake(
contentSize.width + contentInset.left + contentInset.right,
contentSize.height + contentInset.top + contentInset.bottom);
rect = CGRectIntersection((CGRect){CGPointZero, fullSize}, rect);
if (CGRectIsNull(rect)) {
return;
}
[super scrollRectToVisible:rect animated:animated];
}
/**
* Returning `YES` cancels touches for the "inner" `view` and causes a scroll.
* Returning `NO` causes touches to be directed to that inner view and prevents
* the scroll view from scrolling.
*
* `YES` -> Allows scrolling.
* `NO` -> Doesn't allow scrolling.
*
* By default this returns NO for all views that are UIControls and YES for
* everything else. What that does is allows scroll views to scroll even when a
* touch started inside of a `UIControl` (`UIButton` etc). For React scroll
* views, we want the default to be the same behavior as `UIControl`s so we
* return `YES` by default. But there's one case where we want to block the
* scrolling no matter what: When JS believes it has its own responder lock on
* a view that is *above* the scroll view in the hierarchy. So we abuse this
* `touchesShouldCancelInContentView` API in order to stop the scroll view from
* scrolling in this case.
*
* We are not aware of *any* other solution to the problem because alternative
* approaches require that we disable the scrollview *before* touches begin or
* move. This approach (`touchesShouldCancelInContentView`) works even if the
* JS responder is set after touches start/move because
* `touchesShouldCancelInContentView` is called as soon as the scroll view has
* been touched and dragged *just* far enough to decide to begin the "drag"
* movement of the scroll interaction. Returning `NO`, will cause the drag
* operation to fail.
*
* `touchesShouldCancelInContentView` will stop the *initialization* of a
* scroll pan gesture and most of the time this is sufficient. On rare
* occasion, the scroll gesture would have already initialized right before JS
* notifies native of the JS responder being set. In order to recover from that
* timing issue we have a fallback that kills any ongoing pan gesture that
* occurs when native is notified of a JS responder.
*
* Note: Explicitly returning `YES`, instead of relying on the default fixes
* (at least) one bug where if you have a UIControl inside a UIScrollView and
* tap on the UIControl and then start dragging (to scroll), it won't scroll.
* Chat with @andras for more details.
*
* In order to have this called, you must have delaysContentTouches set to NO
* (which is the not the `UIKit` default).
*/
- (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view
{
//TODO: shouldn't this call super if _shouldDisableScrollInteraction returns NO?
return ![self _shouldDisableScrollInteraction];
}
/*
* Automatically centers the content such that if the content is smaller than the
* ScrollView, we force it to be centered, but when you zoom or the content otherwise
* becomes larger than the ScrollView, there is no padding around the content but it
* can still fill the whole view.
*/
- (void)setContentOffset:(CGPoint)contentOffset
{
UIView *contentView = [self contentView];
if (contentView && _centerContent) {
CGSize subviewSize = contentView.frame.size;
CGSize scrollViewSize = self.bounds.size;
if (subviewSize.width <= scrollViewSize.width) {
contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0;
}
if (subviewSize.height <= scrollViewSize.height) {
contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0;
}
}
super.contentOffset = contentOffset;
}
- (void)setFrame:(CGRect)frame
{
// Preserving and revalidating `contentOffset`.
CGPoint originalOffset = self.contentOffset;
[super setFrame:frame];
UIEdgeInsets contentInset = self.contentInset;
CGSize contentSize = self.contentSize;
// If contentSize has not been measured yet we can't check bounds.
if (CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.contentOffset = originalOffset;
} else {
// Make sure offset don't exceed bounds. This could happen on screen rotation.
CGSize boundsSize = self.bounds.size;
self.contentOffset = CGPointMake(
MAX(-contentInset.left, MIN(contentSize.width - boundsSize.width + contentInset.right, originalOffset.x)),
MAX(-contentInset.top, MIN(contentSize.height - boundsSize.height + contentInset.bottom, originalOffset.y)));
}
}
#if !TARGET_OS_TV
- (void)setRctRefreshControl:(RCTRefreshControl *)refreshControl
{
if (_rctRefreshControl) {
[_rctRefreshControl removeFromSuperview];
}
_rctRefreshControl = refreshControl;
[self addSubview:_rctRefreshControl];
}
- (void)setPinchGestureEnabled:(BOOL)pinchGestureEnabled
{
self.pinchGestureRecognizer.enabled = pinchGestureEnabled;
_pinchGestureEnabled = pinchGestureEnabled;
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
// ScrollView enables pinch gesture late in its lifecycle. So simply setting it
// in the setter gets overriden when the view loads.
self.pinchGestureRecognizer.enabled = _pinchGestureEnabled;
}
#endif //TARGET_OS_TV
@end
@interface RCTMJScrollView () <RCTUIManagerObserver>
@end
@implementation RCTMJScrollView
{
RCTEventDispatcher *_eventDispatcher;
CGRect _prevFirstVisibleFrame;
__weak UIView *_firstVisibleView;
RCTMJCustomScrollView *_scrollView;
UIView *_contentView;
NSTimeInterval _lastScrollDispatchTime;
NSMutableArray<NSValue *> *_cachedChildFrames;
BOOL _allowNextScrollNoMatterWhat;
CGRect _lastClippedToRect;
uint16_t _coalescingKey;
NSString *_lastEmittedEventName;
NSHashTable *_scrollListeners;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_scrollView = [[RCTMJCustomScrollView alloc] initWithFrame:CGRectZero];
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_scrollView.delegate = self;
_scrollView.delaysContentTouches = NO;
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
// `contentInsetAdjustmentBehavior` is only available since iOS 11.
// We set the default behavior to "never" so that iOS
// doesn't do weird things to UIScrollView insets automatically
// and keeps it as an opt-in behavior.
if ([_scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
_scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#endif
_automaticallyAdjustContentInsets = YES;
_DEPRECATED_sendUpdatedChildFrames = NO;
_contentInset = UIEdgeInsetsZero;
_contentSize = CGSizeZero;
_lastClippedToRect = CGRectNull;
_scrollEventThrottle = 0.0;
_lastScrollDispatchTime = 0;
_cachedChildFrames = [NSMutableArray new];
_scrollListeners = [NSHashTable weakObjectsHashTable];
[self addSubview:_scrollView];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
static inline void RCTApplyTransformationAccordingLayoutDirection(UIView *view, UIUserInterfaceLayoutDirection layoutDirection) {
view.transform =
layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight ?
CGAffineTransformIdentity :
CGAffineTransformMakeScale(-1, 1);
}
- (void)setReactLayoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
{
[super setReactLayoutDirection:layoutDirection];
RCTApplyTransformationAccordingLayoutDirection(_scrollView, layoutDirection);
RCTApplyTransformationAccordingLayoutDirection(_contentView, layoutDirection);
}
- (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
{
// Does nothing
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
[super insertReactSubview:view atIndex:atIndex];
#if !TARGET_OS_TV
if ([view isKindOfClass:[RCTRefreshControl class]]) {
[_scrollView setRctRefreshControl:(RCTRefreshControl *)view];
} else if ([view isKindOfClass:[MJRefreshHeader class]]){
_scrollView.mj_header = (MJRefreshHeader *)view;
} else
#endif
{
RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
_contentView = view;
RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection);
[(RCTMJCustomScrollView *)_scrollView addSubview:view];
}
}
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
#if !TARGET_OS_TV
if ([subview isKindOfClass:[RCTRefreshControl class]]) {
[_scrollView setRctRefreshControl:nil];
} else if ([subview isKindOfClass:[MJRefreshHeader class]]){
_scrollView.mj_header = nil;
} else
#endif
{
RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
_contentView = nil;
}
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subviews are managed by `insertReactSubview:atIndex:`
}
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
if ([changedProps containsObject:@"contentSize"]) {
[self updateContentOffsetIfNeeded];
}
}
- (BOOL)centerContent
{
return _scrollView.centerContent;
}
- (void)setCenterContent:(BOOL)centerContent
{
_scrollView.centerContent = centerContent;
}
- (void)setClipsToBounds:(BOOL)clipsToBounds
{
super.clipsToBounds = clipsToBounds;
_scrollView.clipsToBounds = clipsToBounds;
}
- (void)dealloc
{
_scrollView.delegate = nil;
if (_maintainVisibleContentPosition != nil) {
[_eventDispatcher.bridge.uiManager.observerCoordinator removeObserver:self];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
RCTAssert(self.subviews.count == 1, @"we should only have exactly one subview");
RCTAssert([self.subviews lastObject] == _scrollView, @"our only subview should be a scrollview");
#if !TARGET_OS_TV
// Adjust the refresh control frame if the scrollview layout changes.
RCTRefreshControl *refreshControl = _scrollView.rctRefreshControl;
if (refreshControl && refreshControl.refreshing) {
refreshControl.frame = (CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}};
}
#endif
[self updateClippedSubviews];
}
- (void)updateClippedSubviews
{
// Find a suitable view to use for clipping
UIView *clipView = [self react_findClipView];
if (!clipView) {
return;
}
static const CGFloat leeway = 1.0;
const CGSize contentSize = _scrollView.contentSize;
const CGRect bounds = _scrollView.bounds;
const BOOL scrollsHorizontally = contentSize.width > bounds.size.width;
const BOOL scrollsVertically = contentSize.height > bounds.size.height;
const BOOL shouldClipAgain =
CGRectIsNull(_lastClippedToRect) ||
!CGRectEqualToRect(_lastClippedToRect, bounds) ||
(scrollsHorizontally && (bounds.size.width < leeway || fabs(_lastClippedToRect.origin.x - bounds.origin.x) >= leeway)) ||
(scrollsVertically && (bounds.size.height < leeway || fabs(_lastClippedToRect.origin.y - bounds.origin.y) >= leeway));
if (shouldClipAgain) {
const CGRect clipRect = CGRectInset(clipView.bounds, -leeway, -leeway);
[self react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView];
_lastClippedToRect = bounds;
}
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
if (UIEdgeInsetsEqualToEdgeInsets(contentInset, _contentInset)) {
return;
}
CGPoint contentOffset = _scrollView.contentOffset;
_contentInset = contentInset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:NO];
_scrollView.contentOffset = contentOffset;
}
- (BOOL)isHorizontal:(UIScrollView *)scrollView
{
return scrollView.contentSize.width > self.frame.size.width;
}
- (void)scrollToOffset:(CGPoint)offset
{
[self scrollToOffset:offset animated:YES];
}
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated
{
if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) {
// Ensure at least one scroll event will fire
_allowNextScrollNoMatterWhat = YES;
[_scrollView setContentOffset:offset animated:animated];
}
}
/**
* If this is a vertical scroll view, scrolls to the bottom.
* If this is a horizontal scroll view, scrolls to the right.
*/
- (void)scrollToEnd:(BOOL)animated
{
BOOL isHorizontal = [self isHorizontal:_scrollView];
CGPoint offset;
if (isHorizontal) {
CGFloat offsetX = _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right;
offset = CGPointMake(fmax(offsetX, 0), 0);
} else {
CGFloat offsetY = _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom;
offset = CGPointMake(0, fmax(offsetY, 0));
}
if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) {
// Ensure at least one scroll event will fire
_allowNextScrollNoMatterWhat = YES;
[_scrollView setContentOffset:offset animated:animated];
}
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[_scrollView zoomToRect:rect animated:animated];
}
- (void)refreshContentInset
{
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
}
#pragma mark - ScrollView delegate
#define RCT_SEND_SCROLL_EVENT(_eventName, _userData) { \
NSString *eventName = NSStringFromSelector(@selector(_eventName)); \
[self sendScrollEventWithName:eventName scrollView:_scrollView userData:_userData]; \
}
#define RCT_FORWARD_SCROLL_EVENT(call) \
for (NSObject<UIScrollViewDelegate> *scrollViewListener in _scrollListeners) { \
if ([scrollViewListener respondsToSelector:_cmd]) { \
[scrollViewListener call]; \
} \
}
#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \
- (void)delegateMethod:(UIScrollView *)scrollView \
{ \
RCT_SEND_SCROLL_EVENT(eventName, nil); \
RCT_FORWARD_SCROLL_EVENT(delegateMethod:scrollView); \
}
RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin)
RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
{
[_scrollListeners addObject:scrollListener];
}
- (void)removeScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
{
[_scrollListeners removeObject:scrollListener];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateClippedSubviews];
NSTimeInterval now = CACurrentMediaTime();
/**
* TODO: this logic looks wrong, and it may be because it is. Currently, if _scrollEventThrottle
* is set to zero (the default), the "didScroll" event is only sent once per scroll, instead of repeatedly
* while scrolling as expected. However, if you "fix" that bug, ScrollView will generate repeated
* warnings, and behave strangely (ListView works fine however), so don't fix it unless you fix that too!
*/
if (_allowNextScrollNoMatterWhat ||
(_scrollEventThrottle > 0 && _scrollEventThrottle < (now - _lastScrollDispatchTime))) {
if (_DEPRECATED_sendUpdatedChildFrames) {
// Calculate changed frames
RCT_SEND_SCROLL_EVENT(onScroll, (@{@"updatedChildFrames": [self calculateChildFramesData]}));
} else {
RCT_SEND_SCROLL_EVENT(onScroll, nil);
}
// Update dispatch time
_lastScrollDispatchTime = now;
_allowNextScrollNoMatterWhat = NO;
}
RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView);
}
- (NSArray<NSDictionary *> *)calculateChildFramesData
{
NSMutableArray<NSDictionary *> *updatedChildFrames = [NSMutableArray new];
[[_contentView reactSubviews] enumerateObjectsUsingBlock:
^(UIView *subview, NSUInteger idx, __unused BOOL *stop) {
// Check if new or changed
CGRect newFrame = subview.frame;
BOOL frameChanged = NO;
if (self->_cachedChildFrames.count <= idx) {
frameChanged = YES;
[self->_cachedChildFrames addObject:[NSValue valueWithCGRect:newFrame]];
} else if (!CGRectEqualToRect(newFrame, [self->_cachedChildFrames[idx] CGRectValue])) {
frameChanged = YES;
self->_cachedChildFrames[idx] = [NSValue valueWithCGRect:newFrame];
}
// Create JS frame object
if (frameChanged) {
[updatedChildFrames addObject: @{
@"index": @(idx),
@"x": @(newFrame.origin.x),
@"y": @(newFrame.origin.y),
@"width": @(newFrame.size.width),
@"height": @(newFrame.size.height),
}];
}
}];
return updatedChildFrames;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
_allowNextScrollNoMatterWhat = YES; // Ensure next scroll event is recorded, regardless of throttle
RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil);
RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginDragging:scrollView);
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
// snapToInterval
// An alternative to enablePaging which allows setting custom stopping intervals,
// smaller than a full page size. Often seen in apps which feature horizonally
// scrolling items. snapToInterval does not enforce scrolling one interval at a time
// but guarantees that the scroll will stop at an interval point.
if (self.snapToInterval) {
CGFloat snapToIntervalF = (CGFloat)self.snapToInterval;
// Find which axis to snap
BOOL isHorizontal = [self isHorizontal:scrollView];
// What is the current offset?
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
// Offset based on desired alignment
CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height;
CGFloat alignmentOffset = 0.0f;
if ([self.snapToAlignment isEqualToString: @"center"]) {
alignmentOffset = (frameLength * 0.5f) + (snapToIntervalF * 0.5f);
} else if ([self.snapToAlignment isEqualToString: @"end"]) {
alignmentOffset = frameLength;
}
// Pick snap point based on direction and proximity
CGFloat fractionalIndex = (targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF;
NSInteger snapIndex =
velocityAlongAxis > 0.0 ?
ceil(fractionalIndex) :
velocityAlongAxis < 0.0 ?
floor(fractionalIndex) :
round(fractionalIndex);
CGFloat newTargetContentOffset = (snapIndex * snapToIntervalF) - alignmentOffset;
// Set new targetContentOffset
if (isHorizontal) {
targetContentOffset->x = newTargetContentOffset;
} else {
targetContentOffset->y = newTargetContentOffset;
}
}
NSDictionary *userData = @{
@"velocity": @{
@"x": @(velocity.x),
@"y": @(velocity.y)
},
@"targetContentOffset": @{
@"x": @(targetContentOffset->x),
@"y": @(targetContentOffset->y)
}
};
RCT_SEND_SCROLL_EVENT(onScrollEndDrag, userData);
RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset);
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDragging:scrollView willDecelerate:decelerate);
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil);
RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginZooming:scrollView withView:view);
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
RCT_SEND_SCROLL_EVENT(onScrollEndDrag, nil);
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndZooming:scrollView withView:view atScale:scale);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// Fire a final scroll event
_allowNextScrollNoMatterWhat = YES;
[self scrollViewDidScroll:scrollView];
// Fire the end deceleration event
RCT_SEND_SCROLL_EVENT(onMomentumScrollEnd, nil);
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDecelerating:scrollView);
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
// Fire a final scroll event
_allowNextScrollNoMatterWhat = YES;
[self scrollViewDidScroll:scrollView];
// Fire the end deceleration event
RCT_SEND_SCROLL_EVENT(onMomentumScrollEnd, nil);
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndScrollingAnimation:scrollView);
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
for (NSObject<UIScrollViewDelegate> *scrollListener in _scrollListeners) {
if ([scrollListener respondsToSelector:_cmd] &&
![scrollListener scrollViewShouldScrollToTop:scrollView]) {
return NO;
}
}
return YES;
}
- (UIView *)viewForZoomingInScrollView:(__unused UIScrollView *)scrollView
{
return _contentView;
}
#pragma mark - Setters
- (CGSize)_calculateViewportSize
{
CGSize viewportSize = self.bounds.size;
if (_automaticallyAdjustContentInsets) {
UIEdgeInsets contentInsets = [RCTView contentInsetsForView:self];
viewportSize = CGSizeMake(self.bounds.size.width - contentInsets.left - contentInsets.right,
self.bounds.size.height - contentInsets.top - contentInsets.bottom);
}
return viewportSize;
}
- (CGPoint)calculateOffsetForContentSize:(CGSize)newContentSize
{
CGPoint oldOffset = _scrollView.contentOffset;
CGPoint newOffset = oldOffset;
CGSize oldContentSize = _scrollView.contentSize;
CGSize viewportSize = [self _calculateViewportSize];
BOOL fitsinViewportY = oldContentSize.height <= viewportSize.height && newContentSize.height <= viewportSize.height;
if (newContentSize.height < oldContentSize.height && !fitsinViewportY) {
CGFloat offsetHeight = oldOffset.y + viewportSize.height;
if (oldOffset.y < 0) {
// overscrolled on top, leave offset alone
} else if (offsetHeight > oldContentSize.height) {
// overscrolled on the bottom, preserve overscroll amount
newOffset.y = MAX(0, oldOffset.y - (oldContentSize.height - newContentSize.height));
} else if (offsetHeight > newContentSize.height) {
// offset falls outside of bounds, scroll back to end of list
newOffset.y = MAX(0, newContentSize.height - viewportSize.height);
}
}
BOOL fitsinViewportX = oldContentSize.width <= viewportSize.width && newContentSize.width <= viewportSize.width;
if (newContentSize.width < oldContentSize.width && !fitsinViewportX) {
CGFloat offsetHeight = oldOffset.x + viewportSize.width;
if (oldOffset.x < 0) {
// overscrolled at the beginning, leave offset alone
} else if (offsetHeight > oldContentSize.width && newContentSize.width > viewportSize.width) {
// overscrolled at the end, preserve overscroll amount as much as possible
newOffset.x = MAX(0, oldOffset.x - (oldContentSize.width - newContentSize.width));
} else if (offsetHeight > newContentSize.width) {
// offset falls outside of bounds, scroll back to end
newOffset.x = MAX(0, newContentSize.width - viewportSize.width);
}
}
// all other cases, offset doesn't change
return newOffset;
}
/**
* Once you set the `contentSize`, to a nonzero value, it is assumed to be
* managed by you, and we'll never automatically compute the size for you,
* unless you manually reset it back to {0, 0}
*/
- (CGSize)contentSize
{
if (!CGSizeEqualToSize(_contentSize, CGSizeZero)) {
return _contentSize;
}
return _contentView.frame.size;
}
- (void)updateContentOffsetIfNeeded
{
CGSize contentSize = self.contentSize;
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
// When contentSize is set manually, ScrollView internals will reset
// contentOffset to {0, 0}. Since we potentially set contentSize whenever
// anything in the ScrollView updates, we workaround this issue by manually
// adjusting contentOffset whenever this happens
CGPoint newOffset = [self calculateOffsetForContentSize:contentSize];
_scrollView.contentSize = contentSize;
_scrollView.contentOffset = newOffset;
}
}
// maintainVisibleContentPosition is used to allow seamless loading of content from both ends of
// the scrollview without the visible content jumping in position.
- (void)setMaintainVisibleContentPosition:(NSDictionary *)maintainVisibleContentPosition
{
if (maintainVisibleContentPosition != nil && _maintainVisibleContentPosition == nil) {
[_eventDispatcher.bridge.uiManager.observerCoordinator addObserver:self];
} else if (maintainVisibleContentPosition == nil && _maintainVisibleContentPosition != nil) {
[_eventDispatcher.bridge.uiManager.observerCoordinator removeObserver:self];
}
_maintainVisibleContentPosition = maintainVisibleContentPosition;
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting:(RCTUIManager *)manager
{
RCTAssertUIManagerQueue();
[manager prependUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
BOOL horz = [self isHorizontal:self->_scrollView];
NSUInteger minIdx = [self->_maintainVisibleContentPosition[@"minIndexForVisible"] integerValue];
for (NSUInteger ii = minIdx; ii < self->_contentView.subviews.count; ++ii) {
// Find the first entirely visible view. This must be done after we update the content offset
// or it will tend to grab rows that were made visible by the shift in position
UIView *subview = self->_contentView.subviews[ii];
if ((horz
? subview.frame.origin.x >= self->_scrollView.contentOffset.x
: subview.frame.origin.y >= self->_scrollView.contentOffset.y) ||
ii == self->_contentView.subviews.count - 1) {
self->_prevFirstVisibleFrame = subview.frame;
self->_firstVisibleView = subview;
break;
}
}
}];
[manager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
if (self->_maintainVisibleContentPosition == nil) {
return; // The prop might have changed in the previous UIBlocks, so need to abort here.
}
NSNumber *autoscrollThreshold = self->_maintainVisibleContentPosition[@"autoscrollToTopThreshold"];
// TODO: detect and handle/ignore re-ordering
if ([self isHorizontal:self->_scrollView]) {
CGFloat deltaX = self->_firstVisibleView.frame.origin.x - self->_prevFirstVisibleFrame.origin.x;
if (ABS(deltaX) > 0.1) {
self->_scrollView.contentOffset = CGPointMake(
self->_scrollView.contentOffset.x + deltaX,
self->_scrollView.contentOffset.y
);
if (autoscrollThreshold != nil) {
// If the offset WAS within the threshold of the start, animate to the start.
if (self->_scrollView.contentOffset.x - deltaX <= [autoscrollThreshold integerValue]) {
[self scrollToOffset:CGPointMake(0, self->_scrollView.contentOffset.y) animated:YES];
}
}
}
} else {
CGRect newFrame = self->_firstVisibleView.frame;
CGFloat deltaY = newFrame.origin.y - self->_prevFirstVisibleFrame.origin.y;
if (ABS(deltaY) > 0.1) {
self->_scrollView.contentOffset = CGPointMake(
self->_scrollView.contentOffset.x,
self->_scrollView.contentOffset.y + deltaY
);
if (autoscrollThreshold != nil) {
// If the offset WAS within the threshold of the start, animate to the start.
if (self->_scrollView.contentOffset.y - deltaY <= [autoscrollThreshold integerValue]) {
[self scrollToOffset:CGPointMake(self->_scrollView.contentOffset.x, 0) animated:YES];
}
}
}
}
}];
}
// Note: setting several properties of UIScrollView has the effect of
// resetting its contentOffset to {0, 0}. To prevent this, we generate
// setters here that will record the contentOffset beforehand, and
// restore it after the property has been set.
#define RCT_SET_AND_PRESERVE_OFFSET(setter, getter, type) \
- (void)setter:(type)value \
{ \
CGPoint contentOffset = _scrollView.contentOffset; \
[_scrollView setter:value]; \
_scrollView.contentOffset = contentOffset; \
} \
- (type)getter \
{ \
return [_scrollView getter]; \
}
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, alwaysBounceHorizontal, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, alwaysBounceVertical, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBounces, bounces, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, bouncesZoom, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, canCancelContentTouches, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, decelerationRate, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, isDirectionalLockEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, indicatorStyle, UIScrollViewIndicatorStyle)
RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, maximumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, minimumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, isScrollEnabled, BOOL)
#if !TARGET_OS_TV
RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, isPagingEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, scrollsToTop, BOOL)
#endif
RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIEdgeInsets);
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior
{
// `contentInsetAdjustmentBehavior` is available since iOS 11.
if ([_scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
CGPoint contentOffset = _scrollView.contentOffset;
_scrollView.contentInsetAdjustmentBehavior = behavior;
_scrollView.contentOffset = contentOffset;
}
}
#endif
- (void)sendScrollEventWithName:(NSString *)eventName
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
if (![_lastEmittedEventName isEqualToString:eventName]) {
_coalescingKey++;
_lastEmittedEventName = [eventName copy];
}
RCTMJScrollEvent *scrollEvent = [[RCTMJScrollEvent alloc] initWithEventName:eventName
reactTag:self.reactTag
scrollViewContentOffset:scrollView.contentOffset
scrollViewContentInset:scrollView.contentInset
scrollViewContentSize:scrollView.contentSize
scrollViewFrame:scrollView.frame
scrollViewZoomScale:scrollView.zoomScale
userData:userData
coalescingKey:_coalescingKey];
[_eventDispatcher sendEvent:scrollEvent];
}
@end
@implementation RCTEventDispatcher (RCTMJScrollView)
- (void)sendFakeScrollEvent:(NSNumber *)reactTag
{
// Use the selector here in case the onScroll block property is ever renamed
NSString *eventName = NSStringFromSelector(@selector(onScroll));
RCTMJScrollEvent *fakeScrollEvent = [[RCTMJScrollEvent alloc] initWithEventName:eventName
reactTag:reactTag
scrollViewContentOffset:CGPointZero
scrollViewContentInset:UIEdgeInsetsZero
scrollViewContentSize:CGSizeZero
scrollViewFrame:CGRectZero
scrollViewZoomScale:0
userData:nil
coalescingKey:0];
[self sendEvent:fakeScrollEvent];
}
@end
... ...
#import <React/RCTConvert.h>
#import <React/RCTViewManager.h>
@interface RCTConvert (UIScrollView)
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
@end
@interface RCTMJScrollViewManager : RCTViewManager
@end
... ...
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMJScrollViewManager.h"
#import <React/RCTBridge.h>
#import "RCTMJScrollView.h"
#import <React/RCTShadowView.h>
#import <React/RCTUIManager.h>
@interface RCTMJScrollView (Private1)
- (NSArray<NSDictionary *> *)calculateChildFramesData;
@end
@implementation RCTConvert (UIScrollView)
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
@"none": @(UIScrollViewKeyboardDismissModeNone),
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
// Backwards compatibility
@"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
}), UIScrollViewKeyboardDismissModeNone, integerValue)
RCT_ENUM_CONVERTER(UIScrollViewIndicatorStyle, (@{
@"default": @(UIScrollViewIndicatorStyleDefault),
@"black": @(UIScrollViewIndicatorStyleBlack),
@"white": @(UIScrollViewIndicatorStyleWhite),
}), UIScrollViewIndicatorStyleDefault, integerValue)
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
RCT_ENUM_CONVERTER(UIScrollViewContentInsetAdjustmentBehavior, (@{
@"automatic": @(UIScrollViewContentInsetAdjustmentAutomatic),
@"scrollableAxes": @(UIScrollViewContentInsetAdjustmentScrollableAxes),
@"never": @(UIScrollViewContentInsetAdjustmentNever),
@"always": @(UIScrollViewContentInsetAdjustmentAlways),
}), UIScrollViewContentInsetAdjustmentNever, integerValue)
#endif
@end
@implementation RCTMJScrollViewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTMJScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL)
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL)
RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL)
RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL)
RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL)
RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle)
RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
#if !TARGET_OS_TV
RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
RCT_REMAP_VIEW_PROPERTY(pinchGestureEnabled, scrollView.pinchGestureEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
#endif
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(DEPRECATED_sendUpdatedChildFrames, BOOL)
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
#endif
// overflow is used both in css-layout as well as by react-native. In css-layout
// we always want to treat overflow as scroll but depending on what the overflow
// is set to from js we want to clip drawing or not. This piece of code ensures
// that css-layout is always treating the contents of a scroll container as
// overflow: 'scroll'.
RCT_CUSTOM_SHADOW_PROPERTY(overflow, YGOverflow, RCTShadowView) {
#pragma unused (json)
view.overflow = YGOverflowScroll;
}
RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMJScrollView *> *viewRegistry) {
RCTMJScrollView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) {
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
return;
}
CGSize size = view.scrollView.contentSize;
callback(@[@{
@"width" : @(size.width),
@"height" : @(size.height)
}]);
}];
}
RCT_EXPORT_METHOD(calculateChildFrames:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMJScrollView *> *viewRegistry) {
RCTMJScrollView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) {
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
return;
}
NSArray<NSDictionary *> *childFrames = [view calculateChildFramesData];
if (childFrames) {
callback(@[childFrames]);
}
}];
}
RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
offsetX:(CGFloat)x
offsetY:(CGFloat)y
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){x, y} animated:animated];
} else {
RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
"with tag #%@", view, reactTag);
}
}];
}
RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToEnd:animated];
} else {
RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
"with tag #%@", view, reactTag);
}
}];
}
RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag
withRect:(CGRect)rect
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
UIView *view = viewRegistry[reactTag];
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view zoomToRect:rect animated:animated];
} else {
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ "
"with tag #%@", view, reactTag);
}
}];
}
RCT_EXPORT_METHOD(flashScrollIndicators:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTMJScrollView *> *viewRegistry){
RCTMJScrollView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) {
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
return;
}
[view.scrollView flashScrollIndicators];
}];
}
@end
... ...
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@interface UIView (Private1)
// remove clipped subviews implementation
- (void)react_remountAllSubviews;
- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView;
- (UIView *)react_findClipView;
@end
... ...
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif
@interface RNVodeMjRefresh : NSObject <RCTBridgeModule>
@end
\ No newline at end of file
... ...
#import "RNVodeMjRefresh.h"
@implementation RNVodeMjRefresh
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_MODULE()
@end
\ No newline at end of file
... ...
Pod::Spec.new do |s|
s.name = "RNVodeMjRefresh"
s.version = "1.0.0"
s.summary = "RNVodeMjRefresh"
s.description = <<-DESC
RNVodeMjRefresh
DESC
s.homepage = ""
s.license = "MIT"
# s.license = { :type => "MIT", :file => "FILE_LICENSE" }
s.author = { "author" => "author@domain.cn" }
s.platform = :ios, "7.0"
s.source = { :git => "https://github.com/author/RNVodeMjRefresh.git", :tag => "master" }
s.source_files = "RNVodeMjRefresh/**/*.{h,m}"
s.requires_arc = true
s.dependency "React"
s.dependency 'MJRefresh', "~> 3.1.1"
#s.dependency "others"
end
\ No newline at end of file
... ...
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
842BE70425402C7F008D3A2E /* RCTMJScrollContentShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE6FB25402C7F008D3A2E /* RCTMJScrollContentShadowView.m */; };
842BE70525402C7F008D3A2E /* RCTMJRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE6FC25402C7F008D3A2E /* RCTMJRefreshHeader.m */; };
842BE70625402C7F008D3A2E /* RCTMJScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE6FD25402C7F008D3A2E /* RCTMJScrollView.m */; };
842BE70725402C7F008D3A2E /* RCTMJScrollViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE6FE25402C7F008D3A2E /* RCTMJScrollViewManager.m */; };
842BE70825402C7F008D3A2E /* RCTMJScrollContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE70025402C7F008D3A2E /* RCTMJScrollContentView.m */; };
842BE70925402C7F008D3A2E /* RCTMJRefreshViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE70125402C7F008D3A2E /* RCTMJRefreshViewManager.m */; };
842BE70A25402C7F008D3A2E /* RCTMJScrollContentViewMananger.m in Sources */ = {isa = PBXBuildFile; fileRef = 842BE70325402C7F008D3A2E /* RCTMJScrollContentViewMananger.m */; };
842BE70C25402C95008D3A2E /* RCTMJRefreshHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE6F725402C7F008D3A2E /* RCTMJRefreshHeader.h */; };
842BE70D25402C95008D3A2E /* RCTMJScrollView.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE6F825402C7F008D3A2E /* RCTMJScrollView.h */; };
842BE70E25402C95008D3A2E /* RCTMJScrollContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE6F925402C7F008D3A2E /* RCTMJScrollContentView.h */; };
842BE70F25402C95008D3A2E /* RCTMJScrollViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE6FA25402C7F008D3A2E /* RCTMJScrollViewManager.h */; };
842BE71025402C95008D3A2E /* UIView+Private1.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE6FF25402C7F008D3A2E /* UIView+Private1.h */; };
842BE71125402C95008D3A2E /* RCTMJScrollContentShadowView.h in Headers */ = {isa = PBXBuildFile; fileRef = 842BE70225402C7F008D3A2E /* RCTMJScrollContentShadowView.h */; };
842BE71225402C95008D3A2E /* RNVodeMjRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNVodeMjRefresh.h */; };
B3E7B58A1CC2AC0600A0062D /* RNVodeMjRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNVodeMjRefresh.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNVodeMjRefresh.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNVodeMjRefresh.a; sourceTree = BUILT_PRODUCTS_DIR; };
842BE6F725402C7F008D3A2E /* RCTMJRefreshHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMJRefreshHeader.h; sourceTree = "<group>"; };
842BE6F825402C7F008D3A2E /* RCTMJScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMJScrollView.h; sourceTree = "<group>"; };
842BE6F925402C7F008D3A2E /* RCTMJScrollContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMJScrollContentView.h; sourceTree = "<group>"; };
842BE6FA25402C7F008D3A2E /* RCTMJScrollViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMJScrollViewManager.h; sourceTree = "<group>"; };
842BE6FB25402C7F008D3A2E /* RCTMJScrollContentShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJScrollContentShadowView.m; sourceTree = "<group>"; };
842BE6FC25402C7F008D3A2E /* RCTMJRefreshHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJRefreshHeader.m; sourceTree = "<group>"; };
842BE6FD25402C7F008D3A2E /* RCTMJScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJScrollView.m; sourceTree = "<group>"; };
842BE6FE25402C7F008D3A2E /* RCTMJScrollViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJScrollViewManager.m; sourceTree = "<group>"; };
842BE6FF25402C7F008D3A2E /* UIView+Private1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Private1.h"; sourceTree = "<group>"; };
842BE70025402C7F008D3A2E /* RCTMJScrollContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJScrollContentView.m; sourceTree = "<group>"; };
842BE70125402C7F008D3A2E /* RCTMJRefreshViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJRefreshViewManager.m; sourceTree = "<group>"; };
842BE70225402C7F008D3A2E /* RCTMJScrollContentShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMJScrollContentShadowView.h; sourceTree = "<group>"; };
842BE70325402C7F008D3A2E /* RCTMJScrollContentViewMananger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMJScrollContentViewMananger.m; sourceTree = "<group>"; };
B3E7B5881CC2AC0600A0062D /* RNVodeMjRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNVodeMjRefresh.h; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* RNVodeMjRefresh.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNVodeMjRefresh.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNVodeMjRefresh.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
842BE6F625402C7F008D3A2E /* RCTMJRefreshHeader */,
B3E7B5881CC2AC0600A0062D /* RNVodeMjRefresh.h */,
B3E7B5891CC2AC0600A0062D /* RNVodeMjRefresh.m */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
842BE6F625402C7F008D3A2E /* RCTMJRefreshHeader */ = {
isa = PBXGroup;
children = (
842BE6F725402C7F008D3A2E /* RCTMJRefreshHeader.h */,
842BE6F825402C7F008D3A2E /* RCTMJScrollView.h */,
842BE6F925402C7F008D3A2E /* RCTMJScrollContentView.h */,
842BE6FA25402C7F008D3A2E /* RCTMJScrollViewManager.h */,
842BE6FB25402C7F008D3A2E /* RCTMJScrollContentShadowView.m */,
842BE6FC25402C7F008D3A2E /* RCTMJRefreshHeader.m */,
842BE6FD25402C7F008D3A2E /* RCTMJScrollView.m */,
842BE6FE25402C7F008D3A2E /* RCTMJScrollViewManager.m */,
842BE6FF25402C7F008D3A2E /* UIView+Private1.h */,
842BE70025402C7F008D3A2E /* RCTMJScrollContentView.m */,
842BE70125402C7F008D3A2E /* RCTMJRefreshViewManager.m */,
842BE70225402C7F008D3A2E /* RCTMJScrollContentShadowView.h */,
842BE70325402C7F008D3A2E /* RCTMJScrollContentViewMananger.m */,
);
path = RCTMJRefreshHeader;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
842BE70B25402C86008D3A2E /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
842BE70C25402C95008D3A2E /* RCTMJRefreshHeader.h in Headers */,
842BE70D25402C95008D3A2E /* RCTMJScrollView.h in Headers */,
842BE70E25402C95008D3A2E /* RCTMJScrollContentView.h in Headers */,
842BE70F25402C95008D3A2E /* RCTMJScrollViewManager.h in Headers */,
842BE71025402C95008D3A2E /* UIView+Private1.h in Headers */,
842BE71125402C95008D3A2E /* RCTMJScrollContentShadowView.h in Headers */,
842BE71225402C95008D3A2E /* RNVodeMjRefresh.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNVodeMjRefresh */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNVodeMjRefresh" */;
buildPhases = (
842BE70B25402C86008D3A2E /* Headers */,
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNVodeMjRefresh;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNVodeMjRefresh.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNVodeMjRefresh" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNVodeMjRefresh */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
842BE70525402C7F008D3A2E /* RCTMJRefreshHeader.m in Sources */,
842BE70925402C7F008D3A2E /* RCTMJRefreshViewManager.m in Sources */,
842BE70A25402C7F008D3A2E /* RCTMJScrollContentViewMananger.m in Sources */,
842BE70425402C7F008D3A2E /* RCTMJScrollContentShadowView.m in Sources */,
842BE70625402C7F008D3A2E /* RCTMJScrollView.m in Sources */,
B3E7B58A1CC2AC0600A0062D /* RNVodeMjRefresh.m in Sources */,
842BE70825402C7F008D3A2E /* RCTMJScrollContentView.m in Sources */,
842BE70725402C7F008D3A2E /* RCTMJScrollViewManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNVodeMjRefresh;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNVodeMjRefresh;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNVodeMjRefresh" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNVodeMjRefresh" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}
... ...
// !$*UTF8*$!
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:RNVodeMjRefresh.xcodeproj">
</FileRef>
</Workspace>
\ No newline at end of file
... ...
{
"name": "react-native-vode-mj-refresh",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"react-native"
],
"author": "",
"license": "",
"peerDependencies": {
"react-native": "^0.41.2"
}
}
... ...