Feed Placement
Feed Placement (Content Recommendations)
Feed placements display content recommendation widget, perfect for keeping users engaged with related content.
UIKit Implementation
- Swift
- Objective-C
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)
}
}
}
#import <UIKit/UIKit.h>
#import <TeadsSDK/TeadsSDK-Swift.h>
@interface ContentViewController : UIViewController
@property (nonatomic, strong) TeadsAdPlacementFeed *feedPlacement;
@property (nonatomic, strong) UIView *feedView;
@end
@implementation ContentViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupFeedPlacement];
}
- (void)setupFeedPlacement {
BOOL isDarkMode = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
// Create the placement directly with the Objective-C-friendly convenience
// initializer — all Swift default parameters must be passed explicitly
self.feedPlacement = [[TeadsAdPlacementFeed alloc] initWithArticleUrl:[NSURL URLWithString:@"https://example.com/article/123"]
widgetId:@"MB_1"
installationKey:@"NANOWDGT01"
widgetIndex:0
userId:nil // Optional user ID for personalization
darkMode:isDarkMode
extId:nil
extSecondaryId:nil
obPubImp:nil
delegate:self];
// getAdView() is used instead of loadAd() — loadAd() returns a Swift-only
// "any UIView & DarkModeTogglable" existential that does not bridge to Objective-C
self.feedView = [self.feedPlacement getAdView];
if (self.feedView) {
[self addFeedToView:self.feedView];
}
}
- (void)addFeedToView:(UIView *)feedView {
[self.view addSubview:feedView];
feedView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[feedView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
[feedView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
[feedView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-20]
]];
}
// Handle dark mode changes
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (self.traitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
// Update dark mode for the feed
[self.feedPlacement toggleDarkMode:self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark];
}
}
@end
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.
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:
- Swift
- Objective-C
// 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
}
}
// Enable explore more when view disappears
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
__weak typeof(self) weakSelf = self;
[self.feedPlacement showExploreMoreOnExploreMoreDismissed:^{
// Called when explore more is dismissed
weakSelf.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.
- Swift
- Objective-C
// Enable explore more when user taps back
func backTapped() {
feedPlacement?.showExploreMore { [weak self] in
// Called when explore more is dismissed
// Perform the back navigation
}
}
// Enable explore more when user taps back
- (void)backTapped {
[self.feedPlacement showExploreMoreOnExploreMoreDismissed:^{
// 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.