Browse Source

[v2] UISplitViewController (#3247)

* Work in progress

* Arbitary changes

* Improvements on API

* Cleanup RNNSplitViewController, allow mergeOptions

* Added documentation and better playground example

* Adding tests for coverage

* Find out why this test is failing.

* Only show split view on iOS

* Add test back in
Birkir Rafn Guðjónsson 6 years ago
parent
commit
07376e9e29

+ 24
- 0
docs/docs/layout-types.md View File

79
     component: {}
79
     component: {}
80
   }
80
   }
81
 }
81
 }
82
+```
83
+
84
+## splitView
85
+
86
+Master and Detail based layout.
87
+
88
+You can change the it's options with `Navigation.mergeOptions('splitView1', { maxWidth: 400 })`.
89
+
90
+```js
91
+const splitView = {
92
+  id: 'splitView1', // Required to update options
93
+  master: {
94
+    // All layout types accepted supported by device, eg. `stack`
95
+  },
96
+  detail: {
97
+    // All layout types accepted supported by device, eg. `stack`
98
+  },
99
+  options: {
100
+    displayMode: 'auto', // Master view display mode: `auto`, `visible`, `hidden` and `overlay`
101
+    primaryEdge: 'leading', // Master view side: `leading` or `trailing`
102
+    minWidth: 150, // Minimum width of master view
103
+    maxWidth: 300, // Maximum width of master view
104
+  },
105
+}
82
 ```
106
 ```

+ 13
- 0
lib/ios/RNNCommandsHandler.m View File

4
 #import "RNNOverlayManager.h"
4
 #import "RNNOverlayManager.h"
5
 #import "RNNNavigationOptions.h"
5
 #import "RNNNavigationOptions.h"
6
 #import "RNNRootViewController.h"
6
 #import "RNNRootViewController.h"
7
+#import "RNNSplitViewController.h"
7
 #import "React/RCTUIManager.h"
8
 #import "React/RCTUIManager.h"
8
 
9
 
9
 static NSString* const setRoot	= @"setRoot";
10
 static NSString* const setRoot	= @"setRoot";
70
 		
71
 		
71
 		[CATransaction commit];
72
 		[CATransaction commit];
72
 	}
73
 	}
74
+
75
+	if ([vc isKindOfClass:[RNNSplitViewController class]]) {
76
+		RNNSplitViewController* splitVc = (RNNSplitViewController*)vc;
77
+		[splitVc.options mergeWith:options];
78
+		[CATransaction begin];
79
+		[CATransaction setCompletionBlock:completion];
80
+		
81
+		[splitVc.options applyOn:vc];
82
+		
83
+		[CATransaction commit];
84
+	}
85
+	
73
 }
86
 }
74
 
87
 
75
 -(void) setDefaultOptions:(NSDictionary*)optionsDict completion:(RNNTransitionCompletionBlock)completion {
88
 -(void) setDefaultOptions:(NSDictionary*)optionsDict completion:(RNNTransitionCompletionBlock)completion {

+ 30
- 0
lib/ios/RNNControllerFactory.m View File

2
 #import "RNNControllerFactory.h"
2
 #import "RNNControllerFactory.h"
3
 #import "RNNLayoutNode.h"
3
 #import "RNNLayoutNode.h"
4
 #import "RNNRootViewController.h"
4
 #import "RNNRootViewController.h"
5
+#import "RNNSplitViewController.h"
6
+#import "RNNSplitViewOptions.h"
5
 #import "RNNSideMenuController.h"
7
 #import "RNNSideMenuController.h"
6
 #import "RNNSideMenuChildVC.h"
8
 #import "RNNSideMenuChildVC.h"
7
 #import "RNNNavigationOptions.h"
9
 #import "RNNNavigationOptions.h"
79
 		result = [self createExternalComponent:node];
81
 		result = [self createExternalComponent:node];
80
 	}
82
 	}
81
 	
83
 	
84
+	else if (node.isSplitView) {
85
+		result = [self createSplitView:node];
86
+	}
87
+	
82
 	if (!result) {
88
 	if (!result) {
83
 		@throw [NSException exceptionWithName:@"UnknownControllerType" reason:[@"Unknown controller type " stringByAppendingString:node.type] userInfo:nil];
89
 		@throw [NSException exceptionWithName:@"UnknownControllerType" reason:[@"Unknown controller type " stringByAppendingString:node.type] userInfo:nil];
84
 	}
90
 	}
204
 	return vc;
210
 	return vc;
205
 }
211
 }
206
 
212
 
213
+- (UIViewController<RNNRootViewProtocol> *)createSplitView:(RNNLayoutNode*)node {
214
+
215
+	NSString* componentId = node.nodeId;
216
+	
217
+	RNNSplitViewOptions* options = [[RNNSplitViewOptions alloc] initWithDict:_defaultOptionsDict];
218
+	[options mergeWith:node.data[@"options"]];
219
+
220
+	RNNSplitViewController* svc = [[RNNSplitViewController alloc] initWithOptions:options withComponentId:componentId rootViewCreator:_creator eventEmitter:_eventEmitter];
221
+
222
+	// We need two children of the node for successful Master / Detail
223
+	NSDictionary *master = node.children[0];
224
+	NSDictionary *detail = node.children[1];
225
+
226
+	// Create view controllers
227
+	RNNRootViewController* masterVc = (RNNRootViewController*)[self fromTree:master];
228
+	RNNRootViewController* detailVc = (RNNRootViewController*)[self fromTree:detail];
229
+
230
+	// Set the controllers and delegate to masterVC
231
+	svc.viewControllers = [NSArray arrayWithObjects:masterVc, detailVc, nil];
232
+	svc.delegate = masterVc;
233
+
234
+	return svc;
235
+}
236
+
207
 @end
237
 @end

+ 2
- 0
lib/ios/RNNLayoutNode.h View File

8
 @property NSString* nodeId;
8
 @property NSString* nodeId;
9
 @property NSDictionary* data;
9
 @property NSDictionary* data;
10
 @property NSArray* children;
10
 @property NSArray* children;
11
+@property NSArray* sidebar;
11
 
12
 
12
 +(instancetype)create:(NSDictionary *)json;
13
 +(instancetype)create:(NSDictionary *)json;
13
 
14
 
20
 -(BOOL)isSideMenuLeft;
21
 -(BOOL)isSideMenuLeft;
21
 -(BOOL)isSideMenuRight;
22
 -(BOOL)isSideMenuRight;
22
 -(BOOL)isSideMenuCenter;
23
 -(BOOL)isSideMenuCenter;
24
+-(BOOL)isSplitView;
23
 
25
 
24
 @end
26
 @end

+ 5
- 0
lib/ios/RNNLayoutNode.m View File

10
 	node.nodeId = json[@"id"];
10
 	node.nodeId = json[@"id"];
11
 	node.data = json[@"data"];
11
 	node.data = json[@"data"];
12
 	node.children = json[@"children"];
12
 	node.children = json[@"children"];
13
+	
13
 	return node;
14
 	return node;
14
 }
15
 }
15
 
16
 
49
 {
50
 {
50
 	return [self.type isEqualToString:@"SideMenuCenter"];
51
 	return [self.type isEqualToString:@"SideMenuCenter"];
51
 }
52
 }
53
+-(BOOL)isSplitView
54
+{
55
+	return [self.type isEqualToString:@"SplitView"];
56
+}
52
 
57
 
53
 @end
58
 @end

+ 1
- 0
lib/ios/RNNNavigationOptions.m View File

5
 #import "RNNTopBarOptions.h"
5
 #import "RNNTopBarOptions.h"
6
 #import "RNNSideMenuController.h"
6
 #import "RNNSideMenuController.h"
7
 #import "RNNRootViewController.h"
7
 #import "RNNRootViewController.h"
8
+#import "RNNSplitViewController.h"
8
 #import "RNNNavigationButtons.h"
9
 #import "RNNNavigationButtons.h"
9
 
10
 
10
 const NSInteger BLUR_STATUS_TAG = 78264801;
11
 const NSInteger BLUR_STATUS_TAG = 78264801;

+ 1
- 1
lib/ios/RNNRootViewProtocol.h View File

1
 #import "RNNNavigationOptions.h"
1
 #import "RNNNavigationOptions.h"
2
 
2
 
3
-@protocol RNNRootViewProtocol <NSObject, UINavigationControllerDelegate, UIViewControllerTransitioningDelegate>
3
+@protocol RNNRootViewProtocol <NSObject, UINavigationControllerDelegate, UIViewControllerTransitioningDelegate, UISplitViewControllerDelegate>
4
 
4
 
5
 @optional
5
 @optional
6
 - (void)mergeOptions:(NSDictionary*)options;
6
 - (void)mergeOptions:(NSDictionary*)options;

+ 27
- 0
lib/ios/RNNSplitViewController.h View File

1
+
2
+#import <Foundation/Foundation.h>
3
+#import <UIKit/UIKit.h>
4
+#import "RNNLayoutNode.h"
5
+#import "RNNRootViewCreator.h"
6
+#import "RNNEventEmitter.h"
7
+#import "RNNNavigationOptions.h"
8
+#import "RNNSplitViewOptions.h"
9
+#import "RNNAnimator.h"
10
+#import "RNNTopTabsViewController.h"
11
+#import "RNNRootViewProtocol.h"
12
+
13
+@interface RNNSplitViewController : UISplitViewController	<RNNRootViewProtocol>
14
+
15
+@property (nonatomic, strong) RNNSplitViewOptions* options;
16
+@property (nonatomic, strong) RNNEventEmitter *eventEmitter;
17
+@property (nonatomic, strong) NSString* componentId;
18
+@property (nonatomic) id<RNNRootViewCreator> creator;
19
+
20
+-(instancetype)initWithOptions:(RNNSplitViewOptions*)options
21
+			withComponentId:(NSString*)componentId
22
+			rootViewCreator:(id<RNNRootViewCreator>)creator
23
+				  eventEmitter:(RNNEventEmitter*)eventEmitter;
24
+
25
+-(BOOL)isCustomTransitioned;
26
+
27
+@end

+ 44
- 0
lib/ios/RNNSplitViewController.m View File

1
+
2
+#import "RNNRootViewController.h"
3
+#import "RNNSplitViewController.h"
4
+
5
+@interface RNNSplitViewController()
6
+@property (nonatomic) BOOL _optionsApplied;
7
+@property (nonatomic, copy) void (^rotationBlock)(void);
8
+@end
9
+
10
+@implementation RNNSplitViewController
11
+
12
+-(instancetype)initWithOptions:(RNNSplitViewOptions*)options
13
+			withComponentId:(NSString*)componentId
14
+			rootViewCreator:(id<RNNRootViewCreator>)creator
15
+			   eventEmitter:(RNNEventEmitter*)eventEmitter {
16
+	self = [super init];
17
+	self.componentId = componentId;
18
+	self.options = options;
19
+	self.eventEmitter = eventEmitter;
20
+	self.creator = creator;
21
+
22
+	self.navigationController.delegate = self;
23
+
24
+	return self;
25
+}
26
+
27
+-(void)viewWillAppear:(BOOL)animated{
28
+	[super viewWillAppear:animated];
29
+	[self.options applyOn:self];
30
+	[self optionsUpdated];
31
+}
32
+
33
+- (void)optionsUpdated {
34
+}
35
+
36
+- (void)mergeOptions:(NSDictionary *)options {
37
+	[self.options mergeIfEmptyWith:options];
38
+}
39
+
40
+-(BOOL)isCustomTransitioned {
41
+	return false;
42
+}
43
+
44
+@end

+ 10
- 0
lib/ios/RNNSplitViewOptions.h View File

1
+#import "RNNOptions.h"
2
+
3
+@interface RNNSplitViewOptions : RNNOptions
4
+
5
+@property (nonatomic, strong) NSString* displayMode;
6
+@property (nonatomic, strong) NSString* primaryEdge;
7
+@property (nonatomic, strong) NSNumber* minWidth;
8
+@property (nonatomic, strong) NSNumber* maxWidth;
9
+
10
+@end

+ 37
- 0
lib/ios/RNNSplitViewOptions.m View File

1
+#import "RNNSplitViewOptions.h"
2
+#import "RNNRootViewProtocol.h"
3
+
4
+@implementation RNNSplitViewOptions
5
+
6
+-(void)applyOn:(UIViewController<RNNRootViewProtocol> *)viewController {
7
+	
8
+	UISplitViewController *svc = (UISplitViewController*) viewController;
9
+
10
+	if (@available(iOS 11.0, *)) {
11
+		if ([self.primaryEdge isEqualToString:@"trailing"]) {
12
+			[svc setPrimaryEdge:UISplitViewControllerPrimaryEdgeTrailing];
13
+		} else {
14
+			[svc setPrimaryEdge:UISplitViewControllerPrimaryEdgeLeading];
15
+		}
16
+	}
17
+	
18
+	if ([self.displayMode isEqualToString:@"visible"]) {
19
+		[svc setPreferredDisplayMode:UISplitViewControllerDisplayModeAllVisible];
20
+	} else if ([self.displayMode isEqualToString:@"hidden"]) {
21
+		[svc setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryHidden];
22
+	} else if ([self.displayMode isEqualToString:@"overlay"]) {
23
+		[svc setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryOverlay];
24
+	} else {
25
+		[svc setPreferredDisplayMode:UISplitViewControllerDisplayModeAutomatic];
26
+	}
27
+
28
+	if (self.minWidth) {
29
+		[svc setMinimumPrimaryColumnWidth:[self.minWidth doubleValue]];
30
+	}
31
+
32
+	if (self.maxWidth) {
33
+		[svc setMaximumPrimaryColumnWidth:[self.maxWidth doubleValue]];
34
+	}
35
+}
36
+
37
+@end

+ 19
- 0
lib/ios/ReactNativeNavigation.xcodeproj/project.pbxproj View File

153
 		7BEF0D1D1E43771B003E96B0 /* RNNLayoutNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BEF0D1B1E43771B003E96B0 /* RNNLayoutNode.m */; };
153
 		7BEF0D1D1E43771B003E96B0 /* RNNLayoutNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BEF0D1B1E43771B003E96B0 /* RNNLayoutNode.m */; };
154
 		A7626BFD1FC2FB2C00492FB8 /* RNNTopBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626BFC1FC2FB2C00492FB8 /* RNNTopBarOptions.m */; };
154
 		A7626BFD1FC2FB2C00492FB8 /* RNNTopBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626BFC1FC2FB2C00492FB8 /* RNNTopBarOptions.m */; };
155
 		A7626C011FC5796200492FB8 /* RNNBottomTabsOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626C001FC5796200492FB8 /* RNNBottomTabsOptions.m */; };
155
 		A7626C011FC5796200492FB8 /* RNNBottomTabsOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7626C001FC5796200492FB8 /* RNNBottomTabsOptions.m */; };
156
+		E33AC20020B5BA0B0090DB8A /* RNNSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC1FF20B5BA0B0090DB8A /* RNNSplitViewController.m */; };
157
+		E33AC20420B5C3890090DB8A /* RNNStatusBarOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = E33AC20220B5C3880090DB8A /* RNNStatusBarOptions.h */; };
158
+		E33AC20520B5C3890090DB8A /* RNNStatusBarOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC20320B5C3890090DB8A /* RNNStatusBarOptions.m */; };
159
+		E33AC20820B5C4F90090DB8A /* RNNSplitViewOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */; };
156
 		E8367B801F7A8A4700675C05 /* VICMAImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */; };
160
 		E8367B801F7A8A4700675C05 /* VICMAImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */; };
157
 		E8367B811F7A8A4700675C05 /* VICMAImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */; };
161
 		E8367B811F7A8A4700675C05 /* VICMAImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */; };
158
 		E83BAD681F2734B500A9F3DD /* RNNNavigationOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */; };
162
 		E83BAD681F2734B500A9F3DD /* RNNNavigationOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */; };
355
 		A7626BFF1FC578AB00492FB8 /* RNNBottomTabsOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNBottomTabsOptions.h; sourceTree = "<group>"; };
359
 		A7626BFF1FC578AB00492FB8 /* RNNBottomTabsOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNBottomTabsOptions.h; sourceTree = "<group>"; };
356
 		A7626C001FC5796200492FB8 /* RNNBottomTabsOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBottomTabsOptions.m; sourceTree = "<group>"; };
360
 		A7626C001FC5796200492FB8 /* RNNBottomTabsOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNBottomTabsOptions.m; sourceTree = "<group>"; };
357
 		D8AFADBD1BEE6F3F00A4592D /* libReactNativeNavigation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeNavigation.a; sourceTree = BUILT_PRODUCTS_DIR; };
361
 		D8AFADBD1BEE6F3F00A4592D /* libReactNativeNavigation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeNavigation.a; sourceTree = BUILT_PRODUCTS_DIR; };
362
+		E33AC1FF20B5BA0B0090DB8A /* RNNSplitViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSplitViewController.m; sourceTree = "<group>"; };
363
+		E33AC20120B5BA550090DB8A /* RNNSplitViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSplitViewController.h; sourceTree = "<group>"; };
364
+		E33AC20220B5C3880090DB8A /* RNNStatusBarOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNNStatusBarOptions.h; sourceTree = "<group>"; };
365
+		E33AC20320B5C3890090DB8A /* RNNStatusBarOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNStatusBarOptions.m; sourceTree = "<group>"; };
366
+		E33AC20620B5C49E0090DB8A /* RNNSplitViewOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNNSplitViewOptions.h; sourceTree = "<group>"; };
367
+		E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNNSplitViewOptions.m; sourceTree = "<group>"; };
358
 		E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VICMAImageView.h; sourceTree = "<group>"; };
368
 		E8367B7E1F7A8A4700675C05 /* VICMAImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VICMAImageView.h; sourceTree = "<group>"; };
359
 		E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VICMAImageView.m; sourceTree = "<group>"; };
369
 		E8367B7F1F7A8A4700675C05 /* VICMAImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VICMAImageView.m; sourceTree = "<group>"; };
360
 		E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationOptionsTest.m; sourceTree = "<group>"; };
370
 		E83BAD671F2734B500A9F3DD /* RNNNavigationOptionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNNNavigationOptionsTest.m; sourceTree = "<group>"; };
501
 		504AFE611FFE52EF0076E904 /* Options */ = {
511
 		504AFE611FFE52EF0076E904 /* Options */ = {
502
 			isa = PBXGroup;
512
 			isa = PBXGroup;
503
 			children = (
513
 			children = (
514
+				E33AC20220B5C3880090DB8A /* RNNStatusBarOptions.h */,
515
+				E33AC20320B5C3890090DB8A /* RNNStatusBarOptions.m */,
504
 				504AFE621FFE53070076E904 /* RNNOptions.h */,
516
 				504AFE621FFE53070076E904 /* RNNOptions.h */,
505
 				504AFE631FFE53070076E904 /* RNNOptions.m */,
517
 				504AFE631FFE53070076E904 /* RNNOptions.m */,
506
 				E83BAD691F27362500A9F3DD /* RNNNavigationOptions.h */,
518
 				E83BAD691F27362500A9F3DD /* RNNNavigationOptions.h */,
537
 				50451D082042E20600695F00 /* RNNTransitionsOptions.m */,
549
 				50451D082042E20600695F00 /* RNNTransitionsOptions.m */,
538
 				50415CB820553B8E00BB682E /* RNNScreenTransition.h */,
550
 				50415CB820553B8E00BB682E /* RNNScreenTransition.h */,
539
 				50415CB920553B8E00BB682E /* RNNScreenTransition.m */,
551
 				50415CB920553B8E00BB682E /* RNNScreenTransition.m */,
552
+				E33AC20620B5C49E0090DB8A /* RNNSplitViewOptions.h */,
553
+				E33AC20720B5C4F90090DB8A /* RNNSplitViewOptions.m */,
540
 			);
554
 			);
541
 			name = Options;
555
 			name = Options;
542
 			sourceTree = "<group>";
556
 			sourceTree = "<group>";
600
 				507F43F61FF525B500D9425B /* RNNSegmentedControl.h */,
614
 				507F43F61FF525B500D9425B /* RNNSegmentedControl.h */,
601
 				507F43F71FF525B500D9425B /* RNNSegmentedControl.m */,
615
 				507F43F71FF525B500D9425B /* RNNSegmentedControl.m */,
602
 				507F441F1FFA8A8800D9425B /* RNNRootViewProtocol.h */,
616
 				507F441F1FFA8A8800D9425B /* RNNRootViewProtocol.h */,
617
+				E33AC1FF20B5BA0B0090DB8A /* RNNSplitViewController.m */,
618
+				E33AC20120B5BA550090DB8A /* RNNSplitViewController.h */,
603
 			);
619
 			);
604
 			name = Controllers;
620
 			name = Controllers;
605
 			sourceTree = "<group>";
621
 			sourceTree = "<group>";
950
 				7BA500751E2544B9001B9E1B /* ReactNativeNavigation.m in Sources */,
966
 				7BA500751E2544B9001B9E1B /* ReactNativeNavigation.m in Sources */,
951
 				263905B21E4C6F440023D7D3 /* MMDrawerController.m in Sources */,
967
 				263905B21E4C6F440023D7D3 /* MMDrawerController.m in Sources */,
952
 				E8AEDB3D1F55A1C2000F5A6A /* RNNElementView.m in Sources */,
968
 				E8AEDB3D1F55A1C2000F5A6A /* RNNElementView.m in Sources */,
969
+				E33AC20520B5C3890090DB8A /* RNNStatusBarOptions.m in Sources */,
953
 				E83BAD6B1F27363A00A9F3DD /* RNNNavigationOptions.m in Sources */,
970
 				E83BAD6B1F27363A00A9F3DD /* RNNNavigationOptions.m in Sources */,
954
 				506A2B1520973DFD00F43A95 /* RNNErrorHandler.m in Sources */,
971
 				506A2B1520973DFD00F43A95 /* RNNErrorHandler.m in Sources */,
955
 				7BBFE5441E25330E002A6182 /* RNNBridgeModule.m in Sources */,
972
 				7BBFE5441E25330E002A6182 /* RNNBridgeModule.m in Sources */,
974
 				214545251F4DC125006E8DA1 /* RNNUIBarButtonItem.m in Sources */,
991
 				214545251F4DC125006E8DA1 /* RNNUIBarButtonItem.m in Sources */,
975
 				263905B81E4C6F440023D7D3 /* UIViewController+MMDrawerController.m in Sources */,
992
 				263905B81E4C6F440023D7D3 /* UIViewController+MMDrawerController.m in Sources */,
976
 				263905CD1E4C6F440023D7D3 /* SidebarWunderlistAnimation.m in Sources */,
993
 				263905CD1E4C6F440023D7D3 /* SidebarWunderlistAnimation.m in Sources */,
994
+				E33AC20820B5C4F90090DB8A /* RNNSplitViewOptions.m in Sources */,
995
+				E33AC20020B5BA0B0090DB8A /* RNNSplitViewController.m in Sources */,
977
 				263905CF1E4C6F440023D7D3 /* TheSidebarController.m in Sources */,
996
 				263905CF1E4C6F440023D7D3 /* TheSidebarController.m in Sources */,
978
 				E8A430121F9CB87B00B61A20 /* RNNAnimatedView.m in Sources */,
997
 				E8A430121F9CB87B00B61A20 /* RNNAnimatedView.m in Sources */,
979
 				507F43F51FF4FCFE00D9425B /* HMSegmentedControl.m in Sources */,
998
 				507F43F51FF4FCFE00D9425B /* HMSegmentedControl.m in Sources */,

+ 35
- 1
lib/src/commands/LayoutTreeParser.test.ts View File

126
       expect(result.children[1].children[0].children[2].children[0].children[2].children[4].type).toEqual('Stack');
126
       expect(result.children[1].children[0].children[2].children[0].children[2].children[4].type).toEqual('Stack');
127
       expect(result.children[1].children[0].children[2].children[0].children[2].data).toEqual({ options: { topBar: { title: { text: 'Hello1'} } } });
127
       expect(result.children[1].children[0].children[2].children[0].children[2].data).toEqual({ options: { topBar: { title: { text: 'Hello1'} } } });
128
     });
128
     });
129
+
130
+    it('split view', () => {
131
+      const result = uut.parse(LayoutExamples.splitView);
132
+      const master = uut.parse(LayoutExamples.splitView.splitView.master);
133
+      const detail = uut.parse(LayoutExamples.splitView.splitView.detail);
134
+
135
+      expect(result.type).toEqual('SplitView');
136
+      expect(result.children[0]).toEqual(master);
137
+      expect(result.children[1]).toEqual(detail);
138
+    });
129
   });
139
   });
130
 
140
 
131
   it('options for all containing types', () => {
141
   it('options for all containing types', () => {
134
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
144
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
135
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
145
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
136
     expect(uut.parse({ sideMenu: { options, center: { component: {} } } }).data.options).toBe(options);
146
     expect(uut.parse({ sideMenu: { options, center: { component: {} } } }).data.options).toBe(options);
147
+    expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
137
   });
148
   });
138
 
149
 
139
   it('pass user provided id as is', () => {
150
   it('pass user provided id as is', () => {
169
   }
180
   }
170
 };
181
 };
171
 
182
 
183
+const optionsSplitView = {
184
+  displayMode: 'auto',
185
+  primaryEdge: 'leading',
186
+  minWidth: 150,
187
+  maxWidth: 300,
188
+};
189
+
172
 const singleComponent = {
190
 const singleComponent = {
173
   component: {
191
   component: {
174
     name: 'MyReactComponent',
192
     name: 'MyReactComponent',
279
   }
297
   }
280
 };
298
 };
281
 
299
 
300
+const splitView = {
301
+  splitView: {
302
+    master: {
303
+      stack: {
304
+        children: [
305
+          singleComponent,
306
+        ],
307
+        options
308
+      },
309
+    },
310
+    detail: stackWithTopBar,
311
+    options: optionsSplitView,
312
+  },
313
+};
314
+
282
 const LayoutExamples = {
315
 const LayoutExamples = {
283
   passProps,
316
   passProps,
284
   options,
317
   options,
288
   sideMenu,
321
   sideMenu,
289
   topTabs,
322
   topTabs,
290
   complexLayout,
323
   complexLayout,
291
-  externalComponent
324
+  externalComponent,
325
+  splitView
292
 };
326
 };

+ 17
- 0
lib/src/commands/LayoutTreeParser.ts View File

20
       return this._component(api.component);
20
       return this._component(api.component);
21
     } else if (api.externalComponent) {
21
     } else if (api.externalComponent) {
22
       return this._externalComponent(api.externalComponent);
22
       return this._externalComponent(api.externalComponent);
23
+    } else if (api.splitView) {
24
+      return this._splitView(api.splitView);
23
     }
25
     }
24
     throw new Error(`unknown LayoutType "${_.keys(api)}"`);
26
     throw new Error(`unknown LayoutType "${_.keys(api)}"`);
25
   }
27
   }
107
       children: []
109
       children: []
108
     };
110
     };
109
   }
111
   }
112
+
113
+  _splitView(api): LayoutNode {
114
+    const master = this.parse(api.master);
115
+    const detail = this.parse(api.detail);
116
+
117
+    return {
118
+      id: api.id,
119
+      type: LayoutType.SplitView,
120
+      data: { name: api.name, options: api.options },
121
+      children: [
122
+        master,
123
+        detail,
124
+      ],
125
+    };
126
+  }
110
 }
127
 }

+ 2
- 1
lib/src/commands/LayoutType.ts View File

7
   SideMenuLeft = 'SideMenuLeft',
7
   SideMenuLeft = 'SideMenuLeft',
8
   SideMenuRight = 'SideMenuRight',
8
   SideMenuRight = 'SideMenuRight',
9
   TopTabs = 'TopTabs',
9
   TopTabs = 'TopTabs',
10
-  ExternalComponent = 'ExternalComponent'
10
+  ExternalComponent = 'ExternalComponent',
11
+  SplitView = 'SplitView'
11
 }
12
 }

+ 9
- 0
playground/ios/playground.xcodeproj/project.pbxproj View File

233
 			remoteGlobalIDString = 2D2A28881D9B049200D4039D;
233
 			remoteGlobalIDString = 2D2A28881D9B049200D4039D;
234
 			remoteInfo = "RCTWebSocket-tvOS";
234
 			remoteInfo = "RCTWebSocket-tvOS";
235
 		};
235
 		};
236
+		E33AC1FB20B5AFC70090DB8A /* PBXContainerItemProxy */ = {
237
+			isa = PBXContainerItemProxy;
238
+			containerPortal = 7B8F2FCA1E840F3600110AEC /* React.xcodeproj */;
239
+			proxyType = 2;
240
+			remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04;
241
+			remoteInfo = "privatedata-tvOS";
242
+		};
236
 		E506412420A3138100EC769F /* PBXContainerItemProxy */ = {
243
 		E506412420A3138100EC769F /* PBXContainerItemProxy */ = {
237
 			isa = PBXContainerItemProxy;
244
 			isa = PBXContainerItemProxy;
238
 			containerPortal = 7B8F2FCA1E840F3600110AEC /* React.xcodeproj */;
245
 			containerPortal = 7B8F2FCA1E840F3600110AEC /* React.xcodeproj */;
907
 				PRODUCT_BUNDLE_IDENTIFIER = com.reactnativenavigation.playground;
914
 				PRODUCT_BUNDLE_IDENTIFIER = com.reactnativenavigation.playground;
908
 				PRODUCT_NAME = playground;
915
 				PRODUCT_NAME = playground;
909
 				PROVISIONING_PROFILE_SPECIFIER = "";
916
 				PROVISIONING_PROFILE_SPECIFIER = "";
917
+				TARGETED_DEVICE_FAMILY = "1,2";
910
 			};
918
 			};
911
 			name = Debug;
919
 			name = Debug;
912
 		};
920
 		};
926
 				PRODUCT_BUNDLE_IDENTIFIER = com.reactnativenavigation.playground;
934
 				PRODUCT_BUNDLE_IDENTIFIER = com.reactnativenavigation.playground;
927
 				PRODUCT_NAME = playground;
935
 				PRODUCT_NAME = playground;
928
 				PROVISIONING_PROFILE_SPECIFIER = "";
936
 				PROVISIONING_PROFILE_SPECIFIER = "";
937
+				TARGETED_DEVICE_FAMILY = "1,2";
929
 			};
938
 			};
930
 			name = Release;
939
 			name = Release;
931
 		};
940
 		};

+ 41
- 0
playground/src/screens/WelcomeScreen.js View File

34
           <Text testID={testIDs.WELCOME_SCREEN_HEADER} style={styles.h1}>{`React Native Navigation!`}</Text>
34
           <Text testID={testIDs.WELCOME_SCREEN_HEADER} style={styles.h1}>{`React Native Navigation!`}</Text>
35
           <Button title='Switch to tab based app' testID={testIDs.TAB_BASED_APP_BUTTON} onPress={this.onClickSwitchToTabs} />
35
           <Button title='Switch to tab based app' testID={testIDs.TAB_BASED_APP_BUTTON} onPress={this.onClickSwitchToTabs} />
36
           <Button title='Switch to app with side menus' testID={testIDs.TAB_BASED_APP_SIDE_BUTTON} onPress={this.onClickSwitchToSideMenus} />
36
           <Button title='Switch to app with side menus' testID={testIDs.TAB_BASED_APP_SIDE_BUTTON} onPress={this.onClickSwitchToSideMenus} />
37
+          {Platform.OS === 'ios' && <Button title='Switch to split view based app' testID={testIDs.SPLIT_VIEW_BUTTON} onPress={this.onClickSplitView} />}
37
           <Button title='Push Lifecycle Screen' testID={testIDs.PUSH_LIFECYCLE_BUTTON} onPress={this.onClickLifecycleScreen} />
38
           <Button title='Push Lifecycle Screen' testID={testIDs.PUSH_LIFECYCLE_BUTTON} onPress={this.onClickLifecycleScreen} />
38
           <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
39
           <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
39
           <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
40
           <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
447
       }
448
       }
448
     });
449
     });
449
   }
450
   }
451
+
452
+  onClickSplitView = () => {
453
+    Navigation.setRoot({
454
+      root: {
455
+        splitView: {
456
+          id: 'SPLITVIEW_ID',
457
+          master: {
458
+            stack: {
459
+              id: 'MASTER_ID',
460
+              children: [
461
+                {
462
+                  component: {
463
+                    name: 'navigation.playground.WelcomeScreen'
464
+                  },
465
+                },
466
+              ]
467
+            },
468
+          },
469
+          detail: {
470
+            stack: {
471
+              id: 'DETAILS_ID',
472
+              children: [
473
+                {
474
+                  component: {
475
+                    name: 'navigation.playground.WelcomeScreen'
476
+                  },
477
+                },
478
+              ]
479
+            }
480
+          },
481
+          options: {
482
+            displayMode: 'auto',
483
+            primaryEdge: 'leading',
484
+            minWidth: 150,
485
+            maxWidth: 300,
486
+          },
487
+        },
488
+      },
489
+    });
490
+  }
450
 }
491
 }
451
 
492
 
452
 module.exports = WelcomeScreen;
493
 module.exports = WelcomeScreen;

+ 1
- 0
playground/src/testIDs.js View File

60
   EXTERNAL_COMPONENT_IN_DEEP_STACK: `EXTERNAL_COMPONENT_IN_DEEP_STACK`,
60
   EXTERNAL_COMPONENT_IN_DEEP_STACK: `EXTERNAL_COMPONENT_IN_DEEP_STACK`,
61
   SET_STACK_ROOT_BUTTON: `SET_STACK_ROOT_BUTTON`,
61
   SET_STACK_ROOT_BUTTON: `SET_STACK_ROOT_BUTTON`,
62
   MODAL_LIFECYCLE_BUTTON: `MODAL_LIFECYCLE_BUTTON`,
62
   MODAL_LIFECYCLE_BUTTON: `MODAL_LIFECYCLE_BUTTON`,
63
+  SPLIT_VIEW_BUTTON: `SPLIT_VIEW_BUTTON`,
63
 
64
 
64
   // Elements
65
   // Elements
65
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,
66
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,

+ 5378
- 0
yarn.lock
File diff suppressed because it is too large
View File