Try it free

User action instrumentation

  • Latest Dynatrace
  • How-to guide
  • Published Jul 01, 2026

A user action represents a significant operation within your app—it groups the requests, navigations, and errors that result from a single user interaction into one event. For a full conceptual overview of how user actions start, end, and what data they capture, see User actions. The sections below cover only iOS-specific instrumentation details.

Automatic instrumentation

Automatic user actions are caused by User interactions with an action handler.

User action names

The general naming pattern is described in User actions. On iOS, the two parts of the name are resolved as follows:

Screen name: The fully qualified class name where the touch occurred, for example, ViewController for UIKit or ContentView for SwiftUI. If you set a custom view name via the API, that name is used instead.

Element name: The agent uses the component type name of the element that handled the selection, for example, UIButton.

touch on UIButton on ViewController

Action handler examples

An action handler is the app code bound to a User interaction that implements the behavior triggered by that interaction. Only user interactions that have an action handler attached can produce a user action.

UIKit: The logic is executed in the UIApplication's sendEvent method.

SwiftUI: The logic is executed via a button's action or via the onTapGesture modifier.

For a detailed explanation of action handlers, refer to User interactions.

Configure automatic instrumentation

Use Dynatrace.setAutomaticUserActionDetectionEnabled(Bool) to activate or deactivate automatic user action detection at runtime. Automatic detection is active by default.

When you deactivate automatic detection, user interactions no longer trigger automatic user action creation. Manual user actions created with createUserAction are not affected.

Dynatrace.setAutomaticUserActionDetectionEnabled(false)
[Dynatrace setAutomaticUserActionDetectionEnabled:NO];

Manual instrumentation

User actions triggered by the API let you track custom workflows that the agent cannot instrument automatically, such as multi-step business processes, background tasks, or interactions that span multiple views.

Create a user action

Use Dynatrace.createUserAction(configuration: DTXUserActionConfiguration) to start a user action triggered by the API. Pass a DTXUserActionConfiguration with a custom name that describes the workflow you track. Call complete() when the workflow finishes.

createUserAction always returns a user action, and invalid arguments are handled silently.

let userAction = Dynatrace.createUserAction(configuration:
DTXUserActionConfiguration(customName: "Checkout Process")
)
// ... perform the workflow ...
userAction.complete()
DTXUserActionConfiguration *configuration = [[DTXUserActionConfiguration alloc] initWithCustomName:@"Checkout Process"];
DTXUserAction *userAction = [Dynatrace createUserActionWithConfiguration:configuration];
// ... perform the workflow ...
[userAction complete];

Automatic completion

By default, you must call complete() explicitly. If your workflow triggers web requests and you want the user action to close automatically once all in-flight requests finish, activate withCompleteAutomatically(true).

let userAction = Dynatrace.createUserAction(configuration:
DTXUserActionConfiguration(customName: "Checkout Process")
.withCompleteAutomatically(true)
)
// The user action closes on its own once pending requests finish.
// You can still call complete() to close it earlier.
DTXUserActionConfiguration *configuration = [[[DTXUserActionConfiguration alloc] initWithCustomName:@"Checkout Process"] withCompleteAutomatically:YES];
DTXUserAction *userAction = [Dynatrace createUserActionWithConfiguration:configuration];
// The user action closes on its own once pending requests finish.
// You can still call complete() to close it earlier.

You can also change this behavior at any point during the user action's lifetime with setCompleteAutomatically(enabled: Bool).

Add event properties

Use addEventProperty to attach custom data to the user action. Property keys must be prefixed with event_properties.. Properties added after complete() is called are silently ignored.

For property naming rules and limits, see Event and session properties.

let userAction = Dynatrace.createUserAction(configuration:
DTXUserActionConfiguration(customName: "Checkout Process")
)
userAction.addEventProperty("event_properties.payment_method", value: "credit_card")
userAction.addEventProperty("event_properties.cart_value", value: 149.99)
userAction.addEventProperty("event_properties.item_count", value: 3)
// ... perform the workflow ...
userAction.complete()
DTXUserActionConfiguration *configuration = [[DTXUserActionConfiguration alloc] initWithCustomName:@"Checkout Process"];
DTXUserAction *userAction = [Dynatrace createUserActionWithConfiguration:configuration];
[userAction addEventProperty:@"event_properties.payment_method" value:@"credit_card"];
[userAction addEventProperty:@"event_properties.cart_value" value:@(149.99)];
[userAction addEventProperty:@"event_properties.item_count" value:@(3)];
// ... perform the workflow ...
[userAction complete];

Example: E-commerce checkout flow

The following example tracks a checkout workflow as a single user action that spans multiple methods. The action is started in the button handler so that its start time matches the user's selection, kept as a field on the CheckoutViewController while the payment is processed, and closed—with outcome properties attached—in both the success and error paths.

final class CheckoutViewController: UIViewController {
// MARK: - Properties
private let cart: Cart
private let paymentService: PaymentService
private var checkoutAction: DTXUserAction?
// MARK: - Private
@objc private func checkoutButtonTapped(_ sender: UIButton) {
let configuration = DTXUserActionConfiguration(customName: "Checkout Process")
checkoutAction = Dynatrace.createUserAction(configuration: configuration)
checkoutAction?.addEventProperty("event_properties.cart_id", value: cart.id)
checkoutAction?.addEventProperty("event_properties.item_count", value: cart.items.count)
checkoutAction?.addEventProperty("event_properties.cart_total", value: cart.total)
checkoutAction?.addEventProperty("event_properties.currency", value: cart.currency)
paymentService.processPayment(for: cart) { [weak self] result in
switch result {
case .success(let order):
self?.completeCheckout(orderId: order.id)
case .failure(let error):
self?.cancelCheckout(reason: error.localizedDescription)
}
}
}
private func completeCheckout(orderId: String) {
checkoutAction?.addEventProperty("event_properties.order_id", value: orderId)
checkoutAction?.addEventProperty("event_properties.checkout_successful", value: true)
checkoutAction?.complete()
checkoutAction = nil
}
private func cancelCheckout(reason: String) {
checkoutAction?.addEventProperty("event_properties.checkout_successful", value: false)
checkoutAction?.addEventProperty("event_properties.cancellation_reason", value: reason)
checkoutAction?.complete()
checkoutAction = nil
}
}
@interface CheckoutViewController ()
@property (nonatomic) Cart *cart;
@property (nonatomic) PaymentService *paymentService;
@property (nonatomic, nullable) DTXUserAction *checkoutAction;
@end
@implementation CheckoutViewController
- (void)checkoutButtonTapped:(UIButton *)sender {
DTXUserActionConfiguration *configuration = [[DTXUserActionConfiguration alloc] initWithCustomName:@"Checkout Process"];
self.checkoutAction = [Dynatrace createUserActionWithConfiguration:configuration];
[self.checkoutAction addEventProperty:@"event_properties.cart_id" value:self.cart.id];
[self.checkoutAction addEventProperty:@"event_properties.item_count" value:@(self.cart.items.count)];
[self.checkoutAction addEventProperty:@"event_properties.cart_total" value:self.cart.total];
[self.checkoutAction addEventProperty:@"event_properties.currency" value:self.cart.currency];
__weak typeof(self) weakSelf = self;
[self.paymentService processPaymentForCart:self.cart completion:^(Order *order, NSError *error) {
if (order) {
[weakSelf completeCheckoutWithOrderId:order.id];
} else {
[weakSelf cancelCheckoutWithReason:error.localizedDescription];
}
}];
}
- (void)completeCheckoutWithOrderId:(NSString *)orderId {
[self.checkoutAction addEventProperty:@"event_properties.order_id" value:orderId];
[self.checkoutAction addEventProperty:@"event_properties.checkout_successful" value:@YES];
[self.checkoutAction complete];
self.checkoutAction = nil;
}
- (void)cancelCheckoutWithReason:(NSString *)reason {
[self.checkoutAction addEventProperty:@"event_properties.checkout_successful" value:@NO];
[self.checkoutAction addEventProperty:@"event_properties.cancellation_reason" value:reason];
[self.checkoutAction complete];
self.checkoutAction = nil;
}
@end
Related tags
Digital Experience