> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 原生广告

> 在 iOS 应用中集成 CloudX 原生广告和 Reels 风格原生视频体验

原生广告允许您使用自定义 UI 组件（标题、正文、图标、媒体、行动号召）按自己的布局渲染广告素材。配合视频素材和正确的播放设置，原生广告即可成为全屏垂直 "Reels"（与 Instagram Reels 或 TikTok 相同的用户体验）。

一个 Reel 由三部分构成：

1. **包含视频内容的原生广告。** 广告的 `mediaView` 包含视频播放器，其他素材（标题、正文、图标、CTA）覆盖在视频之上。
2. **视频播放设置。** 加载器上的三个属性分别用于禁用全屏、开启有声播放和隐藏媒体控件。
3. **全屏分页容器。** 启用分页的 `UICollectionView` 配合垂直流布局，每个单元格即为一个 Reel。

#### Reels API 概览

| 功能     | CloudX API                            | 说明                         |
| ------ | ------------------------------------- | -------------------------- |
| 检测视频素材 | `ad.nativeAd.isVideoContent`          | 当加载的素材为视频时返回 `YES`         |
| 获取视频时长 | `ad.nativeAd.videoDuration`           | 视频时长（秒），未知时为 0             |
| 用户关闭广告 | `didCloseNativeAd:` 代理回调              | 当用户通过 AdChoices 举报或隐藏广告时触发 |
| 禁用全屏   | `loader.disableVideoFullScreen = YES` | 阻止视频在点击时进入全屏模式             |
| 有声播放   | `loader.startVideoUnmuted = YES`      | 开始播放时开启声音                  |
| 隐藏媒体控件 | `loader.hideVideoMediaControls = YES` | 隐藏播放/暂停和静音/取消静音控件          |

Reels 视频播放设置依赖具体适配器。在 CloudX iOS SDK 3.4.0 中，这些设置由 Meta 原生视频素材支持。

<Note>
  原生广告适配器支持情况和依赖要求记录在各适配器页面中。
</Note>

#### 支持的网络

原生广告（包括 Native Banner 和 Native MREC 变体）由以下竞价网络提供：

| 网络                                                          | Native | Native Banner | Native MREC |
| ----------------------------------------------------------- | ------ | ------------- | ----------- |
| [Meta Audience Network](/zh/ios/adapters/meta/overview)     | ✓      | ✓             | ✓           |
| [Digital Turbine](/zh/ios/adapters/digitalturbine/overview) | ✓      | ✓             | ✓           |
| [InMobi](/zh/ios/adapters/inmobi/overview)                  | ✓      | ✓             | ✓           |
| [Mintegral](/zh/ios/adapters/mintegral/overview)            | ✓      | ✓             | ✓           |
| [Moloco](/zh/ios/adapters/moloco/overview)                  | ✓      | ✓             | ✓           |
| [Pangle](/zh/ios/adapters/pangle/overview)                  | ✓      | ✓             | ✓           |
| [Verve](/zh/ios/adapters/verve/overview)                    | ✓      | ✓             | ✓           |
| [Vungle](/zh/ios/adapters/vungle/overview)                  | ✓      | ✓             | ✓           |

[Google Mobile Ads 瀑布流适配器](/zh/ios/adapters/googlewaterfall/overview) 也支持 AdMob 瀑布流需求的原生广告位。各适配器的依赖和设置说明记录在对应的适配器页面中。

#### 创建加载器并配置视频

<CodeGroup>
  ```objc Objective-C theme={null}
  @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;

      self.nativeAdLoader.disableVideoFullScreen = YES;
      self.nativeAdLoader.startVideoUnmuted = YES;
      self.nativeAdLoader.hideVideoMediaControls = YES;
  }

  - (void)dealloc {
      [self.nativeAdLoader destroy];
  }

  @end
  ```

  ```swift Swift theme={null}
  class 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

          nativeAdLoader?.disableVideoFullScreen = true
          nativeAdLoader?.startVideoUnmuted = true
          nativeAdLoader?.hideVideoMediaControls = true
      }

      deinit {
          nativeAdLoader?.destroy()
      }
  }
  ```
</CodeGroup>

| 属性                       | 默认值  | Reels 值 | 说明                |
| ------------------------ | ---- | ------- | ----------------- |
| `disableVideoFullScreen` | `NO` | `YES`   | 阻止视频在点击时进入全屏模式    |
| `startVideoUnmuted`      | `NO` | `YES`   | 开始播放时开启声音         |
| `hideVideoMediaControls` | `NO` | `YES`   | 隐藏播放/暂停和静音/取消静音控件 |

在调用 `loadAd` **之前**设置以上属性。对静态图片素材无效。对于 Reels 风格的信息流，将三个属性全部设置为 `YES`。

#### 构建原生广告布局

创建 `CLXNativeAdView`，使用视图绑定器将自定义子视图映射到素材角色：

<CodeGroup>
  ```objc Objective-C theme={null}
  - (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;
      }];

      CLXNativeAdView *adView = [[CLXNativeAdView alloc] init];
      [adView bindViewsWithViewBinder:binder];
      return adView;
  }
  ```

  ```swift Swift theme={null}
  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
      }

      let adView = CLXNativeAdView()
      adView.bindViews(with: binder)
      return adView
  }
  ```
</CodeGroup>

你也可以直接在 `CLXNativeAdView` 上设置 outlet：

<CodeGroup>
  ```objc Objective-C theme={null}
  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;
  ```

  ```swift Swift theme={null}
  let adView = CLXNativeAdView()
  adView.titleLabel = myTitleLabel
  adView.bodyLabel = myBodyLabel
  adView.iconImageView = myIconImageView
  adView.callToActionButton = myCTAButton
  adView.mediaContentView = myMediaContainer
  adView.optionsContentView = myOptionsContainer
  adView.advertiserLabel = myAdvertiserLabel
  ```
</CodeGroup>

#### 加载广告

**流程 A — 加载到预构建的视图中：**

<CodeGroup>
  ```objc Objective-C theme={null}
  CLXNativeAdView *adView = [self createNativeAdView];
  [self.nativeAdLoader loadAdIntoAdView:adView];
  ```

  ```swift Swift theme={null}
  let adView = createNativeAdView()
  nativeAdLoader?.loadAd(into: adView)
  ```
</CodeGroup>

**流程 B — 先加载，稍后渲染（延迟渲染）：**

<CodeGroup>
  ```objc Objective-C theme={null}
  [self.nativeAdLoader loadAd];

  - (void)didLoadNativeAd:(nullable CLXNativeAdView *)nativeAdView forAd:(CLXAd *)ad {
      CLXNativeAdView *adView = /* 创建广告视图 */;
      [self.nativeAdLoader renderNativeAdView:adView withAd:ad];
      [self.view addSubview:adView];
  }
  ```

  ```swift Swift theme={null}
  nativeAdLoader?.loadAd()

  func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
      let adView = /* 创建广告视图 */
      nativeAdLoader?.renderNativeAdView(adView, with: ad)
      view.addSubview(adView)
  }
  ```
</CodeGroup>

#### 处理回调

<CodeGroup>
  ```objc Objective-C theme={null}
  #pragma mark - CLXNativeAdDelegate（必选）

  - (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（可选）

  - (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);
  }
  ```

  ```swift Swift theme={null}
  // MARK: - CLXNativeAdDelegate（必选）

  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（可选）

  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")")
  }
  ```
</CodeGroup>

#### 清理资源

使用完毕后务必销毁广告：

<CodeGroup>
  ```objc Objective-C theme={null}
  // 销毁特定已加载的广告
  [self.nativeAdLoader destroyAd:ad];

  // 销毁加载器及所有关联资源
  [self.nativeAdLoader destroy];
  ```

  ```swift Swift theme={null}
  // 销毁特定已加载的广告
  nativeAdLoader?.destroyAd(ad)

  // 销毁加载器及所有关联资源
  nativeAdLoader?.destroy()
  ```
</CodeGroup>

#### 原生广告素材（CLXNativeAd）

`CLXNativeAd` 对象可通过代理回调中的 `ad.nativeAd` 获取：

| 属性                        | 类型                  | 说明                        |
| ------------------------- | ------------------- | ------------------------- |
| `title`                   | `NSString?`         | 标题文本                      |
| `body`                    | `NSString?`         | 正文/描述文本                   |
| `callToAction`            | `NSString?`         | CTA 按钮文本（如 "Install Now"） |
| `advertiser`              | `NSString?`         | 广告主名称                     |
| `icon`                    | `CLXNativeAdImage?` | 应用图标图片                    |
| `mainImage`               | `CLXNativeAdImage?` | 主图片（静态素材）                 |
| `mediaView`               | `UIView?`           | 视频/媒体播放器视图（由适配器提供）        |
| `optionsView`             | `UIView?`           | AdChoices 或选项视图（由适配器提供）   |
| `mediaContentAspectRatio` | `CGFloat`           | 媒体内容宽高比                   |
| `starRating`              | `NSNumber?`         | 应用商店评分（0–5）               |
| `isVideoContent`          | `BOOL`              | 素材是否为视频                   |
| `videoDuration`           | `NSTimeInterval`    | 视频时长（秒），未知时为 0            |
| `expired`                 | `BOOL`              | 广告是否已过期                   |

#### Reels 信息流使用提示

* 使用 `UICollectionView`，设置 `pagingEnabled = YES` 并配合垂直方向的 `UICollectionViewFlowLayout`，设置 `minimumLineSpacing = 0`，每个单元格全屏显示。
* 在回收单元格时调用 `CLXNativeAdView` 的 `prepareForReuse`。
* 每个广告位创建一个 `CLXNativeAdLoader`，按顺序加载广告。
* 不再需要广告时通过 `destroyAd:` 销毁。
