Native Ads
Load and render CloudX native ads in custom iOS layouts
Native ads expose individual creative assets—such as the title, body, icon, media, and call to action—so you can render them in a layout that matches your app. CloudX populates the bound views and delegates impression and click tracking to the winning network adapter.
The integration has three steps:
- Bind your layout’s UI components.
- Create a loader and load or render the native ad.
- Destroy the ad and loader when they are no longer needed.
Supported networks
Per-network Native support — including the Native Banner and Native MREC variants — is maintained in the Native ad format matrix. Adapter-specific dependencies and setup notes are documented on each adapter’s overview page.
1. Bind UI components
Create a CLXNativeAdView using a view binder that maps your custom subviews to asset roles. A network may omit optional assets, so make sure your layout can collapse or hide empty views.
The options container is required: CloudX uses it to display the network-provided AdChoices or privacy control.
- (CLXNativeAdView *)createNativeAdView {
CLXNativeAdViewBinder *binder = [[CLXNativeAdViewBinder alloc] initWithBuilderBlock:^(CLXNativeAdViewBinderBuilder *builder) {
builder.titleLabelTag = CLXNativeAdViewTagTitleLabel;
builder.bodyLabelTag = CLXNativeAdViewTagBodyLabel;
builder.iconImageViewTag = CLXNativeAdViewTagIconImageView;
builder.callToActionButtonTag = CLXNativeAdViewTagCallToActionButton;
builder.mediaContentViewTag = CLXNativeAdViewTagMediaViewContainer;
builder.optionsContentViewTag = CLXNativeAdViewTagOptionsContentView;
builder.advertiserLabelTag = CLXNativeAdViewTagAdvertiserLabel;
builder.starRatingContentViewTag = CLXNativeAdViewTagStarRatingContentView;
}];
CLXNativeAdView *adView = [[CLXNativeAdView alloc] init];
[adView bindViewsWithViewBinder:binder];
return adView;
}func createNativeAdView() -> CLXNativeAdView {
let binder = CLXNativeAdViewBinder { builder in
builder.titleLabelTag = CLXNativeAdViewTagTitleLabel
builder.bodyLabelTag = CLXNativeAdViewTagBodyLabel
builder.iconImageViewTag = CLXNativeAdViewTagIconImageView
builder.callToActionButtonTag = CLXNativeAdViewTagCallToActionButton
builder.mediaContentViewTag = CLXNativeAdViewTagMediaViewContainer
builder.optionsContentViewTag = CLXNativeAdViewTagOptionsContentView
builder.advertiserLabelTag = CLXNativeAdViewTagAdvertiserLabel
builder.starRatingContentViewTag = CLXNativeAdViewTagStarRatingContentView
}
let adView = CLXNativeAdView()
adView.bindViews(with: binder)
return adView
}Alternatively, set outlets directly on the CLXNativeAdView:
CLXNativeAdView *adView = [[CLXNativeAdView alloc] init];
adView.titleLabel = myTitleLabel;
adView.bodyLabel = myBodyLabel;
adView.iconImageView = myIconImageView;
adView.callToActionButton = myCTAButton;
adView.mediaContentView = myMediaContainer;
adView.optionsContentView = myOptionsContainer;
adView.advertiserLabel = myAdvertiserLabel;let adView = CLXNativeAdView()
adView.titleLabel = myTitleLabel
adView.bodyLabel = myBodyLabel
adView.iconImageView = myIconImageView
adView.callToActionButton = myCTAButton
adView.mediaContentView = myMediaContainer
adView.optionsContentView = myOptionsContainer
adView.advertiserLabel = myAdvertiserLabelStar rating
CloudX fills the star rating container only when the network provides a rating of 3.0 or higher; otherwise the container is left empty. Design your layout so an empty star rating container collapses or is hidden.
2. Create a loader
@interface YourViewController () <CLXNativeAdDelegate, CLXAdRevenueDelegate>
@property (nonatomic, strong) CLXNativeAdLoader *nativeAdLoader;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nativeAdLoader = [[CloudXCore shared] createNativeAdLoaderWithAdUnitIdentifier:@"your-native-ad-unit-id"];
self.nativeAdLoader.nativeAdDelegate = self;
self.nativeAdLoader.revenueDelegate = self;
}
- (void)dealloc {
[self.nativeAdLoader destroy];
}
@endclass YourViewController: UIViewController, CLXNativeAdDelegate, CLXAdRevenueDelegate {
private var nativeAdLoader: CLXNativeAdLoader?
override func viewDidLoad() {
super.viewDidLoad()
nativeAdLoader = CloudXCore.shared.createNativeAdLoader(adUnitIdentifier: "your-native-ad-unit-id")
nativeAdLoader?.nativeAdDelegate = self
nativeAdLoader?.revenueDelegate = self
}
deinit {
nativeAdLoader?.destroy()
}
}Load the ad
Choose one loading flow.
Load into a pre-built view
Use this flow when the destination view is already available. CloudX populates and registers the view before returning it through didLoadNativeAd.
CLXNativeAdView *adView = [self createNativeAdView];
[self.nativeAdLoader loadAdIntoAdView:adView];let adView = createNativeAdView()
nativeAdLoader?.loadAd(into: adView)Load and render separately
Use deferred rendering when you want to load the ad before its destination view exists. Call renderNativeAdView(...) before adding the view to your hierarchy.
[self.nativeAdLoader loadAd];
- (void)didLoadNativeAd:(nullable CLXNativeAdView *)nativeAdView forAd:(CLXAd *)ad {
CLXNativeAdView *adView = /* create your ad view */;
[self.nativeAdLoader renderNativeAdView:adView withAd:ad];
[self.view addSubview:adView];
}nativeAdLoader?.loadAd()
func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
let adView = /* create your ad view */
nativeAdLoader?.renderNativeAdView(adView, with: ad)
view.addSubview(adView)
}Handle callbacks
Loaded native ads expire one hour after load; didExpireNativeAd fires so you can destroy the expired ad and load a fresh one. In didFailToLoadNativeAd, avoid immediately retrying in a tight loop — retry after a delay or at the next natural display opportunity.
#pragma mark - CLXNativeAdDelegate (Required)
- (void)didLoadNativeAd:(nullable CLXNativeAdView *)nativeAdView forAd:(CLXAd *)ad {
NSLog(@"Native ad loaded from %@", ad.networkName);
if (ad.nativeAd.isVideoContent) {
NSLog(@"Video duration: %.1fs", ad.nativeAd.videoDuration);
}
if (nativeAdView) {
[self.view addSubview:nativeAdView];
}
}
- (void)didFailToLoadNativeAdForAdUnitIdentifier:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"Native ad failed to load: %@", error.localizedDescription);
}
- (void)didClickNativeAd:(CLXAd *)ad {
NSLog(@"Native ad clicked");
}
#pragma mark - CLXNativeAdDelegate (Optional)
- (void)didExpireNativeAd:(CLXAd *)ad {
NSLog(@"Native ad expired — destroy and reload");
[self.nativeAdLoader destroyAd:ad];
[self.nativeAdLoader loadAd];
}
- (void)didCloseNativeAd:(CLXAd *)ad {
NSLog(@"User dismissed the ad via AdChoices");
[self.nativeAdLoader destroyAd:ad];
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Native ad revenue: %@ from %@", ad.revenue, ad.networkName);
}// MARK: - CLXNativeAdDelegate (Required)
func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
print("Native ad loaded from \(ad.networkName ?? "unknown")")
if let nativeAd = ad.nativeAd, nativeAd.isVideoContent {
print("Video duration: \(nativeAd.videoDuration)s")
}
if let nativeAdView = nativeAdView {
view.addSubview(nativeAdView)
}
}
func didFailToLoadNativeAd(forAdUnitIdentifier adUnitId: String, error: CLXError) {
print("Native ad failed to load: \(error.localizedDescription)")
}
func didClickNativeAd(_ ad: CLXAd) {
print("Native ad clicked")
}
// MARK: - CLXNativeAdDelegate (Optional)
func didExpireNativeAd(_ ad: CLXAd) {
print("Native ad expired — destroy and reload")
nativeAdLoader?.destroyAd(ad)
nativeAdLoader?.loadAd()
}
func didCloseNativeAd(_ ad: CLXAd) {
print("User dismissed the ad via AdChoices")
nativeAdLoader?.destroyAd(ad)
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("Native ad revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}3. Destroy native ads
Destroy an individual ad when it is replaced or expires. Destroy the loader when its owning screen or component is finished. This releases network media views and prevents resources from accumulating over time.
// Destroy a specific loaded ad
[self.nativeAdLoader destroyAd:ad];
// Destroy the loader and all associated resources
[self.nativeAdLoader destroy];// Destroy a specific loaded ad
nativeAdLoader?.destroyAd(ad)
// Destroy the loader and all associated resources
nativeAdLoader?.destroy()Native ad assets
The CLXNativeAd object is available via ad.nativeAd in delegate callbacks:
| Property | Type | Description |
|---|---|---|
title | NSString? | Headline text |
body | NSString? | Body / description text |
callToAction | NSString? | CTA button text (e.g., “Install Now”) |
advertiser | NSString? | Advertiser name |
icon | CLXNativeAdImage? | App icon image |
mainImage | CLXNativeAdImage? | Main image (static creatives) |
mediaView | UIView? | Video/media player view (adapter-provided) |
optionsView | UIView? | AdChoices or options view (adapter-provided) |
mediaContentAspectRatio | CGFloat | Aspect ratio of the media content |
starRating | NSNumber? | App store rating (0–5) |
isVideoContent | BOOL | Whether the creative is a video |
videoDuration | NSTimeInterval | Video length in seconds (0 if unknown) |
expired | BOOL | Whether the ad has expired |
Assets vary by network and creative. Treat nullable values as optional and hide the corresponding UI when an asset is unavailable. A mediaContentAspectRatio or videoDuration value of 0 means the network did not provide a usable value yet.
Native video metadata
Use isVideoContent to distinguish video from static native creatives. When it is true, videoDuration contains the duration in seconds if the network has made it available. Query duration after rendering for the most accurate value.