Skip to content

Commit 2d4383e

Browse files
committed
Refactors in IOS Objective-C plugin, to allow many scenarios to instantiate a ViewController from native
1 parent 8b7aec4 commit 2d4383e

File tree

5 files changed

+162
-215
lines changed

5 files changed

+162
-215
lines changed

src/ios/CDVNativeView.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
//
22
// CDVNativeView.h
3-
// IRPF
43
//
54
// Created by Michel Felipe on 05/09/17.
65
//
76
//
87

8+
// Reference: https://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown
9+
#define SuppressPerformSelectorLeakWarning(Stuff) \
10+
do { \
11+
_Pragma("clang diagnostic push") \
12+
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
13+
Stuff; \
14+
_Pragma("clang diagnostic pop") \
15+
} while (0)
16+
917
#import <Cordova/CDV.h>
1018

1119
@interface CDVNativeView : CDVPlugin
1220

21+
@property (strong, nonatomic) NSDictionary *resultExceptions;
22+
1323
- (void)show:(CDVInvokedUrlCommand*)command;
1424

1525
@end

src/ios/CDVNativeView.m

Lines changed: 151 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,192 @@
11
//
22
// CDVNativeView.m
3-
// IRPF
43
//
54
// Created by Michel Felipe on 05/09/17.
65
//
76
//
87

98
#import "CDVNativeView.h"
10-
#import "InstantiateViewControllerError.h"
119
#import <UIKit/UIKit.h>
1210

1311
@interface CDVNativeView (hidden)
1412

15-
-(UIViewController*) instantiateViewControllerWithName: (NSString*) name;
16-
-(UIViewController*) tryInstantiateViewWithName: (NSString*) name;
13+
-(UIViewController *) instantiateViewControllerWithName: (NSString*) name;
14+
-(UIViewController *) tryInstantiateViewWithName: (NSString*) name;
1715

1816
@end
1917

2018
@implementation CDVNativeView
2119

20+
- (instancetype)init
21+
{
22+
self = [super init];
23+
if (self) {
24+
self.resultExceptions = @{
25+
@"IO_EXCEPTION": ^{
26+
return CDVCommandStatus_IO_EXCEPTION;
27+
},
28+
@"CLASS_NOT_FOUND_EXCEPTION": ^{
29+
return CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION;
30+
},
31+
@"PARAM_INVALID_EXCEPTION": ^{
32+
return CDVCommandStatus_ERROR;
33+
},
34+
@"INSTANTIATION_EXCEPTION": ^{
35+
return CDVCommandStatus_INSTANTIATION_EXCEPTION;
36+
}
37+
};
38+
}
39+
return self;
40+
}
2241
- (void)show:(CDVInvokedUrlCommand*)command {
2342

24-
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
25-
26-
NSString* className = [command argumentAtIndex: 0];
27-
NSString* storyboardName = @"";
28-
29-
UIViewController *viewController = nil;
30-
31-
if ([command.arguments count] > 1) {
32-
33-
NSString* secondParam = [command argumentAtIndex: 1];
34-
35-
if (secondParam != nil) {
36-
storyboardName = className;
37-
className = secondParam != nil ? secondParam : @"";
38-
}
39-
40-
}
43+
CDVPluginResult *pluginResult;
4144

42-
if ([self.viewController navigationController] != nil) {
45+
@try {
46+
NSString *viewControllerName;
47+
NSString *storyboardName;
48+
NSString *message;
4349

44-
if ([self.viewController.navigationController.viewControllers count] > 1) {
45-
[self.viewController.navigationController popViewControllerAnimated: YES];
46-
}else{
50+
// Handling arguments
51+
if ([command.arguments count] == 1) {
4752

48-
viewController = [self tryInstantiateViewWithName: className];
53+
NSString *firstParam = [command argumentAtIndex: 0];
4954

50-
[self.viewController.navigationController pushViewController:viewController animated:YES];
51-
}
52-
53-
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
54-
55-
}else if (className.length > 0 ){
56-
57-
if ([[NSBundle mainBundle] pathForResource:storyboardName ofType: @"storyboardc"] != nil
58-
&& storyboardName.length > 0) {
55+
if ([firstParam hasSuffix:@"Storyboard"]) {
56+
// Init viewController from Storyboard with initial view Controlleror or user defined viewControllerName
57+
[self instantiateViewController:nil fromStoryboard:firstParam];
5958

60-
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:[NSBundle mainBundle]];
61-
62-
viewController = [storyboard instantiateViewControllerWithIdentifier:className];
63-
64-
}else{
59+
} else if ([firstParam hasSuffix:@"ViewController"]) {
60+
// Init viewController with or without xib
61+
[self instantiateViewController:firstParam];
6562

66-
viewController = [self tryInstantiateViewWithName: className];
63+
} else {
64+
message = [[NSString alloc] initWithFormat:@"%@ invalid. Must contain Storyboard or Controller in name", firstParam];
65+
@throw [[NSException alloc] initWithName:@"IO_EXCEPTION" reason:message userInfo:nil];
6766
}
67+
68+
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
69+
70+
} else if ([command.arguments count] == 2) {
71+
72+
// first param is Storyboard
73+
storyboardName = [command argumentAtIndex: 0];
74+
75+
// second param is ViewController and/or segueID
76+
viewControllerName = [command argumentAtIndex: 1];
77+
78+
// Init viewController from Storyboard with initial view Controlleror or user defined viewControllerName
79+
[self instantiateViewController:viewControllerName fromStoryboard:storyboardName];
80+
81+
} else {
82+
message = [[NSString alloc] initWithFormat:@"An UIViewController name or Storyboard name is required at least. Please, pass in the first param in JS, like this: 'NativeView.show('MyViewController') or NativeView.show('MyStoryboard') or NativeView.show('MyStoryboard', 'MyViewController')"];
83+
@throw [[NSException alloc] initWithName:@"CLASS_NOT_FOUND_EXCEPTION" reason:message userInfo:nil];
84+
}
85+
} @catch (NSException *e) {
86+
NSLog(@"%@", e.reason);
6887

69-
CDVAppDelegate* appDelegate = [[UIApplication sharedApplication] delegate];
70-
appDelegate.window.rootViewController = viewController;
88+
typedef CDVCommandStatus (^CaseBlock)(void);
7189

72-
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
73-
}else{
74-
@try {
75-
[self raiseClassNameError];
76-
} @catch(InstantiateViewControllerError* e) {
77-
NSLog(@"%@", e.reason);
78-
}
90+
CaseBlock c = self.resultExceptions[e.name];
91+
92+
CDVCommandStatus exceptionType = c ? c() : CDVCommandStatus_ERROR;
93+
pluginResult = [CDVPluginResult resultWithStatus:exceptionType messageAsString:e.reason];
7994
}
8095

81-
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId ];
96+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
8297
}
8398

84-
- (UIViewController*) instantiateViewControllerWithName: (NSString*) name {
85-
86-
if ([[NSBundle mainBundle] pathForResource:name ofType: @"nib"]) {
87-
return [[UIViewController alloc] initWithNibName: name bundle: nil];
88-
}
99+
- (void) instantiateViewController:(NSString *)viewControllerName {
89100

90-
NSString* message = [[NSString alloc] initWithFormat:@"The ViewController: %@ was not found", name];
91-
@throw [[InstantiateViewControllerError alloc] initWithName: @"notFound" reason: message userInfo: nil];
92-
}
93-
94-
- (UIViewController*) tryInstantiateViewWithName:(NSString *)name {
101+
NSString *message;
95102

96-
@try {
97-
return [self instantiateViewControllerWithName: name];
98-
} @catch (InstantiateViewControllerError* e) {
99-
NSLog(@"%@", e.reason);
103+
if (viewControllerName && viewControllerName.length > 0) {
104+
105+
UIViewController *destinyViewController = nil;
106+
107+
// Call preInitializeViewControllerWithName if exists in self.viewController
108+
SEL selector = NSSelectorFromString(@"preInitializeViewControllerWithName:");
109+
110+
if ([self.viewController respondsToSelector:selector]) {
111+
112+
SuppressPerformSelectorLeakWarning(
113+
destinyViewController = [self.viewController performSelector:selector withObject:viewControllerName];
114+
);
115+
}
116+
117+
// if not performSelector, call automatically the viewController
118+
if (!destinyViewController) {
119+
@try {
120+
if ([[NSBundle mainBundle] pathForResource:viewControllerName ofType:@"nib"]) {
121+
// Initialize with nib/xib
122+
destinyViewController = [[UIViewController alloc] initWithNibName:viewControllerName bundle:nil];
123+
} else {
124+
// Initialize without nib/xib
125+
Class viewController = NSClassFromString(viewControllerName);
126+
id anInstance = [[viewController alloc] init];
127+
destinyViewController = anInstance;
128+
}
129+
} @catch(NSException *e) {
130+
message = [[NSString alloc] initWithFormat:@"%@ and/or its own xib does not exist. \nDetail: %@", viewControllerName, e.reason];
131+
@throw [[NSException alloc] initWithName:@"CLASS_NOT_FOUND_EXCEPTION" reason:message userInfo:nil];
132+
}
133+
}
134+
135+
// Call destinyViewController from current viewController
136+
[self.viewController.navigationController pushViewController:destinyViewController animated:YES];
137+
138+
} else {
139+
message = [[NSString alloc] initWithFormat:@"UIViewController with name %@ was not found", viewControllerName];
140+
@throw [[NSException alloc] initWithName:@"PARAM_INVALID_EXCEPTION" reason:message userInfo:nil];
100141
}
101-
102-
return nil;
103142
}
104143

105-
- (void) raiseClassNameError {
144+
- (void) instantiateViewController:(NSString *)viewControllerName fromStoryboard:(NSString *)storyboardName {
145+
146+
NSString *message;
106147

107-
NSString* message = [[NSString alloc] initWithFormat:@"The UIViewController name is required when the project don't have a navigationController. Please, pass a className by param in JS, like this: 'NativeView.show('MyUIViewController')"];
108-
@throw [[InstantiateViewControllerError alloc] initWithName: @"nameNotDefined" reason: message userInfo: nil];
148+
if (storyboardName && storyboardName.length > 0) {
149+
150+
UIViewController *destinyViewController = nil;
151+
152+
// Call preInitializeViewControllerWithName:fromStoryBoardName if exists in self.viewController
153+
SEL selector = NSSelectorFromString(@"preInitializeViewControllerWithName:fromStoryBoardName");
154+
155+
if ([self.viewController respondsToSelector:selector]) {
156+
157+
SuppressPerformSelectorLeakWarning(
158+
destinyViewController = [self.viewController performSelector:selector withObject:viewControllerName withObject:storyboardName];
159+
);
160+
}
161+
162+
// if not performSelector, call automatically the viewController from storyboard
163+
if (!destinyViewController) {
164+
// initialize a storyboard automatically from viewControllerName or default initialViewController property
165+
@try {
166+
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:[NSBundle mainBundle]];
167+
168+
if (viewControllerName && viewControllerName.length > 0) {
169+
message = [[NSString alloc] initWithFormat:@"Identity -> Storyboard ID: %@ not found in storyboard %@", viewControllerName, storyboardName];
170+
// if pass a viewControllerName, initializate the storyboard with viewControllerName initial
171+
destinyViewController = [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
172+
} else {
173+
message = [[NSString alloc] initWithFormat:@"Storyboard -> ViewController -> 'is Initial View Controller' not check in storyboard %@", storyboardName];
174+
// if not pass a viewControllerName, initializate the storyboard with default inicialViewController property
175+
destinyViewController = [storyboard instantiateInitialViewController];
176+
}
177+
} @catch (NSException *e) {
178+
NSString *detailMessage = [[NSString alloc] initWithFormat:@"%@ \nDetail: %@", message, e.reason];
179+
@throw [[NSException alloc] initWithName:@"INSTANTIATION_EXCEPTION" reason:detailMessage userInfo:nil];
180+
}
181+
}
182+
183+
// Call destinyViewController from current viewController
184+
[self.viewController.navigationController pushViewController:destinyViewController animated:YES];
185+
186+
} else {
187+
message = [[NSString alloc] initWithFormat:@"Storyboard %@ was not found", storyboardName];
188+
@throw [[NSException alloc] initWithName:@"PARAM_INVALID_EXCEPTION" reason:message userInfo:nil];
189+
}
109190
}
110191

111192
@end

0 commit comments

Comments
 (0)