Skip to content

Commit 5ec4004

Browse files
committed
adds support for column-based SplitViews on iOS 14+
1 parent 6536973 commit 5ec4004

File tree

8 files changed

+296
-33
lines changed

8 files changed

+296
-33
lines changed

lib/ios/RNNSplitViewController.m

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,43 @@
33

44
@implementation RNNSplitViewController
55

6+
- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
7+
creator:(id<RNNComponentViewCreator>)creator
8+
options:(RNNNavigationOptions *)options
9+
defaultOptions:(RNNNavigationOptions *)defaultOptions
10+
presenter:(RNNBasePresenter *)presenter
11+
eventEmitter:(RNNEventEmitter *)eventEmitter
12+
childViewControllers:(NSArray *)childViewControllers {
13+
if (@available(iOS 14.0, *)) {
14+
NSString* displayMode = options.splitView.displayMode;
15+
NSArray* possibleDisplayModes = [NSArray arrayWithObjects: @"secondaryOnly", @"oneBesideSecondary", @"oneOverSecondary", @"twoBesideSecondary", @"twoDisplaceSecondary", @"twoOverSecondary", nil];
16+
17+
if (childViewControllers.count == 3 && [possibleDisplayModes containsObject:displayMode]) {
18+
self = [self initWithStyle:UISplitViewControllerStyleTripleColumn];
19+
} else if (childViewControllers.count == 2 && [possibleDisplayModes containsObject:displayMode]) {
20+
self = [self initWithStyle:UISplitViewControllerStyleDoubleColumn];
21+
} else {
22+
// Fallback on iOS 14 but without a new displayMode
23+
self = [self init];
24+
}
25+
} else {
26+
// Fallback on earlier versions
27+
self = [self init];
28+
}
29+
self.options = options;
30+
self.defaultOptions = defaultOptions;
31+
self.layoutInfo = layoutInfo;
32+
self.creator = creator;
33+
self.eventEmitter = eventEmitter;
34+
self.presenter = presenter;
35+
[self.presenter bindViewController:self];
36+
self.extendedLayoutIncludesOpaqueBars = YES;
37+
[self loadChildren:childViewControllers];
38+
[self.presenter applyOptionsOnInit:self.resolveOptions];
39+
40+
return self;
41+
}
42+
643
- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers {
744
[super setViewControllers:viewControllers];
845
UIViewController<UISplitViewControllerDelegate>* masterViewController = viewControllers[0];

lib/ios/UISplitViewController+RNNOptions.m

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,34 @@
44
@implementation UISplitViewController (RNNOptions)
55

66
- (void)rnn_setDisplayMode:(NSString *)displayMode {
7-
if ([displayMode isEqualToString:@"visible"]) {
8-
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
9-
} else if ([displayMode isEqualToString:@"hidden"]) {
10-
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
11-
} else if ([displayMode isEqualToString:@"overlay"]) {
12-
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay;
13-
} else {
14-
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
15-
}
7+
if ([displayMode isEqualToString:@"visible"]) {
8+
// deprecated since iOS 14
9+
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
10+
} else if ([displayMode isEqualToString:@"hidden"]) {
11+
// deprecated since iOS 14
12+
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
13+
} else if ([displayMode isEqualToString:@"overlay"]) {
14+
// deprecated since iOS 14
15+
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay;
16+
} else if ([displayMode isEqualToString:@"secondaryOnly"]) {
17+
self.preferredDisplayMode = UISplitViewControllerDisplayModeSecondaryOnly;
18+
} else if ([displayMode isEqualToString:@"oneBesideSecondary"]) {
19+
self.preferredDisplayMode = UISplitViewControllerDisplayModeOneBesideSecondary;
20+
} else if ([displayMode isEqualToString:@"oneOverSecondary"]) {
21+
self.preferredDisplayMode = UISplitViewControllerDisplayModeOneOverSecondary;
22+
} else if (@available(iOS 14.0, *)) {
23+
if ([displayMode isEqualToString:@"twoBesideSecondary"]) {
24+
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoBesideSecondary;
25+
} else if ([displayMode isEqualToString:@"twoDisplaceSecondary"]) {
26+
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoDisplaceSecondary;
27+
} else if ([displayMode isEqualToString:@"twoOverSecondary"]) {
28+
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoOverSecondary;
29+
} else {
30+
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
31+
}
32+
} else {
33+
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
34+
}
1635
}
1736

1837
- (void)rnn_setPrimaryEdge:(NSString *)primaryEdge {

lib/src/commands/LayoutTreeParser.test.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,34 @@ describe('LayoutTreeParser', () => {
139139
expect(result.children[1].children[0].children[2].type).toEqual('Stack');
140140
});
141141

142-
it('split view', () => {
143-
const result = uut.parse(LayoutExamples.splitView);
144-
const master = uut.parse(LayoutExamples.splitView.splitView!.master!);
145-
const detail = uut.parse(LayoutExamples.splitView.splitView!.detail!);
142+
it('classic split view', () => {
143+
const result = uut.parse(LayoutExamples.classicSplitView);
144+
const api = LayoutExamples.classicSplitView.splitView!;
145+
if (!('primary' in api)) {
146+
// api is ClassicLayoutSplitView
147+
const master = uut.parse(api.master!);
148+
const detail = uut.parse(api.detail!);
146149

147-
expect(result.type).toEqual('SplitView');
148-
expect(result.children[0]).toEqual(master);
149-
expect(result.children[1]).toEqual(detail);
150+
expect(result.type).toEqual('SplitView');
151+
expect(result.children[0]).toEqual(master);
152+
expect(result.children[1]).toEqual(detail);
153+
}
154+
});
155+
156+
it('modern split view', () => {
157+
const result = uut.parse(LayoutExamples.modernSplitView);
158+
const api = LayoutExamples.modernSplitView.splitView!;
159+
if ('primary' in api) {
160+
// api is ModernLayoutSplitView
161+
const primary = uut.parse(api.primary);
162+
const supplementary = uut.parse(api.supplementary!);
163+
const secondary = uut.parse(api.secondary);
164+
165+
expect(result.type).toEqual('SplitView');
166+
expect(result.children[0]).toEqual(primary);
167+
expect(result.children[1]).toEqual(supplementary);
168+
expect(result.children[2]).toEqual(secondary);
169+
}
150170
});
151171
});
152172

@@ -158,7 +178,7 @@ describe('LayoutTreeParser', () => {
158178
expect(
159179
uut.parse({ sideMenu: { options, center: { component: { name: 'lool' } } } }).data.options
160180
).toBe(options);
161-
expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
181+
expect(uut.parse(LayoutExamples.classicSplitView).data.options).toBe(optionsSplitView);
162182
});
163183

164184
it('pass user provided id as is', () => {
@@ -291,7 +311,7 @@ const complexLayout: Layout = {
291311
},
292312
};
293313

294-
const splitView: Layout = {
314+
const classicSplitView: Layout = {
295315
splitView: {
296316
master: {
297317
stack: {
@@ -304,6 +324,20 @@ const splitView: Layout = {
304324
},
305325
};
306326

327+
const modernSplitView: Layout = {
328+
splitView: {
329+
primary: {
330+
stack: {
331+
children: [singleComponent],
332+
options,
333+
},
334+
},
335+
supplementary: stackWithTopBar,
336+
secondary: stackWithTopBar,
337+
options: optionsSplitView,
338+
},
339+
};
340+
307341
const LayoutExamples = {
308342
passProps,
309343
options,
@@ -314,5 +348,6 @@ const LayoutExamples = {
314348
topTabs,
315349
complexLayout,
316350
externalComponent,
317-
splitView,
351+
classicSplitView,
352+
modernSplitView,
318353
};

lib/src/commands/LayoutTreeParser.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,29 @@ export class LayoutTreeParser {
126126
}
127127

128128
private splitView(api: LayoutSplitView): LayoutNode {
129-
const master = api.master ? this.parse(api.master) : undefined;
130-
const detail = api.detail ? this.parse(api.detail) : undefined;
129+
if (!('primary' in api)) {
130+
// api is ClassicLayoutSplitView
131+
const master = api.master ? this.parse(api.master) : undefined;
132+
const detail = api.detail ? this.parse(api.detail) : undefined;
131133

132-
return {
133-
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
134-
type: LayoutType.SplitView,
135-
data: { options: api.options },
136-
children: master && detail ? [master, detail] : [],
137-
};
134+
return {
135+
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
136+
type: LayoutType.SplitView,
137+
data: { options: api.options },
138+
children: master && detail ? [master, detail] : [],
139+
};
140+
} else {
141+
// api is ModernLayoutSplitView
142+
const primary = this.parse(api.primary);
143+
const supplementary = api.supplementary ? this.parse(api.secondary) : undefined;
144+
const secondary = this.parse(api.secondary);
145+
146+
return {
147+
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
148+
type: LayoutType.SplitView,
149+
data: { options: api.options },
150+
children: supplementary ? [primary, supplementary, secondary] : [primary, secondary],
151+
};
152+
}
138153
}
139154
}

lib/src/interfaces/Layout.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export interface LayoutSideMenu {
105105
options?: Options;
106106
}
107107

108-
export interface LayoutSplitView {
108+
interface ClassicLayoutSplitView {
109109
/**
110110
* Set ID of the stack so you can use Navigation.mergeOptions to
111111
* update options
@@ -125,6 +125,32 @@ export interface LayoutSplitView {
125125
options?: Options;
126126
}
127127

128+
interface ModernLayoutSplitView {
129+
/**
130+
* Set ID of the stack so you can use Navigation.mergeOptions to
131+
* update options
132+
*/
133+
id?: string;
134+
/**
135+
* Set primary layout
136+
*/
137+
primary: Layout;
138+
/**
139+
* Set supplementary layout (for 3 column layouts on iOS 14+)
140+
*/
141+
supplementary?: Layout;
142+
/**
143+
* Set secondary layout
144+
*/
145+
secondary: Layout;
146+
/**
147+
* Configure split view
148+
*/
149+
options?: Options;
150+
}
151+
152+
export type LayoutSplitView = ClassicLayoutSplitView | ModernLayoutSplitView;
153+
128154
export interface LayoutTopTabs {
129155
/**
130156
* Set the layout's id so Navigation.mergeOptions can be used to update options

lib/src/interfaces/Options.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,21 @@ type Interpolation =
7777

7878
export interface OptionsSplitView {
7979
/**
80-
* Master view display mode
80+
* Master view display mode.
81+
* The following options will only work on iOS 14+: twoBesideSecondary, twoDisplaceSecondary, twoOverSecondary
8182
* @default 'auto'
8283
*/
83-
displayMode?: 'auto' | 'visible' | 'hidden' | 'overlay';
84+
displayMode?:
85+
| 'auto'
86+
| 'visible'
87+
| 'hidden'
88+
| 'overlay'
89+
| 'secondaryOnly'
90+
| 'oneBesideSecondary'
91+
| 'oneOverSecondary'
92+
| 'twoBesideSecondary' // iOS 14+ only
93+
| 'twoDisplaceSecondary' // iOS 14+ only
94+
| 'twoOverSecondary'; // iOS 14+ only
8495
/**
8596
* Master view side. Leading is left. Trailing is right.
8697
* @default 'leading'

0 commit comments

Comments
 (0)