Updates from Thu Mar 5

- [react_native] JS files for D1885531 | Martin Konicek
- Ported TabBarIOS to OSS and unified implementation | Nick Lockwood
- [react-packager] Add minify option as query param | Amjad Masad
- [ReactNative] Fix ExpandingText prop types | Christopher Chedeau
- [react-packager] Make dev a query param option | Amjad Masad
This commit is contained in:
Christopher Chedeau
2015-03-06 09:54:10 -08:00
parent 990979f8a6
commit 61b8c61903
62 changed files with 1290 additions and 579 deletions

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTAnimationType) {
RCTAnimationTypeSpring = 0,
RCTAnimationTypeLinear,
RCTAnimationTypeEaseIn,
RCTAnimationTypeEaseOut,
RCTAnimationTypeEaseInEaseOut,
};

View File

@@ -0,0 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
/**
* Defines a View that wants to support auto insets adjustment
*/
@protocol RCTAutoInsetsProtocol
@property (nonatomic, assign, readwrite) UIEdgeInsets contentInset;
@property (nonatomic, assign, readwrite) BOOL automaticallyAdjustContentInsets;
@end

View File

@@ -41,7 +41,6 @@ NSInteger kNeverProgressed = -10000;
@end
/**
* In general, `RCTNavigator` examines `_currentViews` (which are React child
* views), and compares them to `_navigationController.viewControllers` (which
@@ -138,7 +137,6 @@ NSInteger kNeverProgressed = -10000;
return self;
}
/**
* Invoked when either a navigation item has been popped off, or when a
* swipe-back gesture has began. The swipe-back gesture doesn't respect the
@@ -184,7 +182,6 @@ NSInteger kNeverProgressed = -10000;
@end
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
{
RCTEventDispatcher *_eventDispatcher;
@@ -204,7 +201,7 @@ NSInteger kNeverProgressed = -10000;
*
* - The run loop retains the displayLink.
* - `displayLink` retains its target.
* - We use `reactWillDestroy` to remove the `RCTNavigator`'s reference to the
* - We use `invalidate` to remove the `RCTNavigator`'s reference to the
* `displayLink` and remove the `displayLink` from the run loop.
*
*
@@ -212,7 +209,7 @@ NSInteger kNeverProgressed = -10000;
* --------------
*
* - Even though we could implement the `displayLink` cleanup without the
* `reactWillDestroy` hook by adding and removing it from the run loop at the
* `invalidate` hook by adding and removing it from the run loop at the
* right times (begin/end animation), we need to account for the possibility
* that the view itself is destroyed mid-interaction. So we always keep it
* added to the run loop, but start/stop it with interactions/animations. We
@@ -343,7 +340,7 @@ NSInteger kNeverProgressed = -10000;
NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
_dummyView.frame = (CGRect){destination};
_dummyView.frame = (CGRect){{destination}};
_currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo;
if (indexOfFrom != indexOfTo) {
@@ -450,24 +447,10 @@ NSInteger kNeverProgressed = -10000;
return self.superview ? self.superview : self.reactNavSuperviewLink;
}
- (void)addControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
id responder = [self.superview nextResponder];
while (responder && ![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
}
if (responder) {
[responder addChildViewController:controller];
[controller didMoveToParentViewController:responder];
}
}
}
- (void)reactBridgeDidFinishTransaction
{
// we can't hook up the VC hierarchy in 'init' because the subviews aren't hooked up yet,
// so we do it on demand here
// we can't hook up the VC hierarchy in 'init' because the subviews aren't
// hooked up yet, so we do it on demand here
[self addControllerToClosestParent:_navigationController];
NSInteger viewControllerCount = _navigationController.viewControllers.count;

View File

@@ -2,9 +2,9 @@
#import "RCTNavigatorManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTNavigator.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTUIManager.h"
@@ -12,7 +12,7 @@
- (UIView *)view
{
return [[RCTNavigator alloc] initWithEventDispatcher:self.eventDispatcher];
return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack)
@@ -34,16 +34,12 @@ RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack)
RCT_EXPORT();
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
if (reactTag) {
RCTNavigator *navigator = viewRegistry[reactTag];
if ([navigator isKindOfClass:[RCTNavigator class]]) {
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag);
}
RCTNavigator *navigator = viewRegistry[reactTag];
if ([navigator isKindOfClass:[RCTNavigator class]]) {
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
RCTLogError(@"Tag not specified for requestSchedulingJavaScriptNavigation");
RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag);
}
}];
}

View File

@@ -0,0 +1,10 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTPointerEvents) {
RCTPointerEventsUnspecified = 0, // Default
RCTPointerEventsNone,
RCTPointerEventsBoxNone,
RCTPointerEventsBoxOnly,
};

View File

@@ -2,6 +2,7 @@
#import "RCTScrollViewManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTScrollView.h"
@@ -9,7 +10,7 @@
- (UIView *)view
{
return [[RCTScrollView alloc] initWithEventDispatcher:self.eventDispatcher];
return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal)

View File

@@ -0,0 +1,18 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
/**
* Contains any methods related to scrolling. Any `RCTView` that has scrolling
* features should implement these methods.
*/
@protocol RCTScrollableProtocol
@property (nonatomic, readwrite, weak) NSObject<UIScrollViewDelegate> *nativeMainScrollDelegate;
@property (nonatomic, readonly) CGSize contentSize;
- (void)scrollToOffset:(CGPoint)offset;
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
@end

View File

@@ -325,6 +325,11 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return _reactSubviews;
}
- (RCTShadowView *)reactSuperview
{
return _superview;
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
for (RCTShadowView *shadowView in _reactSubviews) {

View File

@@ -1,10 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@interface RCTStaticImage : UIImageView
@property (nonatomic, assign) UIEdgeInsets capInsets;
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
@end

View File

@@ -1,55 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTStaticImage.h"
@implementation RCTStaticImage
- (void)_updateImage
{
UIImage *image = self.image;
if (!image) {
return;
}
// Apply rendering mode
if (_renderingMode != image.renderingMode) {
image = [image imageWithRenderingMode:_renderingMode];
}
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _capInsets)) {
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
}
// Apply trilinear filtering to smooth out mis-sized images
self.layer.minificationFilter = kCAFilterTrilinear;
self.layer.magnificationFilter = kCAFilterTrilinear;
super.image = image;
}
- (void)setImage:(UIImage *)image
{
if (image != super.image) {
super.image = image;
[self _updateImage];
}
}
- (void)setCapInsets:(UIEdgeInsets)capInsets
{
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
_capInsets = capInsets;
[self _updateImage];
}
}
- (void)setRenderingMode:(UIImageRenderingMode)renderingMode
{
if (_renderingMode != renderingMode) {
_renderingMode = renderingMode;
[self _updateImage];
}
}
@end

View File

@@ -1,43 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTStaticImageManager.h"
#import <UIKit/UIKit.h>
#import "RCTStaticImage.h"
#import "RCTConvert.h"
@implementation RCTStaticImageManager
- (UIView *)view
{
return [[RCTStaticImage alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(capInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, RCTStaticImage *)
{
if (json) {
if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
[view.layer addAnimation:[RCTConvert GIF:json] forKey:@"contents"];
} else {
view.image = [RCTConvert UIImage:json];
}
} else {
view.image = defaultView.image;
}
}
RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *)
{
if (json) {
view.renderingMode = UIImageRenderingModeAlwaysTemplate;
view.tintColor = [RCTConvert UIColor:json];
} else {
view.renderingMode = defaultView.renderingMode;
view.tintColor = defaultView.tintColor;
}
}
@end

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTTabBar : UIView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

139
ReactKit/Views/RCTTabBar.m Normal file
View File

@@ -0,0 +1,139 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTTabBar.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTTabBarItem.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTViewControllerProtocol.h"
#import "RCTWrapperViewController.h"
#import "UIView+ReactKit.h"
@interface RKCustomTabBarController : UITabBarController <RCTViewControllerProtocol>
@end
@implementation RKCustomTabBarController
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
_currentTopLayoutGuide = self.topLayoutGuide;
_currentBottomLayoutGuide = self.bottomLayoutGuide;
}
@end
@interface RCTTabBar() <UITabBarControllerDelegate>
@end
@implementation RCTTabBar
{
BOOL _tabsChanged;
RCTEventDispatcher *_eventDispatcher;
UITabBarController *_tabController;
NSMutableArray *_tabViews;
}
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_tabViews = [[NSMutableArray alloc] init];
_tabController = [[RKCustomTabBarController alloc] init];
_tabController.delegate = self;
[self addSubview:_tabController.view];
}
return self;
}
- (UIViewController *)backingViewController
{
return _tabController;
}
- (void)dealloc
{
_tabController.delegate = nil;
}
- (NSArray *)reactSubviews
{
return _tabViews;
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
if (![view isKindOfClass:[RCTTabBarItem class]]) {
RCTLogError(@"subview should be of type RCTTabBarItem");
return;
}
[_tabViews insertObject:view atIndex:atIndex];
_tabsChanged = YES;
}
- (void)removeReactSubview:(UIView *)subview
{
if (_tabViews.count == 0) {
RCTLogError(@"should have at least one view to remove a subview");
return;
}
[_tabViews removeObject:subview];
_tabsChanged = YES;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_tabController.view.frame = self.bounds;
}
- (void)reactBridgeDidFinishTransaction
{
// we can't hook up the VC hierarchy in 'init' because the subviews aren't
// hooked up yet, so we do it on demand here whenever a transaction has finished
[self addControllerToClosestParent:_tabController];
//[RCTView addViewController:_tabController toBackingViewControllerForView:self];
if (_tabsChanged) {
NSMutableArray *viewControllers = [NSMutableArray array];
for (RCTTabBarItem *tab in [self reactSubviews]) {
UIViewController *controller = tab.backingViewController;
if (!controller) {
controller = [[RCTWrapperViewController alloc] initWithContentView:tab
eventDispatcher:_eventDispatcher];
}
[viewControllers addObject:controller];
}
_tabController.viewControllers = viewControllers;
_tabsChanged = NO;
}
[[self reactSubviews] enumerateObjectsUsingBlock:^(RCTTabBarItem *tab, NSUInteger index, BOOL *stop) {
UIViewController *controller = _tabController.viewControllers[index];
controller.tabBarItem = tab.barItem;
if (tab.selected) {
_tabController.selectedViewController = controller;
}
}];
}
#pragma mark - UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSUInteger index = [tabBarController.viewControllers indexOfObject:viewController];
RCTTabBarItem *tab = [self reactSubviews][index];
[_eventDispatcher sendInputEventWithName:@"topTap" body:@{@"target": tab.reactTag}];
return NO;
}
@end

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@interface RCTTabBarItem : UIView
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem;
@end

View File

@@ -0,0 +1,83 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTTabBarItem.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "UIView+ReactKit.h"
@implementation RCTTabBarItem
@synthesize barItem = _barItem;
- (UITabBarItem *)barItem
{
if (!_barItem) {
_barItem = [[UITabBarItem alloc] init];
}
return _barItem;
}
- (void)setIcon:(NSString *)icon
{
static NSDictionary *systemIcons;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
systemIcons = @{
@"more": @(UITabBarSystemItemMore),
@"favorites": @(UITabBarSystemItemFavorites),
@"featured": @(UITabBarSystemItemFeatured),
@"topRated": @(UITabBarSystemItemTopRated),
@"recents": @(UITabBarSystemItemRecents),
@"contacts": @(UITabBarSystemItemContacts),
@"history": @(UITabBarSystemItemHistory),
@"bookmarks": @(UITabBarSystemItemBookmarks),
@"search": @(UITabBarSystemItemSearch),
@"downloads": @(UITabBarSystemItemDownloads),
@"mostRecent": @(UITabBarSystemItemMostRecent),
@"mostViewed": @(UITabBarSystemItemMostViewed),
};
});
// Update icon
BOOL wasSystemIcon = (systemIcons[_icon] != nil);
_icon = [icon copy];
// Check if string matches any custom images first
UIImage *image = [RCTConvert UIImage:_icon];
UITabBarItem *oldItem = _barItem;
if (image) {
// Recreate barItem if previous item was a system icon
if (wasSystemIcon) {
_barItem = nil;
self.barItem.image = image;
} else {
self.barItem.image = image;
return;
}
} else {
// Not a custom image, may be a system item?
NSNumber *systemIcon = systemIcons[icon];
if (!systemIcon) {
RCTLogError(@"The tab bar icon '%@' did not match any known image or system icon", icon);
return;
}
_barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:[systemIcon integerValue] tag:oldItem.tag];
}
// Reapply previous properties
_barItem.title = oldItem.title;
_barItem.imageInsets = oldItem.imageInsets;
_barItem.selectedImage = oldItem.selectedImage;
_barItem.badgeValue = oldItem.badgeValue;
}
- (UIViewController *)backingViewController
{
return self.superview.backingViewController;
}
@end

View File

@@ -2,6 +2,6 @@
#import "RCTViewManager.h"
@interface RCTStaticImageManager : RCTViewManager
@interface RCTTabBarItemManager : RCTViewManager
@end

View File

@@ -0,0 +1,25 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTTabBarItemManager.h"
#import "RCTConvert.h"
#import "RCTTabBarItem.h"
@implementation RCTTabBarItemManager
- (UIView *)view
{
return [[RCTTabBarItem alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(selected);
RCT_EXPORT_VIEW_PROPERTY(icon);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage);
RCT_REMAP_VIEW_PROPERTY(badgeValue, barItem.badgeValue);
RCT_CUSTOM_VIEW_PROPERTY(title, RCTTabBarItem *)
{
view.barItem.title = json ? [RCTConvert NSString:json] : defaultView.barItem.title;
view.barItem.imageInsets = [view.barItem.title length] ? UIEdgeInsetsZero : (UIEdgeInsets){6, 0, -6, 0};
}
@end

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTTabBarManager : RCTViewManager
@end

View File

@@ -0,0 +1,17 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTTabBarManager.h"
#import "RCTBridge.h"
#import "RCTTabBar.h"
@implementation RCTTabBarManager
@synthesize bridge = _bridge;
- (UIView *)view
{
return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}
@end

View File

@@ -11,7 +11,7 @@
- (UIView *)view
{
return [[RCTTextField alloc] initWithEventDispatcher:self.eventDispatcher];
return [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(caretHidden)

View File

@@ -10,14 +10,18 @@
@interface RCTView : UIView
/**
* Used to control how touch events are processed.
*/
@property (nonatomic, assign) RCTPointerEvents pointerEvents;
+ (void)autoAdjustInsetsForView:(UIView<RCTAutoInsetsProtocol> *)parentView
withScrollView:(UIScrollView *)scrollView
updateOffset:(BOOL)updateOffset;
+ (UIViewController *)backingViewControllerForView:(UIView *)view;
/**
* Find the first view controller whose view, or any subview is the specified view.
*/
+ (UIEdgeInsets)contentInsetsForView:(UIView *)curView;
@end

View File

@@ -5,6 +5,7 @@
#import "RCTAutoInsetsProtocol.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "UIView+ReactKit.h"
static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
{
@@ -23,15 +24,6 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
@implementation RCTView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_pointerEvents = RCTPointerEventsUnspecified;
}
return self;
}
- (NSString *)accessibilityLabel
{
if (super.accessibilityLabel) {
@@ -108,19 +100,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
}
+ (UIViewController *)backingViewControllerForView:(UIView *)view
{
id responder = [view nextResponder];
if ([responder isKindOfClass:[UIViewController class]]) {
return responder;
}
return nil;
}
+ (UIEdgeInsets)contentInsetsForView:(UIView *)view
{
while (view) {
UIViewController *controller = [self backingViewControllerForView:view];
UIViewController *controller = view.backingViewController;
if (controller) {
return (UIEdgeInsets){
controller.topLayoutGuide.length, 0,

View File

@@ -0,0 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* A simple protocol that any React-managed ViewControllers should implement.
* We need all of our ViewControllers to cache layoutGuide changes so any View
* in our View hierarchy can access accurate layoutGuide info at any time.
*/
@protocol RCTViewControllerProtocol <NSObject>
@property (nonatomic, readonly, strong) id<UILayoutSupport> currentTopLayoutGuide;
@property (nonatomic, readonly, strong) id<UILayoutSupport> currentBottomLayoutGuide;
@end

View File

@@ -23,14 +23,6 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
*/
@property (nonatomic, strong) RCTBridge *bridge;
/**
* The event dispatcher is used to send events back to the JavaScript application.
* It can either be used directly by the module, or passed on to instantiated
* view subclasses so that they can handle their own events.
*/
// TODO: remove this, as it can be accessed directly from bridge
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
/**
* The module name exposed to React JS. If omitted, this will be inferred
* automatically by using the view module's class name. It is better to not

View File

@@ -14,11 +14,6 @@
@synthesize bridge = _bridge;
- (RCTEventDispatcher *)eventDispatcher
{
return _bridge.eventDispatcher;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases

View File

@@ -0,0 +1,35 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* Logical node in a tree of application components. Both `ShadowView`s and
* `UIView+ReactKit`s conform to this. Allows us to write utilities that
* reason about trees generally.
*/
@protocol RCTViewNodeProtocol <NSObject>
@property (nonatomic, copy) NSNumber *reactTag;
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
- (NSMutableArray *)reactSubviews;
- (id<RCTViewNodeProtocol>)reactSuperview;
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
// View is an RCTRootView
- (BOOL)isReactRootView;
@optional
// TODO: Deprecate this
// This method is called after layout has been performed for all views known
// to the RCTViewManager. It is only called on UIViews, not shadow views.
- (void)reactBridgeDidFinishTransaction;
@end
// TODO: this is kinda dumb - let's come up with a
// better way of identifying root react views please!
static inline BOOL RCTIsReactRootView(NSNumber *reactTag) {
return reactTag.integerValue % 10 == 1;
}

View File

@@ -2,6 +2,8 @@
#import <UIKit/UIKit.h>
#import "RCTViewControllerProtocol.h"
@class RCTEventDispatcher;
@class RCTNavItem;
@class RCTWrapperViewController;
@@ -13,7 +15,7 @@ didMoveToNavigationController:(UINavigationController *)navigationController;
@end
@interface RCTWrapperViewController : UIViewController
@interface RCTWrapperViewController : UIViewController <RCTViewControllerProtocol>
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@@ -21,7 +23,7 @@ didMoveToNavigationController:(UINavigationController *)navigationController;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@property (nonatomic, readwrite, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong, readwrite) RCTNavItem *navItem;
@property (nonatomic, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong) RCTNavItem *navItem;
@end

View File

@@ -2,22 +2,30 @@
#import "RCTWrapperViewController.h"
#import <UIKit/UIScrollView.h>
#import "RCTEventDispatcher.h"
#import "RCTNavItem.h"
#import "RCTUtils.h"
#import "RCTViewControllerProtocol.h"
#import "UIView+ReactKit.h"
@implementation RCTWrapperViewController
{
UIView *_wrapperView;
UIView *_contentView;
RCTEventDispatcher *_eventDispatcher;
CGFloat _previousTopLayout;
CGFloat _previousBottomLayout;
}
- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithNibName:nil bundle:nil])) {
if (self = [super initWithNibName:nil bundle:nil]) {
_contentView = contentView;
_eventDispatcher = eventDispatcher;
self.automaticallyAdjustsScrollViewInsets = NO;
@@ -25,80 +33,91 @@
return self;
}
- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [self initWithContentView:navItem eventDispatcher:eventDispatcher])) {
if (self = [self initWithContentView:navItem eventDispatcher:eventDispatcher]) {
_navItem = navItem;
}
return self;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
_currentTopLayoutGuide = self.topLayoutGuide;
_currentBottomLayoutGuide = self.bottomLayoutGuide;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:!_navItem animated:animated];
if (!_navItem) {
return;
}
// TODO: find a way to make this less-tightly coupled to navigation controller
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
self.navigationItem.title = _navItem.title;
[self _configureNavBarStyle];
if (_navItem.rightButtonTitle.length > 0) {
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle
style:UIBarButtonItemStyleDone
target:self
action:@selector(rightButtonTapped)];
}
if (_navItem.backButtonTitle.length > 0) {
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
}
}
- (void)_configureNavBarStyle
{
UINavigationBar *bar = self.navigationController.navigationBar;
if (_navItem.barTintColor) {
bar.barTintColor = _navItem.barTintColor;
}
if (_navItem.tintColor) {
BOOL canSetTintColor = _navItem.barTintColor == nil;
if (canSetTintColor) {
bar.tintColor = _navItem.tintColor;
[self.navigationController setNavigationBarHidden:!_navItem animated:animated];
if (!_navItem) {
return;
}
self.navigationItem.title = _navItem.title;
UINavigationBar *bar = self.navigationController.navigationBar;
if (_navItem.barTintColor) {
bar.barTintColor = _navItem.barTintColor;
}
if (_navItem.tintColor) {
BOOL canSetTintColor = _navItem.barTintColor == nil;
if (canSetTintColor) {
bar.tintColor = _navItem.tintColor;
}
}
if (_navItem.titleTextColor) {
[bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}];
}
if (_navItem.rightButtonTitle.length > 0) {
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle
style:UIBarButtonItemStyleDone
target:self
action:@selector(handleNavRightButtonTapped)];
}
if (_navItem.backButtonTitle.length > 0) {
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
}
}
if (_navItem.titleTextColor) {
[bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}];
}
}
- (void)loadView
{
// Add a wrapper so that UIViewControllerWrapperView (managed by the
// add a wrapper so that UIViewControllerWrapperView (managed by the
// UINavigationController) doesn't end up resetting the frames for
// `contentView` which is a react-managed view.
self.view = [[UIView alloc] init];
[self.view addSubview:_contentView];
//`contentView` which is a react-managed view.
_wrapperView = [[UIView alloc] initWithFrame:_contentView.bounds];
[_wrapperView addSubview:_contentView];
self.view = _wrapperView;
}
- (void)rightButtonTapped
- (void)handleNavRightButtonTapped
{
[_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap" body:@{@"target":_navItem.reactTag}];
[_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap"
body:@{@"target":_navItem.reactTag}];
}
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// There's no clear setter for navigation controllers, but did move to parent view controller
// provides the desired effect. This is called after a pop finishes, be it a swipe to go back
// or a standard tap on the back button
// There's no clear setter for navigation controllers, but did move to parent
// view controller provides the desired effect. This is called after a pop
// finishes, be it a swipe to go back or a standard tap on the back button
[super didMoveToParentViewController:parent];
if (parent == nil || [parent isKindOfClass:[UINavigationController class]]) {
[self.navigationListener wrapperViewController:self didMoveToNavigationController:(UINavigationController *)parent];

View File

@@ -8,4 +8,17 @@
@interface UIView (ReactKit) <RCTViewNodeProtocol>
/**
* This method finds and returns the containing view controller for the view.
*/
- (UIViewController *)backingViewController;
/**
* This method attaches the specified controller as a child of the
* the owning view controller of this view. Returns NO if no view
* controller is found (which may happen if the view is not currently
* attached to the view hierarchy).
*/
- (void)addControllerToClosestParent:(UIViewController *)controller;
@end

View File

@@ -5,6 +5,7 @@
#import <objc/runtime.h>
#import "RCTAssert.h"
#import "RCTWrapperViewController.h"
@implementation UIView (ReactKit)
@@ -20,7 +21,7 @@
- (BOOL)isReactRootView
{
return NO;
return RCTIsReactRootView(self.reactTag);
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
@@ -39,7 +40,7 @@
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview.superview == self, @"");
RCTAssert(subview.superview == self, @"%@ is a not a subview of %@", subview, self);
[subview removeFromSuperview];
}
@@ -48,4 +49,34 @@
return self.subviews;
}
- (UIView *)reactSuperview
{
return self.superview;
}
- (UIViewController *)backingViewController
{
id responder = [self nextResponder];
if ([responder isKindOfClass:[RCTWrapperViewController class]]) {
return responder;
}
return nil;
}
- (void)addControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
UIView *parentView = (UIView *)self.reactSuperview;
while (parentView) {
if (parentView.backingViewController) {
[parentView.backingViewController addChildViewController:controller];
[controller didMoveToParentViewController:parentView.backingViewController];
break;
}
parentView = (UIView *)parentView.reactSuperview;
}
return;
}
}
@end