Developer

Implement CoreSpotlight framework – make your content searchable

In current iOS system, content of application can be indexed and searched in search panel. This is a good feature for APP developer.

First we need to know what CoreSpotlight framework can do.

The Core Spotlight framework helps your app participate in search by providing ways to index the content within your app (including user-generated content) and manage the on-device index.

Which means, we need to package information of content and provided to iOS system. Therefore, by Apple Developer Document, the steps needed to implement.

To get started indexing app content, perform these steps:

  • Create a CSSearchableItemAttributeSet object and specify properties that describe the item you want to index.
  • Create a CSSearchableItem object to represent the item. A CSSearchableItem object has a unique identifier that lets you refer to it later.
  • If needed, specify a domain identifier so that you can gather multiple items together and manage them as a group.
  • Associate the attribute set with the searchable item.
  • Add the searchable item to the index.
CoreSpotlight

Like above picture, I set the name, thumbnail, description as meta for content. The following code is how to package necessary data.

The list of content looks like below picture, we can chose to add the items into search list or we remove all of them. Add and Delete buttons perform the action as the code below.

CoreSpotlight_2
#pragma mark - Spotlight framework
-(void)addToSpotLight:(id)sender{
    NSMutableArray * sps = [[NSMutableArray alloc]init];
    for (Content * cot in self.items) {
        CSSearchableItem * si = [self generateContentToSpotlightSearchAbleItem:cot];
        [sps addObject:si];
    }
    [[CSSearchableIndex defaultSearchableIndex]indexSearchableItems:sps completionHandler:^(NSError * _Nullable error) {
        
        NSString * message=@"Success Add";
        if (error) {
            NSLog(@"add search able item error: %@",error);
            message = @"Fail Add";
        }
        UIAlertController * alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            
        }]];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self presentViewController:alert animated:YES completion:^{
                
            }];
        });
    }];
}

-(void)deleteFromSpotLight:(id)sender{
    [[CSSearchableIndex defaultSearchableIndex]deleteSearchableItemsWithDomainIdentifiers:@[SpotlightDomain] completionHandler:^(NSError * _Nullable error) {
        NSString * message=@"Success Deleted";
        if (error) {
            NSLog(@"delete items error :%@",error);
            message = @"Fail Deleted";
        }
        UIAlertController * alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            
        }]];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self presentViewController:alert animated:YES completion:^{
                
            }];
        });
    }];
}

-(CSSearchableItem *)generateContentToSpotlightSearchAbleItem:(Content *)content{
    CSSearchableItem * result = nil;
    CSSearchableItemAttributeSet * ats = [[CSSearchableItemAttributeSet alloc]initWithItemContentType:(NSString*)kUTTypeImage];
    ats.title = content.name;
    ats.contentDescription = content.contentDescription;
    ats.keywords = @[@"CodeTemplate",@"Homer",content.name];
    ats.thumbnailData = UIImagePNGRepresentation([UIImage imageNamed:content.name]);
    result = [[CSSearchableItem alloc]initWithUniqueIdentifier:[[content.objectID URIRepresentation] absoluteString] domainIdentifier:SpotlightDomain attributeSet:ats];
    return result;
}

By the code above, I set the keywords for each item. Keywords are indexed for spotlight search. It is a very important attribute.

When you select item from spotlight search list. we can present the detail page about this image. And how to handle the selection, we can use following function of AppDelegate.

-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler

restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler{
    if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) {
        [LoadNotificationViewController showWithUserActivity:userActivity];
        return YES;
    }
    return NO;
}

//In LoadNotificationViewController
+(void)showWithUserActivity:(NSUserActivity *)userActivity{
    NSLog(@"%@",userActivity);
    NSManagedObjectContext * context = [[TemplateUtility shareInstance] coredataManagerContext];
    NSString * objectIdString = [userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier];
    NSURL * theURL = [NSURL URLWithString:objectIdString];
    NSManagedObjectID * objectID = [context.persistentStoreCoordinator managedObjectIDForURIRepresentation:theURL];
    Content * c = [Content getContentWithId:objectID withContext:context];
    if (c) {
       ... ...
    }
}

In useractivity, there are userInfo which is a NSDictionary that contains meta data about this content. When I print out this dictionary, the result as following image.

CoreSpotlight_3

Above is a implement of spotlight framework. More code can be found in GitHub CodeTemplate. If you have any suggestions and questions about this blog, please feel free to leave a comment. I will appreciate that.

Developer

Some useful notes

for Vim.

yy:copy the line
dd:delete the line
p:to paste the copied or deleted text after the current line
P:to paste the copied or deleted text before the current line

Some useful commands

//list the size of file under this directory
du -h --max-depth=1 | sort -hr 
//grep thread that has name "ChildProcessorLinkProcess" and kill them
ps -ef | grep ChildProcessorLinkProcess | grep -v grep | awk '{print $2}' | xargs kill
//find files with name 'naught.*' and rm it
find . -name "naught.*" -exec rm -i {} \;

Git configuration for short cut comments

[core]
    editor= /usr/bin/vim

[user]
    email = homerzhm@gmail.com
    name = Homer Zuo

[color]
    branch = auto
    diff = auto
    status = auto

[color "branch"]
    current = red reverse
    local = yellow
    remote = green

[color "diff"]
    meta = yellow bold
    frag = magenta bold
    old = red bold
    new = green bold
 
[color "status"]
    added = yellow
    changed = green
    untracked = cyan
 
[alias]
 
    st = status -s
    cl = clone
    ci = commit -s
    cm = commit -m
    cma = commit -a -m
    cas = commit --amend -s
    amend = commit --amend
    caa = commit -a --amend -C HEAD
    cif = commit --fixup
    filelog = log -u
    fl = log -u
 
    ai = add --interactive
 
    co = checkout
    br = branch 
    #"!git branch -ra | grep -v done"
    bra = branch -ra
    #list commands
    le = log --oneline --decorate
    ll = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate --numstat
    ls1 = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate
    lds = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]\\ %C(green)%ad" --decorate --date=short --graph
    ls = log --pretty=format:"%C(green)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]\\ %C(yellow)[%ad]" --decorate --date=relative
    #show file change in commit id passed in arguement
    lc  = "!f() { git ll "$1"^.."$1"; }; f"
    lnc = log --pretty=format:"%h\\ %s\\ [%cn]"
    ld = log --pretty=format:"%C(yellow)%h\\ %Creset%s%Cred\\ [%cn]\\ %ad%Cblue%d" --decorate --abbrev-commit --date=relative
    #list all aliases
    la = "!git config -l | grep alias | cut -c 7-"
    diff = diff --word-diff
    d = diff --word-diff
    dc = diff --cached
    #list modified files in last commit
    dl = "!git ll -1"
    #diff last commit
	dlc = diff --cached HEAD^
    #show code changes in last commit
    dr  = "!f() { git diff "$1"^.."$1"; }; f"
    diffr  = "!f() { git diff "$1"^.."$1"; }; f"
    branch = branch -ra
    bmuser = branch -a --list *smitesh.patel* --merged
    bnmuser = branch -a --list *smitesh.patel* --no-merged
    buser = branch -a --list $1
 
    #reset commands
    r = reset
    r1 = reset HEAD^
    r2 = reset HEAD^^
    rh = reset --hard
    rh1 = reset HEAD^ --hard
    rh2 = reset HEAD^^ --hard
 
    #git svn
    svnr = svn rebase
    svnd = svn dcommit
    svnl = svn log --oneline --show-commit
 
    #stash
    sl = stash list
    sa = stash apply
    ss = stash save
 
    cp = cherry-pick
    grep = grep -Ii
    gr = grep -Ii

    #grep from root foflder
    gra = "!f() { A=$(pwd) && TOPLEVEL=$(git rev-parse --show-toplevel) && cd $TOPLEVEL && git grep --full-name -In $1 | xargs -I{} echo $TOPLEVEL/{} && cd $A; }; f"
 
    #grep on filename
    f = "!git ls-files | grep -i"
 
    #rename branch tree to done-
	done = "!f() { git branch | grep "$1" | cut -c 3- | grep -v done | xargs -I{} git branch -m {} done-{}; }; f"
 
    lasttag = describe --tags --abbrev=0
    lt = describe --tags --abbrev=0
 
    #merges
    ours = "!f() { git co --ours $@ && git add $@; }; f"
	theirs = "!f() { git co --theirs $@ && git add $@; }; f"
 
    #push
	pushremote = "!f() { git push -f origin sp/"$1":dev/"$USER"/"$1"; }; f"
	deleteremote = "!f() { git push -f origin :dev/"$USER"/"$1"; }; f"
 
	updateSubmodules = "submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive"

[push]
	default = matching

[rerere]
	enabled = true

[mergetool]
	keepBackup = false

It will keep Updating…