Skip to content

Instantly share code, notes, and snippets.

@didierbrun
Last active November 5, 2017 00:49
Show Gist options
  • Save didierbrun/0895f231f5de768f2720dd2b97786db2 to your computer and use it in GitHub Desktop.
Save didierbrun/0895f231f5de768f2720dd2b97786db2 to your computer and use it in GitHub Desktop.
Hack react-native-navigation
#import "RCCNavigationController.h"
#import "RCCViewController.h"
#import "RCCManager.h"
#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
#import <React/RCTRootView.h>
#import <objc/runtime.h>
#import "RCCTitleViewHelper.h"
#import "RCCCustomBarButtonItem.h"
#import "UIViewController+Rotation.h"
#import "RCTHelpers.h"
@implementation RCCNavigationController
{
BOOL _transitioning;
NSMutableArray *_queuedViewControllers;
}
NSString const *CALLBACK_ASSOCIATED_KEY = @"RCCNavigationController.CALLBACK_ASSOCIATED_KEY";
NSString const *CALLBACK_ASSOCIATED_ID = @"RCCNavigationController.CALLBACK_ASSOCIATED_ID";
-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self supportedControllerOrientations];
}
- (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary*)globalProps bridge:(RCTBridge *)bridge
{
_queuedViewControllers = [NSMutableArray new];
NSString *component = props[@"component"];
if (!component) return nil;
NSDictionary *passProps = props[@"passProps"];
NSDictionary *navigatorStyle = props[@"style"];
RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:globalProps bridge:bridge];
if (!viewController) return nil;
viewController.controllerId = props[@"id"];
NSArray *leftButtons = props[@"leftButtons"];
if (leftButtons)
{
[self setButtons:leftButtons viewController:viewController side:@"left" animated:NO];
}
NSArray *rightButtons = props[@"rightButtons"];
if (rightButtons)
{
[self setButtons:rightButtons viewController:viewController side:@"right" animated:NO];
}
self = [super initWithRootViewController:viewController];
if (!self) return nil;
self.delegate = self;
self.navigationBar.translucent = NO; // default
[self processTitleView:viewController
props:props
style:navigatorStyle];
[self setRotation:props];
[viewController.navigationController setNavigationBarHidden:YES animated:NO];
return self;
}
- (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actionParams bridge:(RCTBridge *)bridge
{
BOOL animated = actionParams[@"animated"] ? [actionParams[@"animated"] boolValue] : YES;
// push
if ([performAction isEqualToString:@"push"])
{
NSString *component = actionParams[@"component"];
if (!component) return;
NSMutableDictionary *passProps = [actionParams[@"passProps"] mutableCopy];
passProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = COMMAND_TYPE_PUSH;
passProps[GLOBAL_SCREEN_ACTION_TIMESTAMP] = actionParams[GLOBAL_SCREEN_ACTION_TIMESTAMP];
NSDictionary *navigatorStyle = actionParams[@"style"];
NSNumber *keepStyleAcrossPush = [[RCCManager sharedInstance] getAppStyle][@"keepStyleAcrossPush"];
BOOL keepStyleAcrossPushBool = keepStyleAcrossPush ? [keepStyleAcrossPush boolValue] : YES;
if (keepStyleAcrossPushBool) {
if ([self.topViewController isKindOfClass:[RCCViewController class]])
{
RCCViewController *parent = (RCCViewController*)self.topViewController;
NSMutableDictionary *mergedStyle = [NSMutableDictionary dictionaryWithDictionary:parent.navigatorStyle];
// there are a few styles that we don't want to remember from our parent (they should be local)
[mergedStyle removeObjectForKey:@"navBarHidden"];
[mergedStyle removeObjectForKey:@"statusBarHidden"];
[mergedStyle removeObjectForKey:@"navBarHideOnScroll"];
[mergedStyle removeObjectForKey:@"drawUnderNavBar"];
[mergedStyle removeObjectForKey:@"drawUnderTabBar"];
[mergedStyle removeObjectForKey:@"statusBarBlur"];
[mergedStyle removeObjectForKey:@"navBarBlur"];
[mergedStyle removeObjectForKey:@"navBarTranslucent"];
[mergedStyle removeObjectForKey:@"statusBarHideWithNavBar"];
[mergedStyle removeObjectForKey:@"autoAdjustScrollViewInsets"];
[mergedStyle removeObjectForKey:@"statusBarTextColorSchemeSingleScreen"];
[mergedStyle removeObjectForKey:@"disabledBackGesture"];
[mergedStyle removeObjectForKey:@"disabledSimultaneousGesture"];
[mergedStyle removeObjectForKey:@"navBarCustomView"];
[mergedStyle removeObjectForKey:@"navBarComponentAlignment"];
[mergedStyle addEntriesFromDictionary:navigatorStyle];
navigatorStyle = mergedStyle;
}
}
RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:nil bridge:bridge];
viewController.controllerId = passProps[@"screenInstanceID"];
[self processTitleView:viewController
props:actionParams
style:navigatorStyle];
NSString *backButtonTitle = actionParams[@"backButtonTitle"];
if (!backButtonTitle) {
NSNumber *hideBackButtonTitle = [[RCCManager sharedInstance] getAppStyle][@"hideBackButtonTitle"];
BOOL hideBackButtonTitleBool = hideBackButtonTitle ? [hideBackButtonTitle boolValue] : NO;
backButtonTitle = hideBackButtonTitleBool ? @"" : backButtonTitle;
}
if (backButtonTitle)
{
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:backButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
self.topViewController.navigationItem.backBarButtonItem = backItem;
}
else
{
self.topViewController.navigationItem.backBarButtonItem = nil;
}
NSNumber *backButtonHidden = actionParams[@"backButtonHidden"];
BOOL backButtonHiddenBool = backButtonHidden ? [backButtonHidden boolValue] : NO;
if (backButtonHiddenBool)
{
viewController.navigationItem.hidesBackButton = YES;
}
NSArray *leftButtons = actionParams[@"leftButtons"];
if (leftButtons)
{
[self setButtons:leftButtons viewController:viewController side:@"left" animated:NO];
}
NSArray *rightButtons = actionParams[@"rightButtons"];
if (rightButtons)
{
[self setButtons:rightButtons viewController:viewController side:@"right" animated:NO];
}
NSString *animationType = actionParams[@"animationType"];
if ([animationType isEqualToString:@"fade"])
{
CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.type = kCATransitionFade;
[self.view.layer addAnimation:transition forKey:kCATransition];
[self pushViewController:viewController animated:NO];
}
else
{
[self pushViewController:viewController animated:animated];
}
return;
}
// pop
if ([performAction isEqualToString:@"pop"])
{
NSString *animationType = actionParams[@"animationType"];
if ([animationType isEqualToString:@"fade"])
{
CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.type = kCATransitionFade;
[self.view.layer addAnimation:transition forKey:kCATransition];
[self popViewControllerAnimated:NO];
}
else
{
[self popViewControllerAnimated:animated];
}
return;
}
// popToRoot
if ([performAction isEqualToString:@"popToRoot"])
{
NSString *animationType = actionParams[@"animationType"];
if ([animationType isEqualToString:@"fade"])
{
CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.type = kCATransitionFade;
[self.view.layer addAnimation:transition forKey:kCATransition];
[self popToRootViewControllerAnimated:NO];
}
else
{
[self popToRootViewControllerAnimated:animated];
}
return;
}
// resetTo
if ([performAction isEqualToString:@"resetTo"])
{
NSString *component = actionParams[@"component"];
if (!component) return;
NSMutableDictionary *passProps = [actionParams[@"passProps"] mutableCopy];
passProps[@"commantType"] = @"resetTo";
NSDictionary *navigatorStyle = actionParams[@"style"];
RCCViewController *viewController = [[RCCViewController alloc] initWithComponent:component passProps:passProps navigatorStyle:navigatorStyle globalProps:nil bridge:bridge];
viewController.controllerId = passProps[@"screenInstanceID"];
viewController.navigationItem.hidesBackButton = YES;
[self processTitleView:viewController
props:actionParams
style:navigatorStyle];
NSArray *leftButtons = actionParams[@"leftButtons"];
if (leftButtons)
{
[self setButtons:leftButtons viewController:viewController side:@"left" animated:NO];
}
NSArray *rightButtons = actionParams[@"rightButtons"];
if (rightButtons)
{
[self setButtons:rightButtons viewController:viewController side:@"right" animated:NO];
}
BOOL animated = actionParams[@"animated"] ? [actionParams[@"animated"] boolValue] : YES;
NSString *animationType = actionParams[@"animationType"];
if ([animationType isEqualToString:@"fade"])
{
CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.type = kCATransitionFade;
[self.view.layer addAnimation:transition forKey:kCATransition];
[self setViewControllers:@[viewController] animated:NO];
}
else
{
[self setViewControllers:@[viewController] animated:animated];
}
return;
}
// setButtons
if ([performAction isEqualToString:@"setButtons"])
{
NSArray *buttons = actionParams[@"buttons"];
BOOL animated = actionParams[@"animated"] ? [actionParams[@"animated"] boolValue] : YES;
NSString *side = actionParams[@"side"] ? actionParams[@"side"] : @"left";
[self setButtons:buttons viewController:self.topViewController side:side animated:animated];
return;
}
// setTitle
if ([performAction isEqualToString:@"setTitle"] || [performAction isEqualToString:@"setTitleImage"])
{
NSDictionary *navigatorStyle = actionParams[@"style"];
[self processTitleView:self.topViewController
props:actionParams
style:navigatorStyle];
return;
}
// toggleNavBar
if ([performAction isEqualToString:@"setHidden"]) {
NSNumber *animated = actionParams[@"animated"];
BOOL animatedBool = animated ? [animated boolValue] : YES;
NSNumber *setHidden = actionParams[@"hidden"];
BOOL isHiddenBool = setHidden ? [setHidden boolValue] : NO;
RCCViewController *topViewController = ((RCCViewController*)self.topViewController);
topViewController.navigatorStyle[@"navBarHidden"] = setHidden;
[topViewController setNavBarVisibilityChange:animatedBool];
}
// setStyle
if ([performAction isEqualToString:@"setStyle"])
{
NSDictionary *navigatorStyle = actionParams;
// merge the navigatorStyle of our parent
if ([self.topViewController isKindOfClass:[RCCViewController class]])
{
RCCViewController *parent = (RCCViewController*)self.topViewController;
NSMutableDictionary *mergedStyle = [NSMutableDictionary dictionaryWithDictionary:parent.navigatorStyle];
// there are a few styles that we don't want to remember from our parent (they should be local)
[mergedStyle setValuesForKeysWithDictionary:navigatorStyle];
navigatorStyle = mergedStyle;
parent.navigatorStyle = navigatorStyle;
[parent setStyleOnInit];
[parent updateStyle];
}
}
}
-(void)onButtonPress:(UIBarButtonItem*)barButtonItem
{
NSString *callbackId = objc_getAssociatedObject(barButtonItem, &CALLBACK_ASSOCIATED_KEY);
if (!callbackId) return;
NSString *buttonId = objc_getAssociatedObject(barButtonItem, &CALLBACK_ASSOCIATED_ID);
[[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:callbackId body:@
{
@"type": @"NavBarButtonPress",
@"id": buttonId ? buttonId : [NSNull null]
}];
}
-(void)setButtons:(NSArray*)buttons viewController:(UIViewController*)viewController side:(NSString*)side animated:(BOOL)animated
{
NSMutableArray *barButtonItems = [NSMutableArray new];
for (NSDictionary *button in buttons)
{
NSString *title = button[@"title"];
UIImage *iconImage = nil;
id icon = button[@"icon"];
if (icon) iconImage = [RCTConvert UIImage:icon];
NSString *__nullable component = button[@"component"];
UIBarButtonItem *barButtonItem;
if (iconImage)
{
barButtonItem = [[UIBarButtonItem alloc] initWithImage:iconImage style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPress:)];
}
else if (title)
{
barButtonItem = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPress:)];
NSMutableDictionary *buttonTextAttributes = [RCTHelpers textAttributesFromDictionary:button withPrefix:@"button"];
if (buttonTextAttributes.allKeys.count > 0) {
[barButtonItem setTitleTextAttributes:buttonTextAttributes forState:UIControlStateNormal];
}
}
else if (component) {
RCTBridge *bridge = [[RCCManager sharedInstance] getBridge];
barButtonItem = [[RCCCustomBarButtonItem alloc] initWithComponentName:component passProps:button[@"passProps"] bridge:bridge];
}
else continue;
objc_setAssociatedObject(barButtonItem, &CALLBACK_ASSOCIATED_KEY, button[@"onPress"], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[barButtonItems addObject:barButtonItem];
NSString *buttonId = button[@"id"];
if (buttonId)
{
objc_setAssociatedObject(barButtonItem, &CALLBACK_ASSOCIATED_ID, buttonId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
NSNumber *disabled = button[@"disabled"];
BOOL disabledBool = disabled ? [disabled boolValue] : NO;
if (disabledBool) {
[barButtonItem setEnabled:NO];
}
NSNumber *disableIconTintString = button[@"disableIconTint"];
BOOL disableIconTint = disableIconTintString ? [disableIconTintString boolValue] : NO;
if (disableIconTint) {
[barButtonItem setImage:[barButtonItem.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
}
if ([viewController isKindOfClass:[RCCViewController class]]) {
RCCViewController *rccViewController = ((RCCViewController*)viewController);
NSDictionary *navigatorStyle = rccViewController.navigatorStyle;
id disabledButtonColor = navigatorStyle[@"disabledButtonColor"];
if (disabledButtonColor) {
UIColor *color = [RCTConvert UIColor:disabledButtonColor];
[barButtonItem setTitleTextAttributes:@{NSForegroundColorAttributeName : color} forState:UIControlStateDisabled];
}
}
NSString *testID = button[@"testID"];
if (testID)
{
barButtonItem.accessibilityIdentifier = testID;
}
}
if ([side isEqualToString:@"left"])
{
[viewController.navigationItem setLeftBarButtonItems:barButtonItems animated:animated];
}
if ([side isEqualToString:@"right"])
{
[viewController.navigationItem setRightBarButtonItems:barButtonItems animated:animated];
}
}
-(void)processTitleView:(UIViewController*)viewController
props:(NSDictionary*)props
style:(NSDictionary*)style
{
BOOL isSetSubtitleBool = props[@"isSetSubtitle"] ? [props[@"isSetSubtitle"] boolValue] : NO;
RCCTitleViewHelper *titleViewHelper = [[RCCTitleViewHelper alloc] init:viewController
navigationController:self
title:props[@"title"]
subtitle:props[@"subtitle"]
titleImageData:props[@"titleImage"]
isSetSubtitle:isSetSubtitleBool];
[titleViewHelper setup:style];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return [self.topViewController preferredStatusBarStyle];
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if(_transitioning)
{
NSDictionary *pushDetails =@{ @"viewController": viewController, @"animated": @(animated) };
[_queuedViewControllers addObject:pushDetails];
return;
}
_transitioning = YES;
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController setNeedsStatusBarAppearanceUpdate];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
dispatch_async(dispatch_get_main_queue(), ^{
_transitioning = NO;
if ([_queuedViewControllers count] > 0) {
NSDictionary *toPushDetails = [_queuedViewControllers firstObject];
[_queuedViewControllers removeObjectAtIndex:0];
[self pushViewController:toPushDetails[@"viewController"] animated:[toPushDetails[@"animated"] boolValue]];
}
});
}
@end
#import "RCCTabBarController.h"
#import "RCCViewController.h"
#import <React/RCTConvert.h>
#import "RCCManager.h"
#import "RCTHelpers.h"
#import <React/RCTUIManager.h>
#import "UIViewController+Rotation.h"
@interface RCTUIManager ()
- (void)configureNextLayoutAnimation:(NSDictionary *)config
withCallback:(RCTResponseSenderBlock)callback
errorCallback:(__unused RCTResponseSenderBlock)errorCallback;
@end
@implementation RCCTabBarController
-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self supportedControllerOrientations];
}
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
id queue = [[RCCManager sharedInstance].getBridge uiManager].methodQueue;
dispatch_async(queue, ^{
[[[RCCManager sharedInstance].getBridge uiManager] configureNextLayoutAnimation:nil withCallback:^(NSArray* arr){} errorCallback:^(NSArray* arr){}];
});
if (tabBarController.selectedIndex != [tabBarController.viewControllers indexOfObject:viewController]) {
NSDictionary *body = @{
@"selectedTabIndex": @([tabBarController.viewControllers indexOfObject:viewController]),
@"unselectedTabIndex": @(tabBarController.selectedIndex)
};
[RCCTabBarController sendScreenTabChangedEvent:viewController body:body];
[[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:@"bottomTabSelected" body:body];
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController*)viewController;
UIViewController *topViewController = navigationController.topViewController;
if ([topViewController isKindOfClass:[RCCViewController class]]) {
RCCViewController *topRCCViewController = (RCCViewController*)topViewController;
topRCCViewController.commandType = COMMAND_TYPE_BOTTOME_TAB_SELECTED;
topRCCViewController.timestamp = [RCTHelpers getTimestampString];
}
}
} else {
[RCCTabBarController sendScreenTabPressedEvent:viewController body:nil];
}
return YES;
}
- (UIImage *)image:(UIImage*)image withColor:(UIColor *)color1
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextClipToMask(context, rect, image.CGImage);
[color1 setFill];
CGContextFillRect(context, rect);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary*)globalProps bridge:(RCTBridge *)bridge
{
self = [super init];
if (!self) return nil;
self.delegate = self;
self.tabBar.translucent = YES; // default
UIColor *buttonColor = nil;
UIColor *selectedButtonColor = nil;
UIColor *labelColor = nil;
UIColor *selectedLabelColor = nil;
NSDictionary *tabsStyle = props[@"style"];
if (tabsStyle)
{
NSString *tabBarButtonColor = tabsStyle[@"tabBarButtonColor"];
if (tabBarButtonColor)
{
UIColor *color = tabBarButtonColor != (id)[NSNull null] ? [RCTConvert UIColor:tabBarButtonColor] : nil;
self.tabBar.tintColor = color;
buttonColor = color;
selectedButtonColor = color;
}
NSString *tabBarSelectedButtonColor = tabsStyle[@"tabBarSelectedButtonColor"];
if (tabBarSelectedButtonColor)
{
UIColor *color = tabBarSelectedButtonColor != (id)[NSNull null] ? [RCTConvert UIColor:tabBarSelectedButtonColor] : nil;
self.tabBar.tintColor = color;
selectedButtonColor = color;
}
NSString *tabBarLabelColor = tabsStyle[@"tabBarLabelColor"];
if(tabBarLabelColor) {
UIColor *color = tabBarLabelColor != (id)[NSNull null] ? [RCTConvert UIColor:tabBarLabelColor] : nil;
labelColor = color;
}
NSString *tabBarSelectedLabelColor = tabsStyle[@"tabBarSelectedLabelColor"];
if(tabBarLabelColor) {
UIColor *color = tabBarSelectedLabelColor != (id)[NSNull null] ? [RCTConvert UIColor:
tabBarSelectedLabelColor] : nil;
selectedLabelColor = color;
}
NSString *tabBarBackgroundColor = tabsStyle[@"tabBarBackgroundColor"];
if (tabBarBackgroundColor)
{
UIColor *color = tabBarBackgroundColor != (id)[NSNull null] ? [RCTConvert UIColor:tabBarBackgroundColor] : nil;
self.tabBar.barTintColor = color;
}
NSString *tabBarTranslucent = tabsStyle[@"tabBarTranslucent"];
if (tabBarTranslucent)
{
self.tabBar.translucent = [tabBarTranslucent boolValue] ? YES : NO;
}
NSString *tabBarHideShadow = tabsStyle[@"tabBarHideShadow"];
if (tabBarHideShadow)
{
self.tabBar.clipsToBounds = [tabBarHideShadow boolValue] ? YES : NO;
}
}
NSMutableArray *viewControllers = [NSMutableArray array];
// go over all the tab bar items
for (NSDictionary *tabItemLayout in children)
{
// make sure the layout is valid
if (![tabItemLayout[@"type"] isEqualToString:@"TabBarControllerIOS.Item"]) continue;
if (!tabItemLayout[@"props"]) continue;
// get the view controller inside
if (!tabItemLayout[@"children"]) continue;
if (![tabItemLayout[@"children"] isKindOfClass:[NSArray class]]) continue;
if ([tabItemLayout[@"children"] count] < 1) continue;
NSDictionary *childLayout = tabItemLayout[@"children"][0];
UIViewController *viewController = [RCCViewController controllerWithLayout:childLayout globalProps:globalProps bridge:bridge];
if (!viewController) continue;
// create the tab icon and title
NSString *title = tabItemLayout[@"props"][@"title"];
UIImage *iconImage = nil;
id icon = tabItemLayout[@"props"][@"icon"];
if (icon)
{
iconImage = [RCTConvert UIImage:icon];
if (buttonColor)
{
iconImage = [[self image:iconImage withColor:buttonColor] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}
}
UIImage *iconImageSelected = nil;
id selectedIcon = tabItemLayout[@"props"][@"selectedIcon"];
if (selectedIcon) {
iconImageSelected = [RCTConvert UIImage:selectedIcon];
} else {
iconImageSelected = [RCTConvert UIImage:icon];
}
viewController.tabBarItem = [[UITabBarItem alloc] initWithTitle:title image:iconImage tag:0];
viewController.tabBarItem.accessibilityIdentifier = tabItemLayout[@"props"][@"testID"];
viewController.tabBarItem.selectedImage = iconImageSelected;
id imageInsets = tabItemLayout[@"props"][@"iconInsets"];
if (imageInsets && imageInsets != (id)[NSNull null])
{
id topInset = imageInsets[@"top"];
id leftInset = imageInsets[@"left"];
id bottomInset = imageInsets[@"bottom"];
id rightInset = imageInsets[@"right"];
CGFloat top = topInset != (id)[NSNull null] ? [RCTConvert CGFloat:topInset] : 0;
CGFloat left = topInset != (id)[NSNull null] ? [RCTConvert CGFloat:leftInset] : 0;
CGFloat bottom = topInset != (id)[NSNull null] ? [RCTConvert CGFloat:bottomInset] : 0;
CGFloat right = topInset != (id)[NSNull null] ? [RCTConvert CGFloat:rightInset] : 0;
viewController.tabBarItem.imageInsets = UIEdgeInsetsMake(top, left, bottom, right);
}
NSMutableDictionary *unselectedAttributes = [RCTHelpers textAttributesFromDictionary:tabsStyle withPrefix:@"tabBarText" baseFont:[UIFont systemFontOfSize:10]];
if (!unselectedAttributes[NSForegroundColorAttributeName] && labelColor) {
unselectedAttributes[NSForegroundColorAttributeName] = labelColor;
}
[viewController.tabBarItem setTitleTextAttributes:unselectedAttributes forState:UIControlStateNormal];
NSMutableDictionary *selectedAttributes = [RCTHelpers textAttributesFromDictionary:tabsStyle withPrefix:@"tabBarSelectedText" baseFont:[UIFont systemFontOfSize:10]];
if (!selectedAttributes[NSForegroundColorAttributeName] && selectedLabelColor) {
selectedAttributes[NSForegroundColorAttributeName] = selectedLabelColor;
}
[viewController.tabBarItem setTitleTextAttributes:selectedAttributes forState:UIControlStateSelected];
// create badge
NSObject *badge = tabItemLayout[@"props"][@"badge"];
if (badge == nil || [badge isEqual:[NSNull null]])
{
viewController.tabBarItem.badgeValue = nil;
}
else
{
viewController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%@", badge];
}
[viewControllers addObject:viewController];
}
// replace the tabs
self.viewControllers = viewControllers;
NSNumber *initialTab = tabsStyle[@"initialTabIndex"];
if (initialTab)
{
NSInteger initialTabIndex = initialTab.integerValue;
[self setSelectedIndex:initialTabIndex];
}
[self setRotation:props];
return self;
}
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.tabBar.hidden = YES;
}
- (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actionParams bridge:(RCTBridge *)bridge completion:(void (^)(void))completion
{
if ([performAction isEqualToString:@"setBadge"])
{
UIViewController *viewController = nil;
NSNumber *tabIndex = actionParams[@"tabIndex"];
if (tabIndex)
{
int i = (int)[tabIndex integerValue];
if ([self.viewControllers count] > i)
{
viewController = [self.viewControllers objectAtIndex:i];
}
}
NSString *contentId = actionParams[@"contentId"];
NSString *contentType = actionParams[@"contentType"];
if (contentId && contentType)
{
viewController = [[RCCManager sharedInstance] getControllerWithId:contentId componentType:contentType];
}
if (viewController)
{
NSObject *badge = actionParams[@"badge"];
if (badge == nil || [badge isEqual:[NSNull null]])
{
viewController.tabBarItem.badgeValue = nil;
}
else
{
NSString *badgeColor = actionParams[@"badgeColor"];
UIColor *color = badgeColor != (id)[NSNull null] ? [RCTConvert UIColor:badgeColor] : nil;
if ([viewController.tabBarItem respondsToSelector:@selector(badgeColor)]) {
viewController.tabBarItem.badgeColor = color;
}
viewController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%@", badge];
}
}
}
if ([performAction isEqualToString:@"switchTo"])
{
UIViewController *viewController = nil;
NSNumber *tabIndex = actionParams[@"tabIndex"];
if (tabIndex)
{
int i = (int)[tabIndex integerValue];
if ([self.viewControllers count] > i)
{
viewController = [self.viewControllers objectAtIndex:i];
}
}
NSString *contentId = actionParams[@"contentId"];
NSString *contentType = actionParams[@"contentType"];
if (contentId && contentType)
{
viewController = [[RCCManager sharedInstance] getControllerWithId:contentId componentType:contentType];
}
if (viewController)
{
[self setSelectedViewController:viewController];
}
}
if ([performAction isEqualToString:@"setTabButton"])
{
UIViewController *viewController = nil;
NSNumber *tabIndex = actionParams[@"tabIndex"];
if (tabIndex)
{
int i = (int)[tabIndex integerValue];
if ([self.viewControllers count] > i)
{
viewController = [self.viewControllers objectAtIndex:i];
}
}
NSString *contentId = actionParams[@"contentId"];
NSString *contentType = actionParams[@"contentType"];
if (contentId && contentType)
{
viewController = [[RCCManager sharedInstance] getControllerWithId:contentId componentType:contentType];
}
if (viewController)
{
UIImage *iconImage = nil;
id icon = actionParams[@"icon"];
if (icon && icon != (id)[NSNull null])
{
iconImage = [RCTConvert UIImage:icon];
iconImage = [[self image:iconImage withColor:self.tabBar.tintColor] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
viewController.tabBarItem.image = iconImage;
}
UIImage *iconImageSelected = nil;
id selectedIcon = actionParams[@"selectedIcon"];
if (selectedIcon && selectedIcon != (id)[NSNull null])
{
iconImageSelected = [RCTConvert UIImage:selectedIcon];
viewController.tabBarItem.selectedImage = iconImageSelected;
}
}
}
if ([performAction isEqualToString:@"setTabBarHidden"])
{
BOOL hidden = [actionParams[@"hidden"] boolValue];
CGRect nextFrame = self.tabBar.frame;
nextFrame.origin.y = UIScreen.mainScreen.bounds.size.height - (hidden ? 0 : self.tabBar.frame.size.height);
[UIView animateWithDuration: ([actionParams[@"animated"] boolValue] ? 0.45 : 0)
delay: 0
usingSpringWithDamping: 0.75
initialSpringVelocity: 0
options: (hidden ? UIViewAnimationOptionCurveEaseIn : UIViewAnimationOptionCurveEaseOut)
animations:^()
{
[self.tabBar setFrame:nextFrame];
}
completion:^(BOOL finished)
{
if (completion != nil)
{
completion();
}
}];
return;
}
else if (completion != nil)
{
completion();
}
}
+(void)sendScreenTabChangedEvent:(UIViewController*)viewController body:(NSDictionary*)body{
[RCCTabBarController sendTabEvent:@"bottomTabSelected" controller:viewController body:body];
}
+(void)sendScreenTabPressedEvent:(UIViewController*)viewController body:(NSDictionary*)body{
[RCCTabBarController sendTabEvent:@"bottomTabReselected" controller:viewController body:body];
}
+(void)sendTabEvent:(NSString *)event controller:(UIViewController*)viewController body:(NSDictionary*)body{
if ([viewController.view isKindOfClass:[RCTRootView class]]){
RCTRootView *rootView = (RCTRootView *)viewController.view;
if (rootView.appProperties && rootView.appProperties[@"navigatorEventID"]) {
NSString *navigatorID = rootView.appProperties[@"navigatorID"];
NSString *screenInstanceID = rootView.appProperties[@"screenInstanceID"];
NSMutableDictionary *screenDict = [NSMutableDictionary dictionaryWithDictionary:@
{
@"id": event,
@"navigatorID": navigatorID,
@"screenInstanceID": screenInstanceID
}];
if (body) {
[screenDict addEntriesFromDictionary:body];
}
[[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:rootView.appProperties[@"navigatorEventID"] body:screenDict];
}
}
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController*)viewController;
UIViewController *topViewController = [navigationController topViewController];
[RCCTabBarController sendTabEvent:event controller:topViewController body:body];
}
}
@end
#import "RCCViewController.h"
#import "RCCNavigationController.h"
#import "RCCTabBarController.h"
#import "RCCDrawerController.h"
#import "RCCTheSideBarManagerViewController.h"
#import <React/RCTRootView.h>
#import "RCCManager.h"
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import "RCCExternalViewControllerProtocol.h"
#import "RCTHelpers.h"
#import "RCCTitleViewHelper.h"
#import "RCCCustomTitleView.h"
NSString* const RCCViewControllerCancelReactTouchesNotification = @"RCCViewControllerCancelReactTouchesNotification";
const NSInteger BLUR_STATUS_TAG = 78264801;
const NSInteger BLUR_NAVBAR_TAG = 78264802;
const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
@interface RCCViewController() <UIGestureRecognizerDelegate>
@property (nonatomic) BOOL _hidesBottomBarWhenPushed;
@property (nonatomic) BOOL _statusBarHideWithNavBar;
@property (nonatomic) BOOL _statusBarHidden;
@property (nonatomic) BOOL _statusBarTextColorSchemeLight;
@property (nonatomic, strong) NSDictionary *originalNavBarImages;
@property (nonatomic, strong) UIImageView *navBarHairlineImageView;
@property (nonatomic, weak) id <UIGestureRecognizerDelegate> originalInteractivePopGestureDelegate;
@end
@implementation RCCViewController
-(UIImageView *)navBarHairlineImageView {
if (!_navBarHairlineImageView) {
_navBarHairlineImageView = [self findHairlineImageViewUnder:self.navigationController.navigationBar];
}
return _navBarHairlineImageView;
}
+ (UIViewController*)controllerWithLayout:(NSDictionary *)layout globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
{
UIViewController* controller = nil;
if (!layout) return nil;
// get props
if (!layout[@"props"]) return nil;
if (![layout[@"props"] isKindOfClass:[NSDictionary class]]) return nil;
NSDictionary *props = layout[@"props"];
// get children
if (!layout[@"children"]) return nil;
if (![layout[@"children"] isKindOfClass:[NSArray class]]) return nil;
NSArray *children = layout[@"children"];
// create according to type
NSString *type = layout[@"type"];
if (!type) return nil;
// regular view controller
if ([type isEqualToString:@"ViewControllerIOS"])
{
controller = [[RCCViewController alloc] initWithProps:props children:children globalProps:globalProps bridge:bridge];
}
// navigation controller
if ([type isEqualToString:@"NavigationControllerIOS"])
{
controller = [[RCCNavigationController alloc] initWithProps:props children:children globalProps:globalProps bridge:bridge];
}
// tab bar controller
if ([type isEqualToString:@"TabBarControllerIOS"])
{
controller = [[RCCTabBarController alloc] initWithProps:props children:children globalProps:globalProps bridge:bridge];
}
// side menu controller
if ([type isEqualToString:@"DrawerControllerIOS"])
{
NSString *drawerType = props[@"type"];
if ([drawerType isEqualToString:@"TheSideBar"]) {
controller = [[RCCTheSideBarManagerViewController alloc] initWithProps:props children:children globalProps:globalProps bridge:bridge];
}
else {
controller = [[RCCDrawerController alloc] initWithProps:props children:children globalProps:globalProps bridge:bridge];
}
}
// register the controller if we have an id
NSString *componentId = props[@"id"];
if (controller && componentId)
{
[[RCCManager sharedInstance] registerController:controller componentId:componentId componentType:type];
if([controller isKindOfClass:[RCCViewController class]])
{
((RCCViewController*)controller).controllerId = componentId;
}
}
// set background image at root level
NSString *rootBackgroundImageName = props[@"style"][@"rootBackgroundImageName"];
if (rootBackgroundImageName) {
UIImage *image = [UIImage imageNamed: rootBackgroundImageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[controller.view insertSubview:imageView atIndex:0];
}
return controller;
}
-(NSDictionary*)addCommandTypeAndTimestampIfExists:(NSDictionary*)globalProps passProps:(NSDictionary*)passProps {
NSMutableDictionary *modifiedPassProps = [NSMutableDictionary dictionaryWithDictionary:passProps];
NSString *commandType = globalProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE];
if (commandType) {
modifiedPassProps[GLOBAL_SCREEN_ACTION_COMMAND_TYPE] = commandType;
}
NSString *timestamp = globalProps[GLOBAL_SCREEN_ACTION_TIMESTAMP];
if (timestamp) {
modifiedPassProps[GLOBAL_SCREEN_ACTION_TIMESTAMP] = timestamp;
}
return modifiedPassProps;
}
- (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
{
NSString *component = props[@"component"];
if (!component) return nil;
NSDictionary *passProps = props[@"passProps"];
NSDictionary *navigatorStyle = props[@"style"];
NSMutableDictionary *mergedProps = [NSMutableDictionary dictionaryWithDictionary:globalProps];
[mergedProps addEntriesFromDictionary:passProps];
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:component initialProperties:mergedProps];
if (!reactView) return nil;
self = [super init];
if (!self) return nil;
[self commonInit:reactView navigatorStyle:navigatorStyle props:props];
self.navigationController.interactivePopGestureRecognizer.delegate = self;
[self.navigationController setNavigationBarHidden:YES animated:NO];
return self;
}
- (instancetype)initWithComponent:(NSString *)component passProps:(NSDictionary *)passProps navigatorStyle:(NSDictionary*)navigatorStyle globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge
{
NSMutableDictionary *mergedProps = [NSMutableDictionary dictionaryWithDictionary:globalProps];
[mergedProps addEntriesFromDictionary:passProps];
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:component initialProperties:mergedProps];
if (!reactView) return nil;
self = [super init];
if (!self) return nil;
NSDictionary *modifiedPassProps = [self addCommandTypeAndTimestampIfExists:globalProps passProps:passProps];
[self commonInit:reactView navigatorStyle:navigatorStyle props:modifiedPassProps];
[self.navigationController setNavigationBarHidden:YES animated:NO];
return self;
}
- (void)commonInit:(RCTRootView*)reactView navigatorStyle:(NSDictionary*)navigatorStyle props:(NSDictionary*)props
{
self.view = reactView;
self.edgesForExtendedLayout = UIRectEdgeNone; // default
self.automaticallyAdjustsScrollViewInsets = NO; // default
self.navigatorStyle = [NSMutableDictionary dictionaryWithDictionary:[[RCCManager sharedInstance] getAppStyle]];
[self.navigatorStyle addEntriesFromDictionary:navigatorStyle];
[self setStyleOnInit];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onCancelReactTouches) name:RCCViewControllerCancelReactTouchesNotification object:nil];
self.commandType = props[GLOBAL_SCREEN_ACTION_COMMAND_TYPE];
self.timestamp = props[GLOBAL_SCREEN_ACTION_TIMESTAMP];
// In order to support 3rd party native ViewControllers, we support passing a class name as a prop named `ExternalNativeScreenClass`
// In this case, we create an instance and add it as a child ViewController which preserves the VC lifecycle.
// In case some props are necessary in the native ViewController, the ExternalNativeScreenProps can be used to pass them
[self addExternalVCIfNecessary:props];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.view = nil;
}
-(void)onRNReload
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.view = nil;
}
-(void)onCancelReactTouches
{
if ([self.view isKindOfClass:[RCTRootView class]]){
[(RCTRootView*)self.view cancelTouches];
}
}
- (void)sendScreenChangedEvent:(NSString *)eventName
{
if ([self.view isKindOfClass:[RCTRootView class]]){
RCTRootView *rootView = (RCTRootView *)self.view;
if (rootView.appProperties && rootView.appProperties[@"navigatorEventID"]) {
[[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:rootView.appProperties[@"navigatorEventID"] body:@
{
@"type": @"ScreenChangedEvent",
@"id": eventName
}];
}
}
}
- (void)sendGlobalScreenEvent:(NSString *)eventName endTimestampString:(NSString *)endTimestampStr shouldReset:(BOOL)shouldReset {
if (!self.commandType) return;
if ([self.view isKindOfClass:[RCTRootView class]]){
NSString *screenName = [((RCTRootView*)self.view) moduleName];
[[[RCCManager sharedInstance] getBridge].eventDispatcher sendAppEventWithName:eventName body:@
{
@"commandType": self.commandType ? self.commandType : @"",
@"screen": screenName ? screenName : @"",
@"startTime": self.timestamp ? self.timestamp : @"",
@"endTime": endTimestampStr ? endTimestampStr : @""
}];
if (shouldReset) {
self.commandType = nil;
self.timestamp = nil;
}
}
}
-(BOOL)isDisappearTriggeredFromPop:(NSString *)eventName {
NSArray *navigationViewControllers = self.navigationController.viewControllers;
if (navigationViewControllers.lastObject == self || [navigationViewControllers indexOfObject:self] == NSNotFound) {
return YES;
}
return NO;
}
- (NSString *)getTimestampString {
long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
return [NSString stringWithFormat:@"%lld", milliseconds];
}
// This is walk around for React-Native bug.
// https://github.com/wix/react-native-navigation/issues/1446
//
// Buttons in ScrollView after changing route/pushing/showing modal
// while there is a momentum scroll are not clickable.
// Back to normal after user start scroll with momentum
- (void)_traverseAndCall:(UIView*)view
{
if([view isKindOfClass:[UIScrollView class]] && ([[(UIScrollView*)view delegate] respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) ) {
dispatch_async(dispatch_get_main_queue(), ^{
[[(UIScrollView*)view delegate] scrollViewDidEndDecelerating:(id)view];
});
}
[view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self _traverseAndCall:obj];
}];
}
// fix iOS11 safeArea - https://github.com/facebook/react-native/issues/15681
// rnn issue - https://github.com/wix/react-native-navigation/issues/1858
- (void)_traverseAndFixScrollViewSafeArea:(UIView *)view {
#ifdef __IPHONE_11_0
if ([view isKindOfClass:UIScrollView.class] && [view respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
[((UIScrollView*)view) setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
}
[view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self _traverseAndFixScrollViewSafeArea:obj];
}];
#endif
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self sendGlobalScreenEvent:@"didAppear" endTimestampString:[self getTimestampString] shouldReset:YES];
[self sendScreenChangedEvent:@"didAppear"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self _traverseAndFixScrollViewSafeArea:self.view];
[self sendGlobalScreenEvent:@"willAppear" endTimestampString:[self getTimestampString] shouldReset:NO];
[self sendScreenChangedEvent:@"willAppear"];
[self setStyleOnAppear];
}
- (void)viewDidDisappear:(BOOL)animated
{
[self _traverseAndCall:self.view];
[super viewDidDisappear:animated];
[self sendGlobalScreenEvent:@"didDisappear" endTimestampString:[self getTimestampString] shouldReset:YES];
[self sendScreenChangedEvent:@"didDisappear"];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self sendGlobalScreenEvent:@"willDisappear" endTimestampString:[self getTimestampString] shouldReset:NO];
[self sendScreenChangedEvent:@"willDisappear"];
[self setStyleOnDisappear];
}
// most styles should be set here since when we pop a view controller that changed them
// we want to reset the style to what we expect (so we need to reset on every willAppear)
- (void)setStyleOnAppear
{
[self setStyleOnAppearForViewController:self appeared:false];
}
- (void)updateStyle
{
[self setStyleOnAppearForViewController:self appeared:true];
}
-(void)setStyleOnAppearForViewController:(UIViewController*)viewController appeared:(BOOL)appeared
{
NSString *screenBackgroundColor = self.navigatorStyle[@"screenBackgroundColor"];
if (screenBackgroundColor) {
UIColor *color = screenBackgroundColor != (id)[NSNull null] ? [RCTConvert UIColor:screenBackgroundColor] : nil;
viewController.view.backgroundColor = color;
}
NSString *screenBackgroundImageName = self.navigatorStyle[@"screenBackgroundImageName"];
if (screenBackgroundImageName) {
UIImage *image = [UIImage imageNamed: screenBackgroundImageName];
viewController.view.layer.contents = (__bridge id _Nullable)(image.CGImage);
}
NSString *navBarBackgroundColor = self.navigatorStyle[@"navBarBackgroundColor"];
if (navBarBackgroundColor) {
UIColor *color = navBarBackgroundColor != (id)[NSNull null] ? [RCTConvert UIColor:navBarBackgroundColor] : nil;
viewController.navigationController.navigationBar.barTintColor = color;
} else {
viewController.navigationController.navigationBar.barTintColor = nil;
}
NSMutableDictionary *titleTextAttributes = [RCTHelpers textAttributesFromDictionary:self.navigatorStyle withPrefix:@"navBarText" baseFont:[UIFont boldSystemFontOfSize:17]];
[self.navigationController.navigationBar setTitleTextAttributes:titleTextAttributes];
if (self.navigationItem.titleView && [self.navigationItem.titleView isKindOfClass:[RCCTitleView class]]) {
RCCTitleView *titleView = (RCCTitleView *)self.navigationItem.titleView;
RCCTitleViewHelper *helper = [[RCCTitleViewHelper alloc] init:viewController navigationController:viewController.navigationController title:titleView.titleLabel.text subtitle:titleView.subtitleLabel.text titleImageData:nil isSetSubtitle:NO];
[helper setup:self.navigatorStyle];
}
NSMutableDictionary *navButtonTextAttributes = [RCTHelpers textAttributesFromDictionary:self.navigatorStyle withPrefix:@"navBarButton"];
NSMutableDictionary *leftNavButtonTextAttributes = [RCTHelpers textAttributesFromDictionary:self.navigatorStyle withPrefix:@"navBarLeftButton"];
NSMutableDictionary *rightNavButtonTextAttributes = [RCTHelpers textAttributesFromDictionary:self.navigatorStyle withPrefix:@"navBarRightButton"];
if (
navButtonTextAttributes.allKeys.count > 0 ||
leftNavButtonTextAttributes.allKeys.count > 0 ||
rightNavButtonTextAttributes.allKeys.count > 0
) {
for (UIBarButtonItem *item in viewController.navigationItem.leftBarButtonItems) {
[item setTitleTextAttributes:navButtonTextAttributes forState:UIControlStateNormal];
if (leftNavButtonTextAttributes.allKeys.count > 0) {
[item setTitleTextAttributes:leftNavButtonTextAttributes forState:UIControlStateNormal];
}
}
for (UIBarButtonItem *item in viewController.navigationItem.rightBarButtonItems) {
[item setTitleTextAttributes:navButtonTextAttributes forState:UIControlStateNormal];
if (rightNavButtonTextAttributes.allKeys.count > 0) {
[item setTitleTextAttributes:rightNavButtonTextAttributes forState:UIControlStateNormal];
}
}
// At the moment, this seems to be the only thing that gets the back button correctly
[navButtonTextAttributes removeObjectForKey:NSForegroundColorAttributeName];
[[UIBarButtonItem appearance] setTitleTextAttributes:navButtonTextAttributes forState:UIControlStateNormal];
}
NSString *navBarButtonColor = self.navigatorStyle[@"navBarButtonColor"];
if (navBarButtonColor) {
UIColor *color = navBarButtonColor != (id)[NSNull null] ? [RCTConvert UIColor:navBarButtonColor] : nil;
viewController.navigationController.navigationBar.tintColor = color;
} else
{
viewController.navigationController.navigationBar.tintColor = nil;
}
BOOL viewControllerBasedStatusBar = false;
NSObject *viewControllerBasedStatusBarAppearance = [[NSBundle mainBundle] infoDictionary][@"UIViewControllerBasedStatusBarAppearance"];
if (viewControllerBasedStatusBarAppearance && [viewControllerBasedStatusBarAppearance isKindOfClass:[NSNumber class]]) {
viewControllerBasedStatusBar = [(NSNumber *)viewControllerBasedStatusBarAppearance boolValue];
}
NSString *statusBarTextColorSchemeSingleScreen = self.navigatorStyle[@"statusBarTextColorSchemeSingleScreen"];
NSString *statusBarTextColorScheme = self.navigatorStyle[@"statusBarTextColorScheme"];
NSString *finalColorScheme = statusBarTextColorSchemeSingleScreen ? : statusBarTextColorScheme;
if (finalColorScheme && [finalColorScheme isEqualToString:@"light"]) {
if (!statusBarTextColorSchemeSingleScreen) {
viewController.navigationController.navigationBar.barStyle = UIBarStyleBlack;
}
self._statusBarTextColorSchemeLight = true;
if (!viewControllerBasedStatusBarAppearance) {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}
[viewController setNeedsStatusBarAppearanceUpdate];
} else {
if (!statusBarTextColorSchemeSingleScreen) {
viewController.navigationController.navigationBar.barStyle = UIBarStyleDefault;
}
self._statusBarTextColorSchemeLight = false;
if (!viewControllerBasedStatusBarAppearance) {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
}
[viewController setNeedsStatusBarAppearanceUpdate];
}
NSNumber *tabBarHidden = self.navigatorStyle[@"tabBarHidden"];
BOOL tabBarHiddenBool = tabBarHidden ? [tabBarHidden boolValue] : NO;
if (YES) {
UITabBar *tabBar = viewController.tabBarController.tabBar;
tabBar.transform = CGAffineTransformMakeTranslation(0, tabBar.frame.size.height);
}
NSNumber *navBarHidden = self.navigatorStyle[@"navBarHidden"];
BOOL navBarHiddenBool = navBarHidden ? [navBarHidden boolValue] : NO;
if (viewController.navigationController.navigationBarHidden != navBarHiddenBool) {
[viewController.navigationController setNavigationBarHidden:YES animated:NO];
}
NSNumber *navBarHideOnScroll = self.navigatorStyle[@"navBarHideOnScroll"];
BOOL navBarHideOnScrollBool = navBarHideOnScroll ? [navBarHideOnScroll boolValue] : NO;
if (navBarHideOnScrollBool) {
viewController.navigationController.hidesBarsOnSwipe = YES;
} else {
viewController.navigationController.hidesBarsOnSwipe = NO;
}
NSNumber *statusBarBlur = self.navigatorStyle[@"statusBarBlur"];
BOOL statusBarBlurBool = statusBarBlur ? [statusBarBlur boolValue] : NO;
if (statusBarBlurBool && ![viewController.view viewWithTag:BLUR_STATUS_TAG]) {
UIVisualEffectView *blur = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
blur.frame = [[UIApplication sharedApplication] statusBarFrame];
blur.tag = BLUR_STATUS_TAG;
[viewController.view insertSubview:blur atIndex:0];
}
NSNumber *navBarBlur = self.navigatorStyle[@"navBarBlur"];
BOOL navBarBlurBool = navBarBlur ? [navBarBlur boolValue] : NO;
if (navBarBlurBool) {
if (![viewController.navigationController.navigationBar viewWithTag:BLUR_NAVBAR_TAG]) {
[self storeOriginalNavBarImages];
[viewController.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
viewController.navigationController.navigationBar.shadowImage = [UIImage new];
UIVisualEffectView *blur = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
blur.frame = CGRectMake(0, -1 * statusBarFrame.size.height, viewController.navigationController.navigationBar.frame.size.width, viewController.navigationController.navigationBar.frame.size.height + statusBarFrame.size.height);
blur.userInteractionEnabled = NO;
blur.tag = BLUR_NAVBAR_TAG;
[viewController.navigationController.navigationBar insertSubview:blur atIndex:0];
[viewController.navigationController.navigationBar sendSubviewToBack:blur];
}
} else {
UIView *blur = [viewController.navigationController.navigationBar viewWithTag:BLUR_NAVBAR_TAG];
if (blur) {
[blur removeFromSuperview];
[viewController.navigationController.navigationBar setBackgroundImage:self.originalNavBarImages[@"bgImage"] forBarMetrics:UIBarMetricsDefault];
viewController.navigationController.navigationBar.shadowImage = self.originalNavBarImages[@"shadowImage"];
self.originalNavBarImages = nil;
}
}
NSNumber *navBarTransparent = self.navigatorStyle[@"navBarTransparent"];
BOOL navBarTransparentBool = navBarTransparent ? [navBarTransparent boolValue] : NO;
void (^action)() = ^ {
if (navBarTransparentBool)
{
if (![viewController.navigationController.navigationBar viewWithTag:TRANSPARENT_NAVBAR_TAG])
{
[self storeOriginalNavBarImages];
[viewController.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
viewController.navigationController.navigationBar.shadowImage = [UIImage new];
UIView *transparentView = [[UIView alloc] initWithFrame:CGRectZero];
transparentView.tag = TRANSPARENT_NAVBAR_TAG;
[viewController.navigationController.navigationBar insertSubview:transparentView atIndex:0];
}
}
else
{
UIView *transparentView = [viewController.navigationController.navigationBar viewWithTag:TRANSPARENT_NAVBAR_TAG];
if (transparentView)
{
[transparentView removeFromSuperview];
[viewController.navigationController.navigationBar setBackgroundImage:self.originalNavBarImages[@"bgImage"] forBarMetrics:UIBarMetricsDefault];
viewController.navigationController.navigationBar.shadowImage = self.originalNavBarImages[@"shadowImage"];
self.originalNavBarImages = nil;
}
}
};
if (!self.transitionCoordinator || self.transitionCoordinator.initiallyInteractive || !navBarTransparentBool || appeared) {
action();
} else {
UIView* backgroundView = [self.navigationController.navigationBar valueForKey:@"backgroundView"];
CGFloat originalAlpha = backgroundView.alpha;
backgroundView.alpha = navBarTransparentBool ? 0.0 : 1.0;
[self.transitionCoordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
action();
backgroundView.alpha = originalAlpha;
}];
}
NSNumber *autoAdjustsScrollViewInsets = self.navigatorStyle[@"autoAdjustScrollViewInsets"];
viewController.automaticallyAdjustsScrollViewInsets = autoAdjustsScrollViewInsets ? [autoAdjustsScrollViewInsets boolValue] : false;
NSNumber *navBarTranslucent = self.navigatorStyle[@"navBarTranslucent"];
BOOL navBarTranslucentBool = navBarTranslucent ? [navBarTranslucent boolValue] : NO;
if (navBarTranslucentBool || navBarBlurBool) {
viewController.navigationController.navigationBar.translucent = YES;
} else {
viewController.navigationController.navigationBar.translucent = NO;
}
NSNumber *extendedLayoutIncludesOpaqueBars = self.navigatorStyle[@"extendedLayoutIncludesOpaqueBars"];
BOOL extendedLayoutIncludesOpaqueBarsBool = extendedLayoutIncludesOpaqueBars ? [extendedLayoutIncludesOpaqueBars boolValue] : NO;
viewController.extendedLayoutIncludesOpaqueBars = extendedLayoutIncludesOpaqueBarsBool;
NSNumber *drawUnderNavBar = self.navigatorStyle[@"drawUnderNavBar"];
BOOL drawUnderNavBarBool = drawUnderNavBar ? [drawUnderNavBar boolValue] : NO;
if (drawUnderNavBarBool) {
viewController.edgesForExtendedLayout |= UIRectEdgeTop;
}
else {
viewController.edgesForExtendedLayout &= ~UIRectEdgeTop;
}
NSNumber *drawUnderTabBar = self.navigatorStyle[@"drawUnderTabBar"];
BOOL drawUnderTabBarBool = drawUnderTabBar ? [drawUnderTabBar boolValue] : NO;
if (YES) {
viewController.edgesForExtendedLayout |= UIRectEdgeBottom;
} else {
viewController.edgesForExtendedLayout &= ~UIRectEdgeBottom;
}
NSNumber *removeNavBarBorder = self.navigatorStyle[@"navBarNoBorder"];
BOOL removeNavBarBorderBool = removeNavBarBorder ? [removeNavBarBorder boolValue] : NO;
if (removeNavBarBorderBool) {
self.navBarHairlineImageView.hidden = YES;
} else {
self.navBarHairlineImageView.hidden = NO;
}
//Bug fix: in case there is a interactivePopGestureRecognizer, it prevents react-native from getting touch events on the left screen area that the gesture handles
//overriding the delegate of the gesture prevents this from happening while keeping the gesture intact (another option was to disable it completely by demand)
if(self.navigationController.viewControllers.count > 1){
if (self.navigationController != nil && self.navigationController.interactivePopGestureRecognizer != nil)
{
id <UIGestureRecognizerDelegate> interactivePopGestureRecognizer = self.navigationController.interactivePopGestureRecognizer.delegate;
if (interactivePopGestureRecognizer != nil && interactivePopGestureRecognizer != self)
{
self.originalInteractivePopGestureDelegate = interactivePopGestureRecognizer;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
}
NSString *navBarCustomView = self.navigatorStyle[@"navBarCustomView"];
if (navBarCustomView && ![self.navigationItem.titleView isKindOfClass:[RCCCustomTitleView class]]) {
if ([self.view isKindOfClass:[RCTRootView class]]) {
RCTBridge *bridge = ((RCTRootView*)self.view).bridge;
NSDictionary *initialProps = self.navigatorStyle[@"navBarCustomViewInitialProps"];
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:navBarCustomView initialProperties:initialProps];
RCCCustomTitleView *titleView = [[RCCCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:self.navigatorStyle[@"navBarComponentAlignment"]];
titleView.backgroundColor = [UIColor clearColor];
reactView.backgroundColor = [UIColor clearColor];
self.navigationItem.titleView = titleView;
self.navigationItem.titleView.backgroundColor = [UIColor clearColor];
self.navigationItem.titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
self.navigationItem.titleView.clipsToBounds = YES;
}
}
}
-(void)storeOriginalNavBarImages {
NSMutableDictionary *originalNavBarImages = [@{} mutableCopy];
UIImage *bgImage = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
if (bgImage != nil) {
originalNavBarImages[@"bgImage"] = bgImage;
}
UIImage *shadowImage = self.navigationController.navigationBar.shadowImage;
if (shadowImage != nil) {
originalNavBarImages[@"shadowImage"] = shadowImage;
}
self.originalNavBarImages = originalNavBarImages;
}
-(void)setStyleOnDisappear {
self.navBarHairlineImageView.hidden = NO;
if (self.navigationController != nil && self.navigationController.interactivePopGestureRecognizer != nil && self.originalInteractivePopGestureDelegate != nil)
{
self.navigationController.interactivePopGestureRecognizer.delegate = self.originalInteractivePopGestureDelegate;
self.originalInteractivePopGestureDelegate = nil;
}
}
// only styles that can't be set on willAppear should be set here
- (void)setStyleOnInit
{
NSNumber *tabBarHidden = self.navigatorStyle[@"tabBarHidden"];
BOOL tabBarHiddenBool = tabBarHidden ? [tabBarHidden boolValue] : NO;
if (tabBarHiddenBool) {
self._hidesBottomBarWhenPushed = YES;
} else {
self._hidesBottomBarWhenPushed = NO;
}
NSNumber *statusBarHideWithNavBar = self.navigatorStyle[@"statusBarHideWithNavBar"];
BOOL statusBarHideWithNavBarBool = statusBarHideWithNavBar ? [statusBarHideWithNavBar boolValue] : NO;
if (statusBarHideWithNavBarBool) {
self._statusBarHideWithNavBar = YES;
} else {
self._statusBarHideWithNavBar = NO;
}
NSNumber *statusBarHidden = self.navigatorStyle[@"statusBarHidden"];
BOOL statusBarHiddenBool = statusBarHidden ? [statusBarHidden boolValue] : NO;
if (statusBarHiddenBool) {
self._statusBarHidden = YES;
} else {
self._statusBarHidden = NO;
}
}
- (BOOL)hidesBottomBarWhenPushed
{
return YES;
if (!self._hidesBottomBarWhenPushed) return NO;
return (self.navigationController.topViewController == self);
}
- (BOOL)prefersStatusBarHidden
{
if (self._statusBarHidden) {
return YES;
}
if (self._statusBarHideWithNavBar) {
return self.navigationController.isNavigationBarHidden;
} else {
return NO;
}
}
- (void)setNavBarVisibilityChange:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
if (self._statusBarTextColorSchemeLight){
return UIStatusBarStyleLightContent;
} else {
return UIStatusBarStyleDefault;
}
}
- (UIImageView *)findHairlineImageViewUnder:(UIView *)view {
if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
return (UIImageView *)view;
}
for (UIView *subview in view.subviews) {
UIImageView *imageView = [self findHairlineImageViewUnder:subview];
if (imageView) {
return imageView;
}
}
return nil;
}
-(void)addExternalVCIfNecessary:(NSDictionary*)props
{
NSString *externalScreenClass = props[@"externalNativeScreenClass"];
if (externalScreenClass != nil)
{
Class class = NSClassFromString(externalScreenClass);
if (class != NULL)
{
id obj = [[class alloc] init];
if (obj != nil && [obj isKindOfClass:[UIViewController class]] && [obj conformsToProtocol:@protocol(RCCExternalViewControllerProtocol)])
{
((id <RCCExternalViewControllerProtocol>)obj).controllerDelegate = self;
[obj setProps:props[@"externalNativeScreenProps"]];
UIViewController *viewController = (UIViewController*)obj;
[self addChildViewController:viewController];
viewController.view.frame = self.view.bounds;
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
}
else
{
NSLog(@"addExternalVCIfNecessary: could not create instance. Make sure that your class is a UIViewController whihc confirms to RCCExternalViewControllerProtocol");
}
}
else
{
NSLog(@"addExternalVCIfNecessary: could not create class from string. Check that the proper class name wass passed in ExternalNativeScreenClass");
}
}
}
#pragma mark - NewRelic
- (NSString*) customNewRelicInteractionName
{
NSString *interactionName = nil;
if (self.view != nil && [self.view isKindOfClass:[RCTRootView class]])
{
NSString *moduleName = ((RCTRootView*)self.view).moduleName;
if(moduleName != nil)
{
interactionName = [NSString stringWithFormat:@"RCCViewController: %@", moduleName];
}
}
if (interactionName == nil)
{
interactionName = [NSString stringWithFormat:@"RCCViewController with title: %@", self.title];
}
return interactionName;
}
#pragma mark - UIGestureRecognizerDelegate
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
NSNumber *disabledBackGesture = self.navigatorStyle[@"disabledBackGesture"];
BOOL disabledBackGestureBool = disabledBackGesture ? [disabledBackGesture boolValue] : NO;
return !disabledBackGestureBool;
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
NSNumber *disabledSimultaneousGesture = self.navigatorStyle[@"disabledSimultaneousGesture"];
BOOL disabledSimultaneousGestureBool = disabledSimultaneousGesture ? [disabledSimultaneousGesture boolValue] : YES; // make default value of disabledSimultaneousGesture is true
return !disabledSimultaneousGestureBool;
}
@end
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = "react-native-navigation"
s.version = package['version']
s.summary = "A Native Navigation Solution for React Native"
s.authors = "Wix"
s.homepage = "https://github.com/wix/react-native-navigation.git#readme"
s.license = package['license']
s.platform = :ios, "8.0"
s.module_name = 'ReactNativeNavigation'
s.source = { :git => "https://github.com/wix/react-native-navigation.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m}"
s.header_mappings_dir = 'ios'
s.public_header_files = "ios/*.h"
s.dependency 'React'
s.frameworks = 'UIKit'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment