iOS9 Spotlight-Search in a Xamarin.Forms app

With the release of iOS 9 Apple is enabling developers to take advantage of search to make their app’s content more accessible than ever. In his blog post Mike James demonstrates the basics of integrating a Xamarin.iOS app with the new iOS9 Search Apis.

In this blog post i will show how to integrate the new iOS9 search Api with a Xamarin.Forms app.

The basic concept of integrating iOS App content with Spotlight search is to let iOS index those content items, a user has explicitly visited while navigating the app. The mechanics of storing a NSUserActivity are built around the UIViewController, as Mike’s Xamarin.iOS example shows:

 

Whenever the DetailView is activated, it creates a new NSUserActivity object in ViewDidLoad and initializes it’s contents with data displayed in the view.

 

Easy – but do i need a UIViewController?

The short answer is: YES. You cannot just take the CreateActivity function and use it in your Xamarin.Forms app to index user content. Well, you can – but the UserInfo-Entries never get saved, so your indexed item just lives alone, no chance to save any required data. In fact you need the UpdateUserActivityState override to let these additional entries find their way into the search index.

So, we need a UIViewController. This is easy in Xamarin.iOS, but things become tricky in Xamarin.Forms.

 

Integrating iOS9 Spotlight search with a Xamarin.Forms app

Note:
While the following example code emphasizes the integration in a clean MVVM architecture, as outlined e.g. in Jonathan Yates’ great blog, the basic concept also works for non MVVM and non IOC architectures. If you are new to MVVM/IOC principles, i highly recommend reading Jonathan’s blog.

Starting from the tail…

To sketch my use case, i want to start by discussing how the app is called when an indexed item (coming later) is clicked in Spotlight.
As Mike’s iOS-example demonstrated, the answer lies in override of the new ContinueUserActivity method in the AppDelegate class which handles the userActivity parameter, where iOS provides all the search item’s indexed data:

So let’s assume some example requirements:

We shall want to index two types of activities, „com.mycompany.myapp.person“ and „com.mycompany.myapp.newsitem“.

„person“ may indicate that a person’s detail page has been viewed, so we want to index that person. Thus the Person activity has a key named „userid“ with the corresponding value. Other properties, e.g. email-dress can additionally be indexed as well.

„newsitem“ may indicate that a News-Article’s Detail page has been viewed, so we want to index that news. Thus the newsitem activity has a key named „newsid“ with the corresponding value. Other properties, e.g. title can be indexed as well.

 

So in ContinueUserActivity we dispatch the activity types and take action to restore the state of the app – displaying the searched and clicked item. In the sample code below, i fetch the id parameters from the UserInfo object and fire off a MessagingCenter message, which can be handled in the app where appropriate. More on this later.

 

Where do we really start – UIViewController in Xamarin.Forms?

As already discussed above, we need access to a UIViewController to properly index a page’s content. But how do we get access to a Xamarin.Forms ContentPage’s UIViewController? A UIViewController is a iOS specific class, so there’s no direct way to get hold of it in the cross-platform part of a Xamarin.Forms app. But as you may know, whenever platform-specific features are required, Xamarin.Forms provides the CustomRenderer mechanism.

And this helps us out here as well.

CustomPageRenderer to the rescue

A Xamarin.Forms PageRenderer basically allows us to customize the appearance of a page or any other visual element by defining a platform specific Custom Renderer. If we have a PersonDetailView ContentPage in our Xamarin.Forms PCL project, we can define a PersonDetailViewRenderer class in the iOS project. By adding the ExportRenderer attribute, Xamarin.Forms knows it must call the custom class to render the ContentPage.

 


 

In our case, we actually do not want to customize the ContentPage’s rendering, but we want to get hold of its underlying UIViewController. The good news is: We already got it! As the PageRenderer base class in iOS derives from UIViewController, we are already well prepared to handle the indexing of content here.

Creating a PageRenderer base class for indexed ContentPages

In general, an app may not have just one, but multiple views that might be candidates to get indexed. So before adding the NSUserActivity stuff to my custom view renderer, i decided to create a generic base class for PageRenderers, which is able to handle all my indexing requirements automatically.

This CustomRendererBase, let’s call it  UserActivityPageRenderer  , needs a generic way to access my page’s detail data. As i’m a fan of a clean MVVM architecture, i want it to get hold of the Page’s underlying ViewModel and then access the NSUserActivity data through a interface. So i defined an interface for the ViewModels that need to be indexed:

 

Each ViewModel has to implement this interface, when it’s data shall be indexed.

The 3 properties ActivityType, ActivityTitle, ActivityUserValues are self explanatory.

Additionally i added an event „DataReady“ to the interface, because when the view is loaded, the ViewModel may still be asynchronously fetching data from some backend service. By firing the „DataReady“ event, the ViewModel must signal that the data is fetched and in a valid state.

Now back to the UserActivityPageRenderer, which derives from PageRenderer.

This is actually our UIViewController, where we implement the code to index our content item’s data.

First, we have two properties where we save a reference to the NSUserActivity and the Dictionary of UserValues.

In the OnElementChanged override we get the chance to access our ContentPage and subsequently our item’s data. If you don’t do MVVM then just access your page’s data (through an interface). In MVVM style i get hold of my Page’s BindingContext, which is the page’s ViewModel) and see if it implements the required IUserActivityViewModel interface to provide the indexing data. If it does, it registers an EventHandler for the viewModel.DataReady event and within the EventHandler  accesses the ViewModel’s data and finally creates the NSUserActivity.

Note, that iOS-Search integration is only available on iOS9 and later, so we need to check the iOS version we are running on.

Here’s the code:


 

The code above is the PageRenderer base class for all my ContentPages that need to get indexed. For a concrete ContentPage, let’s say PersonDetailView, all i have to do now is to create a CustomRenderer and derive it from UserActivityPageRenderer:

 

For the NewsDetail ContentPage the renderer looks like this:

 

That’s all for the custom view code. Now i need to instrument the two ViewModels i selected for indexing, to implement the IUserActivityViewModel interface:


 

The properties get initialized in the ViewModel’s constructor, as soon as the data are ready.

An example works best:

Let’s assume the ViewModel’s constructor of the PersonDetailViewModel uses some backend-service to asynchronously fetch the person’s data from a webservice. In this case – as we cannot await an asynchronous call in the constructor – the data is not yet ready when the View is displayed and our custom renderer is called. That’s why we need the DataReady event, which is fired in FetchPersonDetails, after the data are loaded:


 

That’s all.

Let’s recap: Our Views now have a iOS CustomRenderer that automatically accesses the ContentPage’s ViewModel and creates a NSUserActivity object to index the ViewModel’s data, exposed by the IUserActivityViewModel interface.

Now the only thing that’s missing is some service that subscribes to the MessagingCenter events, fired in our AppDelegate’s  ContinueUserActivity  override and navigates the app to the corresponding DetailPage to display the searched item’s data.

How you implement this part naturally depends on your app’s needs and navigation structure. In my app i created a class, that will be registered in the IOC container (e.g. AutoFAC) as a Singleton. The class gets the INavigator interface injected, as well as factory functions to create a ViewModel instance of the two types of ViewModels that i want to be indexed:

Just to be complete, in my bootstrap AppModule, where all the injectable classes are registered (with AutoFac), i register this class as a Singleton:

and in my bootstrapper code i create the instance only when running on iOS:


 

And that’s it. iOS9 spotlight search integrated in a Xamarin.Forms app.

As already said, while my example code emphasizes the integration in a clean MVVM architecture, outlined in Jonathan Yates’ great blog, the basic concept also works for non MVVM and non IOC architectures.

The code shown in this post is running under Xamarin.Forms 1.5.1.

It will be interesting to see, if and how Xamarin will embrace the search integration in a future release of the core Xamarin.Forms framework.

Final note, just in case it’s not obvious:

As the iOS9 indexing takes place in a iOS custom renderer, this code only lives in the iOS version of the Xamarin.Forms app. It doesn’t hurt Android or WindowsPhone versions at all.

Happy coding!