Teads iOS SDK Integration Guide
This guide provides comprehensive instructions for integrating the Teads SDK into your iOS application. Whether you're building a news app, content platform, or any other type of application, this guide will help you implement both video advertising and content recommendations.
Before starting: Make sure you've installed the Teads SDK. See the Installation Guide for CocoaPods, Swift Package Manager, or manual installation instructions.
SDK Initialization
Initialize the SDK as early as possible in your app's lifecycle:
SwiftUI
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
    }
}
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
Media Placement (Video Ads)
Media placements display premium video advertisements within your content.
UIKit Implementation
import UIKit
import TeadsSDK
class ArticleViewController: UIViewController {
    private var mediaPlacement: TeadsAdPlacementMedia?
    private var adView: UIView?
    override func viewDidLoad() {
        super.viewDidLoad()
        setupMediaPlacement()
    }
    private func setupMediaPlacement() {
        // Create configuration
        let config = TeadsAdPlacementMediaConfig(
            pid: 84242,  // Your placement ID
            articleUrl: URL(string: "https://example.com/article/123")
        )
        // Create placement with delegate
        mediaPlacement = TeadsAdPlacementMedia(config, delegate: self)
        // OR
        mediaPlacement = Teads.createPlacement(
            with: config,
            delegate: self
        )
        // Load the ad
        do {
            adView = try mediaPlacement?.loadAd()
            // Add to your view hierarchy
            if let adView = adView {
                insertAdViewInContent(adView)
            }
        } catch {
            print("Failed to load ad: \(error)")
        }
    }
    private func insertAdViewInContent(_ adView: UIView) {
        // Add the ad view to your content
        // For example, in a scroll view between paragraphs
        contentStackView.insertArrangedSubview(adView, at: 2)
        // The SDK handles sizing automatically
        adView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            adView.leadingAnchor.constraint(equalTo: contentStackView.leadingAnchor),
            adView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor)
        ])
    }
}
// MARK: - TeadsAdPlacementEventsDelegate
extension ArticleViewController: TeadsAdPlacementEventsDelegate {
    func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
                    didEmitEvent event: TeadsAdPlacementEventName,
                    data: [String : Any]?) {
        switch event {
        case .ready:
            print("Ad is ready to be displayed")
        case .rendered:
            print("Ad has been rendered")
        case .viewed:
            print("Ad has met viewability criteria")
        case .clicked:
            print("User clicked on the ad")
        case .failed:
            if let error = data?["error"] as? String {
                print("Ad failed to load: \(error)")
            }
        case .heightUpdated:
            if let height = data?["height"] as? CGFloat {
                print("Ad height updated to: \(height)")
                // No need to do anything, the placement view will handle it's own size.
            }
        default:
            break
        }
    }
}
SwiftUI Implementation
import SwiftUI
import TeadsSDK
struct ArticleView: View {
    @StateObject private var adManager = MediaAdManager()
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                Text("Article Title")
                    .font(.largeTitle)
                Text("First paragraph of content...")
                // Media Ad Placement
                if let adView = adManager.adView {
                    MediaAdContainer(adView: adView)
                        .frame(height: adManager.adHeight)
                }
                Text("More article content...")
            }
            .padding()
        }
        .onAppear {
            adManager.loadAd()
        }
    }
}
// UIViewRepresentable wrapper for the ad view
struct MediaAdContainer: UIViewRepresentable {
    let adView: UIView
    func makeUIView(context: Context) -> UIView {
        return adView
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}
// Ad Manager
class MediaAdManager: NSObject, ObservableObject {
    @Published var adView: UIView?
    @Published var adHeight: CGFloat = 0
    private var placement: TeadsAdPlacementMedia?
    func loadAd() {
        let config = TeadsAdPlacementMediaConfig(
            pid: 84242,
            articleUrl: URL(string: "https://example.com/article/123")
        )
        placement = TeadsAdPlacementMedia(config, delegate: self)
        // OR
        placement = Teads.createPlacement(
            with: config,
            delegate: self
        )
        do {
            adView = try placement?.loadAd()
        } catch {
            print("Failed to load ad: \(error)")
        }
    }
}
extension MediaAdManager: TeadsAdPlacementEventsDelegate {
    func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
                    didEmitEvent event: TeadsAdPlacementEventName,
                    data: [String : Any]?) {
        if event == .heightUpdated, let height = data?["height"] as? CGFloat {
            DispatchQueue.main.async {
                self.adHeight = height
            }
        }
    }
}
Feed Placement (Content Recommendations)
Feed placements display content recommendation widget, perfect for keeping users engaged with related content.
UIKit Implementation
import UIKit
import TeadsSDK
class ContentViewController: UIViewController {
    private var feedPlacement: TeadsAdPlacementFeed?
    private var feedView: UIView?
    override func viewDidLoad() {
        super.viewDidLoad()
        setupFeedPlacement()
    }
    private func setupFeedPlacement() {
        // Create configuration
        let config = TeadsAdPlacementFeedConfig(
            articleUrl: URL(string: "https://example.com/article/123")!,
            widgetId: "MB_1",
            installationKey: "NANOWDGT01",
            widgetIndex: 0,
            userId: nil,  // Optional user ID for personalization
            darkMode: traitCollection.userInterfaceStyle == .dark
        )
        // Create placement
        feedPlacement = TeadsAdPlacementFeed(config, delegate: self)
        // Load the feed
        do {
            feedView = try feedPlacement?.loadAd()
            if let feedView = feedView {
                addFeedToView(feedView)
            }
        } catch {
            print("Failed to load feed: \(error)")
        }
    }
    private func addFeedToView(_ feedView: UIView) {
        view.addSubview(feedView)
        feedView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            feedView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            feedView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            feedView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20)
        ])
    }
    // Handle dark mode changes
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        if traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle {
            // Update dark mode for the feed
            feedPlacement?.toggleDarkMode(traitCollection.userInterfaceStyle == .dark)
        }
    }
}
Explore More Feature
Option 1: viewWillDisappear
The Feed placement supports an "Explore More" feature that shows additional content when users navigate away:
// Enable explore more when view disappears
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    feedPlacement?.showExploreMore { [weak self] in
        // Called when explore more is dismissed
        self?.feedPlacement = nil
    }
}
Option 2: Navigation object
In the view model (or other object responsible for navigation) when you detect user tap on the back button to exit the content screen, call the explore more instead of performing the navigation and perform the actual navigation when the explore more is dismissed.
// Enable explore more when user taps back
func backTapped() {
    feedPlacement?.showExploreMore { [weak self] in
        // Called when explore more is dismissed
        // Perform the back navigation
    }
}
Media Native Placement
Media Native placements allow you to create custom layouts for video ads that match your app's design.
import UIKit
import TeadsSDK
class NativeAdViewController: UIViewController {
    private var nativePlacement: TeadsAdPlacementMediaNative?
    private var nativeAdView: TeadsNativeAdView?
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNativeAd()
    }
    private func setupNativeAd() {
        // Create custom native ad view
        nativeAdView = createCustomNativeAdView()
        // Create configuration
        let config = TeadsAdPlacementMediaConfig(
            pid: 84242,
            articleUrl: URL(string: "https://example.com/article/123")
        )
        // Create placement
        nativePlacement = TeadsAdPlacementMediaNative(config, delegate: self)
        // Load and bind the ad
        do {
            let binder = try nativePlacement?.loadAd()
            if let nativeAdView = nativeAdView {
                binder?(nativeAdView)
                addNativeAdToView(nativeAdView)
            }
        } catch {
            print("Failed to load native ad: \(error)")
        }
    }
    private func createCustomNativeAdView() -> TeadsNativeAdView {
        // Create your custom native ad view
        // This can be designed in Interface Builder or programmatically
        let adView = TeadsNativeAdView(frame: .zero)
        // Customize the appearance to match your app
        adView.backgroundColor = .systemBackground
        adView.layer.cornerRadius = 12
        adView.layer.shadowColor = UIColor.black.cgColor
        adView.layer.shadowOpacity = 0.1
        adView.layer.shadowRadius = 8
        return adView
    }
    private func addNativeAdToView(_ adView: UIView) {
        view.addSubview(adView)
        adView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            adView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            adView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            adView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            adView.heightAnchor.constraint(equalToConstant: 250)
        ])
    }
}
Recommendations API
For programmatic access to content recommendations without UI:
import TeadsSDK
class RecommendationsManager {
    private var recommendationsPlacement: TeadsAdPlacementRecommendations?
    func fetchRecommendations() async throws -> [OBRecommendation] {
        // Create configuration
        let config = TeadsAdPlacementRecommendationsConfig(
            articleUrl: URL(string: "https://example.com/article/123")!,
            widgetId: "SDK_1",
            widgetIndex: 0
        )
        // Create placement
        recommendationsPlacement = TeadsAdPlacementRecommendations(config, delegate: nil)
        // Fetch recommendations
        let loader = try recommendationsPlacement?.loadAd()
        let recommendations = try await loader?()
        return recommendations ?? []
    }
    // Custom UI for recommendations
    func createCustomRecommendationView(for recommendation: OBRecommendation) -> UIView {
        let view = UIView()
        // Add image
        let imageView = UIImageView()
        if let imageUrl = URL(string: recommendation.imageUrl ?? "") {
            // Load image asynchronously
            URLSession.shared.dataTask(with: imageUrl) { data, _, _ in
                if let data = data, let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        imageView.image = image
                    }
                }
            }.resume()
        }
        // Add title
        let titleLabel = UILabel()
        titleLabel.text = recommendation.title
        titleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
        titleLabel.numberOfLines = 2
        // Add source
        let sourceLabel = UILabel()
        sourceLabel.text = recommendation.sourceName
        sourceLabel.font = .systemFont(ofSize: 12)
        sourceLabel.textColor = .secondaryLabel
        // Layout views...
        return view
    }
}
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:
            if let url = data?["url"] as? String {
                print("Ad clicked: \(url)")
            }
        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)")
        }
    }
}
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
- Review the Migration Guide for Teads Users
- Review the Migration Guide for Outbrain Users
- Learn about Privacy & Compliance
For additional support, see Support