Spark Chamber is a lightweight asynchronous trigger-action framework for iOS, designed to be used for automating analytics, tracking, and/or logging.
If you'd like to see Spark Chamber in action without delay, please follow these steps:
- Install Xcode 8 on your system
- Download and unzip the current release of the project
- Open the package SparkWorkspace.xcworkspace in Xcode
- Build the SparkChamber and SparkKit targets in Xcode
- Open Xcode's Assistant Editor and the Debug Area
- Select the Spark Playground in Xcode's Project Navigator
- Manipulate the Playground's simulation and observe events' output in the debug area
Spark Chamber is built as a trigger-action event tracking system. Its purpose is to allow the attachment of event objects to various UI elements and then execute the event's action
(code) asynchronously when the event's trigger condition is met. The optional value trace
(String) allows debugging, and the optional value identifier
(UUID) allows identification and correlation.
- In Xcode, add the SparkChamber.xcodeproj file by selecting your project and choosing 'Add Files to...' from the File menu
- Next, select your project in Xcode from the project navigator on the left side of the project window
- Select the target to which you want to add frameworks in the project settings editor
- Select the “Build Phases” tab, and click the small triangle next to “Link Binary With Libraries” to view all of the frameworks in your application
- To add the SparkChamber framework, click the “+” below the list of frameworks
At the heart of the system is a Spark Event, composed of a trigger
and an action
. Events can be set to trigger under the following conditions: didAppear
, didDisappear
, didEndTouch
, didBeginScroll
, didBecomeFirstResponder
, didResignFirstResponder
, and targetAction
.
Spark Events can optionally include a trace
(String) for debugging purposes, and a identifier
(UUID) to allow for identification and correlation.
Events are attached to any subclass of NSObject using the sparkEvents
property, which stores an Array/NSArray composed of Spark Event objects. Multiple independent events may be attached to any given UI component to allow for the construction of complex tracking behaviors.
Utilizing these tools, a diverse set of tracking scenarios can be solved: viewing, click-through, time on screen, etc.
Since the events are tied directly to a UI element's object lifecycle, there is no need for managing an event's lifecycle independently.
Events are triggered using the trigger
property, a SparkTriggerType
enum which supports the following values:
enum Value | Object Class | Description |
---|---|---|
none |
NSObject |
The event has no trigger |
didAppear |
UIView |
Triggered when the UI element receives a 'didAppear' or 'willDisplay' message, as appropriate |
didDisappear |
UIView |
Triggered when the UI element receives a 'didDisappear' or 'didEndDisplaying' message, as appropriate |
didEndTouch |
UIView |
Triggered when attached to a responder that has received a touch event with phase 'Ended' |
didBeginScroll |
UIScrollView |
Triggered when attached to a scroll view after scrolling has begun |
didBecomeFirstResponder |
UIView |
Triggered when attached to a responder that has become a first responder |
didResignFirstResponder |
UIView |
Triggered when attached to a responder that has resigned its status as a first responder |
targetAction |
UIView |
Triggered when attached to a responder that has an event action tied to the Detector |
// Swift
let event = SparkEvent(trigger: SparkTriggerType.didAppear) {
_ in
// Your code to send data to a tracking/analytics solution goes here.
}
view.sparkEvents = [event]
// Objective-C
SparkEvent* event = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidAppear
action: ^(NSDate* _Nonnull timestamp)
{
// Your code to send data to a tracking/analytics solution goes here.
};
view.sparkEvents = @[event];
// Swift
let appearEvent = SparkEvent(trigger: SparkTriggerType.didAppear) {
_ in
// Your code to send data to a tracking/analytics solution for the view event goes here.
}
let touchEvent = SparkEvent(trigger: SparkTriggerType.didEndTouch) {
_ in
// Your code to send data to a tracking/analytics solution for the appear event goes here.
}
view.sparkEvents = [appearEvent, touchEvent]
// Objective-C
SparkEvent* appearEvent = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidAppear
action: ^(NSDate* _Nonnull timestamp)
{
// Your code to send data to a tracking/analytics solution for the view event goes here.
};
SparkEvent* touchEvent = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidEndTouch
action: ^(NSDate* _Nonnull timestamp)
{
// Your code to send data to a tracking/analytics solution for the appear event goes here.
};
view.sparkEvents = @[appearEvent, touchEvent];
If your event's UI element has a long object lifecycle, then the following code will construct an event that only triggers once:
// Swift
let event = SparkEvent(trigger: SparkTriggerType.didAppear, action: nil)
event.action = {
_ in
// Your code to send data to a tracking/analytics solution for the touch event goes here.
event.trigger = SparkTriggerType.none
}
view.sparkEvents = [event]
// Objective-C
__block SparkEvent* event = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidAppear
action: nil];
__weak SparkEvent* weakEvent = event;
event.action = ^(NSDate* _Nonnull timestamp)
{
// Your code to send data to a tracking/analytics solution for the touch event goes here.
weakEvent.trigger = SparkTriggerTypeNone;
};
view.sparkEvents = @[event];
If your event's UI element has a short object lifecycle (collection & table view cells, for instance) then in the action block you'd instead want to invalidate the event's data, possibly by utilizing a lookup table or a long-lived model object. This could then be referenced to prevent the re-creation of the event when the appropriate UI element is being reconstructed.
Creating a pair of events that track a UI element's time on screen is achieved by utilizing the Appear
and Disappear
triggers in tandem:
// Swift
var startTime: Date = Date()
let appearEvent = SparkEvent(trigger: SparkTriggerType.didAppear) {
timestamp in
startTime = timestamp
}
let disappearEvent = SparkEvent(trigger: SparkTriggerType.didDisappear) {
timestamp in
print("Time on screen:", timestamp.timeIntervalSince(startTime))
}
view.sparkEvents = [appearEvent, disappearEvent]
// Objective-C
__block NSDate* startTime;
SparkEvent* appearEvent = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidAppear
action: ^(NSDate* _Nonnull timestamp)
{
startTime = timestamp;
};
SparkEvent* disappearEvent = [[SparkEvent alloc] initWithTrigger: SparkTriggerTypeDidDisappear
action: ^(NSDate* _Nonnull timestamp)
{
NSLog(@"Time on screen: %f", [timestamp timeIntervalSinceDate:startTime]);
};
view.sparkEvents = @[appearEvent, disappearEvent];
While Spark Events define the trigger-action-trace events for the system, the Spark Detector is the engine that acts as a discriminator and executor for appropriate event actions. The Spark detector is either invoked from your app's UIKit subclasses to process events, or through the SparkKit framework.
Note: When using subclasses of UI components from SparkKit - the appearance, disappearance, touch, scrolling, and target action methods will automatically be invoked by the superclass or protocol extension provided in SparkKit. You're not required to manually implement the code in the following sections unless your UI component requires unique support.
Appearance events are triggered by calling Spark Detector's class method:
// Swift
class func trackDisplay(forViews views: NSArray?) -> Bool
// Objective-C
+ (BOOL)trackDisplayForViews: (NSArray<__kindof UIView *>*) views
This method accepts an optional NSArray of UIViews and returns a boolean value of true if any supplied view triggers a didAppear
event.
Disappearance events are triggered by calling Spark Detector's class method:
// Swift
class func trackEndDisplaying(forViews views: NSArray?) -> Bool
// Objective-C
+ (BOOL)trackEndDisplayingForViews: (NSArray<__kindof UIView *>*) views
This method accepts an optional NSArray of UIViews and returns a boolean value of true if any supplied view triggers a didDisappear
event.
Disappearance events are triggered by calling Spark Detector's class method:
// Swift
class func trackEnded(withTouches touches: NSSet?) -> Bool
// Objective-C
+ (BOOL)trackEndedWithTouches: (NSSet*) touches
This method accepts an optional NSSet and returns a boolean value of true if any of the touches in the set triggers a didEndTouch
event.
A didEndTouch
event will be triggered if suppliedUITouch
objects with a phase of UITouchPhase.Ended
are attached to a view with the userInteractionEnabled
property set to true when the touch registers either as a touchInside
a UIControl
, or as a pointInside
a UIView
.
Scrolling events are triggered by calling Spark Detector's class method:
// Swift
class func trackBeganScrolling(forScrollView scrollView: UIScrollView?) -> Bool
// Objective-C
+ (BOOL)trackBeganScrollingForScrollView: (UIScrollView*) view
This method accepts an optional UIScrollView and returns a boolean value of true if the supplied scroll view triggers a didBeginScroll
event.
A didBeginScroll
event will be triggered if the supplied UIScrollView's tracking
property is true when this method is called.
Responder events are triggered by calling one of two of Spark Detector's class methods, as below. To track when a view has become a first responder:
// Swift
class func trackBecameFirstResponder(forView view: UIView?) -> Bool
// Objective-C
+ (BOOL)trackBecameFirstResponderForView: (UIView*) view
To track when a view has resigned its first responder status:
// Swift
class func trackResignedFirstResponder(forView view: UIView?) -> Bool
// Objective-C
+ (BOOL)trackResignedFirstResponderForView: (UIView*) view
This method accepts an optional UIView and returns a boolean value of true if the supplied view triggers either a didBecomeFirstResponder
or a didResignFirstResponder
event through the Detector, as appropriate.
Target Action events are triggered by calling Spark Detector's class method:
// Swift
class func trackTargetAction(forView view: UIView?) -> Bool
// Objective-C
+ (BOOL)trackTargetActionForView: (UIView*) view
This method accepts an optional UIView and returns a boolean value of true if the supplied view triggers a targetAction
event through the Detector.
Target Action events are used to tie in to a UIControl's target-action mechanism.
Spark Kit ties Spark Chamber's mechanisms to Apple's UIKit and allows UI to automatically notify the Detector portions of the Spark Chamber framework for seamless event processing.
- In Xcode, add the SparkKit.xcodeproj and SparkChamber.xcodeproj files by selecting your project and choosing 'Add Files to...' from the File menu
- Next, select your project in Xcode from the project navigator on the left side of the project window
- Select the target to which you want to add frameworks in the project settings editor
- Select the “Build Phases” tab, and click the small triangle next to “Link Binary With Libraries” to view all of the frameworks in your application
- To add the SparkKit framework, click the “+” below the list of frameworks
Class Name | Root Class Name | Spark Event Support | Spark Detector Integration |
---|---|---|---|
SparkButton |
UIButton |
didEndTouch |
trackEnded(withTouches:) |
SparkCollectionViewCell |
UICollectionViewCell |
didEndTouch , didAppear , didDisappear |
trackEnded(withTouches:) , trackDisplay(forViews:) , trackEndDisplaying(forViews:) |
SparkTableViewCell |
UITableViewCell |
didEndTouch , didAppear , didDisappear |
trackEnded(withTouches:) , trackDisplay(forViews:) , trackEndDisplaying(forViews:) |
SparkViewController |
UIViewController |
didAppear , didDisappear , didBeginScroll |
trackDisplay(forViews:) , trackEndDisplaying(forViews:) , trackBeganScrolling(forScrollView:) |
If desired, UIApplication-level touch support can be enabled in SparkKit for processing of most touches app-wide. Some responders don't forward touch events, so this support can be unpredictable at times.
To enable this feature: select the SparkKit project in Xcode, choose the SparkKit (framework) target, then in the Build Settings tab find the 'Swift Compiler - Custom Flags' section and add this value to the 'Other Swift Flags' key: -DUIAPPLICATION_EXTENSION_ENABLED