diff --git a/Examples/UIExplorer/ImageMocks.js b/Examples/UIExplorer/ImageMocks.js index b888acbf74..3f1883fa65 100644 --- a/Examples/UIExplorer/ImageMocks.js +++ b/Examples/UIExplorer/ImageMocks.js @@ -39,3 +39,8 @@ declare module 'image!uie_thumb_selected' { declare var uri: string; declare var isStatic: boolean; } + +declare module 'image!NavBarButtonPlus' { + declare var uri: string; + declare var isStatic: boolean; +} diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index eee731bd53..4a2011a654 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -19,6 +19,7 @@ var React = require('react-native'); var ViewExample = require('./ViewExample'); var createExamplePage = require('./createExamplePage'); var { + AlertIOS, PixelRatio, ScrollView, StyleSheet, @@ -92,6 +93,30 @@ var NavigatorIOSExample = React.createClass({ } }); })} + {this._renderRow('Custom Left & Right Icons', () => { + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + leftButtonTitle: 'Custom Left', + onLeftButtonPress: () => this.props.navigator.pop(), + rightButtonIcon: require('image!NavBarButtonPlus'), + onRightButtonPress: () => { + AlertIOS.alert( + 'Bar Button Action', + 'Recognized a tap on the bar button icon', + [ + { + text: 'OK', + onPress: () => console.log('Tapped OK'), + }, + ] + ); + }, + passProps: { + text: 'This page has an icon for the right button in the nav bar', + } + }); + })} {this._renderRow('Pop', () => { this.props.navigator.pop(); })} diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json new file mode 100644 index 0000000000..13726b4e94 --- /dev/null +++ b/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "NavBarButtonPlus@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png b/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png new file mode 100644 index 0000000000..706a9c9052 Binary files /dev/null and b/Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png differ diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 3babd14095..103e749f78 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -12,6 +12,7 @@ 'use strict'; var EventEmitter = require('EventEmitter'); +var Image = require('Image'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var RCTNavigatorManager = require('NativeModules').NavigatorManager; @@ -47,11 +48,16 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({ // NavigatorIOS does not use them all, because some are problematic title: true, barTintColor: true, + leftButtonIcon: true, + leftButtonTitle: true, + onNavLeftButtonTap: true, + rightButtonIcon: true, rightButtonTitle: true, onNavRightButtonTap: true, + backButtonIcon: true, + backButtonTitle: true, tintColor: true, navigationBarHidden: true, - backButtonTitle: true, titleTextColor: true, style: true, }, @@ -79,7 +85,12 @@ type Route = { title: string; passProps: Object; backButtonTitle: string; + backButtonIcon: Object; + leftButtonTitle: string; + leftButtonIcon: Object; + onLeftButtonPress: Function; rightButtonTitle: string; + rightButtonIcon: Object; onRightButtonPress: Function; wrapperStyle: any; }; @@ -212,6 +223,13 @@ var NavigatorIOS = React.createClass({ */ passProps: PropTypes.object, + /** + * If set, the left header button image will appear with this source. Note + * that this doesn't apply for the header of the current view, but the + * ones of the views that are pushed afterward. + */ + backButtonIcon: Image.propTypes.source, + /** * If set, the left header button will appear with this name. Note that * this doesn't apply for the header of the current view, but the ones @@ -219,6 +237,26 @@ var NavigatorIOS = React.createClass({ */ backButtonTitle: PropTypes.string, + /** + * If set, the left header button image will appear with this source + */ + leftButtonIcon: Image.propTypes.source, + + /** + * If set, the left header button will appear with this name + */ + leftButtonTitle: PropTypes.string, + + /** + * Called when the left header button is pressed + */ + onLeftButtonPress: PropTypes.func, + + /** + * If set, the right header button image will appear with this source + */ + rightButtonIcon: Image.propTypes.source, + /** * If set, the right header button will appear with this name */ @@ -560,7 +598,12 @@ var NavigatorIOS = React.createClass({ this.props.itemWrapperStyle, route.wrapperStyle ]} + backButtonIcon={this._imageNameFromSource(route.backButtonIcon)} backButtonTitle={route.backButtonTitle} + leftButtonIcon={this._imageNameFromSource(route.leftButtonIcon)} + leftButtonTitle={route.leftButtonTitle} + onNavLeftButtonTap={route.onLeftButtonPress} + rightButtonIcon={this._imageNameFromSource(route.rightButtonIcon)} rightButtonTitle={route.rightButtonTitle} onNavRightButtonTap={route.onRightButtonPress} navigationBarHidden={this.props.navigationBarHidden} @@ -577,6 +620,10 @@ var NavigatorIOS = React.createClass({ ); }, + _imageNameFromSource: function(source: ?Object) { + return source ? source.uri : undefined; + }, + renderNavigationStackItems: function() { var shouldRecurseToNavigator = this.state.makingNavigatorRequest || diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index d35cae03a1..eac3a77359 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1159,6 +1159,12 @@ RCT_EXPORT_METHOD(clearJSResponder) @"captured": @"onNavigationCompleteCapture" } }, + @"topNavLeftButtonTap": @{ + @"phasedRegistrationNames": @{ + @"bubbled": @"onNavLeftButtonTap", + @"captured": @"onNavLefttButtonTapCapture" + } + }, @"topNavRightButtonTap": @{ @"phasedRegistrationNames": @{ @"bubbled": @"onNavRightButtonTap", diff --git a/React/Views/RCTNavItem.h b/React/Views/RCTNavItem.h index 5ae874522e..cd9833a447 100644 --- a/React/Views/RCTNavItem.h +++ b/React/Views/RCTNavItem.h @@ -12,11 +12,19 @@ @interface RCTNavItem : UIView @property (nonatomic, copy) NSString *title; +@property (nonatomic, strong) UIImage *leftButtonIcon; +@property (nonatomic, copy) NSString *leftButtonTitle; +@property (nonatomic, strong) UIImage *rightButtonIcon; @property (nonatomic, copy) NSString *rightButtonTitle; +@property (nonatomic, strong) UIImage *backButtonIcon; @property (nonatomic, copy) NSString *backButtonTitle; @property (nonatomic, assign) BOOL navigationBarHidden; -@property (nonatomic, copy) UIColor *tintColor; -@property (nonatomic, copy) UIColor *barTintColor; -@property (nonatomic, copy) UIColor *titleTextColor; +@property (nonatomic, strong) UIColor *tintColor; +@property (nonatomic, strong) UIColor *barTintColor; +@property (nonatomic, strong) UIColor *titleTextColor; + +@property (nonatomic, readonly) UIBarButtonItem *backButtonItem; +@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem; +@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem; @end diff --git a/React/Views/RCTNavItem.m b/React/Views/RCTNavItem.m index 6b1e92f44a..56346a363b 100644 --- a/React/Views/RCTNavItem.m +++ b/React/Views/RCTNavItem.m @@ -11,5 +11,104 @@ @implementation RCTNavItem -@end +@synthesize backButtonItem = _backButtonItem; +@synthesize leftButtonItem = _leftButtonItem; +@synthesize rightButtonItem = _rightButtonItem; +- (void)setBackButtonTitle:(NSString *)backButtonTitle +{ + _backButtonTitle = backButtonTitle; + _backButtonItem = nil; +} + +- (void)setBackButtonIcon:(UIImage *)backButtonIcon +{ + _backButtonIcon = backButtonIcon; + _backButtonItem = nil; +} + +- (UIBarButtonItem *)backButtonItem +{ + if (!_backButtonItem) { + if (_backButtonIcon) { + _backButtonItem = [[UIBarButtonItem alloc] initWithImage:_backButtonIcon + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else if (_backButtonTitle.length) { + _backButtonItem = [[UIBarButtonItem alloc] initWithTitle:_backButtonTitle + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else { + _backButtonItem = nil; + } + } + return _backButtonItem; +} + +- (void)setLeftButtonTitle:(NSString *)leftButtonTitle +{ + _leftButtonTitle = leftButtonTitle; + _leftButtonItem = nil; +} + +- (void)setLeftButtonIcon:(UIImage *)leftButtonIcon +{ + _leftButtonIcon = leftButtonIcon; + _leftButtonIcon = nil; +} + +- (UIBarButtonItem *)leftButtonItem +{ + if (!_leftButtonItem) { + if (_leftButtonIcon) { + _leftButtonItem = [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else if (_leftButtonTitle.length) { + _leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else { + _leftButtonItem = nil; + } + } + return _leftButtonItem; +} + +- (void)setRightButtonTitle:(NSString *)rightButtonTitle +{ + _rightButtonTitle = rightButtonTitle; + _rightButtonItem = nil; +} + +- (void)setRightButtonIcon:(UIImage *)rightButtonIcon +{ + _rightButtonIcon = rightButtonIcon; + _rightButtonItem = nil; +} + +- (UIBarButtonItem *)rightButtonItem +{ + if (!_rightButtonItem) { + if (_rightButtonIcon) { + _rightButtonItem = [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else if (_rightButtonTitle.length) { + _rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else { + _rightButtonItem = nil; + } + } + return _rightButtonItem; +} + +@end diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m index fc601632f4..33588c938a 100644 --- a/React/Views/RCTNavItemManager.m +++ b/React/Views/RCTNavItemManager.m @@ -21,12 +21,20 @@ RCT_EXPORT_MODULE() return [[RCTNavItem alloc] init]; } +RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL) +RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor) + RCT_EXPORT_VIEW_PROPERTY(title, NSString) -RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString); -RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString); -RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL); -RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor); -RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor); -RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor); +RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor) + +RCT_EXPORT_VIEW_PROPERTY(backButtonIcon, UIImage) +RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString) + +RCT_EXPORT_VIEW_PROPERTY(leftButtonTitle, NSString) +RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage) + +RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage) +RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString) @end diff --git a/React/Views/RCTWrapperViewController.m b/React/Views/RCTWrapperViewController.m index 53c2f16a75..400ce5fab6 100644 --- a/React/Views/RCTWrapperViewController.m +++ b/React/Views/RCTWrapperViewController.m @@ -64,7 +64,6 @@ // TODO: find a way to make this less-tightly coupled to navigation controller if ([self.parentViewController isKindOfClass:[UINavigationController class]]) { - [self.navigationController setNavigationBarHidden:_navItem.navigationBarHidden animated:animated]; @@ -73,33 +72,23 @@ return; } - self.navigationItem.title = _navItem.title; - UINavigationBar *bar = self.navigationController.navigationBar; - if (_navItem.barTintColor) { - bar.barTintColor = _navItem.barTintColor; - } - if (_navItem.tintColor) { - bar.tintColor = _navItem.tintColor; - } + bar.barTintColor = _navItem.barTintColor; + 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)]; + UINavigationItem *item = self.navigationItem; + item.title = _navItem.title; + item.backBarButtonItem = _navItem.backButtonItem; + if ((item.leftBarButtonItem = _navItem.leftButtonItem)) { + item.leftBarButtonItem.target = self; + item.leftBarButtonItem.action = @selector(handleNavLeftButtonTapped); } - - if (_navItem.backButtonTitle.length > 0) { - self.navigationItem.backBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle - style:UIBarButtonItemStylePlain - target:nil - action:nil]; + if ((item.rightBarButtonItem = _navItem.rightButtonItem)) { + item.rightBarButtonItem.target = self; + item.rightBarButtonItem.action = @selector(handleNavRightButtonTapped); } } } @@ -114,6 +103,12 @@ self.view = _wrapperView; } +- (void)handleNavLeftButtonTapped +{ + [_eventDispatcher sendInputEventWithName:@"topNavLeftButtonTap" + body:@{@"target":_navItem.reactTag}]; +} + - (void)handleNavRightButtonTapped { [_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap"