Skip to main content

Feed Placement

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

SwiftUI Feed Implementation (Simplified Approach)

The SDK provides a native SwiftUI view for Feed placements:

import SwiftUI
import TeadsSDK

struct ContentView: View {
let feedConfig = TeadsAdPlacementFeedConfig(
articleUrl: URL(string: "https://example.com/article/123")!,
widgetId: "MB_1",
installationKey: "NANOWDGT01",
widgetIndex: 0,
userId: nil,
darkMode: false
)

var body: some View {
ScrollView {
VStack(spacing: 16) {
Text("Article Title")
.font(.largeTitle)

Text("Article content goes here...")
.padding(.bottom, 20)

// Feed Placement - Simple one-liner
TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(
config: feedConfig,
delegate: nil // Optional delegate
)
.frame(maxWidth: .infinity) // Important: Set width explicitly
}
.padding()
}
}
}

Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI struct, and SwiftUI views are not bridgeable to Objective-C.

SwiftUI Feed Implementation (With Event Handling)

If you need to handle events or control the Feed placement:

import SwiftUI
import TeadsSDK

struct ContentView: View {
@StateObject private var eventHandler = FeedEventHandler()

let feedConfig = TeadsAdPlacementFeedConfig(
articleUrl: URL(string: "https://example.com/article/123")!,
widgetId: "MB_1",
installationKey: "NANOWDGT01",
widgetIndex: 0,
userId: nil,
darkMode: false
)

var body: some View {
ScrollView {
VStack(spacing: 16) {
Text("Article Title")
.font(.largeTitle)

Text("Article content goes here...")
.padding(.bottom, 20)

// Feed Placement with delegate
TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(
config: feedConfig,
delegate: eventHandler
)
.frame(maxWidth: .infinity) // Important: Set width explicitly

if eventHandler.isLoaded {
Text("Feed loaded successfully!")
.foregroundColor(.green)
}
}
.padding()
}
}
}

// Simple event handler
class FeedEventHandler: NSObject, ObservableObject, TeadsAdPlacementEventsDelegate {
@Published var isLoaded = false
@Published var hasError = false

func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
didEmitEvent event: TeadsAdPlacementEventName,
data: [String : Any]?) {

DispatchQueue.main.async {
switch event {
case .ready, .rendered:
self.isLoaded = true
case .failed:
self.hasError = true
if let error = data?["error"] as? String {
print("Feed failed: \(error)")
}
case .clickedOrganic:
if let url = data?["url"] as? String {
print("Organic content clicked: \(url)")
// Handle navigation
}
default:
break
}
}
}
}

Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI struct, and SwiftUI views are not bridgeable to Objective-C.

Important - SwiftUI Width Sizing

Always add .frame(maxWidth: .infinity) to TeadsAdPlacementSwiftUIView to ensure proper width sizing. Without this modifier, the view may render with 0 width even though the height updates correctly.

SwiftUI Dark Mode Support

The Feed placement automatically adapts to the system's color scheme:

import SwiftUI
import TeadsSDK

struct ContentView: View {
@Environment(\.colorScheme) var colorScheme

var body: some View {
VStack {
// Feed automatically uses the current color scheme
TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(
config: TeadsAdPlacementFeedConfig(
articleUrl: URL(string: "https://example.com/article")!,
widgetId: "MB_1",
installationKey: "NANOWDGT01",
widgetIndex: 0,
userId: nil,
darkMode: colorScheme == .dark // Sync with system
),
delegate: nil
)
.frame(maxWidth: .infinity)
}
}
}

Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI struct, and SwiftUI views are not bridgeable to Objective-C.

For dynamic dark mode toggling after initialization:

struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@StateObject private var feedViewModel = FeedViewModel()

var body: some View {
VStack {
TeadsAdPlacementSwiftUIView<TeadsAdPlacementFeed>(
config: feedViewModel.config,
delegate: feedViewModel
)
.frame(maxWidth: .infinity)
.onChange(of: colorScheme) { newColorScheme in
// Note: toggleDarkMode requires keeping a reference to the placement
// This is a limitation when using the simplified SwiftUI approach
// For dynamic dark mode, consider using the manual approach with FeedManager
}
}
}
}

// Minimal FeedViewModel for dynamic configuration
class FeedViewModel: NSObject, ObservableObject, TeadsAdPlacementEventsDelegate {
var config: TeadsAdPlacementFeedConfig

init() {
self.config = TeadsAdPlacementFeedConfig(
articleUrl: URL(string: "https://example.com/article")!,
widgetId: "MB_1",
installationKey: "NANOWDGT01",
widgetIndex: 0,
userId: nil,
darkMode: false
)
}

func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
didEmitEvent event: TeadsAdPlacementEventName,
data: [String : Any]?) {
// Handle events as needed
}
}

Swift-only: TeadsAdPlacementSwiftUIView is a generic SwiftUI struct, and SwiftUI views are not bridgeable to Objective-C.

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

SwiftUI Implementation

For SwiftUI apps, handling Explore More requires keeping a reference to the placement:

import SwiftUI
import UIKit
import TeadsSDK

// UIViewRepresentable wrapper for the feed view
struct FeedContainer: UIViewRepresentable {
let feedView: UIView

func makeUIView(context: Context) -> UIView {
let containerView = UIView()
containerView.backgroundColor = UIColor.clear

// Add the feed view to the container with proper constraints
containerView.addSubview(feedView)
feedView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
feedView.topAnchor.constraint(equalTo: containerView.topAnchor),
feedView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
feedView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
feedView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])

return containerView
}

func updateUIView(_ uiView: UIView, context: Context) {}
}

struct ArticleView: View {
@StateObject private var feedManager = FeedManagerWithExploreMore()
@Environment(\.dismiss) var dismiss

var body: some View {
ScrollView {
VStack {
// Article content
Text("Article content...")

// Feed placement
if let feedView = feedManager.feedView {
FeedContainer(feedView: feedView)
.frame(maxWidth: .infinity)
.frame(height: feedManager.feedHeight)
}
}
}
.onAppear {
feedManager.loadFeed()
}
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Back") {
// Show explore more before dismissing
feedManager.showExploreMoreAndDismiss {
dismiss()
}
}
}
}
}
}

class FeedManagerWithExploreMore: NSObject, ObservableObject {
@Published var feedView: UIView?
@Published var feedHeight: CGFloat = 400
private var feedPlacement: TeadsAdPlacementFeed?

func loadFeed() {
let config = TeadsAdPlacementFeedConfig(
articleUrl: URL(string: "https://example.com/article")!,
widgetId: "MB_1",
installationKey: "NANOWDGT01",
widgetIndex: 0
)

feedPlacement = Teads.createPlacement(with: config, delegate: self)

do {
feedView = try feedPlacement?.loadAd()
} catch {
print("Failed to load feed: \(error)")
}
}

func showExploreMoreAndDismiss(completion: @escaping () -> Void) {
feedPlacement?.showExploreMore {
completion()
}
}
}

extension FeedManagerWithExploreMore: TeadsAdPlacementEventsDelegate {
func adPlacement(_ placement: TeadsAdPlacementIdentifiable?,
didEmitEvent event: TeadsAdPlacementEventName,
data: [String : Any]?) {
if event == .heightUpdated, let height = data?["height"] as? CGFloat {
DispatchQueue.main.async {
self.feedHeight = height
}
}
}
}

Swift-only: this sample's FeedContainer/ArticleView are SwiftUI View/UIViewRepresentable types, which are not bridgeable to Objective-C. For the underlying "Explore More" call pattern in Objective-C, see Option 1 and Option 2 above.