Swift SDK for Topsort retail media: auctions, event tracking, and banner ads.
Two libraries, zero external dependencies:
Topsort— Core SDK for running auctions and tracking events (impressions, clicks, purchases)TopsortBanners— Drop-in SwiftUI banner component with built-in auction, rendering, and tracking
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/Topsort/topsort.swift.git", from: "1.0.0"),
]Then add the targets you need:
.target(
name: "YourApp",
dependencies: [
"Topsort", // Core SDK (auctions + events)
"TopsortBanners", // Optional: SwiftUI banner component
]
)Or in Xcode: File > Add Package Dependencies and paste https://github.com/Topsort/topsort.swift.git.
Call configure() once at app launch, before any tracking or auction calls:
import Topsort
@main
struct MyApp: App {
init() {
var config = Configuration(apiKey: "your-api-key")
config.auctionsTimeout = 20 // Optional: auction timeout in seconds (default: 60)
config.flushAt = 30 // Optional: event batch size (default: 30)
config.flushInterval = 30 // Optional: flush interval in seconds (default: 30)
config.logLevel = .warning // Optional: .none, .error, .warning, .debug
try! Topsort.shared.configure(config)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Request sponsored listings or banner placements (1-5 auctions per request):
let products = try AuctionProducts(ids: ["p_dsad", "p_dvra", "p_oplf", "p_gjfo"])
let category = AuctionCategory(id: "c_fdfa")
let auctions = [
Auction(type: "banners", slots: 1, slotId: "home-banner", device: "mobile", category: category),
Auction(type: "listings", slots: 2, device: "mobile", products: products),
]
let response = try await Topsort.shared.executeAuctions(auctions: auctions)
for result in response.results {
for winner in result.winners {
print("Winner: \(winner.id), bid: \(winner.resolvedBidId)")
}
}See all auction models in Auctions.swift.
Track impressions, clicks, and purchases. Events are batched automatically and flushed every 30 seconds or when the batch reaches 30 events.
// For promoted results (from an auction)
let event = Event(resolvedBidId: winner.resolvedBidId, occurredAt: Date.now)
// For organic results (no auction)
let event = Event(entity: Entity(type: .product, id: product.id), occurredAt: Date.now)
Topsort.shared.track(impression: event) // on view appear
Topsort.shared.track(click: event) // on taplet items = [
PurchaseItem(productId: "p1", unitPrice: 9.99, quantity: 2),
PurchaseItem(productId: "p2", unitPrice: 14.50),
]
let purchase = PurchaseEvent(items: items, occurredAt: Date.now)
Topsort.shared.track(purchase: purchase)Force-send all queued events (e.g., before a critical navigation):
Topsort.shared.flush()See all event models in Events.swift.
Drop-in banner component that handles the full lifecycle: auction, image loading, impression tracking (on render), and click tracking.
import TopsortBanners
TopsortBanner(bannerAuctionBuilder: .init(slotId: "home-banner", deviceType: "mobile"))
.contentMode(.fill)
.onNoWinners {
// No ads available for this placement
}
.onError { error in
// Handle auction or image loading error
}
.onImageLoad {
// Banner image rendered (impression tracked automatically)
}
.buttonClickedAction { response in
// User tapped the banner (click tracked automatically)
// Navigate to the product page
}
.frame(maxHeight: 200)
.clipped()Topsort (core) TopsortBanners (UI)
├── Topsort.shared └── TopsortBanner (SwiftUI View)
│ ├── configure() ├── Runs auction
│ ├── track(impression:) ├── Loads & renders image
│ ├── track(click:) ├── Tracks impression on render
│ ├── track(purchase:) └── Tracks click on tap
│ ├── flush()
│ └── executeAuctions()
├── EventManager (queue, batch, retry)
├── AuctionManager (async/await)
└── HTTPClient (ephemeral URLSession)
Event pipeline: Events are queued in memory, batched by count or interval, and flushed to the Topsort API. Failed requests are retried with exponential backoff (up to 50 retries, 20 min max). Events persist across app restarts via plist storage.
Offline support: The SDK detects network connectivity. Requests are paused when offline and automatically flushed when the connection is restored.
Lifecycle management: Events are flushed and persisted to disk when the app enters background or terminates.
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String |
Required | Your Topsort API key |
url |
String? |
nil |
Custom API base URL (defaults to https://api.topsort.com/v2) |
auctionsTimeout |
TimeInterval? |
60 |
Auction request timeout in seconds |
flushAt |
Int |
30 |
Number of events that triggers a flush |
flushInterval |
TimeInterval |
30 |
Seconds between automatic flushes |
logLevel |
LogLevel |
.warning |
Log verbosity: .none, .error, .warning, .debug |
The SDK is designed for testability. Inject MockTopsort (conforming to TopsortProtocol) to test your code without network calls:
let mock = MockTopsort(executeAuctionsMockResponse: mockResponse)
let banner = TopsortBanner(bannerAuctionBuilder: builder, topsort: mock)- Swift 5.3+
- iOS 15.0+ / macOS 12.0+ / tvOS 11.0+ / watchOS 7.1+
- No external dependencies
See CONTRIBUTING.md for development setup, code style, and PR guidelines.
See SECURITY.md for vulnerability reporting.
MIT. See LICENSE.