Skip to main content

Installation

info

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

Using CocoaPods

  1. Add pod 'TeadsSDK', '~> 6.2' into your Podfile.
  2. Run pod install.
  3. Open workspace file and run the project.
platform :ios, '14.0'
use_frameworks!

target 'YourApp' do
pod 'TeadsSDK', '~> 6.2'
end

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

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
}
}

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:

// Enable test mode
Teads.testMode = true

Crash Monitoring

Crash monitoring is enabled by default. To disable:

Teads.isCrashMonitoringEnabled = false

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

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)")
}
}
}
Do not open the URL manually

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

  1. Width Sizing Issue

    • Problem: Feed/Media shows 0 width even though height updates correctly
    • Solution: Always add .frame(maxWidth: .infinity)

    Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI View, and SwiftUI has no Objective-C equivalent.

    TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(config: config, delegate: nil)
    .frame(maxWidth: .infinity) // ← Don't forget this!
  2. 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 config
    darkMode: colorScheme == .dark
  3. 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() }
    }
    }
    }
  4. 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 constraints
    return container // ← Return container, not adView
    }
  5. List/LazyVStack Integration

    • Problem: Placements in scrollable lists may have rendering issues
    • Solution: Use fixed frame heights and proper view recycling

    Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI View, and SwiftUI has no Objective-C equivalent.

    LazyVStack {
    ForEach(items) { item in
    if 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:

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
}
}

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):

if let adView = try? placement?.loadAd() {
//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:

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])
}
}
}
}

Next Steps


For additional support, see Support