Skip to content

Commit 19794be

Browse files
committed
feat(action-bar): iOS bar placement settings and titles for icon items
iOS 27.1 can present bars vertically (iPhone Duo), where fewer items fit and the rest move to an overflow menu showing each item's image and title. - keep ActionItem text as the UIBarButtonItem title when an icon is set. Bars still show only the image; the title gives the overflow menu something to display. - add ios.visibilityPriority ('high' | 'standard' | 'low' | number) to control which items move to the overflow menu first. - add ios.axisBehavior ('automatic' | 'horizontalOnly' | 'verticalPreferred') to control whether an item may be presented in a vertical bar. Both settings are ignored where the API is unavailable. Includes a toolbox page and device tests.
1 parent 6d15a7f commit 19794be

6 files changed

Lines changed: 135 additions & 1 deletion

File tree

‎apps/automated/src/ui/action-bar/action-bar-tests.ios.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ export function test_actionItem_visibility() {
107107
TKUnit.assertEqual(leftBarButtonItemsCount, 0, 'Visibility does not work');
108108
}
109109

110+
export function test_actionItem_with_icon_keeps_text_as_title() {
111+
const actionItem = new actionBar.ActionItem();
112+
actionItem.icon = 'sys://star';
113+
actionItem.text = 'Favorite';
114+
actionItem.ios.position = 'right';
115+
const page = createPageAndNavigate();
116+
117+
page.actionBar.actionItems.addItem(actionItem);
118+
119+
const navigationItem: UINavigationItem = (<UIViewController>page.ios).navigationItem;
120+
const barButtonItem = navigationItem.rightBarButtonItems.objectAtIndex(0);
121+
122+
TKUnit.assert(!!barButtonItem.image, 'The icon should be the bar button item image');
123+
TKUnit.assertEqual(barButtonItem.title, 'Favorite', 'The text should be kept as the bar button item title');
124+
}
125+
126+
export function test_actionItem_bar_placement() {
127+
const actionItem = new actionBar.ActionItem();
128+
actionItem.icon = 'sys://star';
129+
actionItem.text = 'Favorite';
130+
actionItem.ios.position = 'right';
131+
actionItem.ios.visibilityPriority = 'low';
132+
actionItem.ios.axisBehavior = 'horizontalOnly';
133+
const page = createPageAndNavigate();
134+
135+
// Must not throw where the placement API (iOS 27.1+) is unavailable.
136+
page.actionBar.actionItems.addItem(actionItem);
137+
138+
const navigationItem: UINavigationItem = (<UIViewController>page.ios).navigationItem;
139+
const barButtonItem: any = navigationItem.rightBarButtonItems.objectAtIndex(0);
140+
if (barButtonItem.respondsToSelector('setVisibilityPriority:')) {
141+
TKUnit.assertEqual(barButtonItem.visibilityPriority, (<any>global).UIBarButtonItemVisibilityPriorityLow, 'visibilityPriority');
142+
TKUnit.assertEqual(barButtonItem.axisBehavior, 1, 'axisBehavior should be horizontalOnly');
143+
}
144+
}
145+
110146
export function test_navigationButton_visibility() {
111147
var actionItem = new actionBar.NavigationButton();
112148
actionItem.text = 'Test';

‎apps/toolbox/src/main-page.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<!-- experiment with calc and colormix with classes: calc-padding colormix -->
99
<StackLayout class="p-20" paddingBottom="40" iosOverflowSafeArea="false">
1010
<Button text="a11y" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
11+
<Button text="action-items" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
1112
<Button text="box-shadow" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
1213
<Button text="css-playground" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
1314
<Button text="datepicker" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Observable, EventData, Page, ActionItem } from '@nativescript/core';
2+
3+
export function navigatingTo(args: EventData) {
4+
const page = <Page>args.object;
5+
page.bindingContext = new ActionItemsModel();
6+
}
7+
8+
export class ActionItemsModel extends Observable {
9+
lastTapped = 'Tap an action item';
10+
11+
onItemTap(args: EventData) {
12+
this.set('lastTapped', `Tapped: ${(<ActionItem>args.object).text}`);
13+
}
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
2+
<Page.actionBar>
3+
<ActionBar title="Action Items" class="action-bar">
4+
<!-- More items than a vertical bar (iPhone Duo) has room for: the lower the priority, the sooner an item moves to the overflow menu, where its text shows next to the icon. -->
5+
<ActionItem icon="sys://star" text="Favorite" ios.position="right" ios.visibilityPriority="high" tap="{{ onItemTap }}" />
6+
<ActionItem icon="sys://square.and.arrow.up" text="Share" ios.position="right" tap="{{ onItemTap }}" />
7+
<ActionItem icon="sys://bookmark" text="Bookmark" ios.position="right" tap="{{ onItemTap }}" />
8+
<ActionItem icon="sys://flag" text="Flag" ios.position="right" tap="{{ onItemTap }}" />
9+
<ActionItem icon="sys://folder" text="Move" ios.position="right" tap="{{ onItemTap }}" />
10+
<ActionItem icon="sys://tag" text="Tag" ios.position="right" ios.visibilityPriority="low" tap="{{ onItemTap }}" />
11+
<ActionItem icon="sys://archivebox" text="Archive" ios.position="right" ios.visibilityPriority="low" tap="{{ onItemTap }}" />
12+
<ActionItem icon="sys://printer" text="Print" ios.position="right" ios.visibilityPriority="low" ios.axisBehavior="horizontalOnly" tap="{{ onItemTap }}" />
13+
</ActionBar>
14+
</Page.actionBar>
15+
16+
<StackLayout padding="20">
17+
<Label text="{{ lastTapped }}" textWrap="true" fontSize="17" />
18+
<Label text="Favorite: high priority. Tag, Archive and Print: low priority. Print: horizontal bars only." textWrap="true" marginTop="12" color="#888" />
19+
</StackLayout>
20+
</Page>

‎packages/core/ui/action-bar/index.d.ts‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class ActionBar extends View {
5353
/**
5454
* Gets the native iOS [UINavigationBar](https://developer.apple.com/documentation/uikit/uinavigationbar) that represents the user interface for this component. Valid only when running on iOS.
5555
*/
56-
ios: any /* UITabBarController */;
56+
ios: any; /* UITabBarController */
5757

5858
/**
5959
* Gets or set the UIImageRenderingMode of the action bar icons in iOS. Defaults to "alwaysOriginal"
@@ -264,6 +264,31 @@ export interface IOSActionItemSettings {
264264
* 23: PageCurl
265265
*/
266266
systemIcon: number;
267+
268+
/**
269+
* Gets or sets how long the item keeps its place in the bar when space is constrained.
270+
* Items with a lower priority move to the overflow menu first.
271+
* 1. high
272+
* 2. standard - This is the default value.
273+
* 3. low
274+
* A number can be used for finer ordering between items; higher values are preserved longer.
275+
* Requires iOS 27.1+, where bars can be presented vertically (iPhone Duo) with room for fewer items. Ignored on earlier versions.
276+
* Note: Property not applicable to NavigationButton
277+
* (https://developer.apple.com/documentation/uikit/uibarbuttonitem/visibilitypriority)
278+
*/
279+
visibilityPriority?: 'high' | 'standard' | 'low' | number;
280+
281+
/**
282+
* Gets or sets which bar axes the item supports when the system presents bars vertically (iPhone Duo).
283+
* 1. automatic - the system infers the supported axes from the contents of the item. This is the default value.
284+
* 2. horizontalOnly - the item only supports horizontal bars and is not shown when no horizontal bar is present.
285+
* 3. verticalPreferred - the item supports both and prefers a vertical bar when both are present.
286+
* An item needs an icon to be presented vertically; set both `icon` and `text` so it also reads well in the overflow menu.
287+
* Requires iOS 27.1+. Ignored on earlier versions.
288+
* Note: Property not applicable to NavigationButton
289+
* (https://developer.apple.com/documentation/uikit/uibarbuttonitem/axisbehavior-swift.property)
290+
*/
291+
axisBehavior?: 'automatic' | 'horizontalOnly' | 'verticalPreferred';
267292
}
268293

269294
/**

‎packages/core/ui/action-bar/index.ios.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,37 @@ function loadActionIcon(item: ActionItemDefinition): any /* UIImage */ {
4343
return img;
4444
}
4545

46+
// iOS 27.1 UIBarButtonItem placement API (vertical bars on iPhone Duo), not yet in the iOS typings.
47+
declare const UIBarButtonItemVisibilityPriorityHigh: number;
48+
declare const UIBarButtonItemVisibilityPriorityLow: number;
49+
const enum BarButtonItemAxisBehavior {
50+
Automatic = 0,
51+
HorizontalOnly = 1,
52+
VerticalPreferred = 2,
53+
}
54+
55+
function applyBarPlacement(barButtonItem: UIBarButtonItem, settings: IOSActionItemSettings): void {
56+
if (!barButtonItem || !settings || !barButtonItem.respondsToSelector('setVisibilityPriority:')) {
57+
return;
58+
}
59+
const placement = barButtonItem as UIBarButtonItem & { visibilityPriority: number; axisBehavior: number };
60+
61+
const priority = settings.visibilityPriority;
62+
if (priority === 'high') {
63+
placement.visibilityPriority = UIBarButtonItemVisibilityPriorityHigh;
64+
} else if (priority === 'low') {
65+
placement.visibilityPriority = UIBarButtonItemVisibilityPriorityLow;
66+
} else if (priority !== undefined && priority !== 'standard' && !isNaN(+priority)) {
67+
placement.visibilityPriority = +priority;
68+
}
69+
70+
if (settings.axisBehavior === 'horizontalOnly') {
71+
placement.axisBehavior = BarButtonItemAxisBehavior.HorizontalOnly;
72+
} else if (settings.axisBehavior === 'verticalPreferred') {
73+
placement.axisBehavior = BarButtonItemAxisBehavior.VerticalPreferred;
74+
}
75+
}
76+
4677
@NativeClass
4778
class TapBarItemHandlerImpl extends NSObject {
4879
private _owner: WeakRef<ActionItemDefinition>;
@@ -350,6 +381,11 @@ export class ActionBar extends ActionBarBase {
350381
if (img) {
351382
const image = img.imageWithRenderingMode(this._getIconRenderingMode());
352383
barButtonItem = UIBarButtonItem.alloc().initWithImageStyleTargetAction(image, UIBarButtonItemStyle.Plain, tapHandler, 'tap');
384+
if (item.text) {
385+
// Bars show the image; the title is what the system uses where an image alone won't do,
386+
// such as the overflow menu items move to when a bar runs out of room.
387+
barButtonItem.title = item.text + '';
388+
}
353389
}
354390
} else {
355391
barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction(item.text + '', UIBarButtonItemStyle.Plain, tapHandler, 'tap');
@@ -361,6 +397,8 @@ export class ActionBar extends ActionBarBase {
361397
barButtonItem.accessibilityTraits = UIAccessibilityTraitButton;
362398
}
363399

400+
applyBarPlacement(barButtonItem, item.ios);
401+
364402
return barButtonItem;
365403
}
366404

0 commit comments

Comments
 (0)