Enable product view signals in an iOS app
The product view signal captures when a user views a product detail screen and tells marketers which products each contact is interested in, enabling segmentation by product and product category in Connect. The product can be physical, digital or a service like a tour or insurance package.
Availability: Premium and Ultimate
Languages: Swift and Objective-C
Implementation considerations
When to fire the signal
Fire the product view signal in viewDidLoad() once the product data is available. Use productView instead of pageView on product detail screens - productView carries the richer schema and is the semantically correct signal type for this context.
If your app shows multiple products simultaneously - for example, a shortlist of hotel rooms - fire one signal per product.
Relationship to the product configuration signal
The product view and product configuration signals work together:
productView- captures the product itself when the screen loadsproductConfiguration- captures user interactions on the product screen, such as selecting a color or size
Contact mapping
Include the audience field to attribute this signal to a contact in Connect. See How behavior signals update Connect data for identifier formats and contact resolution details.
Configuration
Before adding behavior signals, integrate the Connect SDK into your app. A recent beta (debug) version of the SDK is required (2.1.17 or later). See the guide for your development language: Swift or Objective-C.
Method
func logSignal(_ data: [String: Any]?) -> BoolSends the signal to the Acoustic Connect endpoint. Initialize the Connect SDK before calling this method using ConnectSDK.shared.enable(with:).
Pass all fields as a flat [String: Any] dictionary. The SDK handles the signal structure automatically - you do not need to construct a nested object.
- (BOOL)logSignal:(NSDictionary *)values;Sends the signal to the Acoustic Connect endpoint. Initialize the Connect SDK before calling this method using [[ConnectApplicationHelper sharedInstance] enableFramework:withPostMessageUrl:].
Pass all fields as an NSDictionary. The SDK handles the signal structure automatically - you do not need to construct a nested object.
Signal fields
Required
| Field | Type | Description |
|---|---|---|
productId | String | Unique product identifier (may match SKU) |
productName | String | Name of the product |
signalType | String | Signal type. Value: "productView". |
Optional
| Field | Type | Description |
|---|---|---|
audience | [String: Any] | Key-value pairs for contact mapping. Keys must match contact attribute names exactly as they appear in Connect, including capitalization and spacing. |
availability | String | Availability status (for example, "In Stock", "Out of Stock", "Back Order") |
brandName | String | Brand name of the product |
category | String | Signal category. Value: "Behavior". |
currency | String | ISO 4217 currency code (for example, "USD", "EUR"). Defaults to the currency set for your Connect subscription. |
description | String | A description of the signal |
discount | Number | Discount amount or difference between original and current price |
effect | String | Describes the effect of the signal on engagement. Use "positive" for product views. |
imageUrls | [String] | URLs of product images |
inventoryQuantity | Number | Number of units available |
model | String | Model number or description |
msrp | Number | Suggested retail price or original price |
name | String | A label to differentiate this signal from others (for example, "productView from iOS app"). Max 256 characters |
productCategory | String | Product category from your catalog |
productDescription | String | Description of the product |
productRating | Number | Rating of the product |
productStatus | String | Current lifecycle state (for example, "Active", "Discontinued", "Upcoming") |
productUrls | [String] | URLs of product pages |
promotionId | String | ID from a marketing campaign that led to this view |
shoppingCartUrl | String | URL or deep link to the shopping cart |
sku | String | SKU, if different from productId |
unitPrice | Number | Unit price of the product |
virtualCategory | String | Category based on navigation path (for example, "New arrivals", "Sale") |
Things to know
Number fields accept native Swift numeric types (
Int,Double,NSDecimalNumber).Pass
imageUrlsandproductUrlsas[String]arrays.If a required field is missing or invalid, the entire signal is discarded. For fields that don't apply to your business, use a placeholder value.
Basic example
import Connect
ConnectCustomEvent.sharedInstance().logSignal([
"signalType": "productView",
"productId": "SKU-12345",
"productName": "Wireless Headphones"
])#import <Connect/Connect.h>
[[ConnectCustomEvent sharedInstance] logSignal:@{
@"signalType": @"productView",
@"productId": @"SKU-12345",
@"productName": @"Wireless Headphones"
}];Complete example
This example fires a product view signal from a product detail screen when it loads. It includes pricing, category and optional contact mapping.
import Connect
override func viewDidLoad() {
super.viewDidLoad()
// Set up UI ...
var signal: [String: Any] = [
"signalType": "productView",
"productId": product.id,
"productName": product.name,
"unitPrice": product.price.doubleValue,
"productCategory": product.categoryName,
"currency": "USD",
"effect": "positive"
]
if UserSession.shared.isAuthenticated, let email = UserSession.shared.email {
signal["audience"] = ["Email Address": email]
}
let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal)
NSLog("[Connect] productView signal accepted: \(accepted)")
}import Connect
struct ProductDetailScreen: View {
let product: Product
@State private var signalFired = false
var body: some View {
ProductDetailContent(product: product)
.onAppear {
guard !signalFired else { return }
signalFired = true
var signal: [String: Any] = [
"signalType": "productView",
"productId": product.id,
"productName": product.name,
"unitPrice": product.price.doubleValue,
"productCategory": product.categoryName,
"currency": "USD",
"effect": "positive"
]
if UserSession.shared.isAuthenticated, let email = UserSession.shared.email {
signal["audience"] = ["Email Address": email]
}
let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal)
NSLog("[Connect] productView signal accepted: \(accepted)")
}
}
}
Note
.onAppearfires every time the view appears, including on back-navigation returns. The@Stateflag ensures the signal fires only once per view lifecycle.
#import <Connect/Connect.h>
- (void)viewDidLoad {
[super viewDidLoad];
// Set up UI ...
NSMutableDictionary *signal = [@{
@"signalType": @"productView",
@"productId": self.product.id,
@"productName": self.product.name,
@"unitPrice": @(self.product.price.doubleValue),
@"productCategory": self.product.categoryName,
@"currency": @"USD",
@"effect": @"positive"
} mutableCopy];
if ([[UserSession shared] isAuthenticated] && [[UserSession shared] email]) {
signal[@"audience"] = @{ @"Email Address": [[UserSession shared] email] };
}
BOOL accepted = [[ConnectCustomEvent sharedInstance] logSignal:signal];
NSLog(@"[Connect] productView signal accepted: %@", accepted ? @"true" : @"false");
}Verification
After running the app and opening a product detail screen, check the Xcode console for:
[Connect] productView signal accepted: true
The false value means the SDK rejected the call - see Troubleshooting below. Once the console confirms acceptance, verify the signal is available in Connect.
Troubleshooting
Signal not appearing in Connect?
- Confirm the Connect SDK is initialized before any signal calls. See Method.
- Capture the return value and check that it is
true:let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal). Afalsereturn indicates the SDK rejected the call. - Verify the
appKeyandpostURLmatch the Connect org you're checking.
Signal marked as invalid in Connect?
- Confirm all required fields are present:
signalType,productId,productName.
Contact not created or updated?
- Verify attribute key names in
audiencematch exactly how they appear in Connect - including capitalization and spacing.
Related pages
Updated 13 days ago
