/WACoreDataSpotlight

Automatically index your CoreData objects to CoreSpotlight on iOS 9

Primary LanguageObjective-COtherNOASSERTION

Version License Platform

Developed and Maintained by Ipodishima Founder & CTO at Wasappli Inc. (If you need to develop an app, get in touch with our team!)

Purpose

With iOS 9 comes great new features. One of them is CoreSpotlight which purpose is to give you access to the app search on iOS itself. A user can now search for it's hotel booking straight from the search on iOS and get back to the app.

Because several developpers are using CoreData, I thought it would have been a great idea to help indexing using both CoreSpotlight and CoreData apis.

WACoreDataSpotlight, after a quick configuration, automatically index you core data database. Yeah, you heard it. Automatically.

It will:

  • create the index,
  • update the entry on the index,
  • delete the entry from the index.

Automatically (after a save)

#Compatibility

  • Xcode 7 or more
  • iOS 7 or more (on iOS < 9, the indexer will return nil)

Install and use

Test it!

You can use

pod try WACoreDataSpotlight

Then, when the app is launched, go back to the springboard, search for Marian Paul for exemple, of for employee

Installation

CocoaPods

Use CocoaPods, this is the easiest way to install the indexer

pod 'WACoreDataSpotlight'

Setup the indexer

Import

#import <WACoreDataSpotlight/WACoreDataSpotlight.h>

Allocate a new indexer

For allocating the indexer, you need a valid NSManagedObjectContext you are using to create / fetch / etc you core data objects.

self.mainIndexer = [[WACDSIndexer alloc] initWithManagedObjectContext:mainContext];

Create some mappings

Then, you need to create some mappings. You can use WACDSSimpleMapping if it remains simple, or WACDSCustomMapping if you need more access on CSSearchableItemAttributeSet.

Let's start with an easy one

WACDSSimpleMapping *employeeSearchMapping =
[[WACDSSimpleMapping alloc] initWithManagedObjectEntityName:@"Employee"
                                    uniqueIdentifierPattern:@"employee_{#firstName#}_{#lastName#}"
                                               titlePattern:@"{#firstName#} {#lastName#}"
                                  contentDescriptionPattern:@"{#firstName#} {#lastName#} is working as {#jobTitle#} on {#company.name#}"
                                           keywordsPatterns:@[@"employee", @"{#firstName#}", @"{#lastName#}"]
                                       thumbnailDataBuilder:^NSData *(Employee *employee) {
                                            return UIImagePNGRepresentation([UIImage imageNamed:employee.avatarImageName]);
                                       }];

First, you pass Employee class which is a subclass of NSManagedObject. Then you need to pass a unique identifier pattern. It is used to id your object in an unique way in the index. Best idea is to use an itemID property.

Let's stop with the syntax. Assuming Employee has a property firstName and lastName which values are Marian and Paul, using an identifier as employee_{#firstName#}_{#lastName#} will be mapped to employee_Marian_Paul.

{#object property name#} is THE syntax you need to use for this to work correctly.

The titlePattern is mandatory.

Keywords can be hard values (employee) or dynamic ({#firstName#}).

Register the mapping

[self.mainIndexer registerMapping:employeeSearchMapping];

Use the indexer

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    NSManagedObject *object = [self.mainIndexer objectFromUserActivity:userActivity];
    
    // Do something with the object
        
    return YES;
}

Build the app

Build and run the app. Every objects for which you added a mapping on their classe are now automatically added / updated / deleted from the index... !

Custom indexer

You can use custom mapping to create your own attribute set. For example:

WACDSCustomMapping *companyMapping =
[[WACDSCustomMapping alloc] initWithManagedObjectEntityName:@"Company"
                                    uniqueIdentifierPattern:@"company_{#name#}"
                          searchableItemAttributeSetBuilder:^CSSearchableItemAttributeSet *(Company *company) {
                               CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeText];
                              attributeSet.title                         = company.name;
                              attributeSet.contentDescription            = [NSString stringWithFormat:@"The company has its offices in %@ and its primary activity is %@.\n%ld employees", company.address, company.activity, [company.employees count]];
                              attributeSet.keywords                      = @[@"company", company.name, company.activity];
                         
                              return attributeSet;
                          }];
[self.mainIndexer registerMapping:companyMapping];

Index existing objects

You can index existing objects using [self.indexer indexExistingObjects:existingObjects]; Please not that this is up to you to call this only one time in the app's life.

Update the index

Assuming you have an object to index which requires an image download from the URL. Indexing would give the order to download the image, but at the end you need to refresh the index to pass the image.

[self.mainIndexer updateIndexingForObject:company];

Do something with the activity and the object from the search

When the user hits the search result, you grab the object using [self.mainIndexer objectFromUserActivity:userActivity]. But then what?

Well, you could use for example (WAAppRouting)[https://github.com/Wasappli/WAAppRouting] to add some url behavior in your app. It would be as easy as doing:

self.router = [WAAppRouter defaultRouter];
[self.router.registrar
  registerAppRoutePath:@"companies{CompaniesTableViewController}/:companyName{EmployeesTableViewController}/:employeeID{EmployeeFormViewController}!"
  presentingController:nav];

And then on application: continueUserActivity: restorationHandler:

NSManagedObject *object = [self.mainIndexer objectFromUserActivity:userActivity];

if ([object isKindOfClass:[Company class]]) {
    [AppLink goTo:@"companies/%@", ((Company *)object).name];
}

if ([object isKindOfClass:[Employee class]]) {
    [AppLink goTo:@"companies/%@/%@", ((Employee *)object).company.name, ((Employee *)object).employeeID];
}

TODOs

  • Handle the batching
  • Handle the security implementation (on extension)

#Contributing : Problems, Suggestions, Pull Requests?

Please open a new Issue here if you run into a problem specific to WACoreDataSpotlight.

For new features pull requests are encouraged and greatly appreciated! Please try to maintain consistency with the existing code style. If you're considering taking on significant changes or additions to the project, please ask me before by opening a new issue to have a chance for a merge.

#That's all folks !

  • If your are happy don't hesitate to send me a tweet @ipodishima!
  • Distributed under MIT licence.
  • Follow Wasappli on facebook