Action Programming Guide
Welcome to the Hub Framework action programming guide! This guide aims to help you gain a deeper understanding of how actions work, how to create them and how you can use them to easily extend the framework with additional functionality.
If you haven’t already - we really recommend that you read the component programming guide and content programming guide before proceeding with this one.
Table of contents
- Introduction
- Creating an action
- Integrating an action with the framework
- Triggering an action
- Responding to an action in a content operation
- Handling actions
Introduction
The Hub Framework comes with built-in selection handling for components. Whenever a component is selected, the URI
associated with that component’s target
will be opened, using the standard [UIApplication openURL:]
API. However, if you want to customize the selection behavior, or execute other type of actions based on other user interactions - that’s when the Action API comes in handy.
The Action API also makes it easy to send events back to your content operations from your components.
Creating an action
Just like components, content operations and the other aspects of the Hub Framework, actions are defined using a protocol - HUBAction
. Actions are very simple to implement, all you require is one method:
@interface SPTMyHubAction : NSObject <HUBAction>
@end
@implementation SPTMyHubAction
- (BOOL)performWithContext:(id<HUBActionContext>)context
{
// Return a boolean indicating whether the action was performed or not
return YES;
}
@end
The contextual object passed to the action contains useful information like what view controller (and for which view URI) the action is being performed, as well as the component & view model that the action should be performed for.
Integrating an action with the framework
Before an action can be used, it must be integrated into the Hub Framework. This is done through the HUBActionRegistry
API, which works similar to how the HUBComponentRegistry
works for components.
In order to avoid accidentally sharing state between actions, all actions are uniquely created before they are performed, using a factory - an implementation of HUBActionFactory
.
So, to integrate your action into the framework, first create a factory class:
@interface SPTMyActionFactory : NSObject <HUBActionFactory>
@end
@implementation SPTMyActionFactory
- (nullable id<HUBAction>)createActionForName:(NSString *)name
{
if ([name isEqualToString:@"myAction"]) {
return [SPTMyAction new];
}
return nil;
}
@end
Then, register your factory with the action registry (available on HUBManager
):
id<HUBActionFactory> factory = [SPTMyActionFactory new];
[actionRegistry registerActionFactory:factory forNamespace:@"myNamespace"];
The namespace
used when registering the factory are used to match actions when performed - just like the way components are matched.
Triggering an action
Triggering an action from a component model
Actions that are associated with a HUBComponentModel
will automatically be triggered when a component rendering that model was selected by the user.
To add actions to a component model, you add a HUBIdentifier
matching your action’s name
and namespace
(that your factory was registered for) either using code in a content operation:
id<HUBComponentModelBuilder> componentModelBuilder = [viewModelBuilder builderForBodyComponentModelWithIdentifier:@"myComponent"];
[componentModelBuilder.targetBuilder addActionWithNamespace:@"myNamespace"
name:@"myAction"];
Or using JSON:
// A component model dictionary
{
"target": {
"actions": ["myNamespace:myAction"]
}
}
Triggering an action from a component
You can also have a component implementation manually trigger an action. This can be done at any point in time, for example in response to a custom user interaction (such as a swipe), or some other event.
To make a component able to perform actions, make it conform to the HUBComponentActionPerformer
protocol. This gives you an actionDelegate
that you can call to perform an action for a given identifier (which will be resolved exactly the same way as when action identifiers are attached to a HUBComponentModel
). You also have the option of passing any customData
that you can then pick up in the action or in a content operation.
Here’s an example where we perform an action in response to a UIGestureRecognizer
being triggered:
- (void)handleGestureRecognizer:(UIGestureRecognizer *)recognizer
{
HUBIdentifier *actionIdentifier = [[HUBIdentifier alloc] initWithNamespace:@"myFeature" name:@"myAction"];
[self.actionDelegate component:self performActionWithIdentifier:actionIdentifier customData:nil];
}
Responding to an action in a content operation
As mentioned in the introduction, one thing you can use actions for, is to be able to easily communicate with a content operation from a component. For example, you might want to reschedule an operation based on user interaction, or modify your underlying data.
To observe actions performed in the view that a content operation is serving, conform to the HUBContentOperationActionObserver
protocol in your content operation.
In the example below, we are rendering a list of songs based on a SPTSongContentOperation
. We then implement a delete button in one of our components, which will trigger an action to delete the song associated with that component from our data source, and then reschedule our content operation to re-render our view (with the component deleted).
First, the component which performs the action when the delete button is tapped:
@implementation SPTDeletableComponent
- (void)handleDeleteButtonTapped
{
HUBIdentifier *actionIdentifier = [[HUBIdentifier alloc] initWithNamespace:@"delete" name:@"song"];
[self.actionDelegate component:self performActionWithIdentifier:actionIdentifier];
}
@end
Then, the action, which deletes the song from our data source:
@implementation SPTDeleteSongAction
- (BOOL)performWithContext:(id<HUBActionContext>)context
{
NSString *songID = context.componentModel.identifier;
[self.dataSource removeSongWithIdentifier:songID];
return YES;
}
@end
Finally, we observe actions being performed in our content operation, and re-schedule it once a delete action was performed:
@implementation SPTSongContentOperation
- (void)actionPerformedWithContext:(id<HUBActionContext>)context
featureInfo:(id<HUBFeatureInfo>)featureInfo
connectivityState:(HUBConnectivityState)connectivityState
{
HUBIdentifier *deleteSongActionID = [[HUBIdentifier alloc] initWithNamespace:@"delete" name:@"song"];
if ([context.customActionIdentifier isEqual:deleteSongActionID]) {
[self.delegate contentOperationRequiresRescheduling:self];
}
}
@end
The result of the above is that every time a delete button is pressed, the component for that song is deleted from the view.
Handling actions
You can also opt to handle certain actions on a feature level. When you set up your feature with the Hub Framework, you can pass an actionHandler
(an implementation of the HUBActionHandler
protocol), that gets called each time an action is about to be performed. The action handler can then choose to handle the action itself, rather than letting the action being performed.
Let’s take the delete a song
example from just before, and add some protected songs
that are not deletable. That is, when the SPTDeleteSongAction
is about to be performed, we check if the song is protected, and if it is - we veto the action, preventing it from being performed. Let’s implement HUBActionHandler
:
@implementation SPTMyActionHandler
- (BOOL)handleActionWithContext:(id<HUBActionContext>)context
{
NSString *songID = context.componentModel.identifier;
if ([self isSongWithIdentifierProtected:songID]) {
return YES;
}
return NO;
}
@end
By returning YES
above, we tell the Hub Framework that we’ve handled the action in our action handler, which means that the action won’t be performed.
A global action handler can also be added when setting up HUBManager
, that will be used for all features that do not implement their own. For more information about setting up Hub Manager, see the setup guide.