If you are an iOS or macOS developer using Swift, you must be familiar with protocol conformance in order to hand off responsibilities to another class or structure. The Delegate Pattern is found all over Foundation, AppKit and UIKit which are the core frameworks for iOS and macOS development.
Such as UICollectionViewDelegate or URLSessionDelegate.
In order to prevent you from writing the boilerplate code of Delegate APIs, let’s make them Reactive using RxSwift.
The source code of this article is available here:
- RxMKLocalSearchCompleterDelegateProxy.swift
- MKLocalSearchCompleter+Rx.swift
- RxMKLocalSearchCompleterUsageExample.swift
RxSwift version used: 4.3.1
MKLocalSearchCompleterDelegate presentation
If you’ve never used MapKit or any MKLocalSearchCompleter
in an application, this is a quite new API (since iOS 9.3) allowing you to get autocomplete address results based on an incomplete query request which is a simple String. The query string is passed through the MKLocalSearchCompleter
’s property queryFragment.
The MKLocalSearchCompleterDelegate
protocol is quite simple. It’s made of two optional methods
This one is called whenever the parent MKLocalSearchCompleter
results Array gets updated. This way you are always aware of the most recent results based on the last queryFragment
you passed to the parent MKLocalSearchCompleter
’s object.
The results Array is available through the completer’s readonly results property which is an Array of MKLocalSearchCompletion
objects.
This one is the error callback called when the parent’s MKLocalSearchCompleter
is unable to generate a list of search results. This way, you can handle the error and let your users know what and why it went wrong.
The concrete RxCocoa’s DelegateProxy: RxMKLocalSearchCompleterDelegateProxy
Here we are building a concrete DelegateProxy
type that will be used by the upcoming MKLocalSearchCompleter
’s Reactive extension.
First we need to make MKLocaSearchCompleter
conform to the RxCocoa HasDelegate
protocol. We can achieve this with a simple extension.
Here we are setting the HasDelegate
’s associated type Delegate
to the concrete type MKLocaSearchCompleterDelegate
.
This allows us to use the RxCocoa’s DelegateProxyType
delegate setter and getter default implementation through the constraint extension of the DelegateProxyType
. Here is the extension mentioned:
So whenever the DelegateProxyType
’s ParentObject
(associatedType) implements HasDelegate
and DelegateProxyType
’s Delegate
(associatedType) is the same type as ParentObject.Delegate
(from HasDelegate
protocol) you get the above two methods default implementation for free.
Next we have to create a concrete RxMKLocalSearchCompleterDelegateProxy
class that inherits from RxCocoa’s DelegateProxy<ParentObject, Delegate>
class. It also needs to implement RxCocoa’s DelegateProxyType
protocol as well as the MKLocalSearchCompleterDelegate
protocol.
Here ParentObject
will be of type MKLocalSearchCompleter
and Delegate
will be of type MKLocalSearchCompleterDelegate
.
The localSearchCompleter
is declared as weak to prevent retain cycles. It is readonly too.
The init
method stores the given localSearchCompleter
and then initializes the current object using RxCocoa’s superclass DelegateProxy<ParentObject, Delegate>
initializer.
In the registerKnownImplementations()
method we call the superclass method register<Parent>
(its signature is below). In the “make” escaping closure we then return the RxMKLocalSearchCompleterDelegateProxy
initializer method with the MKLocaSearchCompleter
type as a parameter.
The “register” method is called by RxCocoa’s DelegateProxyType
in order to store our proxy subclass to its own proxy factory.
Now that RxMKLocalSearchCompleterDelegateProxy
is ready, let’s make MKLocalSearchCompleter
reactive 🚀.
Rx All the things 🙌🏻
The goal is to make MKLocalSearchCompleter
reactive. To do so, we are going to make an extension to the RxSwift’s Reactive
type, constraining its Base
type to be MKLocalSearchCompleter
.
Then we will need to declare a delegate property inside this extension which is of type DelegateProxy<MKLocalSearchCompleter, MKLocalSearchCompleterDelegate>
.
This delegate property is a computed property initialized by the previously created RxMKLocalSearchCompleterDelegateProxy
class using this method:
You must always call this method to instantiate a DelegateProxyType
object instead of directly instantiating RxMKLocalSearchCompleterDelegateProxy
through its initializer.
Indeed, this method takes care of creating and installing a proxy instance and then returns it or returns an existing proxy instance already installed.
didUpdateResults
The last task is to return observable sequences for each of the two MKLocalSearchCompleterDelegate
’s methods. First let’s see how we do it to recover the last updated MKLocalSearchCompleter
results using a computed property.
We use the delegate proxy using its methodInvoked()
passing the Selector of the corresponding delegate method to let the delegate proxy observe calls to this method returning results as an observable sequence.
We then map the result of methodInvoked()
which is an Array of Any representing the list of the delegate method parameter(s). In this case there is only one parameter of type MKLocalSearchCompleter
so the focus is made on the first parameter (a[0])
.
You must be wondering what is the castOrThrow(...)
method 🤔. This is a method used all over RxCocoa, but not exposed outside of the RxSwift library. This simply tries to cast the given object (2nd parameter) into the given type (1st parameter) or throws a RxCocoaError
if it fails. Here is the implementation found in RxCocoa. You can add this to your file and make it public, fileprivate or private so it fits your needs.
The didUpdateResults
computed property then returns a ControlEvent
of MKLocalSearchCompleter
. The ControlEvent
type is used to provide a safe representation of the observable sequence that represent UI events. The main properties of ControlEvent
sequences are:
- It never fails
- It won’t send any initial value on subscription
- It will Complete sequence on control being deallocated
- It never errors out
- It delivers events on the
MainScheduler.instance
This sequence type is appropriate to what we need here because updated MKLocalSearchCompleter.results
will be propagated to any UI element such as UITableView presenting the results to the user while he is typing his location query.
didFailWithError
Let’s see how the failure delegate method is handled into an observable sequence through the didFailWithError
computed property.
This is really similar to the previous one, I’ll only address the a[1]
syntax. It focuses on the second parameter of the given method invoked which is the actual Error
object, at index 1 in the corresponding Array of parameters.
queryFragment
The MKLocalSearchCompleter
’s queryFragment
property is the entry point of the query mechanism. Whenever it’s value changes, a new request is made.
Let’s make it reactive too! So it can be bound to any text oriented UI like UITextField
or UISearchBar
.
Here it is, just like the text property of a UILabel
in RxCocoa we’ve made a Binder
which takes a String
and affects its value to the underlying queryFragment
property of the MKLocalSearchCompleter
object.
Usage
As an example, we are going to bind a UITextfield
’s reactive text property to a MKLocalSearchCompleter
’s reactive queryFragment
property.
- Initialization of a
MKLocalSearchCompleter
and aUITexfield
object - The
UITextfield
’s reactive text property is unwrapped to be sure it’s not an optionalString
sequence with.orEmpty
and then bound to our newly created reactivequeryFragment
property. - We then subscribe to the
MKLocalSearchCompleter
’s reactivedidUpdateResults
sequence to then print the address completion results.
So now, every user’s input on the UITextfield
is broadcasted to the searchCompleter.queryFragment
property triggering a new address completion request. The results are automatically forwarded to the .didUpdateResults
sequence allowing you to present the autocomplete list of results to your users. 👏🏻