{"id":13749091,"url":"https://github.com/appcelerator-developer-relations/appc-sample-appsearch","last_synced_at":"2026-01-19T23:44:04.149Z","repository":{"id":141788390,"uuid":"42117181","full_name":"appcelerator-developer-relations/appc-sample-appsearch","owner":"appcelerator-developer-relations","description":"This sample app demonstrates how to make the activities and content of your app searchable via Spotlight, Safari and Siri by using new API's introduced in iOS 9 and supported by Titanium 5.0.0.","archived":false,"fork":false,"pushed_at":"2015-10-13T14:21:35.000Z","size":1380,"stargazers_count":12,"open_issues_count":0,"forks_count":3,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-11-15T23:31:56.391Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appcelerator-developer-relations.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-08T14:24:09.000Z","updated_at":"2019-01-17T16:17:56.000Z","dependencies_parsed_at":"2023-03-13T12:32:20.535Z","dependency_job_id":null,"html_url":"https://github.com/appcelerator-developer-relations/appc-sample-appsearch","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appcelerator-developer-relations%2Fappc-sample-appsearch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appcelerator-developer-relations%2Fappc-sample-appsearch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appcelerator-developer-relations%2Fappc-sample-appsearch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appcelerator-developer-relations%2Fappc-sample-appsearch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appcelerator-developer-relations","download_url":"https://codeload.github.com/appcelerator-developer-relations/appc-sample-appsearch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253240350,"owners_count":21876593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T07:00:55.162Z","updated_at":"2026-01-19T23:44:04.109Z","avatar_url":"https://github.com/appcelerator-developer-relations.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# iOS 9 App Search Sample App\n\nThis sample app demonstrates how to make the activities and content of your app searchable via Spotlight, Safari and Siri by using new API's introduced in iOS 9 and supported by Titanium 5.0.0.\n\n![screenshots](docs/screenshots.png)\n\n## The Big Picture\nI highly recommend reading through all of our new [Spotlight Search Guide](http://docs.appcelerator.com/platform/latest/#!/guide/Spotlight_Search) as well as Apple's [App Search Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html) and related documentation but here's the gist of it:\n\n* Use [NSUserActivity](https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html) to [Index Activities and Navigation Points](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/Activities.html) on-device and make them available for private search, Siri and [Handoff](https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html).\n* Use the [Core Spotlight Framework](https://developer.apple.com/library/prerelease/ios/documentation/CoreSpotlight/Reference/CoreSpotlight_Framework/index.html) to [Index App Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/AppContent.html) on-device and make it available for private search.\n* [Mark Up Web Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) to index content on web pages and make it available for public search.\n* Use [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html) and [Smart App Banners](https://developer.apple.com/library/prerelease/ios/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html) to enable users to open the current content or activity in your app.\n* [Combine APIs](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/CombiningAPIs.html) for NSUserActivity, Core Spotlight and Web Content Mark Up for the same content to increase coverage and [ranking](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/SearchUserExperience.html#//apple_ref/doc/uid/TP40016308-CH11-SW1).\n\nThey say a picture says more then 5 bullets:\n\n![picture](docs/diagram.png)\n\nAs you can see Apple wants users to seamlessly move between apps (via search), devices (via handoff) as well as between native apps and websites (via Safari search, universal links and handoff). Apple's programming guide has a nice list of [Example Implementations](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/Choosing.html) for different types of apps to give an idea of how this might work for your app.\n\n## The Sample\nTo show the APIs in action I've created the [iOS App Search Sample App](https://github.com/appcelerator-developer-relations/appc-sample-appsearch).\n\n## Spotlight\n\nThe first tab in the sample app shows you a list of The Beatles. The four individual band members will be indexed by SpotLight. The list itself is a user activity, but we'll come back to that later.\n\n\u003e **Quick Tip:** I use a local instance of an [definition-less model/collection](app/models/array.js) which means I can [populate (reset) the collection](app/controllers/list.js#L25) to use Alloy's data-binding on any array of objects.\n\n### Adding items to the Spotlight index\nScroll down to [line 53](app/controllers/list.js#L53) of `app/controllers/list.js` to see how I add the Beatles to the Spotlight index. There are three parts to it:\n\n* [Ti.App.iOS.SearchableItemAttributeSet](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItemAttributeSet) to create meta data for a..\n* [Ti.App.iOS.SearchableItem](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItem) which I add to an instance of..\n* [Ti.App.iOS.SearchableIndex](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableIndex)\n\nThe attribute set has a [huge amount of properties](https://developer.apple.com/library/prerelease/ios/documentation/CoreSpotlight/Reference/CSSearchableItemAttributeSet_Class/index.html#//apple_ref/doc/uid/TP40016247-CH1-DontLinkElementID_170) you can use to describe the item. Some let iOS play a song, call a phone number or navigate to an address directly from the Spotlight results without even opening your app.\n\nIn short, this is how you'd index a single item:\n\n\tvar index = Ti.App.iOS.createSearchableIndex();\n\t\n\tindex.addToDefaultSearchableIndex([\n\t\n\t\tTi.App.iOS.createSearchableItem({\n\t\t\tuniqueIdentifier: 'my-id',\n\t\t\tdomainIdentifier: 'my.content.type',\n\t\t\tattributeSet: Ti.App.iOS.createSearchableItemAttributeSet({\n\t\t\t\ttitle: 'My Item'\n\t\t\t})\n\t\t})\n\t\n\t], function (e) {\n\t\te.success || alert('Oops!');\n\t});\n\n\n### Deleting items from the Spotlight  index\nIndexed items by default expire after one month unless you have set [Ti.App.iOS.SearchableItem.expirationDate](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItem-property-expirationDate). You can also manually delete all items, items with a shared `domainIdentifier` or specific items by `uniqueIdentifier`.\n\nThe list in the sample app has a Trash/Add icon as the left navigation button to [delete all items](app/controllers/list.js#L121) for the Beatles domain or re-index them. Search for `appsearch` before and after to verify the change is effective immediately.\n\n\tvar index = Ti.App.iOS.createSearchableIndex();\n\t\n\tindex.deleteAllSearchableItemByDomainIdenifiers(['content.type'],\n\t\tfunction (e) {\n\t\t\te.success || alert('Oops!');\n\t\t}\n\t);\n\n### Opening a Spotlight search result\nWhen a user taps on a Spotlight search result, your app will open and receive the [continueactivity](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS-event-continueactivity) event.\n\nBe aware that this event is also fired when a User Activity is opened from the search results or handed off from another device. In the case of a Spotlight search result the event's `activityType` property will be `com.apple.corespotlightitem` and `searchableItemActivityIdentifier` will have the `uniqueIdentifier` you've set on the indexed item.\n\nFrom [line 141](app/controllers/list.js#L141) you can see how to use this information to navigate your app to the content the user requested. In our case we share an [openDetail()](app/controllers/list.js#L197) helper function with the ListView's `itemclick` listener to look up the model and open the detail window.\n\nIn short:\n\n\tTi.App.iOS.addEventListener('continueactivity', function(e) {\n\t\t\n\t\t// Not for us\n\t\tif (e.activityType !== 'com.apple.corespotlightitem') {\n\t\t\treturn\n\t\t}\n\t\t\n\t\tvar uniqueIdentifier = e.searchableItemActivityIdentifier;\n\t\t\t\n\t\t// Navigate to the content\n\t});\n\n## User Activities\n\nThe [NSUserActivity](https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/cl/NSUserActivity) class introduced in iOS 8 to enable [Handoff](https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) can now also be included in both private and even public search results. The new [Ti.App.iOS.UserActivity](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.UserActivity) API in Titanium 5.0 gives you access to all these features.\n\n### Spotlight vs UserActivity\nThere's a very subtle difference between indexing for on-device search using Core Spotlight and User Activities. Think of indexing User Actives as tracking the pages users *have* visited, where Core Spotlight allows you to index the actual content that might be on one or more of these pages. We'll come back to how they work together later.\n\n### Managing User Activities\nIn the app we track two user activities. The first is the activity of viewing the list of Beatles and the other is the activity of viewing a individual Beatle's details.\n\nBoth are created in the same way in [list.js from line 233](app/controllers/list.js#L233) and most of [detail.js](app/controllers/detail.js).\n\nAs you can see we listen to the `focus` and `blur` events of both windows to create and invalidate (end) the related activity. Once invalidated the activity cannot become current again and must be re-created.\n\nThe activity itself is uniquely identified by a reverse-domain `activityType`. The current state of the activity is saved to `userInfo`. In case of the detail window this is where we save the model ID of the Beatle we're viewing. While handoff is enabled by default, we need to set `eligibleForSearch:true` to have Spotlight index the activity.\n\nIf you search for *appsearch* you should find the list activity as *The Beatles*.\n\nWhen you compare lines 73-88 in `list.js` and lines 61-79 in `detail.js` you will see that we can describe both a Spotlight item and a User Activity using the same `Ti.UI.SearchableItemAttributeSet`. In `detail.js` we also set the `relatedUniqueIdentifier` property. This prevents duplicate search results and makes that every time the activity becomes current it will count as a *pageview* for the related Spotlight item and improve its ranking.\n\n### Continue a User Activity from Spotlight or Handoff\nFrom [line 141 of list.js](app/controllers/list.js#L141) you can see we handle a user activity search result in almost the same way as for other Spotlight items. We use the `activityType` to identify it and act accordingly.\n\nThe exact same `continueactivity` event is also what handoff will fire so we get that for free! Install the app on two devices and double-tap home to try it out:\n\n![screenshot](docs/handoff.png)\n\n## Public Indexing \u0026 Web Content\nSince the sample is not in the App Store I cannot fully demonstrate [how to combine](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/CombiningAPIs.html#//apple_ref/doc/uid/TP40016308-CH10-SW1) the NSUserActivity and Core Spotlight APIs with Web Content Mark Up and Universal Links. Fortunately, Apple has a [excellent guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) on this topic.\n\n### Linking Web Content\nI already showed you how to combine NSUserActivity and Core Spotlight APIs for the same content. In the same way you can link Web Content as well.\n\n1. First, set `Ti.App.iOS.UserActivity.webpageURL` to the related webpage URL and use the same value for the Spotlight item's `uniqueIdentifier` which in turn is linked to the activity via `relatedUniqueIdentifier`.\n\n2. Then set the activity's `eligibleForPublicIndexing:true` as well as the required `requiredUserInfoKeys` property. *Eligible* means that a user activity on its own will never show up in search results. It will [Enhance Your Search Results](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/SearchUserExperience.html#//apple_ref/doc/uid/TP40016308-CH11-SW1) for Web Content by counting as *pageviews* for the related content.\n\n3. Last but not least follow Apple's guide to [Mark Up Web Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) and use the [App Search API Validation Tool](https://search.developer.apple.com/appsearch-validation-tool) to verify if you've set it all up correctly.\n\nAnother benefit of setting `webpageURL` is that you can now handoff a user activity to a device (including desktops) that doesn't have the app, in which case it will open the website instead. We will come back to that in a separate Handoff sample.\n\n## Links\n\n* [Appcelerator Spotlight Search Guide](http://docs.appcelerator.com/platform/latest/#!/guide/Spotlight_Search)\n* [Apple App Search Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html)\n\n## Known Issues\n\n* [TIMOB-19467:\nTi.App.iOS.SearchableItemAttributeSet.thumbnailURL is not working](https://jira.appcelerator.org/browse/TIMOB-19467)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappcelerator-developer-relations%2Fappc-sample-appsearch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappcelerator-developer-relations%2Fappc-sample-appsearch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappcelerator-developer-relations%2Fappc-sample-appsearch/lists"}