# Events

Batch allows you to track events that happen in your application. They automatically keep track of their count, the last time it happened and their value.

> **Important**\
> Newly tracked events are hidden by default. You will need to manually display them from the dashboard settings.

### Tracking events

Events are easy to use, but have some rules:

* Event names are strings. They should be made of letters, numbers or underscores *(\[a-z0-9\_])* and can't be longer than 30 characters.
* A custom data object can be attached. See the section "Event Attributes", right under this one.
* Custom attributes have some reserved keys. See the section "Reserved event attributes" under "Event Attributes" for more info.

Here are some examples:

{% tabs %}
{% tab title="Swift" %}

```swift
// Simple event
BatchProfile.trackEvent(name: "ad_seen")

// Event with custom attributes
BatchProfile.trackEvent(name: "add_to_cart", attributes: BatchEventAttributes { data in
  // Custom attribute
  data.put("bags", forKey: "sub_category")
  // Compatibility reserved key
  data.put("activity", forKey: "$label")
 })
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
// Simple event
[BatchProfile trackEvent:@"ad_seen"];

// Event with custom attributes
BatchEventAttributes *attributes = [BatchEventAttributes new];
[attributes putString:@"sub_category" forKey:@"bags"]; // Custom attributes
[attributes putString:@"$label" forKey:@"activity"]; // Compatibility reserved key

[BatchProfile trackEventWithName:@"add_to_cart" attributes:attributes];
```

{% endtab %}
{% endtabs %}

> Please test your implementation using our [debug tool](https://doc.batch.com/developer/sdk/ios/profile-data/debug) and [profile view](https://doc.batch.com/getting-started/features/customer-engagement-platform/profiles/overview) before releasing your app on the store.

#### Event attributes

Custom attributes can be attached to events using `BatchEventAttributes`. You will then use them when calling `BatchProfile.trackEvent()`.

**Attribute name**

They should be made of letters, numbers or underscores *(\[a-z0-9\_])* and can't be longer than 30 characters *(e.g. has\_premium)*. They will be automatically lowercased, so trying to use the same key with different casing will overwrite the previously set value.

**Attribute value**

Values must be any of the following types:

* **NSString**, must not be longer than 300 characters and can't be empty. For better results, you should make them upper/lowercase and trim the whitespaces.
* **Bool**
* **Floats/Doubles**
* **NSInteger**
* **NSDate**
* **NSURL**, not longer than 2048 characters and must follow the format `scheme://[authority][path][?query][#fragment]`.
* **NSArray\<NSString | BatchEventAttributes>**, You can set array of strings (max 300chars) and array of objects. Max 25 items. You cannot mix several attribute types in one array. Arrays can't be nested.
* **Object** (using `BatchEventAttributes`, Objects cannot have more than 3 levels of nesting.

{% hint style="info" %}
Setting a value for an existing key will overwrite it. Any attempt to add an invalid attribute will fail and the event will **NOT** be tracked. You can use the `validateWithError` method which return a list of human-readable errors to ensure your event is valid before sending it.
{% endhint %}

**Reserved event attributes**

Some event attributes have reserved keys, and are all prefixed by a `$` sign. This is the list of currently reserved event attributes. You cannot set an event attribute starting by a *$* sign.

<table><thead><tr><th width="99.2265625">Key</th><th>Description</th></tr></thead><tbody><tr><td><code>$label</code></td><td><p><strong>String</strong> - <em>Optional</em></p><p>Event label. Must be a string, will automatically be bridged as label for application event compatibility. Max 200 chars.</p></td></tr><tr><td><code>$tags</code></td><td><p><strong>NSArray</strong> - Optional</p><p>Event tags. Must be an array of string, will automatically be bridged as tags for application event compatibility. Max 10 items of type string, each no longer than 64chars. The SDK will automatically lowercase them, so two same strings with different casing do not count as two different tags</p></td></tr></tbody></table>

In Batch SDK v1 you were able to set a label and tags at the root of an event, with the limit of 1 label and 10 tags.

Batch SDK v2 introduced Object and Array types in event attributes. You can set more than one array on profiles events. This is only supported for profiles, and not on the install-centric data model, which currently powers push notification and In-App messages.

However, it's still possible to set a label and tags on events in the install-centric data model by using `$label` and `$tags`, and activate compatibility flows. This way, you will be able to use this data for your push and in-app communications.

{% hint style="warning" %}
If you are not running any orchestrations on the Mobile Engagement Platform (MEP), you should use String and Array attributes instead of legacy `$label` and `$tags.`
{% endhint %}

**Example**

{% tabs %}
{% tab title="Swift" %}

```swift
let attributes = BatchEventAttributes { data in
    data.put("man_clothes", forKey: "sub_category")
    data.put(Date(timeIntervalSince1970: 1713432899086), forKey: "end_of_sale_date")
    data.put([
        BatchEventAttributes { itemData in
            itemData.put("Basic Tee", forKey: "name")
            itemData.put("M", forKey: "size")
            itemData.put(23.99, forKey: "price")
            itemData.put(URL(string: "https://batch-store.com/basic-tee")!, forKey: "item_url")
            itemData.put(URL(string: "https://batch-store.com/basic-tee/black/image.png")!, forKey: "item_image")
            itemData.put(true, forKey: "in_sales")
        },
        BatchEventAttributes { itemData in
            itemData.put("Short socks pack x3", forKey: "name")
            itemData.put("38-40", forKey: "size")
            itemData.put(15.99, forKey: "price")
            itemData.put(URL(string: "https://batch-store.com/short-socks-pack-x3")!, forKey: "item_url")
            itemData.put(URL(string: "https://batch-store.com/short-socks-pack-x3/image.png")!, forKey: "item_image")
            itemData.put(false, forKey: "in_sales")
        }
    ], forKey: "items_list")
    data.put("accessories", forKey: "$label")
    data.put(["first_purchase", "in_promo"], forKey: "$tags")
}
do {
    let _ = try attributes.validate()
    BatchProfile.trackEvent(name: "purchased", attributes: attributes)
} catch let error {
    print("Event validation error: \(error.localizedDescription)")
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
BatchEventAttributes *attributes = [BatchEventAttributes new];
[attributes putString:@"man_clothes" forKey:@"sub_category"];
[attributes putDate:[NSDate dateWithTimeIntervalSince1970:1713432899086] forKey:@"end_of_sale_date"];

NSMutableArray *itemsList = [NSMutableArray array];
BatchEventAttributes *item1 = [BatchEventAttributes new];
[item1 putString:@"Basic Tee" forKey:@"name"];
[item1 putString:@"M" forKey:@"size"];
[item1 putFloat:23.99 forKey:@"price"];
[item1 putURL:[NSURL URLWithString:@"https://batch-store.com/basic-tee"] forKey:@"item_url"];
[item1 putURL:[NSURL URLWithString:@"https://batch-store.com/basic-tee/black/image.png"] forKey:@"item_image"];
[item1 putBool:YES forKey:@"in_sales"];
[itemsList addObject:item1];

BatchEventAttributes *item2 = [BatchEventAttributes new];
[item2 putString:@"Short socks pack x3" forKey:@"name"];
[item2 putString:@"38-40" forKey:@"size"];
[item2 putFloat:15.99 forKey:@"price"];
[item2 putURL:[NSURL URLWithString:@"https://batch-store.com/short-socks-pack-x3"] forKey:@"item_url"];
[item2 putURL:[NSURL URLWithString:@"https://batch-store.com/short-socks-pack-x3/image.png"] forKey:@"item_image"];
[item2 putBool:NO forKey:@"in_sales"];
[itemsList addObject:item2];

[attributes putObjectArray:itemsList forKey:@"items_list"];
[attributes putString:@"accessories" forKey:@"$label"];
[attributes putStringArray:@[@"first_purchase", @"in_promo"] forKey:@"$tags"];

NSError *err;
[attributes validateWithError:&err];
if (err == nil) {
    [BatchProfile trackEventWithName:@"purchased" attributes:attributes];
} else {
    NSLog(@"Event validation error: %@", err.description);
}
```

{% endtab %}
{% endtabs %}

### Tracking user location

You can now natively track a user location. This uses CoreLocation's standard CLLocation object, which you usually get from the CoreLocation itself. You can also instantiate one manually from a latitude/longitude.

Here's an example:

{% tabs %}
{% tab title="Swift" %}

```swift
// let location: CLLocation = [...]
BatchProfile.trackLocation(location)
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
// CLLocation *location = [...];
[BatchProfile trackLocation:location];
```

{% endtab %}
{% endtabs %}

This data will allow you to send [geo-targeted push notifications](https://doc.batch.com/getting-started/features/mobile-engagement-platform/push/user-targeting#last-location) from the dashboard or the Campaigns API.

{% hint style="info" %}
The SDK will throttle location tracking to optimize network and battery usage. You can track one location event every 30 seconds, any attempt at updating the location sooner will be ignored by the SDK.
{% endhint %}

### Background events

Events can be sent while the application is in the background by asking `UIApplication` to begin a background task. Once the event has been sent to the server, Batch will emit a `BatchEventTrackerFinishedNotification` NSNotification.

Please note that this notification might be sent multiple times: you may want to dynamically add the observer and remove it once your event has been tracked.

Here is a sample implementation:

```swift
@objc
class BackgroundEventSender : NSObject {
    var eventBackgroundEventTaskID: UIBackgroundTaskIdentifier?
    
    override init() {
        super.init()
        NotificationCenter.default.addObserver(self, selector: #selector(finishBackgroundTask), name: Notification.Name.BatchEventTrackerFinished, object: nil)
    }
    
    func trackBackgroundEvent() {
        guard eventBackgroundEventTaskID == nil else {
            // This sample code doesn't support tracking multiple events at once
            return
        }
        
        eventBackgroundEventTaskID = UIApplication.shared.beginBackgroundTask(withName: "batch_background_event", expirationHandler: self.finishBackgroundTask)
        
        BatchProfile.trackEvent(name: "SAMPLE_BACKGROUND_EVENT")

    }
    
    @objc
    func finishBackgroundTask() {
        guard let task = eventBackgroundEventTaskID else {
            return
        }
        self.eventBackgroundEventTaskID = nil
        UIApplication.shared.endBackgroundTask(task)
    }
}
```
