Skip to content

Commit

Permalink
adds support for column-based SplitViews on iOS 14+
Browse files Browse the repository at this point in the history
  • Loading branch information
jpdriver committed Oct 21, 2020
1 parent 6536973 commit 5ec4004
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 33 deletions.
37 changes: 37 additions & 0 deletions lib/ios/RNNSplitViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,43 @@

@implementation RNNSplitViewController

- (instancetype)initWithLayoutInfo:(RNNLayoutInfo *)layoutInfo
creator:(id<RNNComponentViewCreator>)creator
options:(RNNNavigationOptions *)options
defaultOptions:(RNNNavigationOptions *)defaultOptions
presenter:(RNNBasePresenter *)presenter
eventEmitter:(RNNEventEmitter *)eventEmitter
childViewControllers:(NSArray *)childViewControllers {
if (@available(iOS 14.0, *)) {
NSString* displayMode = options.splitView.displayMode;
NSArray* possibleDisplayModes = [NSArray arrayWithObjects: @"secondaryOnly", @"oneBesideSecondary", @"oneOverSecondary", @"twoBesideSecondary", @"twoDisplaceSecondary", @"twoOverSecondary", nil];

if (childViewControllers.count == 3 && [possibleDisplayModes containsObject:displayMode]) {
self = [self initWithStyle:UISplitViewControllerStyleTripleColumn];
} else if (childViewControllers.count == 2 && [possibleDisplayModes containsObject:displayMode]) {
self = [self initWithStyle:UISplitViewControllerStyleDoubleColumn];
} else {
// Fallback on iOS 14 but without a new displayMode
self = [self init];
}
} else {
// Fallback on earlier versions
self = [self init];
}
self.options = options;
self.defaultOptions = defaultOptions;
self.layoutInfo = layoutInfo;
self.creator = creator;
self.eventEmitter = eventEmitter;
self.presenter = presenter;
[self.presenter bindViewController:self];
self.extendedLayoutIncludesOpaqueBars = YES;
[self loadChildren:childViewControllers];
[self.presenter applyOptionsOnInit:self.resolveOptions];

return self;
}

- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers {
[super setViewControllers:viewControllers];
UIViewController<UISplitViewControllerDelegate>* masterViewController = viewControllers[0];
Expand Down
37 changes: 28 additions & 9 deletions lib/ios/UISplitViewController+RNNOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,34 @@
@implementation UISplitViewController (RNNOptions)

- (void)rnn_setDisplayMode:(NSString *)displayMode {
if ([displayMode isEqualToString:@"visible"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
} else if ([displayMode isEqualToString:@"hidden"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
} else if ([displayMode isEqualToString:@"overlay"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay;
} else {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}
if ([displayMode isEqualToString:@"visible"]) {
// deprecated since iOS 14
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
} else if ([displayMode isEqualToString:@"hidden"]) {
// deprecated since iOS 14
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
} else if ([displayMode isEqualToString:@"overlay"]) {
// deprecated since iOS 14
self.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryOverlay;
} else if ([displayMode isEqualToString:@"secondaryOnly"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeSecondaryOnly;
} else if ([displayMode isEqualToString:@"oneBesideSecondary"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeOneBesideSecondary;
} else if ([displayMode isEqualToString:@"oneOverSecondary"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeOneOverSecondary;
} else if (@available(iOS 14.0, *)) {
if ([displayMode isEqualToString:@"twoBesideSecondary"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoBesideSecondary;
} else if ([displayMode isEqualToString:@"twoDisplaceSecondary"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoDisplaceSecondary;
} else if ([displayMode isEqualToString:@"twoOverSecondary"]) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeTwoOverSecondary;
} else {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}
} else {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}
}

- (void)rnn_setPrimaryEdge:(NSString *)primaryEdge {
Expand Down
55 changes: 45 additions & 10 deletions lib/src/commands/LayoutTreeParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,34 @@ describe('LayoutTreeParser', () => {
expect(result.children[1].children[0].children[2].type).toEqual('Stack');
});

it('split view', () => {
const result = uut.parse(LayoutExamples.splitView);
const master = uut.parse(LayoutExamples.splitView.splitView!.master!);
const detail = uut.parse(LayoutExamples.splitView.splitView!.detail!);
it('classic split view', () => {
const result = uut.parse(LayoutExamples.classicSplitView);
const api = LayoutExamples.classicSplitView.splitView!;
if (!('primary' in api)) {
// api is ClassicLayoutSplitView
const master = uut.parse(api.master!);
const detail = uut.parse(api.detail!);

expect(result.type).toEqual('SplitView');
expect(result.children[0]).toEqual(master);
expect(result.children[1]).toEqual(detail);
expect(result.type).toEqual('SplitView');
expect(result.children[0]).toEqual(master);
expect(result.children[1]).toEqual(detail);
}
});

it('modern split view', () => {
const result = uut.parse(LayoutExamples.modernSplitView);
const api = LayoutExamples.modernSplitView.splitView!;
if ('primary' in api) {
// api is ModernLayoutSplitView
const primary = uut.parse(api.primary);
const supplementary = uut.parse(api.supplementary!);
const secondary = uut.parse(api.secondary);

expect(result.type).toEqual('SplitView');
expect(result.children[0]).toEqual(primary);
expect(result.children[1]).toEqual(supplementary);
expect(result.children[2]).toEqual(secondary);
}
});
});

Expand All @@ -158,7 +178,7 @@ describe('LayoutTreeParser', () => {
expect(
uut.parse({ sideMenu: { options, center: { component: { name: 'lool' } } } }).data.options
).toBe(options);
expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
expect(uut.parse(LayoutExamples.classicSplitView).data.options).toBe(optionsSplitView);
});

it('pass user provided id as is', () => {
Expand Down Expand Up @@ -291,7 +311,7 @@ const complexLayout: Layout = {
},
};

const splitView: Layout = {
const classicSplitView: Layout = {
splitView: {
master: {
stack: {
Expand All @@ -304,6 +324,20 @@ const splitView: Layout = {
},
};

const modernSplitView: Layout = {
splitView: {
primary: {
stack: {
children: [singleComponent],
options,
},
},
supplementary: stackWithTopBar,
secondary: stackWithTopBar,
options: optionsSplitView,
},
};

const LayoutExamples = {
passProps,
options,
Expand All @@ -314,5 +348,6 @@ const LayoutExamples = {
topTabs,
complexLayout,
externalComponent,
splitView,
classicSplitView,
modernSplitView,
};
31 changes: 23 additions & 8 deletions lib/src/commands/LayoutTreeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,29 @@ export class LayoutTreeParser {
}

private splitView(api: LayoutSplitView): LayoutNode {
const master = api.master ? this.parse(api.master) : undefined;
const detail = api.detail ? this.parse(api.detail) : undefined;
if (!('primary' in api)) {
// api is ClassicLayoutSplitView
const master = api.master ? this.parse(api.master) : undefined;
const detail = api.detail ? this.parse(api.detail) : undefined;

return {
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
type: LayoutType.SplitView,
data: { options: api.options },
children: master && detail ? [master, detail] : [],
};
return {
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
type: LayoutType.SplitView,
data: { options: api.options },
children: master && detail ? [master, detail] : [],
};
} else {
// api is ModernLayoutSplitView
const primary = this.parse(api.primary);
const supplementary = api.supplementary ? this.parse(api.secondary) : undefined;
const secondary = this.parse(api.secondary);

return {
id: api.id || this.uniqueIdProvider.generate(LayoutType.SplitView),
type: LayoutType.SplitView,
data: { options: api.options },
children: supplementary ? [primary, supplementary, secondary] : [primary, secondary],
};
}
}
}
28 changes: 27 additions & 1 deletion lib/src/interfaces/Layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export interface LayoutSideMenu {
options?: Options;
}

export interface LayoutSplitView {
interface ClassicLayoutSplitView {
/**
* Set ID of the stack so you can use Navigation.mergeOptions to
* update options
Expand All @@ -125,6 +125,32 @@ export interface LayoutSplitView {
options?: Options;
}

interface ModernLayoutSplitView {
/**
* Set ID of the stack so you can use Navigation.mergeOptions to
* update options
*/
id?: string;
/**
* Set primary layout
*/
primary: Layout;
/**
* Set supplementary layout (for 3 column layouts on iOS 14+)
*/
supplementary?: Layout;
/**
* Set secondary layout
*/
secondary: Layout;
/**
* Configure split view
*/
options?: Options;
}

export type LayoutSplitView = ClassicLayoutSplitView | ModernLayoutSplitView;

export interface LayoutTopTabs {
/**
* Set the layout's id so Navigation.mergeOptions can be used to update options
Expand Down
15 changes: 13 additions & 2 deletions lib/src/interfaces/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,21 @@ type Interpolation =

export interface OptionsSplitView {
/**
* Master view display mode
* Master view display mode.
* The following options will only work on iOS 14+: twoBesideSecondary, twoDisplaceSecondary, twoOverSecondary
* @default 'auto'
*/
displayMode?: 'auto' | 'visible' | 'hidden' | 'overlay';
displayMode?:
| 'auto'
| 'visible'
| 'hidden'
| 'overlay'
| 'secondaryOnly'
| 'oneBesideSecondary'
| 'oneOverSecondary'
| 'twoBesideSecondary' // iOS 14+ only
| 'twoDisplaceSecondary' // iOS 14+ only
| 'twoOverSecondary'; // iOS 14+ only
/**
* Master view side. Leading is left. Trailing is right.
* @default 'leading'
Expand Down
Loading

0 comments on commit 5ec4004

Please sign in to comment.