3030
3131@interface BNCContentDiscoveryManager ()
3232
33- @property (strong , nonatomic ) NSUserActivity *currentUserActivity ;
33+ @property (strong , nonatomic ) NSMutableDictionary *userInfo ;
3434
3535@end
3636
@@ -40,18 +40,14 @@ @implementation BNCContentDiscoveryManager
4040
4141- (NSString *)spotlightIdentifierFromActivity : (NSUserActivity *)userActivity {
4242#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
43- if ([userActivity.activityType hasPrefix: BRANCH_SPOTLIGHT_PREFIX]) {
44- return userActivity.activityType ;
45- }
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];
4646
47- // CoreSpotlight version. Matched if it has our prefix, then the link identifier is just the last piece of the identifier.
48- if ([userActivity.activityType isEqualToString: CSSearchableItemActionType]) {
49- NSString *activityIdentifier = userActivity.userInfo [CSSearchableItemActivityIdentifier];
50- BOOL isBranchIdentifier = [activityIdentifier hasPrefix: BRANCH_SPOTLIGHT_PREFIX];
51-
52- if (isBranchIdentifier) {
53- return activityIdentifier;
54- }
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;
5551 }
5652#endif
5753
@@ -60,16 +56,14 @@ - (NSString *)spotlightIdentifierFromActivity:(NSUserActivity *)userActivity {
6056
6157- (NSString *)standardSpotlightIdentifierFromActivity : (NSUserActivity *)userActivity {
6258#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
63- // CoreSpotlight version. Matched if it has our prefix, then the link identifier is just the last piece of the identifier.
64- if ([userActivity.activityType isEqualToString: CSSearchableItemActionType] && userActivity.userInfo [CSSearchableItemActivityIdentifier]) {
59+ if (userActivity.userInfo [CSSearchableItemActivityIdentifier]) {
6560 return userActivity.userInfo [CSSearchableItemActivityIdentifier];
6661 }
6762#endif
6863
6964 return nil ;
7065}
7166
72-
7367#pragma mark - Content Indexing
7468
7569- (void )indexContentWithTitle : (NSString *)title
@@ -431,8 +425,6 @@ - (void)indexContentWithUrl:(NSString *)url spotlightIdentifier:(NSString *)spot
431425 attributes = ((id (*)(id , SEL , NSString *))[attributes methodForSelector: initAttributesSelector])(attributes, initAttributesSelector, type);
432426 SEL setIdentifierSelector = NSSelectorFromString (@" setIdentifier:" );
433427 ((void (*)(id , SEL , NSString *))[attributes methodForSelector: setIdentifierSelector])(attributes, setIdentifierSelector, spotlightIdentifier);
434- SEL setRelatedUniqueIdentifierSelector = NSSelectorFromString (@" setRelatedUniqueIdentifier:" );
435- ((void (*)(id , SEL , NSString *))[attributes methodForSelector: setRelatedUniqueIdentifierSelector])(attributes, setRelatedUniqueIdentifierSelector, spotlightIdentifier);
436428 SEL setTitleSelector = NSSelectorFromString (@" setTitle:" );
437429 ((void (*)(id , SEL , NSString *))[attributes methodForSelector: setTitleSelector])(attributes, setTitleSelector, title);
438430 SEL setContentDescriptionSelector = NSSelectorFromString (@" setContentDescription:" );
@@ -441,65 +433,86 @@ - (void)indexContentWithUrl:(NSString *)url spotlightIdentifier:(NSString *)spot
441433 ((void (*)(id , SEL , NSURL *))[attributes methodForSelector: setThumbnailURLSelector])(attributes, setThumbnailURLSelector, thumbnailUrl);
442434 SEL setThumbnailDataSelector = NSSelectorFromString (@" setThumbnailData:" );
443435 ((void (*)(id , SEL , NSData *))[attributes methodForSelector: setThumbnailDataSelector])(attributes, setThumbnailDataSelector, thumbnailData);
436+ // NSUserActivity.CSSearchableItemAttributeSet.contentURL
444437 SEL setContentURLSelector = NSSelectorFromString (@" setContentURL:" );
445438 ((void (*)(id , SEL , NSURL *))[attributes methodForSelector: setContentURLSelector])(attributes, setContentURLSelector, [NSURL URLWithString: url]);
446439
447- // Index via the NSUserActivity strategy
448- // Currently (iOS 9 Beta 4) we need a strong reference to this, or it isn't indexed
449- self.currentUserActivity = [[NSUserActivity alloc ] initWithActivityType: spotlightIdentifier];
450- self.currentUserActivity .title = title;
451- 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
452- self.currentUserActivity .eligibleForSearch = YES ;
453- self.currentUserActivity .eligibleForPublicIndexing = publiclyIndexable;
454- SEL setContentAttributeSetSelector = NSSelectorFromString (@" setContentAttributeSet:" );
455- ((void (*)(id , SEL , id ))[self .currentUserActivity methodForSelector: setContentAttributeSetSelector])(self.currentUserActivity , setContentAttributeSetSelector, attributes);
456- self.currentUserActivity .userInfo = userInfo; // As of iOS 9 Beta 4, this gets lost and never makes it through to application:continueActivity:restorationHandler:
457- self.currentUserActivity .requiredUserInfoKeys = [NSSet setWithArray: userInfo.allKeys]; // This, however, seems to force the userInfo to come through.
458- self.currentUserActivity .keywords = keywords;
459- [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];
460449
461- // Index via the CoreSpotlight strategy
462- // get the CSSearchableItem Class object
463- id CSSearchableItemClass = NSClassFromString (@" CSSearchableItem" );
464- // alloc an empty instance
465- id searchableItem = [CSSearchableItemClass alloc ];
466- // create-by-name a selector fot the init method we want
467- SEL initItemSelector = NSSelectorFromString (@" initWithUniqueIdentifier:domainIdentifier:attributeSet:" );
468- // call the selector on the searchableItem with appropriate arguments
469- searchableItem = ((id (*)(id , SEL , NSString *, NSString *, id ))[searchableItem methodForSelector: initItemSelector])(searchableItem, initItemSelector, spotlightIdentifier, BRANCH_SPOTLIGHT_PREFIX, attributes);
470-
471- // create an assignment method to set the expiration date on the searchableItem
472- SEL expirationSelector = NSSelectorFromString (@" setExpirationDate:" );
473- // now invoke it on the searchableItem, providing the expirationdate
474- ((void (*)(id , SEL , NSDate *))[searchableItem methodForSelector: expirationSelector])(searchableItem, expirationSelector, expirationDate);
475-
476-
477- Class CSSearchableIndexClass = NSClassFromString (@" CSSearchableIndex" );
478- SEL defaultSearchableIndexSelector = NSSelectorFromString (@" defaultSearchableIndex" );
479- id defaultSearchableIndex = ((id (*)(id , SEL ))[CSSearchableIndexClass methodForSelector: defaultSearchableIndexSelector])(CSSearchableIndexClass, defaultSearchableIndexSelector);
480- SEL indexSearchableItemsSelector = NSSelectorFromString (@" indexSearchableItems:completionHandler:" );
481- void (^__nullable completionBlock)(NSError *indexError) = ^void (NSError *__nullable indexError) {
482- if (callback || spotlightCallback) {
483- if (indexError) {
484- if (callback) {
485- callback ([BNCPreferenceHelper preferenceHelper ].userUrl , indexError);
486- }
487- else if (spotlightCallback) {
488- spotlightCallback (nil , nil , indexError);
489- }
490- }
491- else {
492- if (callback) {
493- callback (url, nil );
494- }
495- else if (spotlightCallback) {
496- spotlightCallback (url, spotlightIdentifier, nil );
497- }
498- }
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 );
499456 }
500- };
501- ((void (*)(id , SEL , NSArray *, void (^ __nullable)(NSError * __nullable error)))[defaultSearchableIndex methodForSelector: indexSearchableItemsSelector])(defaultSearchableIndex, indexSearchableItemsSelector, @[searchableItem], completionBlock);
457+ }
502458#endif
503459}
504460
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+ if (UIApplicationClass) {
472+ UIViewController *rootViewController = [UIApplication sharedApplication ].keyWindow .rootViewController ;
473+ return [self getActiveViewController: rootViewController];
474+ }
475+
476+ return nil ;
477+ }
478+
479+ - (UIViewController *)getActiveViewController : (UIViewController *)rootViewController {
480+ UIViewController *activeController;
481+ if ([rootViewController isKindOfClass: [UINavigationController class ]]) {
482+ activeController = ((UINavigationController *)rootViewController).topViewController ;
483+ } else if ([rootViewController isKindOfClass: [UITabBarController class ]]) {
484+ activeController = ((UITabBarController *)rootViewController).selectedViewController ;
485+ } else {
486+ activeController = rootViewController;
487+ }
488+ return activeController;
489+ }
490+
491+ - (void )indexUsingNSUserActivity : (NSDictionary *)params {
492+ self.userInfo = params[@" userInfo" ];
493+ self.userInfo [CSSearchableItemActivityIdentifier] = params[@" spotlightId" ];
494+
495+ UIViewController *activeViewController = [self getActiveViewController ];
496+
497+ if (!activeViewController) {
498+ // if no view controller, don't index. Current use case: iMessage extensions
499+ return ;
500+ }
501+ NSString *uniqueIdentifier = [NSString stringWithFormat: @" io.branch.%@ " , [[NSBundle mainBundle ] bundleIdentifier ]];
502+ // Can't create any weak references here to the userActivity, otherwise it will not index.
503+ activeViewController.userActivity = [[NSUserActivity alloc ] initWithActivityType: uniqueIdentifier];
504+ activeViewController.userActivity .delegate = self;
505+ activeViewController.userActivity .title = params[@" title" ];
506+ activeViewController.userActivity .webpageURL = [NSURL URLWithString: params[@" url" ]];
507+ activeViewController.userActivity .eligibleForSearch = YES ;
508+ activeViewController.userActivity .eligibleForPublicIndexing = params[@" publiclyIndexable" ];
509+ activeViewController.userActivity .userInfo = self.userInfo ; // This alone doesn't pass userInfo through
510+ activeViewController.userActivity .requiredUserInfoKeys = [NSSet setWithArray: self .userInfo.allKeys]; // This along with the delegate method userActivityWillSave, however, seem to force the userInfo to come through.
511+ activeViewController.userActivity .keywords = params[@" keywords" ];
512+ SEL setContentAttributeSetSelector = NSSelectorFromString (@" setContentAttributeSet:" );
513+ ((void (*)(id , SEL , id ))[activeViewController.userActivity methodForSelector: setContentAttributeSetSelector])(activeViewController.userActivity , setContentAttributeSetSelector, params[@" attributeSet" ]);
514+
515+ [activeViewController.userActivity becomeCurrent ];
516+ }
517+
505518@end
0 commit comments