Architecture
┌─────────────────────────────────────────────────────┐
│ ContentView (SwiftUI) │
│ └─ IABoxBanner (UIViewRepresentable) │
│ └─ IABoxBannerContainerView (UIView) │
│ ├─ BannerView (Prebid) — bid request │
│ └─ WKWebView — rendering HTML creative │
└─────────────────────────────────────────────────────┘
PrebidMobile.BannerViewsends an OpenRTB bid request to Prebid Server- Server returns bid response with HTML/JS creative (adm)
WKWebViewrenders the received adm
Why WKWebView instead of PrebidMobile built-in renderer?
The iaBox server returns HTML/JS creative with its own SDK (adboxsdk.new.js) containing event tracking in JavaScript, not at the OpenRTB protocol level. The built-in PrebidMobile renderer expects event trackers in OpenRTB format (burl,eventtrackers), so it cannot render such creative. WKWebView renders HTML as-is.
Requirements
- iOS 14.0+
- Xcode 15.0+
- Swift 5.9+
Installation
1. Add Prebid Mobile SDK
PrebidMobile is a third-party open-source package from Prebid.org, hosted on GitHub.
In Xcode: File → Add Package Dependencies, enter URL:
https://github.com/prebid/prebid-mobile-ios
Select only:
- ✅
PrebidMobile
Do not add PrebidMobileAdMobAdapters, PrebidMobileGAMEventHandlers, PrebidMobileMAXAdapters — they are not needed for standalone mode and pull in Google Mobile Ads SDK.
2. Configure Parameters
In IABoxBannerView.swift set your account parameters:
let bannerConfigID = "test_banner" // Config ID placement
let accountID = "com.iabox.ios-sdk-test-1" // Account ID
let serverURL = "https://ia.box/ads/prebid" // Prebid Server URL
3. App Tracking Transparency (IDFA)
To pass the real deviceId (IDFA) to the ad server, you must obtain user permission via App Tracking Transparency.
Without permission the server receives 00000000-0000-0000-0000-000000000000 instead of the real identifier.
ATT and SDK initialization are independent processes. You can initialize the SDK at any time.
Step 1. Add description to Info.plist
In Build Settings of the project (or directly in Info.plist) add key NSUserTrackingUsageDescription:
<key>NSUserTrackingUsageDescription</key>
<string>This identifier is used to deliver personalized ads.</string>
Or via Xcode Build Settings:
INFOPLIST_KEY_NSUserTrackingUsageDescription = "This identifier is used to deliver personalized ads."
Step 2. Request permission in code
ATT request must be called after UI appears — otherwise the system dialog will not display.
Option A — scenePhase (recommended for SwiftUI)
Request when scene transitions to .active. Guarantees the app window is already displayed:
import AppTrackingTransparency
import AdSupport
@main
struct MyApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { phase in
if phase == .active {
if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
ATTrackingManager.requestTrackingAuthorization { _ in }
}
}
}
}
}
}
Important: applicationDidBecomeActive is not called in SwiftUI scene-based apps. Use scenePhase.
Option B — UIKit AppDelegate
For UIKit apps (without scene lifecycle):
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidBecomeActive(_ application: UIApplication) {
if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
ATTrackingManager.requestTrackingAuthorization { _ in }
}
}
}
ATT status behavior
| ATT status | IDFA | Result |
|---|---|---|
.authorized | Real UUID | Personalized ads |
.denied | 00000000-... | Contextual ads |
.restricted | 00000000-... | Contextual ads |
.notDetermined | 00000000-... | Need to request permission |
Testing on simulator: to re-show ATT dialog, delete the app from simulator and run again.
4. Build & Run
Build and run the project: Cmd+R
Project Structure
ios-sdk-test/
├── ios-sdk-test/
│ ├── ios_sdk_testApp.swift # Entry point (SwiftUI App)
│ ├── ContentView.swift # UI with banner load buttons
│ ├── IABoxBannerView.swift # Prebid + WKWebView wrapper
│ └── Assets.xcassets/ # Resources
├── ios-sdk-test.xcodeproj/ # Xcode project
└── README.md
How Integration Works
1. SDK Initialization
Prebid.shared.prebidServerAccountId = accountID
Prebid.shared.timeoutMillis = 10000
Prebid.shared.shareGeoLocation = true
try? Prebid.initializeSDK(serverURL: serverURL) { status, error in
// Handle initialization result
}
initializeSDK checks {serverURL}/status. If this endpoint is not configured (404) — this is not critical, bid requests will still be sent to /openrtb2/auction.
2. Bid Request
let bannerView = BannerView(
frame: CGRect(origin: .zero, size: adSize),
configID: configID,
adSize: adSize
)
bannerView.delegate = self
bannerView.loadAd() // → POST {serverURL}/openrtb2/auction
3. Rendering Creative in WKWebView
When bid response is received, adm (HTML creative) is extracted and loaded into WKWebView:
func bannerView(_ bannerView: BannerView, didReceiveAdWithAdSize adSize: CGSize) {
if let adm = bannerView.lastBidResponse?.winningBid?.adm {
renderAdm(adm) // Loads HTML into WKWebView
}
}
4. SwiftUI Wrapper
struct IABoxBanner: UIViewRepresentable {
@ObservedObject var viewModel: BannerViewModel
let configID: String
let adSize: CGSize
var onStatusUpdate: ((String) -> Void)?
func makeUIView(context: Context) -> IABoxBannerContainerView { ... }
func updateUIView(_ uiView: IABoxBannerContainerView, context: Context) { ... }
}
5. Click Handling
Ad clicks are handled via WKNavigationDelegate and WKUIDelegate. <a href> links are intercepted in decidePolicyFor and opened in Safari. window.open() from JS is intercepted in createWebViewWith and opened in Safari.
Server Response Format (adm)
The iaBox server returns an HTML/JS creative. Event tracking (impression, click, viewability) is performed inside adboxsdk.new.js on the WebView side.
<script src="https://dcdn.adbox.ru/adboxsdk.new.js" async></script>
<script>
window.AdBox.push({
"format": "simple",
"srcType": "url",
"src": "https://ia.box/ads/markup?bid=...&imp=...",
"target": "#slot-...",
"size": {"w": 300, "h": 250},
"track": {
"impression": ["https://ia.box/track/impression?..."],
"click": ["https://ia.box/track/click?..."],
"view": ["https://ia.box/track/viewable?..."],
"load": ["https://ia.box/track/loaded?..."],
"error": ["https://ia.box/track/error?..."]
}
})
</script>
<div id="slot-..."></div>
Troubleshooting
No such module 'PrebidMobile'
Check that Swift Package is added: Project → Package Dependencies. Make sure only PrebidMobile is added, without additional adapters.
SDK status check failed
The /status endpoint is not configured on the server — this is acceptable. Bid requests will work via /openrtb2/auction.
Creative model must be provided with event tracker
Log from built-in PrebidMobile renderer — can be ignored. Creative is rendered via WKWebView, bypassing the built-in renderer.
Google Mobile Ads SDK initialized without an application ID
Extra packages were added (PrebidMobileAdMobAdapters, etc.). Remove them, keep only PrebidMobile.
deviceId = 00000000-0000-0000-0000-000000000000
User did not grant tracking permission (ATT). Check NSUserTrackingUsageDescription in Info.plist. Ensure ATTrackingManager.requestTrackingAuthorization is called after UI appears. On simulator: delete app and rerun.
Banner not displayed
Check Xcode Console logs for loadBanner(), Bid received, Rendering adm messages. Make sure configID exists on the server. Check server availability: curl https://ia.box/ads/prebid/openrtb2/auction