From 24f6d7fe580f9db3489ec0e3b65352695bd8e49f Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Thu, 5 Dec 2024 15:22:52 +0200 Subject: [PATCH 1/2] Update plugin renderer section --- .../pbm-api/ios/pbm-plugin-renderer.md | 193 +++++++++++++++--- 1 file changed, 159 insertions(+), 34 deletions(-) diff --git a/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md b/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md index 845bb00fe7..9733ce8948 100755 --- a/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md +++ b/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md @@ -43,34 +43,165 @@ Please notice that all implementation on mobile related to the Plugin Renderer s ___ -#### Create your implementation from the interface PrebidMobilePluginRenderer +#### Create your implementation of the `PrebidMobilePluginRenderer` + +The PrebidMobile plugin renderer protocols are designed to provide a standardized way for developers to implement custom ad rendering solutions within the Prebid Mobile framework. The `PrebidMobilePluginRenderer` protocol serves as the foundation for all plugin renderers. It defines essential attributes like the plugin's name, version, and optional metadata. It also includes a method to verify whether the plugin supports rendering a specific ad format. Additionally, it provides optional methods for managing event delegates associated with ad configurations, facilitating effective event tracking and handling. Building on this base, the `PrebidMobileAdViewPluginRenderer` protocol introduces functionality specific to rendering ad views. It extends the core protocol by adding a method for creating ad views based on bid responses and configuration details. The `PrebidMobileInterstitialPluginRenderer` protocol focuses on interstitial ad formats. It provides methods for creating controllers that manage the rendering and behavior of interstitial ads. + +Below is the sample implementation of the ad view renderer: ```swift -public class SampleCustomRenderer: NSObject, PrebidMobilePluginRenderer { - - public let name = "SampleCustomRenderer" +public class SampleAdViewRenderer: NSObject, PrebidMobileAdViewPluginRenderer { + public let name = "SampleAdViewRenderer" public let version = "1.0.0" + public var data: [AnyHashable: Any]? = ["custom": "data"] - public var data: [AnyHashable: Any]? = nil + public func isSupportRendering(for format: PrebidMobile.AdFormat?) -> Bool { + [PrebidMobile.AdFormat.banner, PrebidMobile.AdFormat.video].contains(format) + } - private var adViewManager: PBMAdViewManager? + public func createAdView( + with frame: CGRect, + bid: Bid, + adConfiguration: AdUnitConfig, + loadingDelegate: DisplayViewLoadingDelegate, + interactionDelegate: DisplayViewInteractionDelegate + ) -> (UIView & PrebidMobileDisplayViewProtocol)? { + let adView = SampleAdView(frame: frame) + + adView.interactionDelegate = interactionDelegate + adView.loadingDelegate = loadingDelegate + adView.bid = bid + + return adView + } +} + +class SampleAdView: UIView, PrebidMobileDisplayViewProtocol { - public func isSupportRendering(for format: AdFormat?) -> Bool {} - - public func setupBid(_ bid: Bid, adConfiguration: AdUnitConfig, connection: PrebidServerConnectionProtocol) {} + enum SampleError: LocalizedError { + case noAdm + + var errorDescription: String? { + switch self { + case .noAdm: + return "Renderer did fail - there is no ADM in the response." + } + } + } + + weak var interactionDelegate: DisplayViewInteractionDelegate? + weak var loadingDelegate: DisplayViewLoadingDelegate? + + var bid: Bid? + + private let webView: WKWebView = WKWebView() - public func createBannerAdView(with frame: CGRect, bid: Bid, adConfiguration: AdUnitConfig, - connection: PrebidServerConnectionProtocol, adViewDelegate: (any PBMAdViewDelegate)?) { - // TODO "Handle bid response as you want and display your banner ad" + func loadAd() { + DispatchQueue.main.async { + if let adm = self.bid?.adm { + self.webView.loadHTMLString(adm, baseURL: nil) + self.loadingDelegate?.displayViewDidLoadAd(self) + } else { + self.loadingDelegate?.displayView(self, didFailWithError: SampleError.noAdm) + } + } } + + // ... + // Setup view + // ... +} +``` - public func createInterstitialController(bid: Bid, adConfiguration: AdUnitConfig, connection: PrebidServerConnectionProtocol, - adViewManagerDelegate adViewDelegate: InterstitialController?, videoControlsConfig: VideoControlsConfiguration?) { - // TODO "Handle bid response as you want and display your interstitial ad" +Interstitial renderer example: + +```swift +class SampleInterstitialRenderer: PrebidMobileInterstitialPluginRenderer { + + var name = "SampleInterstitialRenderer" + var version = "1.0.0" + var data: [AnyHashable : Any]? = ["custom": "data"] + + func isSupportRendering(for format: PrebidMobile.AdFormat?) -> Bool { + [PrebidMobile.AdFormat.banner, PrebidMobile.AdFormat.video].contains(format) + } + + func createInterstitialController( + bid: Bid, + adConfiguration: AdUnitConfig, + loadingDelegate: InterstitialControllerLoadingDelegate, + interactionDelegate: InterstitialControllerInteractionDelegate + ) -> PrebidMobileInterstitialControllerProtocol? { + let interstitialController = SampleInterstitialController() + + interstitialController.loadingDelegate = loadingDelegate + interstitialController.interactionDelegate = interactionDelegate + interstitialController.bid = bid + + return interstitialController } } +class SampleInterstitialController: NSObject, PrebidMobileInterstitialControllerProtocol { + + enum SampleError: LocalizedError { + case noAdm + case noAvailableController + + var errorDescription: String? { + switch self { + case .noAdm: + return "Renderer did fail - there is no ADM in the response." + case .noAvailableController: + return "Coudn't find a controller to present from." + } + } + } + + weak var loadingDelegate: InterstitialControllerLoadingDelegate? + weak var interactionDelegate: InterstitialControllerInteractionDelegate? + + var bid: Bid? + + private let webView: WKWebView = { + let webView = WKWebView(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 250))) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.backgroundColor = .blue + return webView + }() + + private lazy var interstitialViewController = UIViewController() + + func loadAd() { + DispatchQueue.main.async { + guard let adm = self.bid?.adm else { + self.loadingDelegate?.interstitialController(self, didFailWithError: SampleError.noAdm) + return + } + + self.webView.loadHTMLString(adm, baseURL: nil) + self.loadingDelegate?.interstitialControllerDidLoadAd(self) + } + } + + func show() { + DispatchQueue.main.async { + guard let presentingController = UIApplication.shared.topViewController else { + self.loadingDelegate?.interstitialController( + self, + didFailWithError: SampleError.noAvailableController + ) + return + } + + presentingController.present( + self.interstitialViewController, + animated: true + ) + } + } +} ``` #### Global Initialization of Plugin Renderer @@ -86,7 +217,7 @@ import PrebidMobile class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - let sampleCustomRenderer = SampleCustomRenderer() + let sampleCustomRenderer = SampleAdViewCustomRenderer() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize the Prebid SDK @@ -113,13 +244,11 @@ func applicationWillTerminate(_ application: UIApplication) { If you need to handle plugin registration in a specific view or controller for more granular control, you can still register the Plugin Renderer at that level: ```swift -class CustomRendererBannerController: NSObject, AdaptedController, PrebidConfigurableBannerController, BannerViewDelegate { +class CustomRendererBannerController: UIViewController { - required init(rootController: AdapterViewController) { + override init() { super.init() - self.rootController = rootController Prebid.registerPluginRenderer(sampleCustomRenderer) - setupAdapterController() } deinit { @@ -137,7 +266,7 @@ It is important to notice that the compliant formats you set on `isSupportRender ### Original API -The Plugin Renderer feature does not work with [GAM Original API](/prebid-mobile/pbm-api/android/android-sdk-integration-gam-original-api.html) since the ad rendering does not happen in the Prebid SDK but externally. Despite that if you are using the regular GAM integration it will work fine. +The Plugin Renderer feature does not work with [GAM Original API](/prebid-mobile/pbm-api/ios/ios-sdk-integration-gam-original-api.html) since the ad rendering does not happen in the Prebid SDK but externally. Despite that if you are using the regular GAM integration it will work fine. ## Ad Event Listeners An optional dedicated generic ad event listener is offered in case of the existing event listeners are insufficient to keep your ad consumer fully aware of your ad lifecycle. @@ -171,19 +300,15 @@ ___ #### Handle your plugin event delegate on your Plugin Renderer ```swift -public class SampleCustomRenderer: NSObject, PrebidMobilePluginRenderer { +public class SampleCustomRenderer: NSObject, PrebidMobileAdViewPluginRenderer { // Store your listeners private var pluginEventDelegateMap = [String: SampleCustomRendererEventDelegate]() public let name = "SampleCustomRenderer" - public let version = "1.0.0" - public var data: [AnyHashable: Any]? = nil - private var adViewManager: PBMAdViewManager? - public func isSupportRendering(for format: AdFormat?) -> Bool {} public func registerEventDelegate(pluginEventDelegate: any PluginEventDelegate, adUnitConfigFingerprint: String) { @@ -193,15 +318,15 @@ public class SampleCustomRenderer: NSObject, PrebidMobilePluginRenderer { public func unregisterEventDelegate(pluginEventDelegate: any PluginEventDelegate, adUnitConfigFingerprint: String) { pluginEventDelegateMap.removeValue(forKey: adUnitConfigFingerprint) } - - public func setupBid(_ bid: Bid, adConfiguration: AdUnitConfig, connection: PrebidServerConnectionProtocol) {} - public func createBannerAdView(with frame: CGRect, bid: Bid, adConfiguration: AdUnitConfig, - connection: PrebidServerConnectionProtocol, adViewDelegate: (any PBMAdViewDelegate)?) { - } - - public func createInterstitialController(bid: Bid, adConfiguration: AdUnitConfig, connection: PrebidServerConnectionProtocol, - adViewManagerDelegate adViewDelegate: InterstitialController?, videoControlsConfig: VideoControlsConfiguration?) { + public func createAdView( + with frame: CGRect, + bid: Bid, + adConfiguration: AdUnitConfig, + loadingDelegate: DisplayViewLoadingDelegate, + interactionDelegate: DisplayViewInteractionDelegate + ) -> (UIView & PrebidMobileDisplayViewProtocol)? { + // ....... } } ``` From 3de14ce9127afef415fc6ed97e1dcc92b993772e Mon Sep 17 00:00:00 2001 From: Olena Stepaniuk Date: Wed, 11 Dec 2024 12:30:43 +0200 Subject: [PATCH 2/2] Update pbm-plugin-renderer.md --- .../pbm-api/ios/pbm-plugin-renderer.md | 136 ++++++------------ 1 file changed, 47 insertions(+), 89 deletions(-) diff --git a/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md b/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md index 9733ce8948..5989297b10 100755 --- a/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md +++ b/prebid-mobile/pbm-api/ios/pbm-plugin-renderer.md @@ -45,22 +45,19 @@ ___ #### Create your implementation of the `PrebidMobilePluginRenderer` -The PrebidMobile plugin renderer protocols are designed to provide a standardized way for developers to implement custom ad rendering solutions within the Prebid Mobile framework. The `PrebidMobilePluginRenderer` protocol serves as the foundation for all plugin renderers. It defines essential attributes like the plugin's name, version, and optional metadata. It also includes a method to verify whether the plugin supports rendering a specific ad format. Additionally, it provides optional methods for managing event delegates associated with ad configurations, facilitating effective event tracking and handling. Building on this base, the `PrebidMobileAdViewPluginRenderer` protocol introduces functionality specific to rendering ad views. It extends the core protocol by adding a method for creating ad views based on bid responses and configuration details. The `PrebidMobileInterstitialPluginRenderer` protocol focuses on interstitial ad formats. It provides methods for creating controllers that manage the rendering and behavior of interstitial ads. +The `PrebidMobilePluginRenderer` protocol is designed to provide a standardized way for developers to implement custom ad rendering solutions within the Prebid Mobile framework. It provides a unified interface for managing ad rendering, event handling, and metadata integration. -Below is the sample implementation of the ad view renderer: +Below is the sample implementation of the renderer: ```swift -public class SampleAdViewRenderer: NSObject, PrebidMobileAdViewPluginRenderer { +public class SampleRenderer: NSObject, PrebidMobilePluginRenderer { - public let name = "SampleAdViewRenderer" + public let name = "SampleRenderer" public let version = "1.0.0" - public var data: [AnyHashable: Any]? = ["custom": "data"] + public var data: [String: Any]? - public func isSupportRendering(for format: PrebidMobile.AdFormat?) -> Bool { - [PrebidMobile.AdFormat.banner, PrebidMobile.AdFormat.video].contains(format) - } - - public func createAdView( + /// This method creates an instance of `SampleAdView`, which is a custom view used to display the ad. + public func createBannerView( with frame: CGRect, bid: Bid, adConfiguration: AdUnitConfig, @@ -75,27 +72,35 @@ public class SampleAdViewRenderer: NSObject, PrebidMobileAdViewPluginRenderer { return adView } -} - -class SampleAdView: UIView, PrebidMobileDisplayViewProtocol { - enum SampleError: LocalizedError { - case noAdm + /// This method creates an instance of `SampleInterstitialController`, + /// a custom controller used to display interstitial ads. + public func createInterstitialController( + bid: Bid, + adConfiguration: AdUnitConfig, + loadingDelegate: InterstitialControllerLoadingDelegate, + interactionDelegate: InterstitialControllerInteractionDelegate + ) -> PrebidMobileInterstitialControllerProtocol? { + let interstitialController = SampleInterstitialController() - var errorDescription: String? { - switch self { - case .noAdm: - return "Renderer did fail - there is no ADM in the response." - } - } + interstitialController.loadingDelegate = loadingDelegate + interstitialController.interactionDelegate = interactionDelegate + interstitialController.bid = bid + + return interstitialController } +} + +class SampleAdView: UIView, PrebidMobileDisplayViewProtocol { weak var interactionDelegate: DisplayViewInteractionDelegate? weak var loadingDelegate: DisplayViewLoadingDelegate? var bid: Bid? - - private let webView: WKWebView = WKWebView() + + // ... + // Setup view + // ... func loadAd() { DispatchQueue.main.async { @@ -107,72 +112,19 @@ class SampleAdView: UIView, PrebidMobileDisplayViewProtocol { } } } - - // ... - // Setup view - // ... -} -``` - -Interstitial renderer example: - -```swift -class SampleInterstitialRenderer: PrebidMobileInterstitialPluginRenderer { - - var name = "SampleInterstitialRenderer" - var version = "1.0.0" - var data: [AnyHashable : Any]? = ["custom": "data"] - - func isSupportRendering(for format: PrebidMobile.AdFormat?) -> Bool { - [PrebidMobile.AdFormat.banner, PrebidMobile.AdFormat.video].contains(format) - } - - func createInterstitialController( - bid: Bid, - adConfiguration: AdUnitConfig, - loadingDelegate: InterstitialControllerLoadingDelegate, - interactionDelegate: InterstitialControllerInteractionDelegate - ) -> PrebidMobileInterstitialControllerProtocol? { - let interstitialController = SampleInterstitialController() - - interstitialController.loadingDelegate = loadingDelegate - interstitialController.interactionDelegate = interactionDelegate - interstitialController.bid = bid - - return interstitialController - } } class SampleInterstitialController: NSObject, PrebidMobileInterstitialControllerProtocol { - enum SampleError: LocalizedError { - case noAdm - case noAvailableController - - var errorDescription: String? { - switch self { - case .noAdm: - return "Renderer did fail - there is no ADM in the response." - case .noAvailableController: - return "Coudn't find a controller to present from." - } - } - } - weak var loadingDelegate: InterstitialControllerLoadingDelegate? weak var interactionDelegate: InterstitialControllerInteractionDelegate? var bid: Bid? - - private let webView: WKWebView = { - let webView = WKWebView(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 250))) - webView.translatesAutoresizingMaskIntoConstraints = false - webView.backgroundColor = .blue - return webView - }() - - private lazy var interstitialViewController = UIViewController() - + + // ... + // Setup view + // ... + func loadAd() { DispatchQueue.main.async { guard let adm = self.bid?.adm else { @@ -217,7 +169,7 @@ import PrebidMobile class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - let sampleCustomRenderer = SampleAdViewCustomRenderer() + let sampleCustomRenderer = SampleRenderer() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize the Prebid SDK @@ -260,15 +212,15 @@ class CustomRendererBannerController: UIViewController { ## Limitations ### Supported Ad Formats -Currently the interface `PrebidMobilePluginRenderer` provide the ability to render `BANNER` and `INTERSTITIAL` only. The compability with more ad formats can be supported in future releases. -It is important to notice that the compliant formats you set on `isSupportRenderingFor` implementation are taken into account to add your Plugin Renderer to the bid request or not, according to the ad unit configuration that is bid requesting. +Currently the interface `PrebidMobilePluginRenderer` provide the ability to render `BANNER` and `INTERSTITIAL` only. The compability with more ad formats can be supported in future releases. ### Original API The Plugin Renderer feature does not work with [GAM Original API](/prebid-mobile/pbm-api/ios/ios-sdk-integration-gam-original-api.html) since the ad rendering does not happen in the Prebid SDK but externally. Despite that if you are using the regular GAM integration it will work fine. ## Ad Event Listeners + An optional dedicated generic ad event listener is offered in case of the existing event listeners are insufficient to keep your ad consumer fully aware of your ad lifecycle. ![Plugin Event Listener big picture](/assets/images/prebid-mobile/prebid-plugin-renderer-event-listeners.png) @@ -294,22 +246,19 @@ ___ // TODO on impressions } } - ``` #### Handle your plugin event delegate on your Plugin Renderer ```swift -public class SampleCustomRenderer: NSObject, PrebidMobileAdViewPluginRenderer { +public class SampleRenderer: NSObject, PrebidMobilePluginRenderer { // Store your listeners private var pluginEventDelegateMap = [String: SampleCustomRendererEventDelegate]() - public let name = "SampleCustomRenderer" + public let name = "SampleRenderer" public let version = "1.0.0" public var data: [AnyHashable: Any]? = nil - - public func isSupportRendering(for format: AdFormat?) -> Bool {} public func registerEventDelegate(pluginEventDelegate: any PluginEventDelegate, adUnitConfigFingerprint: String) { pluginEventDelegateMap[adUnitConfigFingerprint] = pluginEventDelegate as? SampleCustomRendererEventDelegate @@ -328,6 +277,15 @@ public class SampleCustomRenderer: NSObject, PrebidMobileAdViewPluginRenderer { ) -> (UIView & PrebidMobileDisplayViewProtocol)? { // ....... } + + public func createInterstitialController( + bid: Bid, + adConfiguration: AdUnitConfig, + loadingDelegate: InterstitialControllerLoadingDelegate, + interactionDelegate: InterstitialControllerInteractionDelegate + ) -> PrebidMobileInterstitialControllerProtocol? { + // ....... + } } ```