Skip to content

Commit 9206147

Browse files
authored
Merge pull request #420 from BranchMetrics/spotlight
AIS-47 - Spotlight
2 parents 92b4fe5 + 2f75491 commit 9206147

File tree

7 files changed

+101
-94
lines changed

7 files changed

+101
-94
lines changed

Branch-SDK/Branch-SDK/BNCContentDiscoveryManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#import "Branch.h"
1010

11-
@interface BNCContentDiscoveryManager : NSObject
11+
@interface BNCContentDiscoveryManager : NSObject<NSUserActivityDelegate>
1212

1313
- (NSString *)spotlightIdentifierFromActivity:(NSUserActivity *)userActivity;
1414
- (NSString *)standardSpotlightIdentifierFromActivity:(NSUserActivity *)userActivity;

Branch-SDK/Branch-SDK/BNCContentDiscoveryManager.m

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
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

Branch-SDK/Branch-SDK/Branch.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ - (BOOL)continueUserActivity:(NSUserActivity *)userActivity {
406406
return NO;
407407
}
408408

409-
//check to see if a spotlight activity needs to be handled
409+
// Check to see if a spotlight activity needs to be handled
410410
NSString *spotlightIdentifier = [self.contentDiscoveryManager spotlightIdentifierFromActivity:userActivity];
411411

412412
if (spotlightIdentifier) {
@@ -418,6 +418,7 @@ - (BOOL)continueUserActivity:(NSUserActivity *)userActivity {
418418
self.preferenceHelper.spotlightIdentifier = nonBranchSpotlightIdentifier;
419419
}
420420
}
421+
421422
[self initUserSessionAndCallCallback:YES];
422423
self.preferenceHelper.shouldWaitForInit = NO;
423424

@@ -1325,4 +1326,4 @@ + (NSString *)kitDisplayVersion {
13251326
return @"0.12.6";
13261327
}
13271328

1328-
@end
1329+
@end

Branch-SDK/Branch-SDK/BranchUniversalObject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ typedef NS_ENUM(NSInteger, ContentIndexMode) {
3434
@property (nonatomic, strong) NSString *spotlightIdentifier;
3535
@property (nonatomic, assign) CGFloat price;
3636
@property (nonatomic, strong) NSString *currency;
37+
@property (nonatomic, assign) BOOL automaticallyListOnSpotlight;
3738

3839

3940
- (instancetype)initWithCanonicalIdentifier:(NSString *)canonicalIdentifier;
@@ -59,7 +60,7 @@ typedef NS_ENUM(NSInteger, ContentIndexMode) {
5960

6061
- (void)listOnSpotlight;
6162
- (void)listOnSpotlightWithCallback:(callbackWithUrl)callback;
62-
- (void)listOnSpotlightWithIdentifierCallback:(callbackWithUrlAndSpotlightIdentifier)spotlightCallback;
63+
- (void)listOnSpotlightWithIdentifierCallback:(callbackWithUrlAndSpotlightIdentifier)spotlightCallback __attribute__((deprecated(("iOS 10 has changed how Spotlight indexing works and we’ve updated the SDK to reflect this. Please see https://dev.branch.io/features/spotlight-indexing/overview/ for instructions on migration"))));;
6364

6465
// Convenience method for initSession methods that return BranchUniversalObject, but can be used safely by anyone.
6566
+ (BranchUniversalObject *)getBranchUniversalObjectFromDictionary:(NSDictionary *)dictionary;

Branch-SDK/Branch-SDK/BranchUniversalObject.m

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,7 @@ - (void)addMetadataKey:(NSString *)key value:(NSString *)value {
4949
}
5050

5151
- (void)registerView {
52-
if (!self.canonicalIdentifier && !self.title) {
53-
[_preferenceHelper logWarning:@"A canonicalIdentifier or title are required to uniquely identify content, so could not register view."];
54-
return;
55-
}
56-
57-
[[Branch getInstance] registerViewWithParams:[self getParamsForServerRequest]
58-
andCallback:nil];
52+
[self registerViewWithCallback:nil];
5953
}
6054

6155
- (void)registerViewWithCallback:(callbackWithParams)callback {
@@ -71,6 +65,9 @@ - (void)registerViewWithCallback:(callbackWithParams)callback {
7165
return;
7266
}
7367

68+
if (self.automaticallyListOnSpotlight) {
69+
[self listOnSpotlight];
70+
}
7471
[[Branch getInstance] registerViewWithParams:[self getParamsForServerRequest] andCallback:callback];
7572
}
7673

@@ -81,6 +78,9 @@ - (void)userCompletedAction:(NSString *)action {
8178
actionPayload[self.canonicalIdentifier] = linkParams;
8279

8380
[[Branch getInstance] userCompletedAction:action withState:actionPayload];
81+
if (self.automaticallyListOnSpotlight && [action isEqualToString:BNCRegisterViewEvent]) {
82+
[self listOnSpotlight];
83+
}
8484
}
8585

8686
#pragma mark - Link Creation Methods
@@ -168,6 +168,8 @@ - (void)showShareSheetWithLinkProperties:(BranchLinkProperties *)linkProperties
168168
[self showShareSheetWithLinkProperties:linkProperties andShareText:shareText fromViewController:viewController anchor:nil completion:completion];
169169
}
170170
- (void)showShareSheetWithLinkProperties:(BranchLinkProperties *)linkProperties andShareText:(NSString *)shareText fromViewController:(UIViewController *)viewController anchor:(UIBarButtonItem *)anchor completion:(shareCompletion)completion {
171+
// Log share initiated event
172+
[self userCompletedAction:BNCShareInitiatedEvent];
171173
UIActivityItemProvider *itemProvider = [self getBranchActivityItemWithLinkProperties:linkProperties];
172174
NSMutableArray *items = [NSMutableArray arrayWithObject:itemProvider];
173175
if (shareText) {
@@ -177,6 +179,8 @@ - (void)showShareSheetWithLinkProperties:(BranchLinkProperties *)linkProperties
177179

178180
if ([shareViewController respondsToSelector:@selector(completionWithItemsHandler)]) {
179181
shareViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
182+
// Log share completed event
183+
[self userCompletedAction:BNCShareCompletedEvent];
180184
if (completion) {
181185
completion(activityType, completed);
182186
[BNCFabricAnswers sendEventWithName:@"Branch Share" andAttributes:[self getDictionaryWithCompleteLinkProperties:linkProperties]];

Branch-TestBed/Branch-TestBed/AppDelegate.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceAppl
6969

7070

7171
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
72-
NSLog(@"application:continueUserActivity:restorationHandler: invoked. userActivity.webpageURL if any: %@", userActivity.webpageURL.absoluteString);
72+
NSLog(@"application:continueUserActivity:restorationHandler: invoked. activityType: %@ userActivity.webpageURL: %@", userActivity.activityType, userActivity.webpageURL.absoluteString);
7373

7474
// Required. Returns YES if Branch Universal Link, else returns NO. Add `branch_universal_link_domains` to .plist (String or Array) for custom domain(s).
7575
[[Branch getInstance] continueUserActivity:userActivity];

Branch-TestBed/Branch-TestBed/ViewController.m

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,6 @@ - (IBAction)shareLinkButtonTouchUpInside:(id)sender {
199199
}
200200

201201

202-
/*
203-
- (IBAction)cmdIndexSpotlight:(id)sender {
204-
[self.branchUniversalObject listOnSpotlightWithCallback:^(NSString *url, NSError *error) {
205-
if (!error) {
206-
NSLog(@"Branch TestBed: ShortURL: %@", url);
207-
} else {
208-
NSLog(@"Branch TestBed: Error: %@", error);
209-
}
210-
}];
211-
}*/
212-
213-
214202
//example using callbackWithURLandSpotlightIdentifier
215203
- (IBAction)registerWithSpotlightButtonTouchUpInside:(id)sender {
216204
[self.branchUniversalObject addMetadataKey:@"deeplink_text" value:@"This link was generated for Spotlight registration"];

0 commit comments

Comments
 (0)