Loading...
Loading...
Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.
npx skill4agent add dpearson2699/swift-ios-skills adattributionkit<key>AdNetworkIdentifiers</key>
<array>
<string>example123.adattributionkit</string>
<string>another456.adattributionkit</string>
</array>.skadnetworkUIEventAttributionViewhandleTap()import UIKit
let attributionView = UIEventAttributionView()
attributionView.frame = adContentView.bounds
attributionView.isUserInteractionEnabled = true
adContentView.addSubview(attributionView)AttributionCopyEndpoint<key>AdAttributionKit</key>
<dict>
<key>AttributionCopyEndpoint</key>
<string>https://example.com</string>
</dict>https://example.com/.well-known/appattribution/report-attribution/<key>AdAttributionKit</key>
<dict>
<key>AttributionCopyEndpoint</key>
<string>https://example.com</string>
<key>OptInForReengagementPostbackCopies</key>
<true/>
</dict>import AdAttributionKit
func applicationDidFinishLaunching() async {
do {
try await Postback.updateConversionValue(0, lockPostback: false)
} catch {
print("Failed to set initial conversion value: \(error)")
}
}AppImpressionimport AdAttributionKit
let impression = try await AppImpression(compactJWS: signedJWSString)guard AppImpression.isSupported else {
// Fall back to alternative ad display
return
}func handleAdViewed(impression: AppImpression) async {
do {
try await impression.handleView()
} catch {
print("Failed to record view-through impression: \(error)")
}
}beginView()endView()try await impression.beginView()
// ... ad remains visible ...
try await impression.endView()handleTap()func handleAdTapped(impression: AppImpression) async {
do {
try await impression.handleTap()
} catch {
print("Failed to record click-through impression: \(error)")
}
}UIEventAttributionViewhandleTap()import StoreKit
let config = SKOverlay.AppConfiguration(appIdentifier: "1234567890",
position: .bottom)
config.appImpression = impression| Window | Duration | Postback delay |
|---|---|---|
| 1st | Days 0-2 | 24-48 hours |
| 2nd | Days 3-7 | 24-144 hours |
| 3rd | Days 8-35 | 24-144 hours |
| Event | Time limit |
|---|---|
| View-through to install | 24 hours (configurable up to 7 days) |
| Click-through to install | 30 days (configurable down to 1 day) |
| Install to first update | 60 days |
| Re-engagement to first update | 2 days |
try await Postback.updateConversionValue(
42,
coarseConversionValue: .high,
lockPostback: true
)| Field | Tier 0 | Tier 1 | Tier 2 | Tier 3 |
|---|---|---|---|---|
| 2 | 2 | 2-4 | 2-4 |
| -- | -- | 1st only | 1st only |
| -- | 1st only | 2nd/3rd | 2nd/3rd |
| -- | -- | -- | Yes |
| -- | -- | -- | Conditional |
try await Postback.updateConversionValue(
35,
coarseConversionValue: .medium,
lockPostback: false
)// CoarseConversionValue cases: .low, .medium, .high
try await Postback.updateConversionValue(
10,
coarseConversionValue: .high,
lockPostback: false
)let installUpdate = PostbackUpdate(
fineConversionValue: 20,
lockPostback: false,
conversionTypes: [.install]
)
try await Postback.updateConversionValue(installUpdate)
let reengagementUpdate = PostbackUpdate(
fineConversionValue: 12,
lockPostback: false,
conversionTypes: [.reengagement]
)
try await Postback.updateConversionValue(reengagementUpdate)let update = PostbackUpdate(
fineConversionValue: 15,
lockPostback: false,
conversionTag: savedConversionTag,
conversionTypes: [.reengagement]
)
try await Postback.updateConversionValue(update)AdAttributionKitReengagementOpeneligible-for-re-engagementtruelet reengagementURL = URL(string: "https://example.com/promo/summer")!
try await impression.handleTap(reengagementURL: reengagementURL)AdAttributionKitReengagementOpenfunc handleUniversalLink(_ url: URL) {
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let isReengagement = components?.queryItems?.contains(where: {
$0.name == Postback.reengagementOpenURLParameter
}) ?? false
if isReengagement {
// AdAttributionKit opened this app via a re-engagement ad
}
}AdAttributionKitReengagementOpen// DON'T -- never updating the conversion value
func appDidLaunch() {
// No conversion value update; postback window never starts
}
// DO -- update conversion value on first launch
func appDidLaunch() async {
try? await Postback.updateConversionValue(0, lockPostback: false)
}<!-- DON'T -->
<string>Example123.AdAttributionKit</string>
<!-- DO -->
<string>example123.adattributionkit</string>// DON'T -- tap without attribution view overlay
try await impression.handleTap()
// Throws AdAttributionKitError.missingAttributionView
// DO -- ensure UIEventAttributionView covers the ad
let attributionView = UIEventAttributionView()
attributionView.frame = adView.bounds
adView.addSubview(attributionView)
// Then handle the tap after the user taps the attribution view
try await impression.handleTap()// DON'T
try? await impression.handleTap()
// DO -- handle specific errors
do {
try await impression.handleTap()
} catch let error as AdAttributionKitError {
switch error {
case .impressionExpired:
// Impression older than 30 days
refreshAdImpression()
case .missingAttributionView:
// UIEventAttributionView not present
break
default:
print("Attribution error: \(error)")
}
}// DON'T -- silently dropping the request
// The device retries up to 9 times over 9 days on HTTP 500
// DO -- respond with 200 OK immediately
// Server handler:
func handlePostback(request: Request) -> Response {
// Process asynchronously, respond immediately
Task { await processPostback(request.body) }
return Response(status: .ok)
}AdNetworkIdentifierskidUIEventAttributionViewupdateConversionValuepostback-identifierAppImpression.isSupported