iOS SDK - Current (25.1.1)
Introduction
The Atomic iOS SDK is a dynamic framework for integrating an Atomic stream container (vertical or horizontal) into your iOS app, presenting cards from a stream to your customers. It is written in Objective-C.
The current stable release is 25.1.1.
Supported iOS version
The SDK supports iOS 12.0 and above.
Boilerplate app
You can use our iOS boilerplate app to help you get started with the Atomic SDK for iOS. You can download it from its GitHub repository. Alternatively, you can follow this guide.
Installation
The SDK can be installed using CocoaPods, Carthage, Swift Package Manager, or manually.
CocoaPods
- Add the path to the SDK spec repo to your Podfile, along with the default specs repo:
source 'https://github.com/atomic-app/action-cards-ios-sdk-specs.git'
source 'https://github.com/CocoaPods/Specs.git'
- Add the SDK as a dependency. You have two options available:
AtomicSDK
: the Atomic SDK distributed as anxcframework
, with support for Apple Silicon (requires Cocoapods 1.9 or above);AtomicSDK-framework
: the Atomic SDK distributed as a fat framework, with slices for arm64 and x86_64.
pod 'AtomicSDK', '25.1.1'
# or
pod 'AtomicSDK-framework', '25.1.1'
- Run
pod update
.
Alternative way to install Atomic SDK
Alternatively, you can install Atomic SDK through a git path. This will install the latest Atomic SDK as an xcframework
.
pod 'AtomicSDK', :git => 'https://github.com/atomic-app/action-cards-ios-sdk-releases'
Carthage
- Add
github "atomic-app/action-cards-ios-sdk-releases"
to yourCartfile
. - Run
carthage update
. - Follow the instructions provided by Carthage to add the SDK to your app.
Note: As Carthage does not currently support xcframework
, this will install the fat framework version, which does not include the arm64 simulator slice.
Swift Package Manager
- Open your Xcode project, and choose File > Add Packages.
- Enter
https://github.com/atomic-app/action-cards-ios-sdk-releases
in the upper right text field 'Search or Enter Package URL'. - Set the dependency rule and click 'Add Package'.
Manual Installation
- You can download releases of the SDK from the Releases page on Github.
- Once you've downloaded the version you need, navigate to your project in Xcode and select the "General" settings tab.
- Drag either
AtomicSDK.xcframework
orAtomicSDK.framework
from the directory where you unzipped the release, to theEmbedded Binaries
section. - When prompted, ensure that "Copy items if needed" is selected, and then click "Finish".
- If you chose AtomicSDK.framework above, you will also need to run the
strip-frameworks.sh
script (downloadable from this repository) as part of aRun Script
phase in your target, to get around an App Store submission bug, caused by iOS simulator architectures being present in the fat framework.
Note: AtomicSDK.xcframework
includes support for Apple Silicon, but requires Xcode 11 or higher, while AtomicSDK.framework
is a fat framework.
Setup
Before you can display a stream container or single card, you will need to configure your API base URL, environment ID, session delegate, and API key.
Convenient initialization method
You can use a convenient method +loginWithEnvironmentId:apiKey:sessionDelegate:apiBaseUrl
to initialize API base URL, environment ID, session delegate, and API key all at once.
It's the equivalent of calling initialiseWithEnvironmentId:apiKey:
, setSessionDelegate:
and setApiBaseUrl
in sequence, all introduced in sections below.
The following code snippet shows how to call this method.
- Swift
- Objective-C
AACSession.login(withEnvironmentId: "<environmentId>", apiKey: "<apiKey>", sessionDelegate: <the session delegate>, apiBaseUrl: <the API base URL>)
[AACSession loginWithEnvironmentId:@"<environmentId>"
apiKey:@"<apiKey>"
sessionDelegate:<the session delegate>
apiBaseUrl:<the API base URL>];
SDK API base URL
You must specify your SDK API base URL when configuring the Atomic SDK. This URL is found in the Atomic Workbench:
- In the Workbench, click on the cog icon in the bottom left and select 'Settings';
- On the screen that appears, click the 'SDK' tab. Your API base URL is displayed in the 'API Host' section.
The SDK API base URL is different to the API base URL endpoint, which is also available under Configuration. The SDK API base URL ends with client-api.atomic.io.
You can specify your SDK API base URL in two ways:
-
By adding the following to your app's
Info.plist
file, replacingAPI_BASE_URL
with your URL: -
By declaring your API base URL in code, replacing
API_BASE_URL
with your URL:
- Swift
- Objective-C
if let url = URL(string: "API_BASE_URL") {
AACSession.setApiBaseUrl(url)
}
NSURL *url = [NSURL URLWithString:@"API_BASE_URL"];
[AACSession setApiBaseUrl:url];
Environment ID and API key
Within your host app, you will need to call the +initialiseWithEnvironmentId:apiKey:
method to configure the SDK. Your environment ID and API key can be found in the Atomic Workbench.
If you do not call this method, and attempt to use any functionality in the SDK, an exception will be raised.
- Swift
- Objective-C
AACSession.initialise(withEnvironmentId: "<environmentId>", apiKey: "<apiKey>")
[AACSession initialiseWithEnvironmentId:@"<environmentId>" apiKey:@"<apiKey>"];
Authenticating requests using a JWT
Atomic SDK uses a JSON Web Token (JWT) to perform authentications.
The SDK Authentication guide provides step-by-step instructions on how to generate a JWT and add a public key to the Workbench.
Within your host app, you will need to call +setSessionDelegate:
method to provide an object conforming to the AACSessionDelegate
protocol, which contains the method:
- Objective-C:
cardSessionDidRequestAuthenticationTokenWithHandler:
or - Swift:
cardSessionDidRequestAuthenticationToken(handler:)
The SDK calls this method when it needs a JWT for authentication. You are then responsible for generating the JWT and supplying it to the SDK by calling the handler
block with the token. If you do not have a valid token, pass nil
to the handler, this will invoke error handling within the SDK. You must also return the token within 5 seconds, otherwise error handling will be triggered.
- Swift
- Objective-C
AACSession.setSessionDelegate(<the session delegate>)
[AACSession setSessionDelegate:<the session delegate>];
Note: by calling +setSessionDelegate:
method, the SDK holds a strong reference of the session delegate.
JWT Expiry interval
The Atomic SDK allows you to configure the time interval to determine whether the JSON Web Token (JWT) has expired. If the interval between the current time and the token's exp
field is smaller than that interval, the token is considered to be expired.
If this method is not set, the default value is 60 seconds. The interval must not be smaller than zero. You must return a constant value from this method, as the value is only retrieved by the SDK once.
- Swift
- Objective-C
func expiryInterval() -> TimeInterval {
return 60
}
- (NSTimeInterval)expiryInterval {
return 60;
}
JWT Retry interval
The Atomic SDK allows you to configure the time interval to determine whether the SDK should call the session delegate for a new JWT. The SDK won't try to call the delegate sooner than this interval after failing to fetch a valid JWT. If this method is not set, the default value is 0 seconds, which means the SDK will call for a new JWT immediately after failures. The interval must not be smaller than zero.
You must return a constant value from this method, as the value is only retrieved by the SDK once.
- Swift
- Objective-C
func retryInterval() -> TimeInterval {
return 60
}
- (NSTimeInterval)retryInterval {
return 60;
}
JWT custom user ID field
(introduced in 25.1.1)
To use a custom field in your JWT for the user ID used by the Atomic Platform you must first configure this field in the Atomic Workbench for the relevant SDK API Key.
Once you have the API Key configuration in place, you need to return the custom field in AACSessionDelegate
's tokenUserIdAttribute
method:
Note: You must return a constant value from this method, as the value is only retrieved by the SDK once.
- Swift
- Objective-C
func tokenUserIdAttribute() -> String? {
"custom-user-id-field"
}
- (NSString *)tokenUserIdAttribute {
return @"custom-user-id-field";
}
WebSockets and HTTP API Protocols
Atomic SDK uses WebSocket as the default protocol and HTTP as a backup. However, you can switch to HTTP by using setApiProtocol
, which accepts a parameter of type AACApiProtocol
. You can call this method at any time and it will take effect immediately. The setting will last until the host app restarts.
The AACApiProtocol
enum has two values:
AACApiProtocolWebSockets
: Represent the WebSockets protocol.AACApiProtocolHttp
: Represent the HTTP protocol.
- Swift
- Objective-C
AACSession.setApiProtocol(.http)
[AACSession setApiProtocol:AACApiProtocolHttp];
Logging out the current user
The SDK provides a method [AACSession logOut:]
for clearing user-related data when a previous user logs out or when the active user changes, so that the cache (including JWT) is clear when a new user logs in to your app. This method also sends any pending analytics events back to the Atomic Platform.
The following behaviors apply to this method:
- After logging out, you must log in to the SDK (by calling either
+[AACSession loginWithEnvironmentId:...]
or the appropriate initialization methods) to proceed with another user. Otherwise, the Atomic SDK will raise exceptions. - This method also purges all cached card data stored by the SDK and disables all SDK activities.
- This method also invalidates existing stream containers, single card views, and card count observers. However, they are not deallocated by the SDK in order to prevent visual flickering. For a complete log-out, you must handle deallocation yourself.
- There is another logout method which takes an extra parameter -
deregisterNotifications
. Set this parameter toYES
to de-register push notifications when logging out. See below for the example code snippet.
This method takes an optional parameter - completionHandler
. This completion handler is invoked with a nil error object if any pending analytics events were successfully sent, or a non-nil error object if the sending of pending analytics failed.
The following code snippet shows how to call this method and handle the error.
- Swift
- Objective-C
AACSession.logout { error in
if let error = error as? NSError {
if let errorCode = AACSessionLogoutError.Code(rawValue: error.code) {
switch errorCode {
case .dataError:
// Deal with data error.
let dataError = error.userInfo[NSUnderlyingErrorKey] as? NSError
case .networkError:
// Deal with network error.
let networkError = error.userInfo[NSUnderlyingErrorKey] as? NSError
@unknown default:
// A new type of error is added in the future.
let unknownError = error.userInfo[NSUnderlyingErrorKey] as? NSError
}
}
}
}
[AACSession logout:^(NSError *error) {
if(error != nil) {
AACSessionLogoutErrorCode errorCode = error.code;
switch (errorCode) {
case AACSessionLogoutErrorCodeDataError: {
// Deal with data error.
NSError *dataError = error.userInfo[NSUnderlyingErrorKey];
}
break;
case AACSessionLogoutErrorCodeNetworkError: {
// Deal with network error.
NSError *networkError = error.userInfo[NSUnderlyingErrorKey];
}
break;
}
}
}];
The following code snippet shows how to de-register push notifications when logging out.
- Swift
- Objective-C
AACSession.logout(withNotificationsDeregistered: true) { error in
// The completion handler.
}
[AACSession logoutWithNotificationsDeregistered:YES completionHandler:^(NSError *error) {
// The completion handler.
}];
Error handling
If the error
object is non-nil, the error domain will be AACSessionLogoutErrorDomain
- look for a specific error code in the AACSessionLogoutErrorCode
enumeration to determine the cause of the error. NSUnderlyingErrorKey
will also be populated in the error's userInfo
dictionary.
Displaying containers
This section applies to all types of container: vertical, horizontal and single card view.
To display an Atomic stream container in your app, create an instance of AACStreamContainerViewController
. To create an instance, you must supply:
- A stream container ID, which uniquely identifies the stream container in the app;
- A configuration object, which provides initial styling and presentation information to the SDK for this stream container.
Stream container ID
First, you’ll need to locate your stream container ID.
Navigate to the Workbench, select Configuration > SDK > Stream containers and find the ID next to the stream container you are integrating.
Configurations options
The configuration object is a class of AACConfiguration
, which allows you to configure a stream container, horizontal container or single card view via the following properties:
Style and presentation
presentationStyle
: indicates how the stream container is being displayed:- With no button in its top left;
- With an action button that triggers a custom action you handle. This value has no effect in horizontal container view;
- With a contextual button, which displays
Close
for modal presentations, orBack
when inside a navigation controller. This value has no effect in horizontal container view.
launchBackgroundColor
: The background color to use for the launch screen, seen on the first load. Defaults to white.launchTextColor
: The text color to use for the view displayed when the SDK is first presented. Defaults to black at 50% opacity.launchLoadingIndicatorColor
: The color to use for the loading spinner on the first time loading screen. Defaults to black.launchButtonColor
: The color of the buttons that allow the user to retry the first load if the request fails. Defaults to black.interfaceStyle
: The interface style (light, dark or automatic) to apply to the stream container. Defaults toAACConfigurationInterfaceStyleAutomatic
. See Dark mode for more details.enabledUiElements
: A bitmask of UI elements that should be enabled in the stream container. Defaults to showing toast messages and the card list header. Possible values are:AACUIElementNone
: No UI elements should be displayed. Do not use it in conjunction with any other values.AACUIElementCardListToast
: Toast messages will show at the screen's bottom. These messages pop up when cards are submitted, dismissed, snoozed, voted up/down, or if an error happens during these actions. Toast messages are also available for single card views.AACUIElementCardListFooterMessage
: A footer message should be displayed below the last card in the card list, if at least one is present. The message is customized using theAACCustomStringCardListFooterMessage
custom string. This value has no effect in horizontal container views and single card views.AACUIElementCardListHeader
: The header should display at the top of the card list, allowing the user to pull down from the top of the screen to refresh the card list. This value has no effect in single card views.
customHeaderDelegate
: An optional delegate that supports displaying a custom view on top of the card list. This delegate has no effect in horizontal card view and single card view.
Note: In Swift, to disable all UI elements, you need to set the enabledUiElements
to an empty array []
.
- Swift
- Objective-C
let config = AACConfiguration()
config.enabledUiElements = []
AACConfiguration *configuration = [[AACConfiguration alloc] init];
config.enabledUiElements = AACUIElementNone;
Maximum card width
(introduced in 24.1.0)
cardMaxWidth
: You can now specify a maximum width for each card within the vertical stream container or a single card view, with center alignment for the cards.
To set this, use the cardMaxWidth
property in AACConfiguration
to define the desired width, and apply this configuration when initializing the stream container.
The default value for cardMaxWidth
is 0
, which means the card will automatically adjust its width to match that of the stream container.
However, there are a few considerations for using this property:
-
It's advised not to set the
cardMaxWidth
to less than200
to avoid layout constraint warnings due to possible insufficient space for the content within the cards. -
Any negative values for this property will be reset to
0
. -
If the specified
cardMaxWidth
exceeds the width of the stream container, the property will be ignored. -
In horizontal stream containers, the
cardMaxWidth
property behaves the same as thecardWidth
property, and it must be > 0.
The following code snippet sets the maximum card width to 500.
- Swift
- Objective-C
let config = AACConfiguration()
config.cardMaxWidth = 500
let streamContainer = AACStreamContainerViewController(identifier: "1234", configuration: config)
present(streamContainer, animated: true)
AACConfiguration *config = [[AACConfiguration alloc] init];
config.cardMaxWidth = 500;
AACStreamContainerViewController *streamContainer = [[AACStreamContainerViewController alloc] initWithIdentifier:@"1234" configuration:config];
[self presentViewController:streamContainer animated:YES completion:nil];
Functionality
cardListRefreshInterval
: How frequently the card list should be automatically refreshed. Defaults to 15 seconds, and must be at least 1 second. If set to 0, the card list will not automatically refresh after the initial load.cardListRefreshInterval
only applies to HTTP polling and has no effect when WebSockets is on.
Setting the card refresh interval to a value less than 15 seconds may negatively impact device battery life and is not recommended.
actionDelegate
: An optional delegate that handles actions triggered inside the stream container, such as the tap of the custom action button in the top left of the stream container, or submit and link buttons with custom actions.runtimeVariableDelegate
: An optional runtime variable delegate that resolves runtime variable for the cards.cardEventDelegate
: An optional delegate that responds to card events in the stream container.runtimeVariableResolutionTimeout
: The maximum amount of time, in seconds, allocated to the resolution of runtime variables in yourruntimeVariableDelegate
'scardSessionDidRequestRuntimeVariables:completionHandler:
method. If you do not call the providedcompletionHandler
passed to this method before the timeout is reached, the default values for all runtime variables will be used. If you do not implement this delegate method, this property is not used. Defaults to 5 seconds.cardVotingOptions
: A bitmask representing the voting options that a user can choose from in a card's overflow menu. Voting options allow a user to flag a card as useful or not useful.
Custom strings
The configuration object also allows you to specify custom strings for features in the SDK, using the setValue:forCustomString:
method:
AACCustomStringCardListTitle
: The title for the card list in this stream container - defaults to "Cards".AACCustomStringCardSnoozeTitle
: The title for the feature allowing a user to snooze a card - defaults to "Remind me".AACCustomStringAwaitingFirstCard
: The message displayed over the card list, when the user has never received a card before - defaults to "Cards will appear here when there’s something to action."AACCustomStringAllCardsCompleted
: The message displayed when the user has received at least one card before, and there are no cards to show - defaults to "All caught up".AACCustomStringVotingUseful
: The title to display for the action a user taps when they flag a card as useful - defaults to "This is useful".AACCustomStringVotingNotUseful
: The title to display for the action a user taps when they flag a card as not useful - defaults to "This isn't useful".AACCustomStringVotingFeedbackTitle
: The title to display at the top of the screen allowing a user to provide feedback on why they didn't find a card useful - defaults to "Send feedback".AACCustomStringCardListFooterMessage
: The message to display below the last card in the card list, provided there is at least one present. Does not apply in horizontal container and single card view, and requiresenabledUiElements
to containAACUIElementCardListFooterMessage
. Defaults to an empty string.AACCustomStringNoInternetConnectionMessage
: The error message shown when the user does not have an internet connection. Defaults to "No internet connection".AACCustomStringDataLoadFailedMessage
: The error message shown when the theme or card list cannot be loaded due to an API error. Defaults to "Couldn't load data".AACCustomStringTryAgainTitle
: The title of the button allowing the user to retry the failed request for the card list or theme. Defaults to "Try again".AACCustomStringToastCardDismissedMessage
: Customized toast message for when the user dismisses a card - defaults to "Card dismissed".AACCustomStringToastCardCompletedMessage
: Customized toast message for when the user completes a card - defaults to "Card completed".AACCustomStringToastCardSnoozeMessage
: Customized toast messages for when the user snoozes a card - defaults to "Snoozed until X" where X is the time the user dismissed the card until.AACCustomStringToastCardFeedbackMessage
: Customized toast message for when the user sends feedback (votes) for a card - defaults to "Feedback received".
(the following options are introduced in 24.3.0)
AACCustomStringProcessingStateMessage
: The message displayed on the upload processing overlay during file upload. Defaults to "Sending, please wait...".AACCustomStringProcessingStateCancelButtonTitle
: The text displayed on the cancel button in the upload overlay during file upload. Defaults to "Cancel process".AACCustomStringToastFileUploadFailedMessage
: Customised toast message shown when file(s) fail to upload during card submission. Defaults to "Couldn't upload file(s)".AACCustomStringRequestCameraAccessMessage
: Customised toast message shown when requesting camera access from the user. Defaults to "Access to your camera is required to take photos. Please enable camera access in your device settings".AACCustomStringRequestCameraAccessSettingsTitle
: The title for the button in the toast message prompting for camera access, which navigates to the Settings app. Defaults toSettings
.
Thumbnail CTA title
(the following options are introduced in 24.2.7 and 24.3.2)
AACCustomStringThumbnailImageActionLinkTitle
: The call-to-action text displayed at the bottom of a thumbnail image element. Defaults to “View”.AACCustomStringThumbnailVideoActionLinkTitle
: The call-to-action text displayed at the bottom of a thumbnail video element. Defaults to "Watch".
Displaying a vertical container
To display a vertical container, you do not need to supply any additional parameters.
Creating a vertical stream container
You can now create a stream container by supplying the stream container ID and configuration object on instantiation:
- Swift
- Objective-C
let config = AACConfiguration()
config.presentationStyle = .withContextualButton
config.launchBackgroundColor = .white
config.launchIconColor = .blue
config.launchButtonColor = .blue
config.launchTextColor = .white
let streamContainer = AACStreamContainerViewController(identifier: "1234", configuration: config)
present(streamContainer, animated: true)
AACConfiguration *config = [[AACConfiguration alloc] init];
config.presentationStyle = AACConfigurationPresentationStyleWithContextualButton;
config.launchBackgroundColor = [UIColor whiteColor];
config.launchIconColor = [UIColor blueColor];
config.launchButtonColor = [UIColor blueColor];
config.launchTextColor = [UIColor whiteColor];
AACStreamContainerViewController *streamContainer = [[AACStreamContainerViewController alloc] initWithIdentifier:@"1234" configuration:config];
[self presentViewController:streamContainer animated:YES completion:nil];
Displaying a custom header
Custom header is only available for AACStreamContainerViewController
. It has no effect in the single card view and horizontal stream container.
You can provide a custom UIView to the SDK as a header that scrolls up along with the cards. This custom view is placed below the built-in header if the built-in one is enabled. It is provided by your app via the -streamContainerDidRequestCustomHeader:proposedWidth:
on AACStreamContainerHeaderDelegate
delegate. You pass an object conforming to AACStreamContainerHeaderDelegate
to AACConfiguration.customHeaderDelegate
when creating a stream container.
You are responsible for managing the layout of the header before passing it to the SDK. The -streamContainerDidRequestCustomHeader:proposedWidth:
method, when called by the SDK, provides you with a width that indicates the displaying area of the header. It's your responsibility to lay out the header based on that width and resize its height accordingly. Once it's done, pass it back to the SDK.
There are no restrictions for views in the header as long as they are supported by iOS, which can also display dynamic contents. However, you should not change its size unless the SDK calls this method.
The simplest way to create a custom header is through pure code. Here is an example.
- Swift
- Objective-C
// 1. Define the delegate.
@objcMembers class ACSSwiftCustomHeaderDelegate: NSObject, AACStreamContainerHeaderDelegate {
private override init() {
super.init()
}
// The singleton is only for demonstration. It's not compulsory.
static let sharedInstance = ACSSwiftCustomHeaderDelegate()
func tapped() {
print("You tapped the button.")
}
func streamContainerDidRequestCustomHeader(_ streamContainer: AACStreamContainerViewController, proposedWidth: CGFloat) -> UIView {
let topView = UIView(frame: CGRect(x: 0, y: 10, width: proposedWidth, height: 90))
topView.backgroundColor = .blue
let title1 = UILabel(frame: CGRect(x: 30, y: 20, width: 0, height: 0))
title1.text = "Name: John Doe"
title1.textColor = .white
title1.sizeToFit()
topView.addSubview(title1)
let title2 = UILabel(frame: CGRect(x: 30, y: 45, width: 0, height: 0))
title2.text = "Container ID: \(streamContainer.identifier)"
title2.textColor = .white
title2.sizeToFit()
topView.addSubview(title2)
let button = UIButton(frame: CGRect(x: 10, y: 110, width: proposedWidth - 20, height: 40))
button.backgroundColor = .red
button.setTitle("Tap this", for: .normal)
button.addTarget(self, action: #selector(ACSSwiftCustomHeaderDelegate.tapped), for: .touchUpInside)
let header = UIView(frame: CGRect(x: 0, y: 0, width: proposedWidth, height: 160))
header.backgroundColor = .clear
header.addSubview(topView)
header.addSubview(button)
return header
}
}
// 2. Assign the delegate to the configuration
let config = AACConfiguration()
config.customHeaderDelegate = ACSSwiftCustomHeaderDelegate.sharedInstance
// 1. Define the delegate.
@implementation ACSCustomHeaderDelegate
// The singleton is only for demonstration. It's not compulsory.
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static ACSCustomHeaderDelegate *headerDelegate;
dispatch_once(&onceToken, ^{
headerDelegate = [[ACSCustomHeaderDelegate alloc] init];
});
return headerDelegate;
}
- (void)tapped {
NSLog(@"You tapped the button.");
}
- (UIView *)streamContainerDidRequestCustomHeader:(AACStreamContainerViewController *)streamContainer proposedWidth:(CGFloat)proposedWidth {
self.streamContainer = streamContainer;
UIView *topView = [[UIView alloc] initWithFrame:CGRectMake(0, 10, proposedWidth, 90)];
topView.backgroundColor = [UIColor blueColor];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(30, 20, 0, 0)];
title.text = @"Name: John Doe";
title.textColor = [UIColor whiteColor];
[title sizeToFit];
[topView addSubview:title];
UILabel *title2 = [[UILabel alloc] initWithFrame:CGRectMake(30, 45, 0, 0)];
title2.text = [NSString stringWithFormat:@"Container ID: %@", streamContainer.identifier];
title2.textColor = [UIColor whiteColor];
[title2 sizeToFit];
[topView addSubview:title2];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 110, proposedWidth-20, 40)];
button.backgroundColor = [UIColor redColor];
[button setTitle:@"Tap this" forState:UIControlStateNormal];
[button addTarget:self action:@selector(tapped) forControlEvents:UIControlEventTouchUpInside];
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, proposedWidth, 160)];
header.backgroundColor = [UIColor clearColor];
[header addSubview:topView];
[header addSubview:button];
return header;
}
@end
// 2. Assign the delegate to the configuration
AACConfiguration *configuration = [[AACConfiguration alloc] init];
configuration.customHeaderDelegate = ACSCustomHeaderDelegate.sharedInstance;
You can also create the view using the storyboard. Then pass the instantiated view to the SDK.
Displaying a horizontal stream container
The Atomic iOS SDK also supports rendering a horizontally laid stream container in your host app. The horizontal view renders cards from left to right.
Create an instance of AACHorizontalContainerView
, which is a UIView
that is configured in the same way as a stream container. On instantiation, you supply the following parameters:
- The ID of the stream container to render in the horizontal container view.
- A configuration object, which provides initial styling and presentation information to the SDK for the horizontal container view.
The configuration options, supplied using the configuration object above, are a type of AACHorizontalContainerConfiguration
, which is the subclass of AACConfiguration
. You must specify its cardWidth
(that is > 0) before passing it, otherwise an exception will be raised.
- Swift
- Objective-C
let config = AACHorizontalContainerConfiguration()
config.cardWidth = 400
let horizontalView = AACHorizontalContainerView(frame: self.view.bounds, containerIdentifier: "1234", configuration: config)
self.view.addSubview(horizontalView)
AACHorizontalContainerConfiguration *config = [[AACHorizontalContainerConfiguration alloc] init];
config.cardWidth = 400;
AACHorizontalContainerView *horizontalContainerView = [[AACHorizontalContainerView alloc] initWithFrame:self.view.bounds containerIdentifier:@"1234" configuration:config];
[self.view addSubview:horizontalContainerView];
Pull to refresh functionality is disabled.
You can set a delegate (conforming to AACHorizontalContainerViewDelegate
) on the horizontal container view to be notified when the view changes height, either because a card is submitted, dismissed or snoozed, or because a new card arrived into the container view. This allows you to animate changes to the intrinsicContentSize
of the horizontal container view.
- Swift
- Objective-C
func horizontalContainerView(_ containerView: AACHorizontalContainerView, willChange newSize: CGSize) {
// Perform animation here.
}
- (void)horizontalContainerView:(AACHorizontalContainerView *)containerView willChangeSize:(CGSize)newSize {
// Perform animation here.
}
Configuration options for a horizontal stream container
Other than properties inherited from AACConfiguration
, the configuration object allows you to configure a horizontal container view via the following properties:
-
cardWidth
: The width of each card in this horizontal container view. It must be > 0. Cards in horizontal container view all have the same width that must be assigned explicitly. The width of the view itself can not be used to determine the card width anymore. -
emptyStyle
: The style of the empty state (when there are no cards) for the horizontal container. Possible values are:AACHorizontalContainerConfigurationEmptyStyleStandard
: Default value. The horizontal container displays a no-card user interface.AACHorizontalContainerConfigurationEmptyStyleShrink
: The horizontal container shrinks itself.
-
headerAlignment
: The alignment of the title in the horizontal header. Possible values are:AACHorizontalContainerConfigurationHeaderAlignmentCenter
: Default value. The title is aligned in the middle of the header.AACHorizontalContainerConfigurationHeaderAlignmentLeft
: The title is aligned to the left of the header.
-
scrollMode
: The scrolling behaviors in the horizontal container. Possible values are:AACHorizontalContainerConfigurationScrollModeSnap
: Default value. The container scrolls over one card at a time. The card is placed in the middle of the viewport when the scrolling terminates.AACHorizontalContainerConfigurationScrollModeFree
: The container scrolls freely.
-
lastCardAlignment
: The alignment of the card when it is the only one in the horizontal container. Possible values are:AACHorizontalContainerConfigurationLastCardAlignmentLeft
: Default value. The last card is aligned to the left of the container.AACHorizontalContainerConfigurationLastCardAlignmentCenter
: The last card is aligned in the middle of the container.
AACUIElementCardListFooterMessage
andAACCustomStringCardListFooterMessage
have no effects in horizontal container view.presentationStyle
is alwaysAACConfigurationPresentationStyleWithoutButton
in horizontal container view.
Displaying a single card
The Atomic iOS SDK also supports rendering a single card in your host app.
To create an instance of AACSingleCardView
, which is a UIView
that is configured in the same way as a stream container, you supply the following parameters on instantiation:
- The ID of the stream container to render in the single card view. The single card view renders only the first card that appears in that stream container;
- A configuration object, which provides initial styling and presentation information to the SDK for the single card view.
The configuration options, supplied using the configuration object above, are the same as those for a stream container. The only configuration option that does not apply is presentationStyle
, as the single card view does not display a header, and therefore does not show a button in its top left.
- Swift
- Objective-C
let config = AACConfiguration()
config.launchBackgroundColor = .white
config.launchIconColor = .blue
config.launchButtonColor = .blue
config.launchTextColor = .white
let cardView = AACSingleCardView(frame: view.bounds, containerIdentifier: "1234", configuration: config)
view.addSubview(cardView)
AACConfiguration *config = [[AACConfiguration alloc] init];
config.launchBackgroundColor = [UIColor whiteColor];
config.launchIconColor = [UIColor blueColor];
config.launchButtonColor = [UIColor blueColor];
config.launchTextColor = [UIColor whiteColor];
AACSingleCardView *singleCardView = [[AACSingleCardView alloc] initWithFrame:self.view.frame containerIdentifier:@"1234" configuration:config];
[self.view addSubview:singleCardView];
Pull to refresh functionality is disabled for single card view.
You can set a delegate (conforming to AACSingleCardViewDelegate
) on the single card view to be notified when the view changes height, either because a card is submitted, dismissed or snoozed, or because a new card arrived into the single card view (if polling is enabled or is using WebSockets). This allows you to animate changes to the intrinsicContentSize
of the single card view.
- Swift
- Objective-C
func singleCardView(_ cardView: AACSingleCardView, willChange newSize: CGSize) {
// Perform animation here.
}
- (void)singleCardView:(AACSingleCardView *)cardView willChangeSize:(CGSize)newSize {
// Perform animation here.
}
Configuration options for the single card view
There is a subclass of AACConfiguration
- AACSingleCardConfiguration
- which can be used to enable features that only apply to single card view.
- Swift
- Objective-C
let config = AACSingleCardConfiguration()
config.automaticallyLoadNextCard = true
AACSingleCardConfiguration *config = [[AACSingleCardConfiguration alloc] init];
config.automaticallyLoadNextCard = YES;
Available features are:
automaticallyLoadNextCard
: When enabled, will automatically display the next card in the single card view if there is one, using a locally cached card list. Defaults toNO
.
Toasts for single card view
You can display toast messages for a single card view, just like with other container types. Use enabledUiElements
in AACSingleCardConfiguration
to choose if you want to show these toast messages.
enabledUiElements
is a bitmask indicating which UI elements are active in the stream container. By default, toast messages are displayed. For a single card view, the possible values are:
AACUIElementNone
: Don't display any UI elements. This shouldn't be mixed with other values.AACUIElementCardListToast
: Toast messages will show at the screen's bottom. These messages pop up when cards are submitted, dismissed, snoozed, voted up/down, or if an error happens during these actions.
- Swift
- Objective-C
let config = AACSingleCardConfiguration()
config.enabledUiElements = .cardListToast
let singleCardView = AACSingleCardView(frame: self.view.frame, containerIdentifier: "1234", configuration: config)
self.view.addSubview(singleCardView)
AACSingleCardConfiguration *configuration = [[AACSingleCardConfiguration alloc] init];
config.enabledUiElements = AACUIElementCardListToast;
AACSingleCardView *singleCardView = [[AACSingleCardView alloc] initWithFrame:self.view.frame containerIdentifier:@"1234" configuration:config];
[self.view addSubview:singleCardView];
To disable the toast, simply set enabledUiElements
to AACUIElementNone
, or []
in Swift.
- Swift
- Objective-C
let config = AACSingleCardConfiguration()
config.enabledUiElements = []
AACSingleCardConfiguration *configuration = [[AACSingleCardConfiguration alloc] init];
config.enabledUiElements = AACUIElementNone;
Closing a stream container
Vertical stream containers, horizontal stream containers and single card views are dismissed of like other views or controllers. There is no specific method that needs be to called.
Customizing the first time loading behavior
When a stream container with a given ID is launched for the first time on a user's device, the SDK loads the theme and caches it for future use. On subsequent launches of the same stream container, the cached theme is used and the theme is updated in the background, for the next launch. Note that this first-time loading screen is not presented in single card view and horizontal container view - if those views fail to load, they collapse to a height of 0.
The SDK supports some basic properties to style the first-time load screen, which displays a loading spinner in the center of the container. If the theme or card list fails to load for the first time, an error message is displayed with a 'Try again' button. One of two error messages is possible - 'Couldn't load data' or 'No internet connection'.
First-time loading screen colors are customized using the following properties on AACStreamContainer
:
launchBackgroundColor
: The background color to use for the launch screen, seen on the first load. Defaults to white.launchTextColor
: The text color to use for the view displayed when the SDK is first presented. Defaults to black at 50% opacity.launchLoadingIndicatorColor
: The color to use for the loading spinner on the first-time loading screen. Defaults to black.launchButtonColor
: The color of the buttons that allow the user to retry the first load if the request fails. Defaults to black.
You can also customize the text for the first load screen error messages and the 'Try again' button, using the setValue:forCustomString:
method of AACConfiguration
.
Note: These customized error messages also apply to the card list screen.
AACCustomStringNoInternetConnectionMessage
: The error message shown when the user does not have an internet connection. Defaults to "No internet connection".AACCustomStringDataLoadFailedMessage
: The error message shown when the theme or card list cannot be loaded due to an API error. Defaults to "Couldn't load data".AACCustomStringTryAgainTitle
: The title of the button allowing the user to retry the failed request for the card list or theme. Defaults to "Try again".
API-driven card containers
(introduced in 23.4.0)
In version 23.4.0, we've introduced a new feature for observing stream containers with pure SDK API, even when that container's UI is not loaded into memory.
When you opt to observe a stream container, it is updated by default immediately after any changes in the published cards. Should the WebSocket be unavailable, the cards are updated at regular intervals, which you can specify. Upon any change in cards, the handler block is executed with the updated card list or nil
if the cards couldn't be fetched. Note that the specified time interval for updates cannot be less than 1 second.
The following code snippet shows the simplest use case scenario:
- Swift
- Objective-C
// Observe the stream container
AACSession.observeStreamContainer(identifier: "1", configuration: nil) { cards in
if let cards = cards {
print("There are \(cards.count) cards in the container.")
}
}
// Observe the stream container
[AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {
if(cards != nil) {
NSLog(@"There are %ld cards in the container", cards.count);
}
}];
This method returns a token that you can use to stop the observation, see Stopping the observation for more details.
In the callback, the cards
parameter is an array of AACCardInstance
objects. Each AACCardInstance
contains a variety of other class types that represent the card elements defined in Workbench. Detailed documentation for the classes involved in constructing an AACCardInstance object is not included in this guide. However, you can refer to the examples provided below, which demonstrate several typical use cases.
Configuration options
The method accepts an optional configuration parameter. The configuration object, AACStreamContainerObserverConfiguration
, allows you to customize the observer's behavior with the following properties, which are all optional:
- cardListRefreshInterval: defines how frequently the system checks for updates when the WebSocket service is unavailable. The default interval is 15 seconds, but it must be at least 1 second. If a value less than 1 second is specified, it defaults to 1 second.
- filters: filters applied when fetching cards for the stream container. It defaults to
nil
, meaning no filters are applied. See Filtering cards for more details of stream filtering. - runtimeVariableDelegate: a delegate for resolving runtime variables for the cards. Defaults to
nil
. See Runtime variables for more details of runtime variables. - runtimeVariableResolutionTimeout: the maximum time allocated for resolving variables in the delegate. If the tasks within the delegate method exceed this timeout, or if the completionHandler is not called within this timeframe, default values will be used for all runtime variables. The default timeout is 5 seconds and it cannot be negative.
- sendRuntimeVariableAnalytics: whether the
runtime-vars-updated
analytics event, which includes the resolved values of each runtime variable, should be sent upon resolution. The default setting is NO. If you set this flag to YES, ensure that the resolved values of your runtime variables do not contain sensitive information that shouldn't appear in analytics. See SDK analytics for more details on runtime variable analytics.
Stopping the observation
The observer ceases to function when you call +[AACSession logout:]
. Alternatively, you can stop the observation using the token returned from the observation call mentioned above:
- Swift
- Objective-C
let token = AACSession.observeStreamContainer(identifier: "1", configuration: nil) { _ in }
AACSession.stopObservingStreamContainer(token: token)
id token = [AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {}];
[AACSession stopObservingStreamContainer:token];
Examples
Accessing card metadata
Card metadata encompasses data that, while not part of the card's content, are still critical pieces of information. Key metadata elements include:
- Card instance ID: This is the unique identifier assigned to a card upon its publication.
- Card priority: Defined in the Workbench, this determines the card's position within the feed. The priority will be an integer between 1 & 10, a priority of 1 indicates the highest priority, placing the card at the top of the feed.
- Action flags: Also defined in the Workbench, these flags dictate the visibility of options such as dismissing, snoozing, and voting menus for the card.
The code snippet below shows how to access these metadata elements for a card instance.
- Swift
- Objective-C
AACSession.observeStreamContainer(identifier: "1", configuration: nil) { cards in
if let card = cards?.first {
print("The card instance ID is \(card.detail.cardId)")
print("The priority of the card is \(card.metadata.priority)")
print("The card has dismiss overflow menu \(card.actionFlags.dismissOverflowDisabled ? "disabled" :"enabled").")
}
}
[AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {
AACCardInstance *card = [cards firstObject];
if(card != nil) {
NSLog(@"The card instance ID is %@.", card.detail.cardId);
NSLog(@"The priority of the card is %@.", card.metadata.priority);
NSLog(@"The card has dismiss overflow menu %@.", card.actionFlags.dismissOverflowDisabled ? @"disabled" : @"enabled");
}
}];
Traversing card elements
Elements refer to the contents that are defined in the Workbench on the Content
page of a card. All elements that are at the same hierarchical level within a card are encapsulated in an AACCardLayout
object. Elements at the top level are accessible through the defaultLayout
property of the AACCardInstance
. The nodes
property within defaultLayout
contains them.
The code snippet below shows how to traverse through all the elements in a card and extract the text representing the card's category.
- Swift
- Objective-C
AACSession.observeStreamContainer(identifier: "1", configuration: nil) { cards in
if let card = cards?.first {
for element in card.defaultLayout.nodes {
if let category = element as? AACCardNodeCategory {
print("The category of card \(card.detail.cardId) is \(category.text).")
}
}
}
}
[AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {
AACCardInstance *card = [cards firstObject];
if(card != nil) {
for (AACCardNode *node in card.defaultLayout.nodes) {
if([node isKindOfClass:AACCardNodeCategory.class]) {
AACCardNodeCategory *category = (AACCardNodeCategory *)node;
NSLog(@"The category of card %@ is %@.", card.detail.cardId, category.text);
}
}
}
}];
Note: There are several classes derived from AACCardNode
that correspond to the various elements you can create in Workbench. Detailed documentation for all these classes is not provided here. For information on each class, please refer to the documentation in the header files.
Accessing subviews
Subviews are layouts that differ from the defaultLayout
and each has a unique subview ID. See Link to subview on how to get the subview ID.
The following code snippet shows how to retrieve a subview layout using a specific subview ID.
- Swift
- Objective-C
AACSession.observeStreamContainer(identifier: "1", configuration: nil) { cards in
if let card = cards?.first, let subview = card.layout(withName: "subviewID") {
print("Accessing subview \(subview.title).")
}
}
[AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {
AACCardInstance *card = [cards firstObject];
AACCardLayout *subview = [card layoutWithName:@"subviewID"];
if(subview != nil) {
NSLog(@"Accessing subview %@", subview.title);
}
}];
API-driven card actions
(introduced in 23.4.0)
In version 23.4.0, we've introduced a new feature that execute card actions through pure SDK API. The currently supported actions are: dismiss, submit, and snooze. To execute these card actions, follow these two steps:
-
Create a card action object: Use the corresponding initialization methods of the
AACSessionCardAction
class. You'll need at least a container ID and a card instance ID for this. The container ID should be from the container where the card is published, and the card instance ID can be obtained from the API-driven stream container. See API-driven card containers for more details. -
Execute the action: Call the method
+[AACSession onCardAction:completionHandler:]
to perform the card action.
Dismissing a card
The following code snippet shows how to dismiss a card.
- Swift
- Objective-C
let action = AACSessionCardAction(dismissActionWithContainerId: "1", cardId: "card-id")
AACSession.onCardAction(action) { error in
if let error = error {
print("An error happened when dismissing the card.")
} else {
print("Card dismissed.")
}
}
AACSessionCardAction *action = [[AACSessionCardAction alloc] initDismissActionWithContainerId:@"1" cardId:@"card-id"];
[AACSession onCardAction:action completionHandler:^(NSError *error) {
if(error != nil) {
NSLog(@"An error happened when dismissing the card.");
} else {
NSLog(@"Card dismissed.");
}
}];
Submitting a Card
You have the option to submit certain values along with a card. These values are optional and should be encapsulated in an NSDictionary
object, using NSString
keys and values that are either strings, numbers, or booleans.
While editing cards in Workbench, you can add input components onto cards and apply various validation rules, such as Required
, Minimum length
, or Maximum length
. The input elements can be used to submit user-input values, where the validation rules are applied when submitting cards through UIs of stream containers.
However, for this non-UI version, support for input components is not available yet. There is currently no mechanism to store values in these input components through this API, and the specified validation rules won't be enforced when submitting cards.
Submitting a card in SDK 24.2.0
As of version 24.2.0, Atomic cards include button names when they are submitted. The button name will be added to analytics to enable referencing the triggering button in an Action Flow. Therefore, you need to provide a button name when submitting cards.
Getting the button name
The button name of a submit button can be acquired when receiving cards through API-driven cards. The following code snippet shows how to obtain button name(s) from the top-level of the first card.
- Swift
- Objective-C
AACSession.observeStreamContainer(identifier: "1", configuration: nil) { cards in
if let card = cards?.first {
// Pass `nil` for top-level buttons, or a subview ID for subview buttons.
let buttons = card.buttons(withSubviewId: nil)
for button in buttons {
if let submitButton = button as? AACCardNodeSubmitButton, let buttonName = submitButton.buttonName {
print("The button name of submit button \(submitButton.text) is \(buttonName)")
}
}
}
}
[AACSession observeStreamContainerWithIdentifier:@"1" configuration:nil completionHandler:^(NSArray<AACCardInstance *> *cards) {
AACCardInstance *card = [cards firstObject];
if(card != nil) {
// Pass `nil` for top-level buttons, or a subview ID for subview buttons.
NSArray *buttons = [card buttonsWithSubviewId:nil];
for(AACCardBaseButton *button in buttons) {
if([button isKindOfClass:AACCardNodeSubmitButton.class]) {
AACCardNodeSubmitButton *submitButton = (AACCardNodeSubmitButton *)button;
NSLog(@"The button name of submit button %@ is %@", submitButton.text, submitButton.buttonName);
}
}
}
}];
Submitting the card
With button name obtained, you can now submit the card. The following code snippet shows how to submit a card with specific values.
- Swift
- Objective-C
let action = AACSessionCardAction(submitActionWithContainerId: "1", cardId: "card-id", submitButtonName: "button-name", submitValues: [
"submit-key": "submitted values",
"submit-key2": 999
])
AACSession.onCardAction(action) { error in
if let error = error {
print("An error happened when submitting the card.")
} else {
print("Card submitted.")
}
}
AACSessionCardAction *action = [[AACSessionCardAction alloc] initSubmitActionWithContainerId:@"1"
cardId:@"card-id"
submitButtonName:@"button-name"
submitValues:@{
@"submit-key": @"submitted-values",
@"submit-key2": @(999)
}];
[AACSession onCardAction:action completionHandler:^(NSError *error) {
if(error != nil) {
NSLog(@"An error happened when submitting the card.");
} else {
NSLog(@"Card submitted.");
}
}];
Submitting a card prior SDK version 24.2.0
Before version 24.2.0, you don't need a button name when submitting cards. The following code snippet shows how to submit a card with specific values.
- Swift
- Objective-C
let action = AACSessionCardAction(submitActionWithContainerId: "1", cardId: "card-id", submitValues: [
"submit-key": "submitted values",
"submit-key2": 999
])
AACSession.onCardAction(action) { error in
if let error = error {
print("An error happened when submitting the card.")
} else {
print("Card submitted.")
}
}
AACSessionCardAction *action = [[AACSessionCardAction alloc] initSubmitActionWithContainerId:@"1" cardId:@"card-id" submitValues:@{
@"submit-key": @"submitted-values",
@"submit-key2": @(999)
}];
[AACSession onCardAction:action completionHandler:^(NSError *error) {
if(error != nil) {
NSLog(@"An error happened when submitting the card.");
} else {
NSLog(@"Card submitted.");
}
}];
Snoozing a Card
When snoozing a card, you must specify a non-negative interval in seconds. Otherwise an error will be returned.
The following code snippet shows how to snooze a card for a duration of 1 minute.
- Swift
- Objective-C
let action = AACSessionCardAction(snoozeActionWithContainerId: "1", cardId: "card-id", snoozeInterval: 60)
AACSession.onCardAction(action) { error in
if let error = error {
print("An error happened when snoozing the card.")
} else {
print("Card snoozed.")
}
}
AACSessionCardAction *action = [[AACSessionCardAction alloc] initSnoozeActionWithContainerId:@"1" cardId:@"card-id" snoozeInterval: 60];
[AACSession onCardAction:action completionHandler:^(NSError *error) {
if(error != nil) {
NSLog(@"An error happened when snoozing the card.");
} else {
NSLog(@"Card snoozed.");
}
}];
Error handling
If the action executes successfully, the error object in the completion handler will be nil
. If there's an error, it will be returned with the error domain set as AACSessionCardActionsErrorDomain
.
To identify the specific cause of the error, refer to the AACSessionCardActionsErrorCode
enumeration for the relevant error code. Additionally, the NSUnderlyingErrorKey
will be populated in the userInfo dictionary of the error, providing further details.
Dark mode
Stream containers in the Atomic iOS SDK support dark mode. You configure an (optional) dark theme for your stream container in the Atomic Workbench.
The interface style determines which theme is rendered:
AACConfigurationInterfaceStyleAutomatic
: If the user's device is currently set to light mode, the stream container will use the light (default) theme. If the user's device is currently set to dark mode, the stream container will use the dark theme (or fallback to the light theme if this has not been configured). On iOS versions less than 13, this setting is equivalent toAACConfigurationInterfaceStyleLight
.AACConfigurationInterfaceStyleLight
: The stream container will always render in light mode, regardless of the device setting.AACConfigurationInterfaceStyleDark
: The stream container will always render in dark mode, regardless of the device setting.
To change the interface style, set the corresponding value for the interfaceStyle
property on the AACConfiguration
object when creating the stream container.
If this property is left unset, it will default to AACConfigurationInterfaceStyleAutomatic
.
Filtering cards
Stream containers (vertical or horizontal), single card views and container card count observers can have one or more filters applied. These filters determine which cards are displayed, or how many cards are counted.
A stream container filter consists of two parts: a filter value and an operator.
Filter values
The filter value is used to filter cards in a stream container. The following list outlines all card attributes that can be used as a filter value.
Card attribute | Description | Value type |
---|---|---|
Priority | Card priority defined in Workbench, Card -> Delivery | NSInteger |
Card template created date | The date time when a card template is created | NSDate |
Card template ID | The template ID of a card, see below for how to get it | NSString |
Card template name | The template name of a card | NSString |
Custom variable | The variables defined for a card in Workbench, Card -> Variables | Multiple |
Use corresponding static methods of AACCardFilterValue
to create a filter value.
Examples
Card priority
The following code snippet shows how to create a filter value that represents a card priority 6.
- Swift
- Objective-C
let filterValue = AACCardFilterValue.byPriority(6)
AACCardFilterValue *filterValue = [AACCardFilterValue byPriority:6];
Custom variable
The following code snippet shows how to create a filter value that represents a boolean custom variable isSpecial
.
- Swift
- Objective-C
let filterValue = AACCardFilterValue.byVariableName("isSpecial", boolean: true)
AACCardFilterValue *filterValue = [AACCardFilterValue byVariableName:@"isSpecial" boolean:YES];
Note: It's important to specify the right value type when referencing custom variables for filter value. There are five types of variables in the Workbench, currently four are supported: String, Number, Date and Boolean. You can use methods like +[AACCardFilterValue byVariableName:string:]
On the card editing page, click on the ID part of the overflow menu at the upper-right corner.
Filter operators
The operator is the operational logic applied to a filter value (some operators require 2 or more values).
The following table outlines available operators.
Operator | Description | Supported types |
---|---|---|
equalTo | Equal to the filter value | NSInteger, NSDate, NSString, BOOL |
notEqualTo | Not equal to the filter value | NSInteger, NSDate, NSString, BOOL |
greaterThan | Greater than the filter value | NSInteger, NSDate |
greaterThanOrEqualTo | Greater than or equal to the filter value | NSInteger, NSDate |
lessThan | Less than the filter value | NSInteger, NSDate |
lessThanOrEqualTo | Less than or equal to the filter value | NSInteger, NSDate |
in | In one of the filter values | NSInteger, NSDate, NSString |
notIn | Not in one of the filter values | NSInteger, NSDate, NSString |
between | In the range of start and end, inclusive | NSInteger, NSDate |
After creating a filter value, use the corresponding static method on the AACCardListFilter class to combine it with an operator.
Examples
Card priority range
The following code snippet shows how to create a filter that filters card with priority between 2 and 6 inclusive.
- Swift
- Objective-C
let filterValue1 = AACCardFilterValue.byPriority(2)
let filterValue2 = AACCardFilterValue.byPriority(6)
let filter = AACCardListFilter.filter(byCardsBetweenStartValue: filterValue1, endValue: filterValue2)
AACCardFilterValue *filterValue1 = [AACCardFilterValue byPriority:2];
AACCardFilterValue *filterValue2 = [AACCardFilterValue byPriority:6];
AACCardFilter *filter = [AACCardListFilter filterByCardsBetweenStartValue:filterValue1 endValue:filterValue2];
Each operator supports different type of values. For example, operator lessThan
only support NSInteger and NSDate. So passing NSString values to that operator will raise an exception.
Applying filters to a stream container or a card count observer
There are three steps to filter cards in a stream container or for a card count observer:
-
Create one or more
AACCardFilterValue
objects. -
Combine filter values with filter operators to form a
AACCardFilter
. -
Apply filter(s).
3.1. For stream containers, call
to apply multiple filters, otherwise call-[AACStreamContainer applyFilters:]
. To delete all existing filters, pass either-[AACStreamContainer applyFilter:]
nil
or an empty list[]
to theapplyFilters
method, ornil
to theapplyFilter
method.3.2. For card count observers, pass an array of filters to parameter
filters
when creating an observer using ;+[AACSession:observeCardCountForStreamContainerWithIdentifier:]
Examples
Card priority 5 and above
The following code snippet shows how to only display cards with priority > 5 in a stream container.
- Swift
- Objective-C
let filterValue = AACCardFilterValue.byPriority(5)
let filter = AACCardListFilter.filter(byCardsGreaterThan: filterValue)
...
// Acquire the stream container object and call the filtering method.
streamContainer.apply(filter)
AACCardFilterValue *filterValue = [AACCardFilterValue byPriority:5];
AACCardFilter *filter = [AACCardListFilter filterByCardsGreaterThan:filterValue];
...
// Acquire the stream container object and call the filtering method.
[streamContainer applyFilter:filter];
Earlier than a set date
The following code snippet shows how to only display cards created earlier than 9/Jan/2023 inclusive in a stream container.
- Swift
- Objective-C
let filterValue = AACCardFilterValue.byCreatedDate(Date(timeIntervalSince1970: 1673226000))
let filter = AACCardListFilter.filter(byCardsLessThanOrEqualTo: filterValue)
...
streamContainer.apply(filter)
AACCardFilterValue *filterValue = [AACCardFilterValue byCreatedDate:[NSDate dateWithTimeIntervalSince1970:1673226000]];
AACCardFilter *filter = [AACCardListFilter filterByCardsLessThanOrEqualTo:filterValue];
...
[streamContainer applyFilter:filter];
Card template names
The following code snippet shows how to only display cards with the template names 'card 1', 'card 2', or 'card 3' in a stream container.
- Swift
- Objective-C
let filterValue1 = AACCardFilterValue.byCardTemplateName("card1")
let filterValue2 = AACCardFilterValue.byCardTemplateName("card2")
let filterValue3 = AACCardFilterValue.byCardTemplateName("card3")
let filter = AACCardListFilter.filter(byCardsIn: [filterValue1, filterValue2, filterValue3])
...
streamContainer.apply(filter)
AACCardFilterValue *filterValue1 = [AACCardFilterValue byCardTemplateName:@"card1"];
AACCardFilterValue *filterValue2 = [AACCardFilterValue byCardTemplateName:@"card2"];
AACCardFilterValue *filterValue3 = [AACCardFilterValue byCardTemplateName:@"card3"];
AACCardFilter *filter = [AACCardListFilter filterByCardsIn:@[filterValue1, filterValue2, filterValue3]];
...
[streamContainer applyFilter:filter];
Combination of filter values
The following code snippet shows how to only display cards with priority != 6 and custom variable isSpecial
== true in a stream container.
Note: isSpecial
is a Boolean custom variable defined in Workbench.
- Swift
- Objective-C
let filterValue1 = AACCardFilterValue.byPriority(6)
let filter1 = AACCardListFilter.filter(byCardsNotEqualTo: filterValue1)
let filterValue2 = AACCardFilterValue.byVariableName("isSpecial", boolean: true)
let filter2 = AACCardListFilter.filter(byCardsEqualTo: filterValue2)
...
streamContainer.apply([filter1, filter2])
AACCardFilterValue *filterValue1 = [AACCardFilterValue byPriority:6];
AACCardFilter *filter1 = [AACCardListFilter filterByCardsNotEqualTo:filterValue1];
AACCardFilterValue *filterValue2 = [AACCardFilterValue byVariableName:@"isSpecial" boolean:YES];
AACCardFilter *filter2 = [AACCardListFilter filterByCardsEqualTo:filterValue2];
...
[streamContainer applyFilters:@[filter1, filter2]];
Legacy filter
The legacy filter is still supported - AACCardListFilter.filter(byCardInstanceId:)
. This filter requests that the stream container or single card view show only a card matching the specified card instance ID, if it exists. An instance of this filter can be created using the corresponding static method on the AACCardListFilter
class.
The card instance ID can be found in the push notification payload, allowing you to apply the filter in response to a push notification being tapped.
- Swift
- Objective-C
let filter = AACCardListFilter.filter(byCardInstanceId: "ABCD-1234")
streamContainer.apply(filter)
AACCardFilter *filter = [AACCardListFilter filterByCardInstanceId:@"ABCD-1234"];
[streamContainer applyFilter:filter];
Removing all filters
- For stream containers, specify
nil
or an empty list[]
to the method, or-[AACStreamContainer applyFilters:]
nil
to the method.-[AACStreamContainer applyFilter:]
- For card count observers, create a new card counter observer with
nil
to thefilters
parameter.
Supporting custom actions on submit and link buttons
In the Atomic Workbench, you can create a submit or link button with a custom action payload.
- When such a link button is tapped, the
streamContainerDidTapLinkButton:withAction:
method is called on your action delegate. - When such a submit button is tapped, and after the card is successfully submitted, the
streamContainerDidTapSubmitButton:withAction:
method is called on your action delegate.
The second parameter to each of these methods is an action object, containing the payload that was defined in the Workbench for that button. You can use this payload to determine the action to take, within your app, when the submit or link button is tapped.
The action object also contains the card instance ID and stream container ID where the custom action was triggered.
- Swift
- Objective-C
// 1. Assign the action delegate
let config = AACConfiguration()
config.actionDelegate = self
// 2. Implement the callbacks
func streamContainerDidTapLinkButton(_ streamContainer: AACStreamContainerViewController, with action: AACCardCustomAction) {
if let screenName = action.actionPayload["screen"] as? String, screenName == "home-screen" {
// Perform an action
}
}
func streamContainerDidTapSubmitButton(_ streamContainer: AACStreamContainerViewController, with action: AACCardCustomAction) {
if let outcome = action.actionPayload["outcome"] as? String, outcome == "success" {
// Perform an action
}
}
// 1. Assign the action delegate
AACConfiguration *config = [[AACConfiguration alloc] init];
config.actionDelegate = self;
// 2. Implement the callbacks
- (void)streamContainerDidTapLinkButton:(AACStreamContainerViewController*)streamContainer withAction:(AACCardCustomAction*)action {
if([action.actionPayload[@"screen"] isEqualToString:@"home-screen"]) {
[self navigateToHomeScreen];
}
}
- (void)streamContainerDidTapSubmitButton:(AACStreamContainerViewController*)streamContainer withAction:(AACCardCustomAction*)action {
if([action.actionPayload[@"outcome"] isEqualToString:@"success"]) {
[self navigateToSuccessScreen];
}
}
Customizing toast messages for card events
You can customize any of the toast messages used when dismissing, completing, snoozing and placing feedback on a card. This is configurable for each stream container. You simply supply a string for each custom message. If you do not supply a string, the defaults will be used.
See the Custom strings section to read more about other custom strings that can be specified in the configuration object using the setValue:forCustomString:
method.
Options are:
AACCustomStringToastCardDismissedMessage
: Customized toast message for when the user dismisses a card - defaults to "Card dismissed".AACCustomStringToastCardCompletedMessage
: Customized toast message for when the user completes a card - defaults to "Card completed".AACCustomStringToastCardSnoozeMessage
: Customized toast messages for when the user snoozes a card - defaults to "Snoozed until X" where X is the time the user dismissed the card until.AACCustomStringToastCardFeedbackMessage
: Customized toast message for when the user sends feedback (votes) for a card - defaults to "Feedback received".
(the following options are introduced in 24.3.0)
AACCustomStringToastFileUploadFailedMessage
: Customised toast message shown when file(s) fail to upload during card submission. Defaults to "Couldn't upload file(s)".AACCustomStringRequestCameraAccessMessage
: Customised toast message shown when requesting camera access from the user. Defaults to "Access to your camera is required to take photos. Please enable camera access in your device settings".AACCustomStringRequestCameraAccessSettingsTitle
: The title for the button in the toast message prompting for camera access, which navigates to the Settings app. Defaults toSettings
.
Card snoozing
The Atomic SDKs provide the ability to snooze a card from a stream container or single card view. Snooze functionality is exposed through the card’s action buttons, overflow menu and the quick actions menu (exposed by swiping a card to the left, on iOS and Android).
Tapping on the snooze option from either location brings up the snooze date and time selection screen. The user selects a date and time in the future until which the card will be snoozed. Snoozing a card will result in the card disappearing from the user’s card list or single card view, and reappearing again at the selected date and time. A user can snooze a card more than once.
When a card comes out of a snoozed state, if the card has an associated push notification, and the user has push notifications enabled, the user will see another notification, where the title is prefixed with Snoozed:
.
You can customize the title of the snooze functionality, as displayed in a card’s overflow menu and in the title of the card snooze screen. The default title, if none is specified, is Remind me
.
On the AACConfiguration
object, call the setValue:forCustomString:
method to customize the title for the card snooze functionality:
- Swift
- Objective-C
config.setValue("Snooze", for: .cardSnoozeTitle)
[configuration setValue:@"Snooze" forCustomString:AACCustomStringCardSnoozeTitle];
Prevent Snoozing Beyond Expiry Date
(introduced in 24.3.0)
Selecting a date & time combination beyond a card's expiry date is now prevented when snoozing a card using the SDK's built-in interface.
There are three ways to snooze a card in the Atomic SDK:
- Overflow Menu: Select the overflow menu item (default "Remind me"), then choose a date & time in the built-in selector.
- Snooze Button: Tap a snooze button on the card and select a date & time via the built-in selector.
- Pre-set Snooze Button: Tap a snooze button with a pre-set snooze period, which snoozes the card immediately without showing the selector.
If an expiry date is set on the card, you cannot select dates beyond this expiry in scenarios 1 and 2. Scenario 3 remains unaffected, as the snooze period is explicitly pre-configured in Workbench.