# iOS Integration

This guide shows how to integrate Nexx360 Direct InApp with the Google Mobile Ads SDK on iOS.

{% hint style="info" %}
The Nexx360 Direct InApp call must be made **before** the Google Ad Manager ad request. The returned targeting key-values must be set on the `GAMRequest` before calling `loadRequest:`.
{% endhint %}

## Prerequisites

* Google Mobile Ads SDK integrated in your app ([Quick Start Guide](https://developers.google.com/ad-manager/mobile-ads-sdk/ios/quick-start))
* A Nexx360 account with a configured `tag_id` or `placement`

## Integration Flow

```
1. App starts ad loading
2. App calls Nexx360 /directinapp endpoint (HTTP GET)
3. Nexx360 returns targeting key-values
4. App creates GAMRequest with targeting key-values
5. App loads ad via Google Ad Manager SDK
```

## Step 1: Create the Nexx360 Helper

### Swift

Create a helper class to call the Nexx360 endpoint and parse the response:

```swift
import Foundation

class Nexx360DirectInApp {

    static let baseURL = "https://fast.nexx360.io/directinapp"

    struct TargetingResult {
        let targeting: [String: String]
        let success: Bool
    }

    static func fetchTargeting(
        tagId: String? = nil,
        placement: String? = nil,
        bundle: String,
        sizes: String,
        ifa: String? = nil,
        gdpr: String? = nil,
        gdprConsent: String? = nil,
        completion: @escaping (TargetingResult) -> Void
    ) {
        var params = [String]()
        if let tagId = tagId { params.append("tag_id=\(tagId)") }
        if let placement = placement {
            params.append("placement=\(placement)")
        }
        params.append("bundle=\(bundle)")
        params.append("sizes=\(sizes)")
        if let ifa = ifa { params.append("ifa=\(ifa)") }
        if let gdpr = gdpr { params.append("gdpr=\(gdpr)") }
        if let gdprConsent = gdprConsent {
            params.append(
                "gdpr_consent=\(gdprConsent)"
            )
        }

        let urlString = "\(baseURL)?\(params.joined(separator: "&"))"
        guard let url = URL(string: urlString) else {
            completion(TargetingResult(targeting: [:], success: false))
            return
        }

        var request = URLRequest(url: url)
        request.timeoutInterval = 1.0

        URLSession.shared.dataTask(with: request) { data, response, error in
            guard error == nil,
                  let httpResponse = response as? HTTPURLResponse
            else {
                DispatchQueue.main.async {
                    completion(
                        TargetingResult(targeting: [:], success: false)
                    )
                }
                return
            }

            if httpResponse.statusCode == 204 {
                DispatchQueue.main.async {
                    completion(
                        TargetingResult(targeting: [:], success: true)
                    )
                }
                return
            }

            guard httpResponse.statusCode == 200,
                  let data = data,
                  let json = try? JSONSerialization.jsonObject(
                    with: data
                  ) as? [String: Any],
                  let targetingDict = json["targeting"]
                    as? [String: String]
            else {
                DispatchQueue.main.async {
                    completion(
                        TargetingResult(targeting: [:], success: false)
                    )
                }
                return
            }

            DispatchQueue.main.async {
                completion(
                    TargetingResult(
                        targeting: targetingDict,
                        success: true
                    )
                )
            }
        }.resume()
    }

    // Async/await version (iOS 13+)
    @available(iOS 13.0, *)
    static func fetchTargeting(
        tagId: String? = nil,
        placement: String? = nil,
        bundle: String,
        sizes: String,
        ifa: String? = nil,
        gdpr: String? = nil,
        gdprConsent: String? = nil
    ) async -> TargetingResult {
        await withCheckedContinuation { continuation in
            fetchTargeting(
                tagId: tagId,
                placement: placement,
                bundle: bundle,
                sizes: sizes,
                ifa: ifa,
                gdpr: gdpr,
                gdprConsent: gdprConsent
            ) { result in
                continuation.resume(returning: result)
            }
        }
    }
}
```

## Step 2: Load a Banner Ad with Nexx360 Targeting

### Using async/await (iOS 13+)

```swift
import GoogleMobileAds

class BannerViewController: UIViewController {

    var bannerView: GAMBannerView!

    override func viewDidLoad() {
        super.viewDidLoad()

        bannerView = GAMBannerView(adSize: GADAdSizeMediumRectangle)
        bannerView.adUnitID = "/your-network-id/your-ad-unit"
        bannerView.rootViewController = self
        view.addSubview(bannerView)

        loadAdWithNexx360()
    }

    func loadAdWithNexx360() {
        Task {
            // Step 1: Fetch targeting from Nexx360
            let result = await Nexx360DirectInApp.fetchTargeting(
                tagId: "your_tag_id",
                bundle: Bundle.main.bundleIdentifier ?? "",
                sizes: "300x250,320x50",
                ifa: getIDFA()
            )

            // Step 2: Build the ad request with targeting
            let request = GAMRequest()

            // Step 3: Add Nexx360 targeting key-values
            var customTargeting = [String: String]()
            for (key, value) in result.targeting {
                customTargeting[key] = value
            }
            request.customTargeting = customTargeting

            // Step 4: Load the ad
            bannerView.load(request)
        }
    }

    func getIDFA() -> String? {
        if #available(iOS 14, *) {
            // Requires ATT permission
            // Return nil if tracking not authorized
        }
        return ASIdentifierManager.shared()
            .advertisingIdentifier.uuidString
    }
}
```

### Using completion handler

```swift
import GoogleMobileAds

class BannerViewController: UIViewController {

    var bannerView: GAMBannerView!

    override func viewDidLoad() {
        super.viewDidLoad()

        bannerView = GAMBannerView(adSize: GADAdSizeMediumRectangle)
        bannerView.adUnitID = "/your-network-id/your-ad-unit"
        bannerView.rootViewController = self
        view.addSubview(bannerView)

        loadAdWithNexx360()
    }

    func loadAdWithNexx360() {
        // Step 1: Fetch targeting from Nexx360
        Nexx360DirectInApp.fetchTargeting(
            tagId: "your_tag_id",
            bundle: Bundle.main.bundleIdentifier ?? "",
            sizes: "300x250,320x50",
            ifa: getIDFA()
        ) { [weak self] result in
            // Step 2: Build the ad request with targeting
            let request = GAMRequest()

            // Step 3: Add Nexx360 targeting key-values
            request.customTargeting = result.targeting

            // Step 4: Load the ad
            self?.bannerView.load(request)
        }
    }
}
```

## Using Placement Instead of Tag ID

If your Nexx360 setup uses placements, pass the `placement` parameter instead of `tagId`:

```swift
let result = await Nexx360DirectInApp.fetchTargeting(
    placement: "my-placement-code",
    bundle: Bundle.main.bundleIdentifier ?? "",
    sizes: "300x250,320x50"
)
```

## GDPR Consent

If your app collects GDPR consent (e.g., via a CMP), pass the consent string:

```swift
let result = await Nexx360DirectInApp.fetchTargeting(
    tagId: "your_tag_id",
    bundle: Bundle.main.bundleIdentifier ?? "",
    sizes: "300x250",
    gdpr: "1",
    gdprConsent: "CPMeqrnPMeqrn..."
)
```

## App Tracking Transparency (iOS 14.5+)

On iOS 14.5+, you must request ATT permission before accessing the IDFA:

```swift
import AppTrackingTransparency

func requestTrackingPermission(completion: @escaping (String?) -> Void) {
    ATTrackingManager.requestTrackingAuthorization { status in
        switch status {
        case .authorized:
            let idfa = ASIdentifierManager.shared()
                .advertisingIdentifier.uuidString
            completion(idfa)
        default:
            completion(nil)
        }
    }
}
```

Pass the IDFA to the `ifa` parameter only when tracking is authorized.

{% hint style="warning" %}
Always call the Nexx360 endpoint **before** `load(request)`. The targeting key-values must be present in the `GAMRequest` for the bid to compete in Google Ad Manager.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.nexx360.io/integration-methods/direct-inapp-sdkless/ios-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
