From 805f701c165208dd1b19cb724fe983817e29021b Mon Sep 17 00:00:00 2001 From: James Ide Date: Thu, 19 Feb 2015 16:08:14 -0800 Subject: [PATCH] [Nav] Add support for bar button icons and left buttons NavigatorIOS supports four new properties: - **rightButtonImageSource:** The source of an image to display in the top right. This must be a static image since UINavigationController only supports UIImages. Adding support for UIImageViews (or arbitrary views) is more complicated because custom views do not fade on touch and do not have hit slop the same way that UIImage buttons do. Usage: `rightButtonImageSource: ix('ImageName')` - **backButtonImageSource:** Use a custom image for the back button. This does not replace the back caret (`<`) but instead replaces the text next to it. - **leftButtonTitle**: Text for the left nav button, which supersedes the previous nav item's back button when specified. The main use case for this is your initial screen/UIVC which has nothing to go back to (since it is the first VC on the stack) but need to display a left button. This does hide the back button if there would have been one otherwise. - **leftButtonImageSource:** Image source for the left button, supersedes the left button title. Added UIExplorer example to demonstrate. --- Examples/UIExplorer/NavigatorIOSExample.js | 25 ++++++++++ .../NavBarButtonPlus.imageset/Contents.json | 21 ++++++++ .../NavBarButtonPlus@3x.png | Bin 0 -> 348 bytes .../Components/Navigation/NavigatorIOS.ios.js | 42 ++++++++++++++++ ReactKit/Modules/RCTUIManager.m | 6 +++ ReactKit/Views/RCTNavItem.h | 4 ++ ReactKit/Views/RCTNavItemManager.m | 16 ++++++ ReactKit/Views/RCTWrapperViewController.m | 46 +++++++++++++++--- 8 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/Contents.json create mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/NavBarButtonPlus.imageset/NavBarButtonPlus@3x.png diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index 56bb6ab784a646..b448604b5d23b4 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -13,6 +13,7 @@ var React = require('react-native'); var ViewExample = require('./ViewExample'); var { + AlertIOS, PixelRatio, ScrollView, StyleSheet, @@ -86,6 +87,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(), + rightButtonImageSource: 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 00000000000000..8af814b687aebd --- /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", + "scale" : "3x", + "filename" : "NavBarButtonPlus@3x.png" + } + ], + "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 0000000000000000000000000000000000000000..551cea0d08693d0e34f2cd6b8d70f2642791c478 GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^#vshW1|+Q(8fXD2#^NA%Cx&(BWL^R}oCO|{#S9FJ z79h;%I?XTvC@7QZ;vWK}nSdAsq<~lhN=pLiC9|)tz5}ESJY5_^GFactaO7hU;9>du z|8mBomdk2)SM(fdToRT(XUjC}Up95~cXoCzf8Tp#*P8`1!>=Z?8M86j7&E76G8qe* zISZ!oOjycs#^VN$>kLDOX-WpliN+#{#~32J4)nC-ir#$@de{2j)>URg-~TP2$;iO) z|9{N-k0n66V?lPmow$*gK|!FU?*G!OjB7kH>}EvBxy_hq+5K+a`8@`=euiF`+kCM0 wn3>PdsSBUIiA~KpJSAQ->)gFiyh1A;a`*pzterOPA;_}~p00i_>zopr0Gl>}6aWAK literal 0 HcmV?d00001 diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 06c353592d716c..50f0fe244bdce0 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -11,6 +11,7 @@ 'use strict'; var EventEmitter = require('EventEmitter'); +var Image = require('Image'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var RCTNavigatorManager = require('NativeModules').NavigatorManager; @@ -46,9 +47,14 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({ // NavigatorIOS does not use them all, because some are problematic title: true, barTintColor: true, + leftButtonImageName: true, + leftButtonTitle: true, + onNavLeftButtonTap: true, + rightButtonImageName: true, rightButtonTitle: true, onNavRightButtonTap: true, tintColor: true, + backButtonImageName: true, backButtonTitle: true, titleTextColor: true, style: true, @@ -187,6 +193,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. + */ + backButtonImageSource: 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 @@ -194,6 +207,26 @@ var NavigatorIOS = React.createClass({ */ backButtonTitle: PropTypes.string, + /** + * If set, the left header button image will appear with this source + */ + leftButtonImageSource: 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 + */ + rightButtonImageSource: Image.propTypes.source, + /** * If set, the right header button will appear with this name */ @@ -508,7 +541,12 @@ var NavigatorIOS = React.createClass({ this.props.itemWrapperStyle, route.wrapperStyle ]} + backButtonImageName={this._imageNameFromSource(route.backButtonImageSource)} backButtonTitle={route.backButtonTitle} + leftButtonImageName={this._imageNameFromSource(route.leftButtonImageSource)} + leftButtonTitle={route.leftButtonTitle} + onNavLeftButtonTap={route.onLeftButtonPress} + rightButtonImageName={this._imageNameFromSource(route.rightButtonImageSource)} rightButtonTitle={route.rightButtonTitle} onNavRightButtonTap={route.onRightButtonPress} tintColor={this.props.tintColor}> @@ -522,6 +560,10 @@ var NavigatorIOS = React.createClass({ ); }, + _imageNameFromSource: function(source) { + return source ? source.uri : undefined; + }, + renderNavigationStackItems: function() { var shouldRecurseToNavigator = this.state.makingNavigatorRequest || diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 73c6929a7117c6..bb0b4d3806bbae 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -1130,6 +1130,12 @@ - (NSDictionary *)customBubblingEventTypes @"captured": @"onNavigationCompleteCapture" } }, + @"topNavLeftButtonTap": @{ + @"phasedRegistrationNames": @{ + @"bubbled": @"onNavLeftButtonTap", + @"captured": @"onNavLefttButtonTapCapture" + } + }, @"topNavRightButtonTap": @{ @"phasedRegistrationNames": @{ @"bubbled": @"onNavRightButtonTap", diff --git a/ReactKit/Views/RCTNavItem.h b/ReactKit/Views/RCTNavItem.h index 6db429db27495d..5abd9ed3ded1ab 100644 --- a/ReactKit/Views/RCTNavItem.h +++ b/ReactKit/Views/RCTNavItem.h @@ -12,7 +12,11 @@ @interface RCTNavItem : UIView @property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) UIImage *leftButtonImage; +@property (nonatomic, copy) NSString *leftButtonTitle; +@property (nonatomic, copy) UIImage *rightButtonImage; @property (nonatomic, copy) NSString *rightButtonTitle; +@property (nonatomic, copy) UIImage *backButtonImage; @property (nonatomic, copy) NSString *backButtonTitle; @property (nonatomic, copy) UIColor *tintColor; @property (nonatomic, copy) UIColor *barTintColor; diff --git a/ReactKit/Views/RCTNavItemManager.m b/ReactKit/Views/RCTNavItemManager.m index 1c574cbd18beee..2ad64efbd4f327 100644 --- a/ReactKit/Views/RCTNavItemManager.m +++ b/ReactKit/Views/RCTNavItemManager.m @@ -20,11 +20,27 @@ - (UIView *)view } RCT_EXPORT_VIEW_PROPERTY(title) +RCT_EXPORT_VIEW_PROPERTY(leftButtonTitle); RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle); RCT_EXPORT_VIEW_PROPERTY(backButtonTitle); RCT_EXPORT_VIEW_PROPERTY(tintColor); RCT_EXPORT_VIEW_PROPERTY(barTintColor); RCT_EXPORT_VIEW_PROPERTY(titleTextColor); +- (void)set_leftButtonImageName:(id)json forView:(RCTNavItem *)view withDefaultView:(RCTNavItem *)defaultView +{ + view.leftButtonImage = json ? [RCTConvert UIImage:json] : defaultView.leftButtonImage; +} + +- (void)set_rightButtonImageName:(id)json forView:(RCTNavItem *)view withDefaultView:(RCTNavItem *)defaultView +{ + view.rightButtonImage = json ? [RCTConvert UIImage:json] : defaultView.rightButtonImage; +} + +- (void)set_backButtonImageName:(id)json forView:(RCTNavItem *)view withDefaultView:(RCTNavItem *)defaultView +{ + view.backButtonImage = json ? [RCTConvert UIImage:json] : defaultView.backButtonImage; +} + @end diff --git a/ReactKit/Views/RCTWrapperViewController.m b/ReactKit/Views/RCTWrapperViewController.m index e9b9d5c7798363..e010678a2f1f15 100644 --- a/ReactKit/Views/RCTWrapperViewController.m +++ b/ReactKit/Views/RCTWrapperViewController.m @@ -86,17 +86,43 @@ - (void)viewWillAppear:(BOOL)animated [bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}]; } - if (_navItem.rightButtonTitle.length > 0) { + if (_navItem.leftButtonImage) { + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithImage:_navItem.leftButtonImage + style:UIBarButtonItemStylePlain + target:self + action:@selector(handleNavLeftButtonTapped)]; + } else if (_navItem.leftButtonTitle.length > 0) { + self.navigationItem.leftBarButtonItem = + [[UIBarButtonItem alloc] initWithTitle:_navItem.leftButtonTitle + style:UIBarButtonItemStylePlain + target:self + action:@selector(handleNavLeftButtonTapped)]; + } + + if (_navItem.rightButtonImage) { self.navigationItem.rightBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle - style:UIBarButtonItemStyleDone - target:self - action:@selector(handleNavRightButtonTapped)]; + [[UIBarButtonItem alloc] initWithImage:_navItem.rightButtonImage + style:UIBarButtonItemStylePlain + target:self + action:@selector(handleNavRightButtonTapped)]; + } else 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) { + if (_navItem.backButtonImage) { + self.navigationItem.backBarButtonItem = + [[UIBarButtonItem alloc] initWithImage:_navItem.backButtonImage + style:UIBarButtonItemStylePlain + target:nil + action:nil]; + } else if (_navItem.backButtonTitle.length > 0) { self.navigationItem.backBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle + [[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle style:UIBarButtonItemStylePlain target:nil action:nil]; @@ -114,6 +140,12 @@ - (void)loadView self.view = _wrapperView; } +- (void)handleNavLeftButtonTapped +{ + [_eventDispatcher sendInputEventWithName:@"topNavLeftButtonTap" + body:@{@"target":_navItem.reactTag}]; +} + - (void)handleNavRightButtonTapped { [_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap"