Skip to content

Commit 0137261

Browse files
committed
[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.
1 parent c412585 commit 0137261

9 files changed

Lines changed: 161 additions & 8 deletions

File tree

Examples/UIExplorer/ImageMocks.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ declare module 'image!uie_thumb_selected' {
3939
declare var uri: string;
4040
declare var isStatic: boolean;
4141
}
42+
43+
declare module 'image!NavBarButtonPlus' {
44+
declare var uri: string;
45+
declare var isStatic: boolean;
46+
}

Examples/UIExplorer/NavigatorIOSExample.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var React = require('react-native');
1919
var ViewExample = require('./ViewExample');
2020
var createExamplePage = require('./createExamplePage');
2121
var {
22+
AlertIOS,
2223
PixelRatio,
2324
ScrollView,
2425
StyleSheet,
@@ -92,6 +93,30 @@ var NavigatorIOSExample = React.createClass({
9293
}
9394
});
9495
})}
96+
{this._renderRow('Custom Left & Right Icons', () => {
97+
this.props.navigator.push({
98+
title: NavigatorIOSExample.title,
99+
component: EmptyPage,
100+
leftButtonTitle: 'Custom Left',
101+
onLeftButtonPress: () => this.props.navigator.pop(),
102+
rightButtonImageSource: require('image!NavBarButtonPlus'),
103+
onRightButtonPress: () => {
104+
AlertIOS.alert(
105+
'Bar Button Action',
106+
'Recognized a tap on the bar button icon',
107+
[
108+
{
109+
text: 'OK',
110+
onPress: () => console.log('Tapped OK'),
111+
},
112+
]
113+
);
114+
},
115+
passProps: {
116+
text: 'This page has an icon for the right button in the nav bar',
117+
}
118+
});
119+
})}
95120
{this._renderRow('Pop', () => {
96121
this.props.navigator.pop();
97122
})}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"idiom" : "universal",
9+
"scale" : "2x"
10+
},
11+
{
12+
"idiom" : "universal",
13+
"scale" : "3x",
14+
"filename" : "NavBarButtonPlus@3x.png"
15+
}
16+
],
17+
"info" : {
18+
"version" : 1,
19+
"author" : "xcode"
20+
}
21+
}
348 Bytes
Loading

Libraries/Components/Navigation/NavigatorIOS.ios.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'use strict';
1313

1414
var EventEmitter = require('EventEmitter');
15+
var Image = require('Image');
1516
var React = require('React');
1617
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
1718
var RCTNavigatorManager = require('NativeModules').NavigatorManager;
@@ -47,11 +48,16 @@ var RCTNavigatorItem = createReactIOSNativeComponentClass({
4748
// NavigatorIOS does not use them all, because some are problematic
4849
title: true,
4950
barTintColor: true,
51+
leftButtonImageName: true,
52+
leftButtonTitle: true,
53+
onNavLeftButtonTap: true,
54+
rightButtonImageName: true,
5055
rightButtonTitle: true,
5156
onNavRightButtonTap: true,
57+
backButtonImageName: true,
58+
backButtonTitle: true,
5259
tintColor: true,
5360
navigationBarHidden: true,
54-
backButtonTitle: true,
5561
titleTextColor: true,
5662
style: true,
5763
},
@@ -79,7 +85,12 @@ type Route = {
7985
title: string;
8086
passProps: Object;
8187
backButtonTitle: string;
88+
backButtonImageSource: Object;
89+
leftButtonTitle: string;
90+
leftButtonImageSource: Object;
91+
onLeftButtonPress: Function;
8292
rightButtonTitle: string;
93+
rightButtonImageSource: Object;
8394
onRightButtonPress: Function;
8495
wrapperStyle: any;
8596
};
@@ -212,13 +223,40 @@ var NavigatorIOS = React.createClass({
212223
*/
213224
passProps: PropTypes.object,
214225

226+
/**
227+
* If set, the left header button image will appear with this source. Note
228+
* that this doesn't apply for the header of the current view, but the
229+
* ones of the views that are pushed afterward.
230+
*/
231+
backButtonImageSource: Image.propTypes.source,
232+
215233
/**
216234
* If set, the left header button will appear with this name. Note that
217235
* this doesn't apply for the header of the current view, but the ones
218236
* of the views that are pushed afterward.
219237
*/
220238
backButtonTitle: PropTypes.string,
221239

240+
/**
241+
* If set, the left header button image will appear with this source
242+
*/
243+
leftButtonImageSource: Image.propTypes.source,
244+
245+
/**
246+
* If set, the left header button will appear with this name
247+
*/
248+
leftButtonTitle: PropTypes.string,
249+
250+
/**
251+
* Called when the left header button is pressed
252+
*/
253+
onLeftButtonPress: PropTypes.func,
254+
255+
/**
256+
* If set, the right header button image will appear with this source
257+
*/
258+
rightButtonImageSource: Image.propTypes.source,
259+
222260
/**
223261
* If set, the right header button will appear with this name
224262
*/
@@ -550,7 +588,12 @@ var NavigatorIOS = React.createClass({
550588
this.props.itemWrapperStyle,
551589
route.wrapperStyle
552590
]}
591+
backButtonImageName={this._imageNameFromSource(route.backButtonImageSource)}
553592
backButtonTitle={route.backButtonTitle}
593+
leftButtonImageName={this._imageNameFromSource(route.leftButtonImageSource)}
594+
leftButtonTitle={route.leftButtonTitle}
595+
onNavLeftButtonTap={route.onLeftButtonPress}
596+
rightButtonImageName={this._imageNameFromSource(route.rightButtonImageSource)}
554597
rightButtonTitle={route.rightButtonTitle}
555598
onNavRightButtonTap={route.onRightButtonPress}
556599
navigationBarHidden={this.props.navigationBarHidden}
@@ -565,6 +608,10 @@ var NavigatorIOS = React.createClass({
565608
);
566609
},
567610

611+
_imageNameFromSource: function(source: ?Object) {
612+
return source ? source.uri : undefined;
613+
},
614+
568615
renderNavigationStackItems: function() {
569616
var shouldRecurseToNavigator =
570617
this.state.makingNavigatorRequest ||

React/Modules/RCTUIManager.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,12 @@ - (NSDictionary *)customBubblingEventTypes
11401140
@"captured": @"onNavigationCompleteCapture"
11411141
}
11421142
},
1143+
@"topNavLeftButtonTap": @{
1144+
@"phasedRegistrationNames": @{
1145+
@"bubbled": @"onNavLeftButtonTap",
1146+
@"captured": @"onNavLefttButtonTapCapture"
1147+
}
1148+
},
11431149
@"topNavRightButtonTap": @{
11441150
@"phasedRegistrationNames": @{
11451151
@"bubbled": @"onNavRightButtonTap",

React/Views/RCTNavItem.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
@interface RCTNavItem : UIView
1313

1414
@property (nonatomic, copy) NSString *title;
15+
@property (nonatomic, copy) UIImage *leftButtonImage;
16+
@property (nonatomic, copy) NSString *leftButtonTitle;
17+
@property (nonatomic, copy) UIImage *rightButtonImage;
1518
@property (nonatomic, copy) NSString *rightButtonTitle;
19+
@property (nonatomic, copy) UIImage *backButtonImage;
1620
@property (nonatomic, copy) NSString *backButtonTitle;
1721
@property (nonatomic, assign) BOOL navigationBarHidden;
1822
@property (nonatomic, copy) UIColor *tintColor;

React/Views/RCTNavItemManager.m

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,24 @@ - (UIView *)view
2222
}
2323

2424
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
25+
RCT_EXPORT_VIEW_PROPERTY(leftButtonTitle, NSString);
2526
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString);
2627
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString);
2728
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL);
2829
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
2930
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor);
3031
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor);
32+
RCT_CUSTOM_VIEW_PROPERTY(leftButtonImageName, UIImage, RCTNavItem)
33+
{
34+
view.leftButtonImage = json ? [RCTConvert UIImage:json] : defaultView.leftButtonImage;
35+
}
36+
RCT_CUSTOM_VIEW_PROPERTY(rightButtonImageName, UIImage, RCTNavItem)
37+
{
38+
view.rightButtonImage = json ? [RCTConvert UIImage:json] : defaultView.rightButtonImage;
39+
}
40+
RCT_CUSTOM_VIEW_PROPERTY(backButtonImageName, UIImage, RCTNavItem)
41+
{
42+
view.backButtonImage = json ? [RCTConvert UIImage:json] : defaultView.backButtonImage;
43+
}
3144

3245
@end

React/Views/RCTWrapperViewController.m

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,43 @@ - (void)viewWillAppear:(BOOL)animated
8989
[bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}];
9090
}
9191

92-
if (_navItem.rightButtonTitle.length > 0) {
92+
if (_navItem.leftButtonImage) {
93+
self.navigationItem.leftBarButtonItem =
94+
[[UIBarButtonItem alloc] initWithImage:_navItem.leftButtonImage
95+
style:UIBarButtonItemStylePlain
96+
target:self
97+
action:@selector(handleNavLeftButtonTapped)];
98+
} else if (_navItem.leftButtonTitle.length > 0) {
99+
self.navigationItem.leftBarButtonItem =
100+
[[UIBarButtonItem alloc] initWithTitle:_navItem.leftButtonTitle
101+
style:UIBarButtonItemStylePlain
102+
target:self
103+
action:@selector(handleNavLeftButtonTapped)];
104+
}
105+
106+
if (_navItem.rightButtonImage) {
93107
self.navigationItem.rightBarButtonItem =
94-
[[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle
95-
style:UIBarButtonItemStyleDone
96-
target:self
97-
action:@selector(handleNavRightButtonTapped)];
108+
[[UIBarButtonItem alloc] initWithImage:_navItem.rightButtonImage
109+
style:UIBarButtonItemStylePlain
110+
target:self
111+
action:@selector(handleNavRightButtonTapped)];
112+
} else if (_navItem.rightButtonTitle.length > 0) {
113+
self.navigationItem.rightBarButtonItem =
114+
[[UIBarButtonItem alloc] initWithTitle:_navItem.rightButtonTitle
115+
style:UIBarButtonItemStyleDone
116+
target:self
117+
action:@selector(handleNavRightButtonTapped)];
98118
}
99119

100-
if (_navItem.backButtonTitle.length > 0) {
120+
if (_navItem.backButtonImage) {
121+
self.navigationItem.backBarButtonItem =
122+
[[UIBarButtonItem alloc] initWithImage:_navItem.backButtonImage
123+
style:UIBarButtonItemStylePlain
124+
target:nil
125+
action:nil];
126+
} else if (_navItem.backButtonTitle.length > 0) {
101127
self.navigationItem.backBarButtonItem =
102-
[[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle
128+
[[UIBarButtonItem alloc] initWithTitle:_navItem.backButtonTitle
103129
style:UIBarButtonItemStylePlain
104130
target:nil
105131
action:nil];
@@ -117,6 +143,12 @@ - (void)loadView
117143
self.view = _wrapperView;
118144
}
119145

146+
- (void)handleNavLeftButtonTapped
147+
{
148+
[_eventDispatcher sendInputEventWithName:@"topNavLeftButtonTap"
149+
body:@{@"target":_navItem.reactTag}];
150+
}
151+
120152
- (void)handleNavRightButtonTapped
121153
{
122154
[_eventDispatcher sendInputEventWithName:@"topNavRightButtonTap"

0 commit comments

Comments
 (0)