How to use Swift or Objective C Delegates with NativeScript
iOS Delegates are incredibly useful and fundamental concepts to understand when creating custom platform behavior. Let's take a look at how to create and use them with NativeScript.
This blog post will show you how to create and use iOS delegates in NativeScript.
From What is a delegate in iOS?:
A delegate is any object that should be notified when something interesting has happened. What that "something interesting" means depends on the context: for example, a table view's delegate gets notified when the user taps on a row, whereas a navigation controller's delegate gets notified when the user moves between view controllers.
ColorPicker Example
Let's take a look at UIColorPickerViewController which provides a way to present color choices a user can select. When the controller detects the user color selection, it needs a way to inform your app about the color picked. The way it does that is through a delegate. Apple provides different protocols for different purposes. For the UIColorPickerViewController, it provides the UIColorPickerViewControllerDelegate protocol.
Using this delegate, let's update the backgroundColor of a StackLayout in NativeScript. To receive the selected color from the picker, we conform to the UIColorPickerViewControllerDelegate protocol.
We can always approach controller/delegate setup in 2 stages.
Note: Be sure to understand the brief but important best practice usage of delegates in NativeScript: Delegates, delegates, DELEGATES!!.
Stage 1: Create a Delegate Implementation
Create a delegate implementation class, let's call it ColorPickerDelegateImpl, that extends NSObject and conforms (aka implements) to the delegate protocol UIColorPickerViewControllerDelegate.
@NativeClass()
class ColorPickerDelegateImpl
extends NSObject
implements UIColorPickerViewControllerDelegate {
// informs NativeScript to wire up the protocol
static ObjCProtocols = [UIColorPickerViewControllerDelegate];
// best practice to have an owner weak reference
owner: WeakRef<HelloWorldModel>;
// common pattern with statically initializing an implementation
static initWithOwner(owner: WeakRef<HelloWorldModel>) {
const delegate = <ColorPickerDelegateImpl>ColorPickerDelegateImpl.new();
delegate.owner = owner;
return delegate;
}
// implement delegate methods here...
}
Understanding highlights:
- When creating platform class implementations with NativeScript, we always decorate them with
@NativeClass(). The@NativeClass()decorator ensures compliance with the NativeScript runtime which you can learn more about here. - We extend
NSObjectmost commonly because it provides all the common baseline iOS behavior our implementation needs and also because our delegate is just a protocol, aka aninterface, which cannot be extended (just conformed to by an implementation). - The
static ObjCProtocolsArray can contain any number of protocols we want our implementation to use and informs NativeScript to wire up the specified protocols on our behalf. - A common best practice is to allow delegate implementations to have an owner weak reference. An owner is the class that is being communicated with from this delegate. You can learn more about WeakRef here.
- There's many ways to instantiate an implementation class but it's become quite common to use the
static initWithOwner(owner: WeakRef<HelloWorldModel>)pattern since it allows you to pass in other references if needed as additional method arguments without interfering with a platform (super) constructor up chain. Of note, theColorPickerDelegateImpl.new()is a convenient simple constructor that NativeScript adds to all platform classes avoiding particularinitarguments which you may want to handle later and returns anNSObjectwhich is why we simply cast it to our type<ColorPickerDelegateImpl>.
The owner weak reference comes into play when wanting to pass events and data back/forth between the delegate and it's owner. Let's do exactly that by implementing several methods provided by the UIColorPickerViewControllerDelegate:
import { Color } from '@nativescript/core';
@NativeClass()
class ColorPickerDelegateImpl
extends NSObject
implements UIColorPickerViewControllerDelegate
{
// ...
// all delegate methods come from Apple documentation:
// https://developer.apple.com/documentation/uikit/uicolorpickerviewcontrollerdelegate#3635512
colorPickerViewControllerDidFinish(
viewController: UIColorPickerViewController
) {
// did close/finish event
this.owner?.deref()
.changeColor(Color.fromIosColor(viewController.selectedColor));
}
colorPickerViewControllerDidSelectColorContinuously(
viewController: UIColorPickerViewController,
color: UIColor,
continuously: boolean
) {
// selecting colors event
}
}
Stage 2: Use the Delegate
Create the controller which will use your delegate:
const picker = UIColorPickerViewController.alloc().init();
Init the delegate implementation we created above while assigning it to an instance property as mentioned in the best practices:
this.colorDelegate = ColorPickerDelegateImpl.initWithOwner(
new WeakRef(this)
);
Set the delegate property the controller that needs it:
picker.delegete = this.colorDelegate;
Watch the platform come to life.
Try it out right now in your hand via StackBlitz
- Full code for this tutorial can be found here
Bonus surprise 🎉
Want even more neat delegate examples? Here's another implementing table view delegates from scratch to sort list views!
Credits
- Thank you to Nathan Walker for mentoring