Skip to main content

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.

info

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


For additional support, see Support