diff --git a/Podfile b/Podfile index e5b5d1f3..38d02c77 100644 --- a/Podfile +++ b/Podfile @@ -7,11 +7,11 @@ target 'TeadsSampleApp' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! - pod 'TeadsSDK', '5.5.1' + pod 'TeadsSDK', '6.0.0' - pod 'TeadsSASAdapter', '5.5.1' - pod 'TeadsAdMobAdapter', '5.5.1' - pod 'TeadsAppLovinAdapter', '5.5.1' + pod 'TeadsSASAdapter', '6.0.0' + pod 'TeadsAdMobAdapter', '6.0.0' + pod 'TeadsAppLovinAdapter', '6.0.0' pod 'SwiftFormat/CLI' end diff --git a/Podfile.lock b/Podfile.lock index 74a3ed28..2345f799 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,29 +1,29 @@ PODS: - - AppLovinSDK (13.3.1) - - Google-Mobile-Ads-SDK (12.7.0): + - AppLovinSDK (13.5.0) + - Google-Mobile-Ads-SDK (12.12.0): - GoogleUserMessagingPlatform (>= 1.1) - - GoogleUserMessagingPlatform (3.0.0) + - GoogleUserMessagingPlatform (3.1.0) - Smart-Display-SDK (7.24.2) - SwiftFormat/CLI (0.56.4) - - TeadsAdMobAdapter (5.5.1): + - TeadsAdMobAdapter (6.0.0): - Google-Mobile-Ads-SDK (>= 12.2.0) - - TeadsSDK (= 5.5.1) - - TeadsAppLovinAdapter (5.5.1): + - TeadsSDK (= 6.0.0) + - TeadsAppLovinAdapter (6.0.0): - AppLovinSDK (>= 11.5.1) - - TeadsSDK (= 5.5.1) - - TeadsSASAdapter (5.5.1): + - TeadsSDK (= 6.0.0) + - TeadsSASAdapter (6.0.0): - Smart-Display-SDK (>= 7.6.2) - - TeadsSDK (= 5.5.1) - - TeadsSDK (5.5.1): - - TeadsSDK/Core (= 5.5.1) - - TeadsSDK/Core (5.5.1) + - TeadsSDK (= 6.0.0) + - TeadsSDK (6.0.0): + - TeadsSDK/Core (= 6.0.0) + - TeadsSDK/Core (6.0.0) DEPENDENCIES: - SwiftFormat/CLI - - TeadsAdMobAdapter (= 5.5.1) - - TeadsAppLovinAdapter (= 5.5.1) - - TeadsSASAdapter (= 5.5.1) - - TeadsSDK (= 5.5.1) + - TeadsAdMobAdapter (= 6.0.0) + - TeadsAppLovinAdapter (= 6.0.0) + - TeadsSASAdapter (= 6.0.0) + - TeadsSDK (= 6.0.0) SPEC REPOS: trunk: @@ -38,16 +38,16 @@ SPEC REPOS: - TeadsSDK SPEC CHECKSUMS: - AppLovinSDK: 5075837afdee4fb429121799b8cbcc5918ef3a23 - Google-Mobile-Ads-SDK: 90a3936b11fcb1a9a69b2a33fb608e793666ce73 - GoogleUserMessagingPlatform: f8d0cdad3ca835406755d0a69aa634f00e76d576 + AppLovinSDK: bf8974163120910e6b902e9610e7c5a2c0f577b6 + Google-Mobile-Ads-SDK: 4dde70a8c18d96b14f9548759b8cec6ecb0bc3e6 + GoogleUserMessagingPlatform: befe603da6501006420c206222acd449bba45a9c Smart-Display-SDK: e2c9c881236a0a9f0f6e99e2a2410ca74d9bb41a SwiftFormat: 40d72dd2f67aa4d8ed5deb5e30011067c986f9cb - TeadsAdMobAdapter: b8d300374bac7c6e23115ae6ddb492cefa2d4be4 - TeadsAppLovinAdapter: 1548d97367cd4d0c3ae691a082a082a8d534542d - TeadsSASAdapter: 540464b2b592ef6c8770e734511e6341ee464f32 - TeadsSDK: 35d63fcaf798494f84fc61c19a56e4bc0c86a66e + TeadsAdMobAdapter: 217491fb7645382dcb7722bb52f47d8eddcb8115 + TeadsAppLovinAdapter: c365029a91c7d16a8b9fafa220dd1819073f117b + TeadsSASAdapter: 60b3e09e1c0ca24faea08cda75f788c1d4f66eed + TeadsSDK: 3f3f102735c45aa127becc84017d85060dae67e5 -PODFILE CHECKSUM: 2a1c753588923a4acdf293c9321c923d3e8a3a54 +PODFILE CHECKSUM: b1e6f42268f24ac51b54ad1b2bbd8454ea14611d COCOAPODS: 1.16.2 diff --git a/TeadsSampleApp.xcodeproj/project.pbxproj b/TeadsSampleApp.xcodeproj/project.pbxproj index 21dd16ad..4df0c71d 100644 --- a/TeadsSampleApp.xcodeproj/project.pbxproj +++ b/TeadsSampleApp.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 28AED0FC9C839CB0F08FB2ED /* Pods_TeadsSampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25FE69E124DD7520D4468526 /* Pods_TeadsSampleApp.framework */; }; + 3C4D052A2EB8AD280050513C /* CustomNativeAdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4D05292EB8AD280050513C /* CustomNativeAdView.swift */; }; 4079F243252CBE7F00B0AC31 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4079F242252CBE7F00B0AC31 /* ColorExtension.swift */; }; 4079F253252CC1EF00B0AC31 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4079F252252CC1EF00B0AC31 /* RootViewController.swift */; }; 408A2D49253D995200CCFF44 /* RootButtonCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408A2D46253D995200CCFF44 /* RootButtonCollectionViewCell.swift */; }; @@ -71,6 +72,7 @@ /* Begin PBXFileReference section */ 25FE69E124DD7520D4468526 /* Pods_TeadsSampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TeadsSampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3C4D05292EB8AD280050513C /* CustomNativeAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNativeAdView.swift; sourceTree = ""; }; 40077B422534AABF00D53197 /* InReadDirectCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InReadDirectCollectionViewController.swift; sourceTree = ""; }; 4079F242252CBE7F00B0AC31 /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; 4079F252252CC1EF00B0AC31 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; @@ -384,6 +386,7 @@ B5555A4B1F7CEF1C007406D4 /* Cells */ = { isa = PBXGroup; children = ( + 3C4D05292EB8AD280050513C /* CustomNativeAdView.swift */, E9984EF726B2D46F000D42D3 /* AdmobNativeAdTableViewCell.swift */, E9984EF526B2D46F000D42D3 /* FakeArticleNativeTableViewCell.swift */, E9984EF626B2D46F000D42D3 /* NativeTableViewCell.swift */, @@ -795,6 +798,7 @@ 4079F243252CBE7F00B0AC31 /* ColorExtension.swift in Sources */, E96C1F9A27E4902D00151530 /* UIView-extension.swift in Sources */, E9B7170627C7965900FB7DCB /* InReadAppLovinScrollViewController.swift in Sources */, + 3C4D052A2EB8AD280050513C /* CustomNativeAdView.swift in Sources */, B5966D1F269D9982005CA2FF /* InReadAdmobWebViewController.swift in Sources */, E9984EED26AF3CCC000D42D3 /* NativeAdmobTableViewController.swift in Sources */, ); diff --git a/TeadsSampleApp/Cells/CustomNativeAdView.swift b/TeadsSampleApp/Cells/CustomNativeAdView.swift new file mode 100644 index 00000000..d5004b15 --- /dev/null +++ b/TeadsSampleApp/Cells/CustomNativeAdView.swift @@ -0,0 +1,223 @@ +// +// CustomNativeAdView.swift +// demo +// +// Created by Assistant on 13/08/2025. +// + +import TeadsSDK +import UIKit + +/// Factory for creating customized TeadsNativeAdView instances +@available(iOS 13.0, *) +class CustomNativeAdView { + static func create() -> TeadsNativeAdView { + let nativeAdView = TeadsNativeAdView(frame: .zero) + setupCustomLayout(for: nativeAdView) + return nativeAdView + } + + private static func setupCustomLayout(for adView: TeadsNativeAdView) { +// adView.translatesAutoresizingMaskIntoConstraints = false + adView.backgroundColor = .systemBackground + adView.layer.cornerRadius = 12 + adView.layer.borderWidth = 1 + adView.layer.borderColor = UIColor.systemGray4.cgColor + + // Add shadow + adView.layer.shadowColor = UIColor.black.cgColor + adView.layer.shadowOpacity = 0.1 + adView.layer.shadowOffset = CGSize(width: 0, height: 2) + adView.layer.shadowRadius = 4 + + // Create and configure UI components + let containerStackView = createContainerStack() + let headerStackView = createHeaderStack() + let contentStackView = createContentStack() + + // Create native ad components + let titleLabel = createTitleLabel() + let contentLabel = createContentLabel() + let mediaView = createMediaView() + let iconImageView = createIconImageView() + let advertiserLabel = createAdvertiserLabel() + let callToActionButton = createCallToActionButton() + let sponsoredLabel = createSponsoredLabel() + + // Connect outlets + adView.titleLabel = titleLabel + adView.contentLabel = contentLabel + adView.mediaView = mediaView + adView.iconImageView = iconImageView + adView.advertiserLabel = advertiserLabel + adView.callToActionButton = callToActionButton + + // Setup hierarchy + adView.addSubview(containerStackView) + + // Header with icon and advertiser + headerStackView.addArrangedSubview(iconImageView) + headerStackView.addArrangedSubview(advertiserLabel) + headerStackView.addArrangedSubview(UIView()) // Spacer + headerStackView.addArrangedSubview(sponsoredLabel) + + // Content section + contentStackView.addArrangedSubview(titleLabel) + contentStackView.addArrangedSubview(contentLabel) + contentStackView.addArrangedSubview(mediaView) + contentStackView.addArrangedSubview(callToActionButton) + + // Main container + containerStackView.addArrangedSubview(headerStackView) + containerStackView.addArrangedSubview(contentStackView) + + // Setup constraints + setupConstraints( + containerStackView: containerStackView, + iconImageView: iconImageView, + mediaView: mediaView, + sponsoredLabel: sponsoredLabel, + callToActionButton: callToActionButton, + adView: adView + ) + } + + // MARK: - Component Factories + + private static func createContainerStack() -> UIStackView { + let stack = UIStackView() + stack.axis = .vertical + stack.spacing = 12 + stack.distribution = .fill + stack.alignment = .fill + stack.translatesAutoresizingMaskIntoConstraints = false + return stack + } + + private static func createHeaderStack() -> UIStackView { + let stack = UIStackView() + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .horizontal + stack.spacing = 8 + stack.alignment = .center + stack.distribution = .fill + return stack + } + + private static func createContentStack() -> UIStackView { + let stack = UIStackView() + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 8 + stack.distribution = .fill + stack.alignment = .fill + return stack + } + + private static func createTitleLabel() -> UILabel { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.boldSystemFont(ofSize: 16) + label.textColor = .label + label.numberOfLines = 2 + return label + } + + private static func createContentLabel() -> UILabel { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 14) + label.textColor = .secondaryLabel + label.numberOfLines = 3 + return label + } + + private static func createMediaView() -> TeadsMediaView { + let mediaView = TeadsMediaView() + mediaView.translatesAutoresizingMaskIntoConstraints = false + mediaView.contentMode = .scaleAspectFill + mediaView.clipsToBounds = true + mediaView.layer.cornerRadius = 8 + mediaView.backgroundColor = .systemGray5 + return mediaView + } + + private static func createIconImageView() -> UIImageView { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.layer.cornerRadius = 16 + imageView.backgroundColor = .systemGray4 + return imageView + } + + private static func createAdvertiserLabel() -> UILabel { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 12, weight: .medium) + label.textColor = .tertiaryLabel + label.numberOfLines = 1 + return label + } + + private static func createCallToActionButton() -> UIButton { + let button = UIButton(type: .system) + button.translatesAutoresizingMaskIntoConstraints = false + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14) + button.backgroundColor = .systemBlue + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 6 + return button + } + + private static func createSponsoredLabel() -> UILabel { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "Sponsored" + label.font = UIFont.systemFont(ofSize: 10) + label.textColor = .tertiaryLabel + label.backgroundColor = .systemGray6 + label.textAlignment = .center + label.layer.cornerRadius = 4 + label.clipsToBounds = true + return label + } + + // MARK: - Constraints + + private static func setupConstraints( + containerStackView: UIStackView, + iconImageView: UIImageView, + mediaView: TeadsMediaView, + sponsoredLabel: UILabel, + callToActionButton: UIButton, + adView: TeadsNativeAdView + ) { + NSLayoutConstraint.activate([ + // Container + containerStackView.topAnchor.constraint(equalTo: adView.topAnchor, constant: 12), + containerStackView.leadingAnchor.constraint( + equalTo: adView.leadingAnchor, constant: 12 + ), + containerStackView.trailingAnchor.constraint( + equalTo: adView.trailingAnchor, constant: -12 + ), + containerStackView.bottomAnchor.constraint(equalTo: adView.bottomAnchor, constant: -12), + + // Icon + iconImageView.widthAnchor.constraint(equalToConstant: 32), + iconImageView.heightAnchor.constraint(equalToConstant: 32), + + // Media view + mediaView.heightAnchor.constraint(equalToConstant: 160), + + // Sponsored label + sponsoredLabel.widthAnchor.constraint(equalToConstant: 70), + sponsoredLabel.heightAnchor.constraint(equalToConstant: 18), + + // CTA button + callToActionButton.heightAnchor.constraint(equalToConstant: 36), + ]) + } +} diff --git a/TeadsSampleApp/Controllers/InRead/Direct/CollectionView/InReadDirectCollectionViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/CollectionView/InReadDirectCollectionViewController.swift index 5122dcd1..6c9d425c 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/CollectionView/InReadDirectCollectionViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/CollectionView/InReadDirectCollectionViewController.swift @@ -12,7 +12,7 @@ import UIKit class InReadDirectCollectionViewController: TeadsViewController { enum TeadsElement: Equatable { case article - case ad(_ ad: TeadsInReadAd) + case adView(_ adView: UIView) case trackerView(_ trackerView: TeadsAdOpportunityTrackerView) } @@ -26,7 +26,7 @@ class InReadDirectCollectionViewController: TeadsViewController { return trackerViewItemNumber + 1 } - var placement: TeadsInReadAdPlacement? + var placement: TeadsAdPlacementMedia? private var elements = [TeadsElement]() @@ -41,11 +41,22 @@ class InReadDirectCollectionViewController: TeadsViewController { settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createInReadPlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) + + // Load ad with unified API + do { + if let adView = try placement?.loadAd() { + elements.insert(.adView(adView), at: adItemNumber) + collectionView.insertItems(at: [IndexPath(item: adItemNumber, section: 0)]) + } + } catch { + print("Failed to load ad: \(error)") + } collectionView.register(AdOpportunityTrackerCollectionViewCell.self, forCellWithReuseIdentifier: AdOpportunityTrackerCollectionViewCell.identifier) } @@ -59,17 +70,24 @@ class InReadDirectCollectionViewController: TeadsViewController { collectionView.collectionViewLayout.invalidateLayout() } - func closeSlot(ad: TeadsAd) { - guard let inReadAd = ad as? TeadsInReadAd else { - return + func closeSlot(adView: UIView) { + elements.removeAll { + if case let .adView(view) = $0 { + return view == adView + } + return false } - elements.removeAll { $0 == .ad(inReadAd) } collectionView.reloadData() } - func updateAdSize(ad: TeadsInReadAd) { - if let row = elements.firstIndex(of: .ad(ad)) { - collectionView.reloadItems(at: [IndexPath(row: row, section: 0)]) + func updateAdSize(adView: UIView) { + if let row = elements.firstIndex(where: { + if case let .adView(view) = $0 { + return view == adView + } + return false + }) { + collectionView.reloadItems(at: [IndexPath(item: row, section: 0)]) collectionView.collectionViewLayout.invalidateLayout() } } @@ -83,11 +101,14 @@ extension InReadDirectCollectionViewController: UICollectionViewDelegate, UIColl func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if indexPath.item == 0 { return collectionView.dequeueReusableCell(withReuseIdentifier: contentCell, for: indexPath) - } else if case let .ad(ad) = elements[indexPath.row] { + } else if case let .adView(adView) = elements[indexPath.row] { let cellAd = collectionView.dequeueReusableCell(withReuseIdentifier: teadsAdCellIndentifier, for: indexPath) - let teadsAdView = TeadsInReadAdView(bind: ad) - cellAd.contentView.addSubview(teadsAdView) - teadsAdView.setupConstraintsToFitSuperView(horizontalMargin: 10) + cellAd.contentView.addSubview(adView) + adView.translatesAutoresizingMaskIntoConstraints = false + adView.topAnchor.constraint(equalTo: cellAd.contentView.topAnchor).isActive = true + adView.leadingAnchor.constraint(equalTo: cellAd.contentView.leadingAnchor, constant: 10).isActive = true + adView.trailingAnchor.constraint(equalTo: cellAd.contentView.trailingAnchor, constant: -10).isActive = true + adView.bottomAnchor.constraint(equalTo: cellAd.contentView.bottomAnchor).isActive = true return cellAd } else if case let .trackerView(trackerView) = elements[indexPath.row], let cellAd = collectionView.dequeueReusableCell(withReuseIdentifier: AdOpportunityTrackerCollectionViewCell.identifier, for: indexPath) as? AdOpportunityTrackerCollectionViewCell { @@ -106,10 +127,10 @@ extension InReadDirectCollectionViewController: UICollectionViewDelegate, UIColl return CGSize.zero } return CGSize(width: collectionView.bounds.width, height: bounds.height) - } else if case let .ad(ad) = elements[indexPath.row] { + } else if case .adView = elements[indexPath.row] { + // Height is managed by the placement through events let width = collectionView.frame.width - 20 - let height = ad.adRatio.calculateHeight(for: width) - return .init(width: width, height: height) + return .init(width: width, height: 300) // Default height, will be updated via heightUpdated event } else if case .trackerView = elements[indexPath.row] { return .init(width: 1, height: 0) } else { @@ -118,45 +139,35 @@ extension InReadDirectCollectionViewController: UICollectionViewDelegate, UIColl } } -extension InReadDirectCollectionViewController: TeadsAdDelegate { - func didRecordImpression(ad _: TeadsAd) {} - - func didRecordClick(ad _: TeadsAd) {} - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad: TeadsAd, error _: Error) { - closeSlot(ad: ad) - } - - func didClose(ad: TeadsAd) { - closeSlot(ad: ad) - } -} - -extension InReadDirectCollectionViewController: TeadsInReadAdPlacementDelegate { - func didReceiveAd(ad: TeadsInReadAd, adRatio _: TeadsAdRatio) { - elements.insert(.ad(ad), at: adItemNumber) - let indexPaths = [IndexPath(row: adItemNumber, section: 0)] - collectionView.insertItems(at: indexPaths) - collectionView.collectionViewLayout.invalidateLayout() - ad.delegate = self - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd: \(reason.description)") - } - - func didUpdateRatio(ad: TeadsInReadAd, adRatio _: TeadsAdRatio) { - updateAdSize(ad: ad) - } - - func adOpportunityTrackerView(trackerView: TeadsAdOpportunityTrackerView) { - elements.insert(.trackerView(trackerView), at: trackerViewItemNumber) - let indexPaths = [IndexPath(row: trackerViewItemNumber, section: 0)] - collectionView.insertItems(at: indexPaths) - collectionView.collectionViewLayout.invalidateLayout() +extension InReadDirectCollectionViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + // Ad is ready, view should already be added + break + case .heightUpdated: + if let height = data?["height"] as? CGFloat { + // Find and update the ad item height + for (index, element) in elements.enumerated() { + if case .adView = element { + collectionView.reloadItems(at: [IndexPath(item: index, section: 0)]) + collectionView.collectionViewLayout.invalidateLayout() + break + } + } + } + case .failed: + print("didFailToReceiveAd: \(String(describing: data?["error"]))") + case .complete: + if let adView = data?["adView"] as? UIView { + closeSlot(adView: adView) + } + default: + break + } } } diff --git a/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadDirectPageViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadDirectPageViewController.swift index aaa746f0..9f581e76 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadDirectPageViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadDirectPageViewController.swift @@ -14,7 +14,7 @@ class InReadDirectPageViewController: TeadsViewController { @IBOutlet var teadsAdHeightConstraint: NSLayoutConstraint! @IBOutlet private var articleLabel: UILabel! var articleLabelText: String? - weak var placement: TeadsInReadAdPlacement? // strong reference is maintained by InReadPageViewController + weak var placement: TeadsAdPlacementMedia? // strong reference is maintained by InReadPageViewController override func viewDidLoad() { super.viewDidLoad() @@ -27,9 +27,18 @@ class InReadDirectPageViewController: TeadsViewController { } func loadAd() { - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + do { + if let adView = try placement?.loadAd() { + teadsAdView.addSubview(adView) + adView.translatesAutoresizingMaskIntoConstraints = false + adView.topAnchor.constraint(equalTo: teadsAdView.topAnchor).isActive = true + adView.leadingAnchor.constraint(equalTo: teadsAdView.leadingAnchor).isActive = true + adView.trailingAnchor.constraint(equalTo: teadsAdView.trailingAnchor).isActive = true + adView.bottomAnchor.constraint(equalTo: teadsAdView.bottomAnchor).isActive = true + } + } catch { + print("Failed to load ad: \(error)") + } } func resizeTeadsAd(adRatio: TeadsAdRatio) { @@ -42,8 +51,4 @@ class InReadDirectPageViewController: TeadsViewController { } } -extension InReadDirectPageViewController: TeadsAdDelegate { - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - self - } -} +// TeadsAdDelegate is handled through unified events system diff --git a/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadPageViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadPageViewController.swift index 760ddad3..54fb39c6 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadPageViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/PageView/InReadPageViewController.swift @@ -16,7 +16,7 @@ class InReadPageViewController: UIPageViewController { var currentViewControlelr: UIViewController? // keep a strong reference to placement instance - var placement: TeadsInReadAdPlacement? + var placement: TeadsAdPlacementMedia? override func viewDidLoad() { super.viewDidLoad() @@ -39,7 +39,11 @@ class InReadPageViewController: UIPageViewController { let placementSettings = TeadsAdPlacementSettings { settings in settings.enableDebug() } - placement = Teads.createInReadPlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) } } @@ -63,29 +67,30 @@ extension InReadPageViewController: UIPageViewControllerDataSource { } } -extension InReadPageViewController: TeadsInReadAdPlacementDelegate { - func didReceiveAd(ad: TeadsInReadAd, adRatio: TeadsAdRatio) { +extension InReadPageViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { guard let currentViewController = currentViewControlelr as? InReadDirectPageViewController else { return } - ad.delegate = currentViewController - currentViewController.resizeTeadsAd(adRatio: adRatio) - currentViewController.teadsAdView.bind(ad) - } - - func didUpdateRatio(ad _: TeadsInReadAd, adRatio: TeadsAdRatio) { - guard let currentViewController = currentViewControlelr as? InReadDirectPageViewController else { - return + switch event { + case .ready: + if let adRatio = data?["adRatio"] as? TeadsAdRatio { + currentViewController.resizeTeadsAd(adRatio: adRatio) + // Ad view should be handled by the child view controller + } + case .heightUpdated: + if let adRatio = data?["adRatio"] as? TeadsAdRatio { + currentViewController.resizeTeadsAd(adRatio: adRatio) + } + case .failed: + print("didFailToReceiveAd") + default: + break } - currentViewController.resizeTeadsAd(adRatio: adRatio) - } - - func didFailToReceiveAd(reason _: AdFailReason) { - print(#function) - } - - func adOpportunityTrackerView(trackerView _: TeadsAdOpportunityTrackerView) { - print(#function) } } diff --git a/TeadsSampleApp/Controllers/InRead/Direct/ScrollView/InReadDirectScrollViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/ScrollView/InReadDirectScrollViewController.swift index 471c5b14..dd2dc3a9 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/ScrollView/InReadDirectScrollViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/ScrollView/InReadDirectScrollViewController.swift @@ -14,7 +14,7 @@ class InReadDirectScrollViewController: TeadsViewController { @IBOutlet var teadsAdView: TeadsInReadAdView! @IBOutlet var teadsAdHeightConstraint: NSLayoutConstraint! var adRatio: TeadsAdRatio? - var placement: TeadsInReadAdPlacement? + var placement: TeadsAdPlacementMedia? override func viewDidLoad() { super.viewDidLoad() @@ -22,11 +22,27 @@ class InReadDirectScrollViewController: TeadsViewController { settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createInReadPlacement(pid: Int(pid) ?? 0, settings: pSettings, delegate: self) - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) + + // Load ad with unified API + do { + if let adView = try placement?.loadAd() { + teadsAdView.addSubview(adView) + adView.translatesAutoresizingMaskIntoConstraints = false + adView.topAnchor.constraint(equalTo: teadsAdView.topAnchor).isActive = true + adView.leadingAnchor.constraint(equalTo: teadsAdView.leadingAnchor).isActive = true + adView.trailingAnchor.constraint(equalTo: teadsAdView.trailingAnchor).isActive = true + adView.bottomAnchor.constraint(equalTo: teadsAdView.bottomAnchor).isActive = true + } + } catch { + print("Failed to load ad: \(error)") + closeAd() + } // We use an observer to know when a rotation happened, to resize the ad // You can use whatever way you want to do so NotificationCenter.default.addObserver(self, selector: #selector(rotationDetected), name: UIDevice.orientationDidChangeNotification, object: nil) @@ -54,44 +70,31 @@ class InReadDirectScrollViewController: TeadsViewController { } } -extension InReadDirectScrollViewController: TeadsInReadAdPlacementDelegate { - func adOpportunityTrackerView(trackerView: TeadsAdOpportunityTrackerView) { - teadsAdView.addSubview(trackerView) - } - - func didReceiveAd(ad: TeadsInReadAd, adRatio: TeadsAdRatio) { - teadsAdView.bind(ad) - ad.delegate = self - resizeTeadsAd(adRatio: adRatio) - } - - func didFailToReceiveAd(reason _: AdFailReason) { - closeAd() - } - - func didUpdateRatio(ad _: TeadsInReadAd, adRatio: TeadsAdRatio) { - resizeTeadsAd(adRatio: adRatio) +extension InReadDirectScrollViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + if let adRatioValue = data?["adRatio"] as? TeadsAdRatio { + resizeTeadsAd(adRatio: adRatioValue) + } + case .heightUpdated: + if let height = data?["height"] as? CGFloat { + teadsAdHeightConstraint.constant = height + } else if let adRatioValue = data?["adRatio"] as? TeadsAdRatio { + resizeTeadsAd(adRatio: adRatioValue) + } + case .failed: + closeAd() + case .complete: + closeAd() + default: + break + } } } -extension InReadDirectScrollViewController: TeadsAdDelegate { - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad _: TeadsAd, error _: Error) { - closeAd() - } - - func didClose(ad _: TeadsAd) { - closeAd() - } - - func didRecordImpression(ad _: TeadsAd) {} - - func didRecordClick(ad _: TeadsAd) {} - - func didExpandedToFullscreen(ad _: TeadsAd) {} - - func didCollapsedFromFullscreen(ad _: TeadsAd) {} -} +// TeadsAdDelegate is handled through unified events system diff --git a/TeadsSampleApp/Controllers/InRead/Direct/TableView/InReadDirectTableViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/TableView/InReadDirectTableViewController.swift index 09148b22..cf14b7d6 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/TableView/InReadDirectTableViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/TableView/InReadDirectTableViewController.swift @@ -19,11 +19,11 @@ class InReadDirectTableViewController: TeadsViewController { static let incrementPosition = 3 var adRequestedIndices = Set() - var placement: TeadsInReadAdPlacement? + var placement: TeadsAdPlacementMedia? enum TeadsElement: Equatable { case article - case ad(_ ad: TeadsInReadAd) + case adView(_ adView: UIView) case trackerView(_ trackerView: TeadsAdOpportunityTrackerView) } @@ -58,16 +58,22 @@ class InReadDirectTableViewController: TeadsViewController { settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createInReadPlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) tableView.register(AdOpportunityTrackerTableViewCell.self, forCellReuseIdentifier: AdOpportunityTrackerTableViewCell.identifier) } - func closeSlot(ad: TeadsAd) { - guard let inReadAd = ad as? TeadsInReadAd else { - return + func closeSlot(adView: UIView) { + elements.removeAll { + if case let .adView(view) = $0 { + return view == adView + } + return false } - elements.removeAll { $0 == .ad(inReadAd) } tableView.reloadData() } } @@ -80,20 +86,30 @@ extension InReadDirectTableViewController: UITableViewDelegate, UITableViewDataS func tableView(_: UITableView, willDisplay _: UITableViewCell, forRowAt indexPath: IndexPath) { if indexPath.row % InReadDirectTableViewController.incrementPosition == 0, elements[indexPath.row] == .article, !adRequestedIndices.contains(indexPath.row) { adRequestedIndices.insert(indexPath.row) - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + do { + if let adView = try placement?.loadAd() { + let adRowIndex = adRowNumber(requestIdentifier: nil) + elements.insert(.adView(adView), at: adRowIndex) + let indexPaths = [IndexPath(row: adRowIndex, section: 0)] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } catch { + print("Failed to load ad: \(error)") + } } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.row == 0 { return tableView.dequeueReusableCell(withIdentifier: contentCell, for: indexPath) - } else if case let .ad(ad) = elements[indexPath.row] { + } else if case let .adView(adView) = elements[indexPath.row] { let cellAd = tableView.dequeueReusableCell(withIdentifier: teadsAdCellIndentifier, for: indexPath) - let teadsAdView = TeadsInReadAdView(bind: ad) - cellAd.contentView.addSubview(teadsAdView) - teadsAdView.setupConstraintsToFitSuperView(horizontalMargin: 10) + cellAd.contentView.addSubview(adView) + adView.translatesAutoresizingMaskIntoConstraints = false + adView.topAnchor.constraint(equalTo: cellAd.contentView.topAnchor).isActive = true + adView.leadingAnchor.constraint(equalTo: cellAd.contentView.leadingAnchor, constant: 10).isActive = true + adView.trailingAnchor.constraint(equalTo: cellAd.contentView.trailingAnchor, constant: -10).isActive = true + adView.bottomAnchor.constraint(equalTo: cellAd.contentView.bottomAnchor).isActive = true return cellAd } else if case let .trackerView(trackerView) = elements[indexPath.row], let cellAd = tableView.dequeueReusableCell(withIdentifier: AdOpportunityTrackerTableViewCell.identifier, for: indexPath) as? AdOpportunityTrackerTableViewCell { @@ -105,9 +121,10 @@ extension InReadDirectTableViewController: UITableViewDelegate, UITableViewDataS } } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - if case let .ad(ad) = elements[indexPath.row] { - return ad.adRatio.calculateHeight(for: tableView.frame.width - 20) + func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if case .adView = elements[indexPath.row] { + // Height is managed by the placement through events + return 300 // Default height, will be updated via heightUpdated event } else if case .trackerView = elements[indexPath.row] { return 0 } @@ -115,52 +132,35 @@ extension InReadDirectTableViewController: UITableViewDelegate, UITableViewDataS } } -extension InReadDirectTableViewController: TeadsInReadAdPlacementDelegate { - func didReceiveAd(ad: TeadsInReadAd, adRatio _: TeadsAdRatio) { - let adRowIndex = adRowNumber(requestIdentifier: ad.requestIdentifier) - - elements.insert(.ad(ad), at: adRowIndex) - ad.delegate = self - let indexPaths = [IndexPath(row: adRowIndex, section: 0)] - tableView.insertRows(at: indexPaths, with: .automatic) - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd: \(reason.description)") - } - - func didUpdateRatio(ad: TeadsInReadAd, adRatio _: TeadsAdRatio) { - if let row = elements.firstIndex(of: .ad(ad)) { - tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic) +extension InReadDirectTableViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + // Ad is ready, view should already be added in willDisplay + break + case .heightUpdated: + if let height = data?["height"] as? CGFloat { + // Find and update the ad row height + for (index, element) in elements.enumerated() { + if case .adView = element { + tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none) + break + } + } + } + case .failed: + print("didFailToReceiveAd: \(String(describing: data?["error"]))") + default: + break } } - - func adOpportunityTrackerView(trackerView: TeadsAdOpportunityTrackerView) { - let trackerRowIndex = trackerViewRowNumber(requestIdentifier: trackerView.requestIdentifier) - elements.insert(.trackerView(trackerView), at: trackerRowIndex) - - let indexPaths = [IndexPath(row: trackerRowIndex, section: 0)] - tableView.insertRows(at: indexPaths, with: .automatic) - } } -extension InReadDirectTableViewController: TeadsAdDelegate { - func didRecordImpression(ad _: TeadsAd) {} - - func didRecordClick(ad _: TeadsAd) {} - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad: TeadsAd, error _: Error) { - closeSlot(ad: ad) - } - - func didClose(ad: TeadsAd) { - closeSlot(ad: ad) - } -} +// TeadsAdDelegate is handled through unified events system extension UIView { func setupConstraintsToFitSuperView(horizontalMargin: CGFloat = 0) { diff --git a/TeadsSampleApp/Controllers/InRead/Direct/WebView/InReadDirectWebViewController.swift b/TeadsSampleApp/Controllers/InRead/Direct/WebView/InReadDirectWebViewController.swift index 1cdda0e2..963e975b 100644 --- a/TeadsSampleApp/Controllers/InRead/Direct/WebView/InReadDirectWebViewController.swift +++ b/TeadsSampleApp/Controllers/InRead/Direct/WebView/InReadDirectWebViewController.swift @@ -14,7 +14,7 @@ class InReadDirectWebViewController: TeadsViewController, WKNavigationDelegate { @IBOutlet var webView: WKWebView! var webViewHelper: TeadsWebViewHelper? - var placement: TeadsInReadAdPlacement? + var placement: TeadsAdPlacementMedia? override func viewDidLoad() { super.viewDidLoad() @@ -37,63 +37,44 @@ class InReadDirectWebViewController: TeadsViewController, WKNavigationDelegate { // settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createInReadPlacement(pid: Int(pid) ?? 0, settings: pSettings, delegate: self) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) } } -extension InReadDirectWebViewController: TeadsInReadAdPlacementDelegate { - func didUpdateRatio(ad _: TeadsInReadAd, adRatio: TeadsAdRatio) { - // update slot with the right ratio - webViewHelper?.updateSlot(adRatio: adRatio) - print("didUpdateRatio") - } - - func didReceiveAd(ad: TeadsInReadAd, adRatio: TeadsAdRatio) { - // open the slot - webViewHelper?.openSlot(ad: ad, adRatio: adRatio) - print("didReceiveAd") - ad.playbackDelegate = self - ad.delegate = self - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd \(reason.localizedDescription)") - } - - func adOpportunityTrackerView(trackerView: TeadsAdOpportunityTrackerView) { - webViewHelper?.setAdOpportunityTrackerView(trackerView) - } -} - -extension InReadDirectWebViewController: TeadsAdDelegate { - func didClose(ad _: TeadsAd) { - webViewHelper?.closeSlot() - } - - func didRecordImpression(ad _: TeadsAd) {} - - func didRecordClick(ad _: TeadsAd) {} - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - print("willPresentModalView") - return self - } - - func didCatchError(ad _: TeadsAd, error: Error) { - print("didCatchError \(error.localizedDescription)") +extension InReadDirectWebViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + if let adRatio = data?["adRatio"] as? TeadsAdRatio { + // For WebView integration, we need to get the ad and adRatio + // Note: WebView helper may need special handling with the new API + print("didReceiveAd - ready event") + } + case .heightUpdated: + if let adRatio = data?["adRatio"] as? TeadsAdRatio { + webViewHelper?.updateSlot(adRatio: adRatio) + print("didUpdateRatio") + } + case .failed: + if let error = data?["error"] { + print("didFailToReceiveAd \(error)") + } + default: + break + } } } -extension InReadDirectWebViewController: TeadsPlaybackDelegate { - func adStartPlayingAudio(_: TeadsAd) { - print("adStartPlayingAudio") - } - - func adStopPlayingAudio(_: TeadsAd) { - print("adStopPlayingAudio") - } -} +// TeadsAdDelegate is handled through unified events system extension InReadDirectWebViewController: TeadsWebViewHelperDelegate { func webViewHelperSlotStartToShow() { @@ -106,9 +87,15 @@ extension InReadDirectWebViewController: TeadsWebViewHelperDelegate { func webViewHelperSlotFoundSuccessfully() { print("webViewHelperSlotFoundSuccessfully") - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + do { + if let adView = try placement?.loadAd() { + // For WebView, the ad needs special handling through webViewHelper + // The helper may need to be updated to work with the new API + print("Ad loaded successfully") + } + } catch { + print("Failed to load ad: \(error)") + } } func webViewHelperSlotNotFound() { diff --git a/TeadsSampleApp/Controllers/Native/Direct/CollectionView/NativeDirectCollectionViewController.swift b/TeadsSampleApp/Controllers/Native/Direct/CollectionView/NativeDirectCollectionViewController.swift index 9b2efada..e8a2a9e8 100644 --- a/TeadsSampleApp/Controllers/Native/Direct/CollectionView/NativeDirectCollectionViewController.swift +++ b/TeadsSampleApp/Controllers/Native/Direct/CollectionView/NativeDirectCollectionViewController.swift @@ -16,9 +16,9 @@ class NativeDirectCollectionViewController: TeadsViewController { let teadsAdCellIndentifier = "NativeAdCollectionViewCell" let fakeArticleCell = "fakeArticleCell" let adItemNumber = 3 - var placement: TeadsNativeAdPlacement? + var placement: TeadsAdPlacementMedia? - private var elements = [TeadsNativeAd?]() + private var elements: [Any?] = [] // Changed to Any? to accommodate different ad types override func viewDidLoad() { super.viewDidLoad() @@ -31,12 +31,21 @@ class NativeDirectCollectionViewController: TeadsViewController { settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createNativePlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) - - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) + + // Load ad with unified API + do { + if let adView = try placement?.loadAd() { + // For native ads, we may need to extract the native ad object differently + } + } catch { + print("Failed to load ad: \(error)") + } } } @@ -51,11 +60,20 @@ extension NativeDirectCollectionViewController: UICollectionViewDelegate, UIColl cell.contentView.translatesAutoresizingMaskIntoConstraints = false cell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width).isActive = true return cell - } else if let ad = elements[indexPath.item] { + } else if let nativeAd = elements[indexPath.item] as? TeadsNativeAd { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: teadsAdCellIndentifier, for: indexPath) as? NativeAdCollectionViewCell else { return UICollectionViewCell() } - cell.adView.bind(ad) + cell.adView.bind(nativeAd) + return cell + } else if let adView = elements[indexPath.item] as? UIView { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: teadsAdCellIndentifier, for: indexPath) + cell.contentView.addSubview(adView) + adView.translatesAutoresizingMaskIntoConstraints = false + adView.topAnchor.constraint(equalTo: cell.contentView.topAnchor).isActive = true + adView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor).isActive = true + adView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor).isActive = true + adView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true return cell } else { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: fakeArticleCell, for: indexPath) as? FakeArticleNativeCollectionViewCell else { @@ -70,48 +88,40 @@ extension NativeDirectCollectionViewController: UICollectionViewDelegate, UIColl return CGSize(width: collectionView.bounds.width, height: 250) } - func closeSlot(ad: TeadsAd) { - elements.removeAll { $0 == ad } + func closeSlot(ad: Any) { + elements.removeAll { + if let element = $0, let elementAsEquatable = element as? AnyHashable, let adAsEquatable = ad as? AnyHashable { + return elementAsEquatable == adAsEquatable + } + return false + } collectionView.reloadData() } } -extension NativeDirectCollectionViewController: TeadsAdDelegate { - func didRecordImpression(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func didRecordClick(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad: TeadsAd, error _: Error) { - closeSlot(ad: ad) - } - - func didClose(ad: TeadsAd) { - closeSlot(ad: ad) - } -} - -extension NativeDirectCollectionViewController: TeadsNativeAdPlacementDelegate { - func didReceiveAd(ad: TeadsNativeAd) { - elements.insert(ad, at: adItemNumber) - let indexPaths = [IndexPath(item: adItemNumber, section: 0)] - collectionView.insertItems(at: indexPaths) - collectionView.reloadData() - ad.delegate = self - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd: \(reason.description)") - } - - func adOpportunityTrackerView(trackerView _: TeadsAdOpportunityTrackerView) { - // not relevant in collectionView integration +extension NativeDirectCollectionViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + // For native ads, the ad object may be in the data dictionary + if let nativeAd = data?["nativeAd"] as? TeadsNativeAd { + elements.insert(nativeAd, at: adItemNumber) + let indexPaths = [IndexPath(item: adItemNumber, section: 0)] + collectionView.insertItems(at: indexPaths) + collectionView.reloadData() + } else if let adView = data?["adView"] as? UIView { + elements.insert(adView, at: adItemNumber) + let indexPaths = [IndexPath(item: adItemNumber, section: 0)] + collectionView.insertItems(at: indexPaths) + } + case .failed: + print("didFailToReceiveAd: \(String(describing: data?["error"]))") + default: + break + } } } diff --git a/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeDirectTableViewController.swift b/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeDirectTableViewController.swift index 4d858146..41d688ef 100644 --- a/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeDirectTableViewController.swift +++ b/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeDirectTableViewController.swift @@ -9,6 +9,7 @@ import TeadsSDK import UIKit +@available(iOS 13.0, *) class NativeDirectTableViewController: TeadsViewController { @IBOutlet var tableView: UITableView! @@ -18,10 +19,11 @@ class NativeDirectTableViewController: TeadsViewController { let adRowNumber = 3 var adRatio: TeadsAdRatio? var teadsAdIsLoaded = false - var placement: TeadsNativeAdPlacement? + var placement: TeadsAdPlacementMediaNative? + private var nativeAdView: TeadsNativeAdView? var tableViewAdCellWidth: CGFloat! - private var elements = [TeadsNativeAd?]() + private var elements: [Any?] = [] override func viewDidLoad() { super.viewDidLoad() @@ -30,16 +32,40 @@ class NativeDirectTableViewController: TeadsViewController { elements.append(nil) } - let placementSettings = TeadsAdPlacementSettings { settings in - settings.enableDebug() + // Create custom native ad view + nativeAdView = createCustomNativeAdView() + + // Create configuration + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + + // Create placement + placement = TeadsAdPlacementMediaNative(config, delegate: self) + + // Load and bind the ad + do { + if let binder = try placement?.loadAd(), let nativeAdView = nativeAdView { + binder(nativeAdView) + } + } catch { + print("Failed to load native ad: \(error)") } + } + + @available(iOS 13.0, *) + private func createCustomNativeAdView() -> TeadsNativeAdView { + // Create your custom native ad view + let adView = TeadsNativeAdView(frame: .zero) - // keep a strong reference to placement instance - placement = Teads.createNativePlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) + adView.backgroundColor = .systemBackground + adView.layer.cornerRadius = 12 + adView.layer.shadowColor = UIColor.black.cgColor + adView.layer.shadowOpacity = 0.1 + adView.layer.shadowRadius = 8 - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + return adView } override func viewDidLayoutSubviews() { @@ -47,8 +73,13 @@ class NativeDirectTableViewController: TeadsViewController { tableViewAdCellWidth = tableView.frame.width - 20 } - func closeSlot(ad: TeadsAd) { - elements.removeAll { $0 == ad } + func closeSlot(ad: Any) { + elements.removeAll { + if let element = $0, let elementAsEquatable = element as? AnyHashable, let adAsEquatable = ad as? AnyHashable { + return elementAsEquatable == adAsEquatable + } + return false + } tableView.reloadData() } @@ -57,6 +88,7 @@ class NativeDirectTableViewController: TeadsViewController { } } +@available(iOS 13.0, *) extension NativeDirectTableViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { return elements.count @@ -66,11 +98,22 @@ extension NativeDirectTableViewController: UITableViewDelegate, UITableViewDataS if indexPath.row == 0 { let cell = tableView.dequeueReusableCell(withIdentifier: headerCell, for: indexPath) return cell - } else if let ad = elements[indexPath.row] { + } else if elements[indexPath.row] != nil && elements[indexPath.row] is String, let nativeAdView = nativeAdView { guard let cell = tableView.dequeueReusableCell(withIdentifier: teadsAdCellIndentifier, for: indexPath) as? NativeAdTableViewCell else { return UITableViewCell() } - cell.adView.bind(ad) + // The native ad view is already bound, just ensure it's in the cell + if nativeAdView.superview != cell.contentView { + // Remove from any previous parent + nativeAdView.removeFromSuperview() + // Add to cell + cell.contentView.addSubview(nativeAdView) + nativeAdView.translatesAutoresizingMaskIntoConstraints = false + nativeAdView.topAnchor.constraint(equalTo: cell.contentView.topAnchor).isActive = true + nativeAdView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor).isActive = true + nativeAdView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor).isActive = true + nativeAdView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true + } return cell } else { guard let cell = tableView.dequeueReusableCell(withIdentifier: fakeArticleCell, for: indexPath) as? FakeArticleNativeTableViewCell else { @@ -86,42 +129,27 @@ extension NativeDirectTableViewController: UITableViewDelegate, UITableViewDataS } } -extension NativeDirectTableViewController: TeadsNativeAdPlacementDelegate { - func didReceiveAd(ad: TeadsNativeAd) { - elements.insert(ad, at: adRowNumber) - let indexPaths = [IndexPath(row: adRowNumber, section: 0)] - tableView.insertRows(at: indexPaths, with: .automatic) - tableView.reloadData() - ad.delegate = self - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd: \(reason.description)") - } - - func adOpportunityTrackerView(trackerView _: TeadsAdOpportunityTrackerView) { - // not relevant in tableView integration +@available(iOS 13.0, *) +extension NativeDirectTableViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + // Ad is ready, insert a marker in the elements array to indicate ad is ready + if let _ = nativeAdView { + elements.insert("nativeAd" as Any, at: adRowNumber) + let indexPaths = [IndexPath(row: adRowNumber, section: 0)] + tableView.insertRows(at: indexPaths, with: .automatic) + } + case .failed: + print("didFailToReceiveAd: \(String(describing: data?["error"]))") + default: + break + } } } -extension NativeDirectTableViewController: TeadsAdDelegate { - func didRecordImpression(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func didRecordClick(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad: TeadsAd, error _: Error) { - closeSlot(ad: ad) - } - - func didClose(ad: TeadsAd) { - closeSlot(ad: ad) - } -} +// TeadsAdDelegate is handled through unified events system diff --git a/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeTagDirectTableViewController.swift b/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeTagDirectTableViewController.swift index 7298fef1..e8fe83f1 100644 --- a/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeTagDirectTableViewController.swift +++ b/TeadsSampleApp/Controllers/Native/Direct/TableView/NativeTagDirectTableViewController.swift @@ -18,7 +18,7 @@ class NativeTagDirectTableViewController: TeadsViewController { let adRowNumber = 3 var adRatio: TeadsAdRatio? var teadsAdIsLoaded = false - var placement: TeadsNativeAdPlacement? + var placement: TeadsAdPlacementMedia? var tableViewAdCellWidth: CGFloat! private var elements = [TeadsNativeAd?]() @@ -34,12 +34,21 @@ class NativeTagDirectTableViewController: TeadsViewController { settings.enableDebug() } - // keep a strong reference to placement instance - placement = Teads.createNativePlacement(pid: Int(pid) ?? 0, settings: placementSettings, delegate: self) - - placement?.requestAd(requestSettings: TeadsAdRequestSettings { settings in - settings.pageUrl("https://www.teads.com") - }) + // Create placement with unified API + let config = TeadsAdPlacementMediaConfig( + pid: Int(pid) ?? 0, + articleUrl: URL(string: "https://www.teads.com") + ) + placement = TeadsAdPlacementMedia(config, delegate: self) + + // Load ad with unified API + do { + if let adView = try placement?.loadAd() { + // For native ads with tags, we may need to extract the native ad object differently + } + } catch { + print("Failed to load ad: \(error)") + } } override func viewDidLayoutSubviews() { @@ -93,42 +102,31 @@ extension NativeTagDirectTableViewController: UITableViewDelegate, UITableViewDa } } -extension NativeTagDirectTableViewController: TeadsNativeAdPlacementDelegate { - func didReceiveAd(ad: TeadsNativeAd) { - elements.insert(ad, at: adRowNumber) - let indexPaths = [IndexPath(row: adRowNumber, section: 0)] - tableView.insertRows(at: indexPaths, with: .automatic) - tableView.reloadData() - ad.delegate = self - } - - func didFailToReceiveAd(reason: AdFailReason) { - print("didFailToReceiveAd: \(reason.description)") - } - - func adOpportunityTrackerView(trackerView _: TeadsAdOpportunityTrackerView) { - // not relevant in tableView integration +extension NativeTagDirectTableViewController: TeadsAdPlacementEventsDelegate { + func adPlacement( + _: TeadsAdPlacementIdentifiable?, + didEmitEvent event: TeadsAdPlacementEventName, + data: [String: Any]? + ) { + switch event { + case .ready: + // For native ads, the ad object may be in the data dictionary + if let nativeAd = data?["nativeAd"] as? TeadsNativeAd { + elements.insert(nativeAd, at: adRowNumber) + let indexPaths = [IndexPath(row: adRowNumber, section: 0)] + tableView.insertRows(at: indexPaths, with: .automatic) + tableView.reloadData() + } else if let adView = data?["adView"] as? UIView { +// elements.insert(adView, at: adRowNumber) +// let indexPaths = [IndexPath(row: adRowNumber, section: 0)] +// tableView.insertRows(at: indexPaths, with: .automatic) + } + case .failed: + print("didFailToReceiveAd: \(String(describing: data?["error"]))") + default: + break + } } } -extension NativeTagDirectTableViewController: TeadsAdDelegate { - func didRecordImpression(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func didRecordClick(ad _: TeadsAd) { - // you may want to use this callback for your own analytics - } - - func willPresentModalView(ad _: TeadsAd) -> UIViewController? { - return self - } - - func didCatchError(ad: TeadsAd, error _: Error) { - closeSlot(ad: ad) - } - - func didClose(ad: TeadsAd) { - closeSlot(ad: ad) - } -} +// TeadsAdDelegate is handled through unified events system diff --git a/TeadsSampleApp/Controllers/RootController/RootViewController.swift b/TeadsSampleApp/Controllers/RootController/RootViewController.swift index c59580c4..7bf678ac 100644 --- a/TeadsSampleApp/Controllers/RootController/RootViewController.swift +++ b/TeadsSampleApp/Controllers/RootController/RootViewController.swift @@ -11,7 +11,7 @@ import UIKit class RootViewController: TeadsViewController { @IBOutlet var collectionView: UICollectionView! - private var selectionList = [inReadFormat, nativeFormat] + private var selectionList = [inReadFormat, nativeFormat, feedFormat, recommendationFormat] private let headerCell = "RootHeaderCollectionReusableView" private let buttonCell = "RootButtonCollectionViewCell" diff --git a/TeadsSampleApp/Models/Format.swift b/TeadsSampleApp/Models/Format.swift index 436d3ced..96ae5867 100644 --- a/TeadsSampleApp/Models/Format.swift +++ b/TeadsSampleApp/Models/Format.swift @@ -51,11 +51,13 @@ struct Integration { // Formats enum Formats { - case inRead, native + case inRead, native, feed, recommendations func format() -> Format { switch self { case .inRead: return inReadFormat case .native: return nativeFormat + case .feed: return feedFormat + case .recommendations: return recommendationFormat } } } @@ -65,6 +67,8 @@ let appLovinInReadCreativeTypes = [landscape, vertical, square, carousel, appLov let inReadFormat = Format(name: .inRead, providers: [inReadDirectProvider, inReadAdmobProvider, inReadAppLovinProvider, inReadSASProvider], isSelected: true, creativeTypes: defaultInReadCreativeTypes) let nativeFormat = Format(name: .native, providers: [nativeDirectProvider, nativeAdmobProvider, nativeAppLovinProvider, nativeSASProvider], isSelected: false, creativeTypes: [display, custom]) +let feedFormat = Format(name: .feed, providers: [inReadDFeedProvider], isSelected: false, creativeTypes: defaultInReadCreativeTypes) +let recommendationFormat = Format(name: .feed, providers: [inReadDFeedProvider], isSelected: false, creativeTypes: defaultInReadCreativeTypes) // inRead Providers let inReadDirectProvider = Provider(name: .direct, integrations: [ @@ -74,6 +78,16 @@ let inReadDirectProvider = Provider(name: .direct, integrations: [ pageViewIntegration, webViewIntegration, ], isSelected: true) +let inReadDFeedProvider = Provider(name: .direct, integrations: [ + scrollViewIntegration, + tableViewIntegration, + collectionViewIntegration, +], isSelected: true) +let inReadRecommendationsProvider = Provider(name: .direct, integrations: [ + scrollViewIntegration, + tableViewIntegration, + collectionViewIntegration, +], isSelected: true) let inReadAdmobProvider = Provider(name: .admob, integrations: [ scrollViewIntegration, tableViewIntegration, @@ -167,6 +181,8 @@ enum PID { enum FormatName: String { case inRead case native = "Native" + case feed = "Feed" + case recommendations = "Recommendations" } enum ProviderName: String {