6
6
// Copyright © 2015 Branch Metrics. All rights reserved.
7
7
//
8
8
9
+ #import " BNCPreferenceHelper.h"
9
10
#import " BNCContentDiscoveryManager.h"
10
11
#import " BNCSystemObserver.h"
11
12
#import " BNCError.h"
29
30
30
31
@interface BNCContentDiscoveryManager ()
31
32
32
- @property (strong , nonatomic ) NSUserActivity *currentUserActivity ;
33
+ @property (strong , nonatomic ) NSMutableDictionary *userInfo ;
33
34
34
35
@end
35
36
@@ -39,18 +40,14 @@ @implementation BNCContentDiscoveryManager
39
40
40
41
- (NSString *)spotlightIdentifierFromActivity : (NSUserActivity *)userActivity {
41
42
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
42
- if ([userActivity.activityType hasPrefix: BRANCH_SPOTLIGHT_PREFIX]) {
43
- return userActivity.activityType ;
44
- }
43
+ // If it has our prefix, then the link identifier is just the last piece of the identifier.
44
+ NSString *activityIdentifier = userActivity.userInfo [CSSearchableItemActivityIdentifier] ;
45
+ BOOL isBranchIdentifier = [activityIdentifier hasPrefix: BRANCH_SPOTLIGHT_PREFIX];
45
46
46
- // CoreSpotlight version. Matched if it has our prefix, then the link identifier is just the last piece of the identifier.
47
- if ([userActivity.activityType isEqualToString: CSSearchableItemActionType]) {
48
- NSString *activityIdentifier = userActivity.userInfo [CSSearchableItemActivityIdentifier];
49
- BOOL isBranchIdentifier = [activityIdentifier hasPrefix: BRANCH_SPOTLIGHT_PREFIX];
50
-
51
- if (isBranchIdentifier) {
52
- return activityIdentifier;
53
- }
47
+ // Checking for CSSearchableItemActionType in the activity for legacy spotlight indexing (pre 0.12.7)
48
+ // Now we index NSUserActivies with type set to io.branch. + bundleId for better SEO
49
+ if ([userActivity.activityType isEqualToString: CSSearchableItemActionType] || isBranchIdentifier) {
50
+ return activityIdentifier;
54
51
}
55
52
#endif
56
53
@@ -59,16 +56,14 @@ - (NSString *)spotlightIdentifierFromActivity:(NSUserActivity *)userActivity {
59
56
60
57
- (NSString *)standardSpotlightIdentifierFromActivity : (NSUserActivity *)userActivity {
61
58
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
62
- // CoreSpotlight version. Matched if it has our prefix, then the link identifier is just the last piece of the identifier.
63
- if ([userActivity.activityType isEqualToString: CSSearchableItemActionType] && userActivity.userInfo [CSSearchableItemActivityIdentifier]) {
59
+ if (userActivity.userInfo [CSSearchableItemActivityIdentifier]) {
64
60
return userActivity.userInfo [CSSearchableItemActivityIdentifier];
65
61
}
66
62
#endif
67
63
68
64
return nil ;
69
65
}
70
66
71
-
72
67
#pragma mark - Content Indexing
73
68
74
69
- (void )indexContentWithTitle : (NSString *)title
@@ -309,7 +304,7 @@ - (void)indexContentWithTitle:(NSString *)title
309
304
if ([BNCSystemObserver getOSVersion ].integerValue < 9 ) {
310
305
NSError *error = [NSError errorWithDomain: BNCErrorDomain code: BNCVersionError userInfo: @{ NSLocalizedDescriptionKey : @" Cannot use CoreSpotlight indexing service prior to iOS 9" }];
311
306
if (callback) {
312
- callback (nil , error);
307
+ callback ([BNCPreferenceHelper preferenceHelper ]. userUrl , error);
313
308
}
314
309
else if (spotlightCallback) {
315
310
spotlightCallback (nil , nil , error);
@@ -319,7 +314,7 @@ - (void)indexContentWithTitle:(NSString *)title
319
314
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < 90000
320
315
NSError *error = [NSError errorWithDomain: BNCErrorDomain code: BNCBadRequestError userInfo: @{ NSLocalizedDescriptionKey : @" CoreSpotlight is not available because the base SDK for this project is less than 9.0" }];
321
316
if (callback) {
322
- callback (nil , error);
317
+ callback ([BNCPreferenceHelper preferenceHelper ]. userUrl , error);
323
318
}
324
319
else if (spotlightCallback) {
325
320
spotlightCallback (nil , nil , error);
@@ -334,7 +329,7 @@ - (void)indexContentWithTitle:(NSString *)title
334
329
if (!isIndexingAvailable) {
335
330
NSError *error = [NSError errorWithDomain: BNCErrorDomain code: BNCVersionError userInfo: @{ NSLocalizedDescriptionKey : @" Cannot use CoreSpotlight indexing service on this device/OS" }];
336
331
if (callback) {
337
- callback (nil , error);
332
+ callback ([BNCPreferenceHelper preferenceHelper ]. userUrl , error);
338
333
}
339
334
else if (spotlightCallback) {
340
335
spotlightCallback (nil , nil , error);
@@ -345,7 +340,7 @@ - (void)indexContentWithTitle:(NSString *)title
345
340
if (!title) {
346
341
NSError *error = [NSError errorWithDomain: BNCErrorDomain code: BNCBadRequestError userInfo: @{ NSLocalizedDescriptionKey : @" Spotlight Indexing requires a title" }];
347
342
if (callback) {
348
- callback (nil , error);
343
+ callback ([BNCPreferenceHelper preferenceHelper ]. userUrl , error);
349
344
}
350
345
else if (spotlightCallback) {
351
346
spotlightCallback (nil , nil , error);
@@ -397,7 +392,7 @@ - (void)indexContentWithTitle:(NSString *)title
397
392
[[Branch getInstance ] getSpotlightUrlWithParams: spotlightLinkData callback: ^(NSDictionary *data, NSError *urlError) {
398
393
if (urlError) {
399
394
if (callback) {
400
- callback (nil , urlError);
395
+ callback ([BNCPreferenceHelper preferenceHelper ]. userUrl , urlError);
401
396
}
402
397
else if (spotlightCallback) {
403
398
spotlightCallback (nil , nil , urlError);
@@ -430,8 +425,6 @@ - (void)indexContentWithUrl:(NSString *)url spotlightIdentifier:(NSString *)spot
430
425
attributes = ((id (*)(id , SEL , NSString *))[attributes methodForSelector: initAttributesSelector])(attributes, initAttributesSelector, type);
431
426
SEL setIdentifierSelector = NSSelectorFromString (@" setIdentifier:" );
432
427
((void (*)(id , SEL , NSString *))[attributes methodForSelector: setIdentifierSelector])(attributes, setIdentifierSelector, spotlightIdentifier);
433
- SEL setRelatedUniqueIdentifierSelector = NSSelectorFromString (@" setRelatedUniqueIdentifier:" );
434
- ((void (*)(id , SEL , NSString *))[attributes methodForSelector: setRelatedUniqueIdentifierSelector])(attributes, setRelatedUniqueIdentifierSelector, spotlightIdentifier);
435
428
SEL setTitleSelector = NSSelectorFromString (@" setTitle:" );
436
429
((void (*)(id , SEL , NSString *))[attributes methodForSelector: setTitleSelector])(attributes, setTitleSelector, title);
437
430
SEL setContentDescriptionSelector = NSSelectorFromString (@" setContentDescription:" );
@@ -440,65 +433,82 @@ - (void)indexContentWithUrl:(NSString *)url spotlightIdentifier:(NSString *)spot
440
433
((void (*)(id , SEL , NSURL *))[attributes methodForSelector: setThumbnailURLSelector])(attributes, setThumbnailURLSelector, thumbnailUrl);
441
434
SEL setThumbnailDataSelector = NSSelectorFromString (@" setThumbnailData:" );
442
435
((void (*)(id , SEL , NSData *))[attributes methodForSelector: setThumbnailDataSelector])(attributes, setThumbnailDataSelector, thumbnailData);
436
+ // NSUserActivity.CSSearchableItemAttributeSet.contentURL
443
437
SEL setContentURLSelector = NSSelectorFromString (@" setContentURL:" );
444
438
((void (*)(id , SEL , NSURL *))[attributes methodForSelector: setContentURLSelector])(attributes, setContentURLSelector, [NSURL URLWithString: url]);
445
439
446
- // Index via the NSUserActivity strategy
447
- // Currently (iOS 9 Beta 4) we need a strong reference to this, or it isn't indexed
448
- self.currentUserActivity = [[NSUserActivity alloc ] initWithActivityType: spotlightIdentifier];
449
- self.currentUserActivity .title = title;
450
- self.currentUserActivity .webpageURL = [NSURL URLWithString: url]; // This should allow indexed content to fall back to the web if user doesn't have the app installed. Unable to test as of iOS 9 Beta 4
451
- self.currentUserActivity .eligibleForSearch = YES ;
452
- self.currentUserActivity .eligibleForPublicIndexing = publiclyIndexable;
453
- SEL setContentAttributeSetSelector = NSSelectorFromString (@" setContentAttributeSet:" );
454
- ((void (*)(id , SEL , id ))[self .currentUserActivity methodForSelector: setContentAttributeSetSelector])(self.currentUserActivity , setContentAttributeSetSelector, attributes);
455
- self.currentUserActivity .userInfo = userInfo; // As of iOS 9 Beta 4, this gets lost and never makes it through to application:continueActivity:restorationHandler:
456
- self.currentUserActivity .requiredUserInfoKeys = [NSSet setWithArray: userInfo.allKeys]; // This, however, seems to force the userInfo to come through.
457
- self.currentUserActivity .keywords = keywords;
458
- [self .currentUserActivity becomeCurrent ];
440
+ NSDictionary *userActivityIndexingParams = @{@" title" : title,
441
+ @" url" : url,
442
+ @" spotlightId" : spotlightIdentifier,
443
+ @" userInfo" : [userInfo mutableCopy ],
444
+ @" keywords" : keywords,
445
+ @" publiclyIndexable" : [NSNumber numberWithBool: publiclyIndexable],
446
+ @" attributeSet" : attributes
447
+ };
448
+ [self indexUsingNSUserActivity: userActivityIndexingParams];
459
449
460
- // Index via the CoreSpotlight strategy
461
- // get the CSSearchableItem Class object
462
- id CSSearchableItemClass = NSClassFromString (@" CSSearchableItem" );
463
- // alloc an empty instance
464
- id searchableItem = [CSSearchableItemClass alloc ];
465
- // create-by-name a selector fot the init method we want
466
- SEL initItemSelector = NSSelectorFromString (@" initWithUniqueIdentifier:domainIdentifier:attributeSet:" );
467
- // call the selector on the searchableItem with appropriate arguments
468
- searchableItem = ((id (*)(id , SEL , NSString *, NSString *, id ))[searchableItem methodForSelector: initItemSelector])(searchableItem, initItemSelector, spotlightIdentifier, BRANCH_SPOTLIGHT_PREFIX, attributes);
469
-
470
- // create an assignment method to set the expiration date on the searchableItem
471
- SEL expirationSelector = NSSelectorFromString (@" setExpirationDate:" );
472
- // now invoke it on the searchableItem, providing the expirationdate
473
- ((void (*)(id , SEL , NSDate *))[searchableItem methodForSelector: expirationSelector])(searchableItem, expirationSelector, expirationDate);
474
-
475
-
476
- Class CSSearchableIndexClass = NSClassFromString (@" CSSearchableIndex" );
477
- SEL defaultSearchableIndexSelector = NSSelectorFromString (@" defaultSearchableIndex" );
478
- id defaultSearchableIndex = ((id (*)(id , SEL ))[CSSearchableIndexClass methodForSelector: defaultSearchableIndexSelector])(CSSearchableIndexClass, defaultSearchableIndexSelector);
479
- SEL indexSearchableItemsSelector = NSSelectorFromString (@" indexSearchableItems:completionHandler:" );
480
- void (^__nullable completionBlock)(NSError *indexError) = ^void (NSError *__nullable indexError) {
481
- if (callback || spotlightCallback) {
482
- if (indexError) {
483
- if (callback) {
484
- callback (nil , indexError);
485
- }
486
- else if (spotlightCallback) {
487
- spotlightCallback (nil , nil , indexError);
488
- }
489
- }
490
- else {
491
- if (callback) {
492
- callback (url, nil );
493
- }
494
- else if (spotlightCallback) {
495
- spotlightCallback (url, spotlightIdentifier, nil );
496
- }
497
- }
450
+ // Not handling error scenarios because they are already handled upstream by the caller
451
+ if (url) {
452
+ if (callback) {
453
+ callback (url, nil );
454
+ } else if (spotlightCallback) {
455
+ spotlightCallback (url, spotlightIdentifier, nil );
498
456
}
499
- };
500
- ((void (*)(id , SEL , NSArray *, void (^ __nullable)(NSError * __nullable error)))[defaultSearchableIndex methodForSelector: indexSearchableItemsSelector])(defaultSearchableIndex, indexSearchableItemsSelector, @[searchableItem], completionBlock);
457
+ }
501
458
#endif
502
459
}
503
460
461
+ #pragma mark Delegate Methods
462
+
463
+ - (void )userActivityWillSave : (NSUserActivity *)userActivity {
464
+ [userActivity addUserInfoEntriesFromDictionary: self .userInfo];
465
+ }
466
+
467
+ #pragma mark Helper Methods
468
+
469
+ - (UIViewController *)getActiveViewController {
470
+ Class UIApplicationClass = NSClassFromString (@" UIApplication" );
471
+ UIViewController *rootViewController = [UIApplicationClass sharedApplication ].keyWindow .rootViewController ;
472
+ return [self getActiveViewController: rootViewController];
473
+ }
474
+
475
+ - (UIViewController *)getActiveViewController : (UIViewController *)rootViewController {
476
+ UIViewController *activeController;
477
+ if ([rootViewController isKindOfClass: [UINavigationController class ]]) {
478
+ activeController = ((UINavigationController *)rootViewController).topViewController ;
479
+ } else if ([rootViewController isKindOfClass: [UITabBarController class ]]) {
480
+ activeController = ((UITabBarController *)rootViewController).selectedViewController ;
481
+ } else {
482
+ activeController = rootViewController;
483
+ }
484
+ return activeController;
485
+ }
486
+
487
+ - (void )indexUsingNSUserActivity : (NSDictionary *)params {
488
+ self.userInfo = params[@" userInfo" ];
489
+ self.userInfo [CSSearchableItemActivityIdentifier] = params[@" spotlightId" ];
490
+
491
+ UIViewController *activeViewController = [self getActiveViewController ];
492
+
493
+ if (!activeViewController) {
494
+ // if no view controller, don't index. Current use case: iMessage extensions
495
+ return ;
496
+ }
497
+ NSString *uniqueIdentifier = [NSString stringWithFormat: @" io.branch.%@ " , [[NSBundle mainBundle ] bundleIdentifier ]];
498
+ // Can't create any weak references here to the userActivity, otherwise it will not index.
499
+ activeViewController.userActivity = [[NSUserActivity alloc ] initWithActivityType: uniqueIdentifier];
500
+ activeViewController.userActivity .delegate = self;
501
+ activeViewController.userActivity .title = params[@" title" ];
502
+ activeViewController.userActivity .webpageURL = [NSURL URLWithString: params[@" url" ]];
503
+ activeViewController.userActivity .eligibleForSearch = YES ;
504
+ activeViewController.userActivity .eligibleForPublicIndexing = [params[@" publiclyIndexable" ] boolValue ];
505
+ activeViewController.userActivity .userInfo = self.userInfo ; // This alone doesn't pass userInfo through
506
+ activeViewController.userActivity .requiredUserInfoKeys = [NSSet setWithArray: self .userInfo.allKeys]; // This along with the delegate method userActivityWillSave, however, seem to force the userInfo to come through.
507
+ activeViewController.userActivity .keywords = params[@" keywords" ];
508
+ SEL setContentAttributeSetSelector = NSSelectorFromString (@" setContentAttributeSet:" );
509
+ ((void (*)(id , SEL , id ))[activeViewController.userActivity methodForSelector: setContentAttributeSetSelector])(activeViewController.userActivity , setContentAttributeSetSelector, params[@" attributeSet" ]);
510
+
511
+ [activeViewController.userActivity becomeCurrent ];
512
+ }
513
+
504
514
@end
0 commit comments