66// Copyright © 2015 Branch Metrics. All rights reserved.
77//
88
9+
910#import " BNCStrongMatchHelper.h"
11+ #import < objc/runtime.h>
1012#import " BNCConfig.h"
1113#import " BNCPreferenceHelper.h"
1214#import " BNCSystemObserver.h"
1315#import " BranchConstants.h"
1416
17+
18+ #pragma mark BNCStrongMatchHelper iOS 8.0
19+
20+
1521// Stub the class for older Xcode versions, methods don't actually do anything.
1622#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < 90000
1723
1824@implementation BNCStrongMatchHelper
1925
20- + (BNCStrongMatchHelper *)strongMatchHelper { return nil ; }
21- - (void )createStrongMatchWithBranchKey : (NSString *)branchKey { }
22- - (BOOL )shouldDelayInstallRequest { return NO ; }
23- + (NSURL *)getUrlForCookieBasedMatchingWithBranchKey : (NSString *)branchKey redirectUrl : (NSString *)redirectUrl { return nil ; }
26+ + (BNCStrongMatchHelper *)strongMatchHelper {
27+ return nil ;
28+ }
29+
30+ - (void )createStrongMatchWithBranchKey : (NSString *)branchKey {
31+ }
32+
33+ - (BOOL )shouldDelayInstallRequest {
34+ return NO ;
35+ }
36+
37+ + (NSURL *)getUrlForCookieBasedMatchingWithBranchKey : (NSString *)branchKey
38+ redirectUrl : (NSString *)redirectUrl {
39+ return nil ;
40+ }
2441
2542@end
2643
27- #else
2844
29- NSInteger const ABOUT_30_DAYS_TIME_IN_SECONDS = 60 * 60 * 24 * 30 ;
45+ #else // ------------------------------------------------------------------------------ iOS >= 9.0
46+
47+
48+ #pragma mark - BNCMatchViewController
49+
50+
51+ #import < SafariServices/SafariServices.h>
52+
53+
54+ // This is a class interface that will be dynamically subclassed to the SFSafariViewController
55+ @interface BNCMatchViewController : UIViewController
56+ @end
57+
58+
59+ @implementation BNCMatchViewController
60+
61+ - (instancetype ) initWithURL : (NSURL *)URL {
62+ self = [super init ];
63+ return self;
64+ }
65+
66+ - (BOOL ) canBecomeFirstResponder {
67+ // NSLog(@"Can be!!! Responder.");
68+ return NO ;
69+ }
70+
71+ - (BOOL ) becomeFirstResponder {
72+ // NSLog(@"First!! Responder.");
73+ return NO ;
74+ }
75+
76+ - (UIResponder*) nextResponder {
77+ // NSLog(@"Next!! Responder.");
78+ return nil ;
79+ }
80+
81+ - (void ) setDelegate : (id )delegate {
82+ }
83+
84+ @end
85+
86+
87+ #pragma mark - BNCStrongMatchHelper iOS 9.0
3088
31- @interface BNCStrongMatchHelper ()
3289
33- @property ( strong , nonatomic ) UIWindow *secondWindow;
90+ @interface BNCStrongMatchHelper ()
3491@property (assign , nonatomic ) BOOL requestInProgress;
3592@property (assign , nonatomic ) BOOL shouldDelayInstallRequest;
36-
93+ @property (strong , nonatomic ) UIWindow *primaryWindow;
94+ @property (strong , nonatomic ) BNCMatchViewController *matchViewController;
3795@end
3896
97+
3998@implementation BNCStrongMatchHelper
4099
41100+ (BNCStrongMatchHelper *)strongMatchHelper {
@@ -49,7 +108,8 @@ + (BNCStrongMatchHelper *)strongMatchHelper {
49108 return strongMatchHelper;
50109}
51110
52- + (NSURL *)getUrlForCookieBasedMatchingWithBranchKey : (NSString *)branchKey redirectUrl : (NSString *)redirectUrl {
111+ + (NSURL *)getUrlForCookieBasedMatchingWithBranchKey : (NSString *)branchKey
112+ redirectUrl : (NSString *)redirectUrl {
53113 if (!branchKey) {
54114 return nil ;
55115 }
@@ -95,69 +155,123 @@ + (NSURL *)getUrlForCookieBasedMatchingWithBranchKey:(NSString *)branchKey redir
95155}
96156
97157- (void )createStrongMatchWithBranchKey : (NSString *)branchKey {
98- if (self.requestInProgress ) {
99- return ;
100- }
158+ @synchronized (self) {
159+ if (self.requestInProgress ) return ;
101160
102- self.requestInProgress = YES ;
103-
104- NSDate *thirtyDaysAgo = [NSDate dateWithTimeIntervalSinceNow: -ABOUT_30_DAYS_TIME_IN_SECONDS];
105- NSDate *lastCheck = [BNCPreferenceHelper preferenceHelper ].lastStrongMatchDate ;
106- if ([lastCheck compare: thirtyDaysAgo] == NSOrderedDescending) {
107- self.requestInProgress = NO ;
108- return ;
109- }
110-
111- self.shouldDelayInstallRequest = YES ;
112- [self presentSafariVCWithBranchKey: branchKey];
113- }
161+ NSInteger const ABOUT_30_DAYS_TIME_IN_SECONDS = 60 * 60 * 24 * 30 ;
162+ NSDate *thirtyDaysAgo = [NSDate dateWithTimeIntervalSinceNow: -ABOUT_30_DAYS_TIME_IN_SECONDS];
163+ NSDate *lastCheck = [BNCPreferenceHelper preferenceHelper ].lastStrongMatchDate ;
164+ if ([lastCheck compare: thirtyDaysAgo] == NSOrderedDescending) return ;
165+
166+ NSURL *strongMatchUrl =
167+ [BNCStrongMatchHelper
168+ getUrlForCookieBasedMatchingWithBranchKey: branchKey
169+ redirectUrl: nil ];
170+ if (!strongMatchUrl) return ;
171+
172+ self.requestInProgress = YES ;
173+ self.shouldDelayInstallRequest = YES ;
114174
115- - (void )presentSafariVCWithBranchKey : (NSString *)branchKey {
116- NSURL *strongMatchUrl = [BNCStrongMatchHelper getUrlForCookieBasedMatchingWithBranchKey: branchKey redirectUrl: nil ];
117- if (!strongMatchUrl) {
118- self.shouldDelayInstallRequest = NO ;
119- self.requestInProgress = NO ;
120- return ;
121- }
122-
123- Class SFSafariViewControllerClass = NSClassFromString (@" SFSafariViewController" );
124- Class UIApplicationClass = NSClassFromString (@" UIApplication" );
125- if (SFSafariViewControllerClass) {
126-
127175 // Must be on next run loop to avoid a warning
128176 dispatch_async (dispatch_get_main_queue (), ^{
129- UIViewController * safController = [[SFSafariViewControllerClass alloc ] initWithURL: strongMatchUrl];
130- self.secondWindow = [[UIWindow alloc ] initWithFrame: [[UIScreen mainScreen ] bounds ]];
131- self.secondWindow .rootViewController = safController;
132- self.secondWindow .windowLevel = UIWindowLevelNormal - 100 ;
133- [self .secondWindow setHidden: NO ];
134- UIWindow *keyWindow = [[UIApplicationClass sharedApplication ] keyWindow ];
135- [self .secondWindow makeKeyWindow ];
136-
137- // Give enough time for Safari to load the request (optimized for 3G)
138- dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t )(3 * NSEC_PER_SEC)), dispatch_get_main_queue (), ^{
139- [keyWindow makeKeyWindow ];
140-
141- // Remove the window and release it's strong reference. This is important to ensure that
142- // applications using view controller based status bar appearance are restored.
143- [self .secondWindow removeFromSuperview ];
144- self.secondWindow = nil ;
145-
146- [BNCPreferenceHelper preferenceHelper ].lastStrongMatchDate = [NSDate date ];
177+
178+ if (![self willLoadViewControllerWithURL: strongMatchUrl]) {
179+ self.shouldDelayInstallRequest = NO ;
147180 self.requestInProgress = NO ;
148- });
181+ return ;
182+ }
183+
184+ // Give enough time for Safari to load the request (optimized for 3G)
185+ dispatch_after (
186+ dispatch_time (DISPATCH_TIME_NOW, (int64_t )(3 * NSEC_PER_SEC)),
187+ dispatch_get_main_queue (),
188+ ^{ [self unloadViewController ]; }
189+ );
149190 });
150191 }
151- else {
152- self.requestInProgress = NO ;
192+ }
193+
194+ - (BOOL ) subclass : (Class )subclass selector : (SEL )selector {
195+ Class templateClass = objc_getClass (" BNCMatchViewController" );
196+ if (!templateClass) return NO ;
197+
198+ Method method = class_getInstanceMethod (templateClass, selector);
199+ if (!method) return NO ;
200+
201+ const char * typeEncoding = method_getTypeEncoding (method);
202+ if (!typeEncoding) return NO ;
203+
204+ IMP implementation = class_getMethodImplementation (templateClass, selector);
205+ if (!implementation) return NO ;
206+
207+ class_addMethod (subclass, selector, implementation, typeEncoding);
208+ return YES ;
209+ }
210+
211+ - (BOOL ) willLoadViewControllerWithURL : (NSURL *)matchURL {
212+ if (self.primaryWindow ) return NO ;
213+
214+ // Dynamically subclass the SFSafariViewController if available.
215+ // This allows us to compile and link to an app that doesn't
216+ // include SafariServices, but is also able to compile and link
217+ // when it is.
218+
219+ Class SFSafariViewControllerClass = NSClassFromString (@" SFSafariViewController" );
220+ if (!SFSafariViewControllerClass) return NO ;
221+
222+ Class BNCMatchViewControllerSubclass = NSClassFromString (@" BNCMatchViewController_Safari" );
223+ if (!BNCMatchViewControllerSubclass) {
224+ // The class isn't registered. Create it:
225+
226+ BNCMatchViewControllerSubclass =
227+ objc_allocateClassPair (SFSafariViewControllerClass, " BNCMatchViewController_Safari" , 0 );
228+ if (!BNCMatchViewControllerSubclass) return NO ;
229+
230+ BOOL fail = NO ;
231+ fail |= ![self subclass: BNCMatchViewControllerSubclass selector: @selector (becomeFirstResponder )];
232+ fail |= ![self subclass: BNCMatchViewControllerSubclass selector: @selector (canBecomeFirstResponder )];
233+ fail |= ![self subclass: BNCMatchViewControllerSubclass selector: @selector (nextResponder )];
234+ if (fail) {
235+ objc_disposeClassPair (BNCMatchViewControllerSubclass);
236+ return NO ;
237+ }
238+ objc_registerClassPair (BNCMatchViewControllerSubclass);
153239 }
240+
241+ self.matchViewController = [[BNCMatchViewControllerSubclass alloc ] initWithURL: matchURL];
242+ if (!self.matchViewController ) return NO ;
243+
244+ self.primaryWindow = [[UIApplication sharedApplication ] keyWindow ];
245+ self.matchViewController .delegate = self;
246+ self.matchViewController .view .frame = self.primaryWindow .bounds ;
247+
248+ [self .primaryWindow.rootViewController addChildViewController: self .matchViewController];
249+ [self .primaryWindow insertSubview: self .matchViewController.view atIndex: 0 ];
250+ [self .matchViewController didMoveToParentViewController: self .primaryWindow.rootViewController];
251+
252+ return YES ;
154253}
155254
156- - (BOOL )shouldDelayInstallRequest {
157- return _shouldDelayInstallRequest;
255+ - (void ) unloadViewController {
256+ // NSLog(@"unloadViewController");
257+ [self .matchViewController willMoveToParentViewController: nil ];
258+ [self .matchViewController.view removeFromSuperview ];
259+ [self .matchViewController removeFromParentViewController ];
260+ self.matchViewController .delegate = nil ;
261+ self.matchViewController = nil ;
262+ self.primaryWindow = nil ;
263+
264+ [BNCPreferenceHelper preferenceHelper ].lastStrongMatchDate = [NSDate date ];
265+ self.shouldDelayInstallRequest = NO ;
266+ self.requestInProgress = NO ;
158267}
159268
269+ - (void )safariViewController : (SFSafariViewController *)controller
270+ didCompleteInitialLoad : (BOOL )didLoadSuccessfully {
271+ NSLog (@" Safari Did load. Success: %d ." , didLoadSuccessfully);
272+ [self unloadViewController ];
273+ }
160274
161275@end
162276
163- #endif
277+ #endif // ------------------------------------------------------------------------------ iOS >= 9.0
0 commit comments