Installation
Toolchain requirement: TeadsSDK ships as a binary framework compiled with the Swift 6.2.3 toolchain (Xcode 26.2). Your app must be built with Xcode 26.2 or newer — older toolchains can't link against it.
The framework is built in Swift 5 language mode with library evolution enabled, so it can be consumed from apps using either the Swift 5 or Swift 6 language mode. Adopting Swift 6 in your app is not required.
Installation Methods
- CocoaPods
- Swift Package Manager
- Manual
Using CocoaPods
- Add
pod 'TeadsSDK', '~> 6.2'into your Podfile. - Run
pod install. - Open workspace file and run the project.
platform :ios, '14.0'
use_frameworks!
target 'YourApp' do
pod 'TeadsSDK', '~> 6.2'
end
Using Swift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. You can add the TeadsSDK SPM dependency by using the following URL:
https://github.com/teads/TeadsSDK-iOS.git
We recommend setting the dependency version constraint to .upToNextMajor(from: "6.2.1").
Adding via Xcode
- In Xcode, select File > Add Package Dependencies
- Enter the repository URL:
https://github.com/teads/TeadsSDK-iOS.git - Select version rule: Up to Next Major Version with 6.2.1
Adding via Package.swift
Adding TeadsSDK as a SPM package dependency is as easy as adding it to the dependencies value of your Package.swift:
dependencies: [
.package(url: "https://github.com/teads/TeadsSDK-iOS.git", .upToNextMajor(from: "6.2.1"))
]
Download XCFramework
For manual installation, you can download the latest TeadsSDK.xcframework:
- Download the latest TeadsSDK.xcframework from the GitHub releases
- Drag and drop it into your Xcode project
- Ensure "Copy items if needed" is checked
- Add to your target's Frameworks, Libraries, and Embedded Content
Sample Application
The official Teads SDK sample application demonstrates best practices and integration examples.
GitHub Repository: TeadsSDK-iOS
Settings
Find the full settings list for each placement in the Settings Configuration.
SDK Initialization
Initialize the SDK as early as possible in your app's lifecycle:
SwiftUI
App/Scene/WindowGroup are SwiftUI-only constructs with no Objective-C equivalent — see the UIKit example below for the Objective-C initialization pattern.
import SwiftUI
import TeadsSDK
@main
struct YourApp: App {
init() {
// Initialize Teads SDK with your partner key
Teads.configure(with: "YOUR_PARTNER_KEY")
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
UIKit
- Swift
- Objective-C
import UIKit
import TeadsSDK
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize Teads SDK
Teads.configure(with: "YOUR_PARTNER_KEY")
return true
}
}
@import UIKit;
@import TeadsSDK;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
// Initialize Teads SDK
[Teads configureWith:@"YOUR_PARTNER_KEY"];
return YES;
}
@end
Global Settings Configuration
The Teads SDK provides global settings that apply across all placements. Configure these settings after SDK initialization.
Test Mode
Enable test mode for detailed logging including viewability percentage and other diagnostic information:
- Swift
- Objective-C
// Enable test mode
Teads.testMode = true
// Enable test mode
Teads.testMode = YES;
Crash Monitoring
Crash monitoring is enabled by default. To disable:
- Swift
- Objective-C
Teads.isCrashMonitoringEnabled = false
Teads.isCrashMonitoringEnabled = NO;
Implementation Samples
For complete working examples and sample applications, refer to our public GitHub repositories:
- iOS Sample App - Complete iOS implementation with all placement types
These repositories contain:
- Full working applications demonstrating all placement types
- Best practices and common integration patterns
Event Handling
- Swift
- Objective-C
extension YourViewController: TeadsAdPlacementEventsDelegate {
func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
didEmitEvent event: TeadsAdPlacementEventName,
data: [String : Any]?) {
print("Event: \(event) from placement: \(placement?.placementId ?? "unknown")")
switch event {
case .ready:
print("Ad is ready to display")
case .rendered:
print("Ad rendered - send impression to analytics")
case .viewed:
print("Ad is viewable - track viewable impression")
case .clicked:
print("Ad clicked")
// SDK handles URL opening automatically
case .clickedOrganic:
// User clicked on organic content (Feed placement)
if let url = data?["url"] as? String {
print("Organic content clicked: \(url)")
// Perform your content navigation here
}
case .failed:
if let error = data?["error"] as? String {
print("Ad failed to load: \(error)")
}
case .play:
print("Video started playing")
case .pause:
print("Video paused")
case .complete:
print("Video completed")
case .heightUpdated:
if let height = data?["height"] as? CGFloat {
print("Ad height updated to: \(height)")
// No need to do anything, ad placements handle their own layout.
}
case .loaded:
print("Content loaded")
@unknown default:
print("Unknown event: \(event)")
}
}
}
@interface YourViewController () <TeadsAdPlacementEventsDelegate>
@end
@implementation YourViewController
- (void)adPlacement:(id<TeadsAdPlacementIdentifiable>)placement
didEmitEvent:(TeadsAdPlacementEventName)event
data:(NSDictionary<NSString *, id> *)data {
NSLog(@"Event: %ld from placement: %@", (long)event, placement.placementId ?: @"unknown");
switch (event) {
case TeadsAdPlacementEventNameReady:
NSLog(@"Ad is ready to display");
break;
case TeadsAdPlacementEventNameRendered:
NSLog(@"Ad rendered - send impression to analytics");
break;
case TeadsAdPlacementEventNameViewed:
NSLog(@"Ad is viewable - track viewable impression");
break;
case TeadsAdPlacementEventNameClicked:
NSLog(@"Ad clicked");
// SDK handles URL opening automatically
break;
case TeadsAdPlacementEventNameClickedOrganic: {
// User clicked on organic content (Feed placement)
NSString *url = data[@"url"];
if (url) {
NSLog(@"Organic content clicked: %@", url);
// Perform your content navigation here
}
break;
}
case TeadsAdPlacementEventNameFailed: {
NSString *error = data[@"error"];
if (error) {
NSLog(@"Ad failed to load: %@", error);
}
break;
}
case TeadsAdPlacementEventNamePlay:
NSLog(@"Video started playing");
break;
case TeadsAdPlacementEventNamePause:
NSLog(@"Video paused");
break;
case TeadsAdPlacementEventNameComplete:
NSLog(@"Video completed");
break;
case TeadsAdPlacementEventNameHeightUpdated: {
NSNumber *height = data[@"height"];
if (height) {
NSLog(@"Ad height updated to: %@", height);
// No need to do anything, ad placements handle their own layout.
}
break;
}
case TeadsAdPlacementEventNameLoaded:
NSLog(@"Content loaded");
break;
default:
NSLog(@"Unknown event: %ld", (long)event);
break;
}
}
@end
The SDK automatically handles opening the click URL in the .clicked event. Do not implement URL navigation in this event handler — doing so will result in the browser opening twice.
Use this event only for tracking/analytics purposes.
SwiftUI-Specific Considerations
Common Gotchas and Solutions
-
Width Sizing Issue
- Problem: Feed/Media shows 0 width even though height updates correctly
- Solution: Always add
.frame(maxWidth: .infinity)
Swift-only:
TeadsAdPlacementSwiftUIViewis a generic SwiftUIView, and SwiftUI has no Objective-C equivalent.TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(config: config, delegate: nil).frame(maxWidth: .infinity) // ← Don't forget this! -
Dynamic Dark Mode
- Problem:
toggleDarkMode()requires placement reference - Solution: Pass dark mode in config for initial state, or use manual approach for dynamic updates
@Environment(\.colorScheme) var colorScheme// Pass current scheme in configdarkMode: colorScheme == .dark - Problem:
-
Explore More Navigation
- Problem: Need to intercept back navigation for explore more
- Solution: Use custom back button with
showExploreMore()call
.navigationBarBackButtonHidden(true).toolbar {ToolbarItem(placement: .navigationBarLeading) {Button("Back") {feedManager.showExploreMoreAndDismiss { dismiss() }}}} -
Custom UIViewRepresentable
- Problem: Direct view return causes layout issues
- Solution: Always wrap in container with constraints
func makeUIView(context: Context) -> UIView {let container = UIView()container.addSubview(adView)// Add Auto Layout constraintsreturn container // ← Return container, not adView} -
List/LazyVStack Integration
- Problem: Placements in scrollable lists may have rendering issues
- Solution: Use fixed frame heights and proper view recycling
Swift-only:
TeadsAdPlacementSwiftUIViewis a generic SwiftUIView, and SwiftUI has no Objective-C equivalent.LazyVStack {ForEach(items) { item inif item.isAd {TeadsAdPlacementSwiftUIView<TeadsAdPlacementMedia>(config: config, delegate: nil).frame(maxWidth: .infinity).frame(height: 250) // Fixed height for list items}}}
Best Practices
1. Placement Lifecycle Management
Always maintain strong references to placement objects:
- Swift
- Objective-C
class ViewController: UIViewController {
// ✅ Good: Strong reference maintained
private var mediaPlacement: TeadsAdPlacementMedia?
override func viewDidLoad() {
super.viewDidLoad()
// Create and store placement
mediaPlacement = TeadsAdPlacementMedia(config, delegate: self)
}
deinit {
// Cleanup when done
mediaPlacement = nil
}
}
// TeadsAdPlacementMediaConfig is a Swift struct and doesn't bridge to Objective-C;
// use the convenience initializer instead (equivalent pid + articleUrl + delegate).
@interface ViewController : UIViewController
@property (nonatomic, strong, nullable) TeadsAdPlacementMedia *mediaPlacement;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Create and store placement
self.mediaPlacement = [[TeadsAdPlacementMedia alloc] initWithPid:84242
articleUrl:articleUrl
delegate:self];
}
- (void)dealloc {
// Cleanup when done
self.mediaPlacement = nil;
}
@end
2. Error Handling
Error handling is optional - ad placements have a height of 0 by default, so in the worst case they simply won't show (in case of Media or Feed placement):
- Swift
- Objective-C
if let adView = try? placement?.loadAd() {
//add adView to your UI
}
NSError *error = nil;
UIView *adView = [placement loadAdAndReturnError:&error];
if (adView) {
// add adView to your UI
}
3. TableView/CollectionView Integration
UITableView
For table views, no extra work is required - placements handle their own sizing automatically.
UICollectionView
For collection views, use UICollectionViewDelegateFlowLayout to provide dynamic cell sizes:
- Swift
- Objective-C
class YourViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
private var placement: TeadsAdPlacementMedia?
private var adCellHeight: CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
// Create and configure placement
placement = Teads.createPlacement(
with: config,
delegate: self
)
}
}
extension YourViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
// For the ad cell, return the dynamic height
if indexPath.item == 2 { // Assuming ad is at index 2
return CGSize(width: width, height: adCellHeight)
}
// Return fixed height for other cells
return CGSize(width: width, height: 100.0)
}
}
extension YourViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10 // Example: 10 items with ad at index 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 2 {
// Ad cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AdCell", for: indexPath)
if let adView = try? placement?.loadAd() {
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
cell.contentView.addSubview(adView)
adView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
adView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
adView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor),
adView.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
adView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor)
])
}
return cell
} else {
// Regular content cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ContentCell", for: indexPath)
// Configure your content cell...
return cell
}
}
}
extension YourViewController: TeadsAdPlacementEventsDelegate {
func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
didEmitEvent event: TeadsAdPlacementEventName,
data: [String : Any]?) {
if event == .heightUpdated,
let height = data?["height"] as? CGFloat {
// Update the stored height
adCellHeight = height
// Reload the ad cell to apply new size
let adIndexPath = IndexPath(item: 2, section: 0)
collectionView.performBatchUpdates {
collectionView.reloadItems(at: [adIndexPath])
}
}
}
}
// Teads.createPlacement<T>(with:delegate:) is a Swift generic and doesn't bridge to
// Objective-C; use TeadsAdPlacementMedia's convenience initializer instead.
@interface YourViewController : UIViewController <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, TeadsAdPlacementEventsDelegate>
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, strong, nullable) TeadsAdPlacementMedia *placement;
@property (nonatomic, assign) CGFloat adCellHeight;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
// Create and configure placement
self.placement = [[TeadsAdPlacementMedia alloc] initWithPid:84242
articleUrl:articleUrl
delegate:self];
}
@end
@implementation YourViewController (UICollectionViewDelegateFlowLayout)
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat width = collectionView.frame.size.width;
// For the ad cell, return the dynamic height
if (indexPath.item == 2) { // Assuming ad is at index 2
return CGSizeMake(width, self.adCellHeight);
}
// Return fixed height for other cells
return CGSizeMake(width, 100.0);
}
@end
@implementation YourViewController (UICollectionViewDataSource)
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 10; // Example: 10 items with ad at index 2
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.item == 2) {
// Ad cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AdCell" forIndexPath:indexPath];
NSError *error = nil;
UIView *adView = [self.placement loadAdAndReturnError:&error];
if (adView) {
for (UIView *subview in cell.contentView.subviews) {
[subview removeFromSuperview];
}
[cell.contentView addSubview:adView];
adView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[adView.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor],
[adView.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor],
[adView.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor],
[adView.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor]
]];
}
return cell;
} else {
// Regular content cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ContentCell" forIndexPath:indexPath];
// Configure your content cell...
return cell;
}
}
@end
@implementation YourViewController (TeadsAdPlacementEventsDelegate)
- (void)adPlacement:(id<TeadsAdPlacementIdentifiable>)placement
didEmitEvent:(TeadsAdPlacementEventName)event
data:(NSDictionary<NSString *, id> *)data {
if (event == TeadsAdPlacementEventNameHeightUpdated) {
NSNumber *height = data[@"height"];
if (height) {
// Update the stored height
self.adCellHeight = height.doubleValue;
// Reload the ad cell to apply new size
NSIndexPath *adIndexPath = [NSIndexPath indexPathForItem:2 inSection:0];
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadItemsAtIndexPaths:@[adIndexPath]];
} completion:nil];
}
}
}
@end
Next Steps
- Review the Migration Guide for Teads Users
- Review the Migration Guide for Outbrain Users
- Learn about Privacy & Compliance
For additional support, see Support