You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter集成Google原生广告iOS端如何用SwiftUI替代XIB实现

实现方案

核心误区:GADNativeAdView是UIKit框架下的UIView子类,SwiftUI的View是值类型结构体,无法直接继承或替代UIKit类返回给Admob SDK。你需要做UIKit和SwiftUI的桥接,整体实现不需要修改原有AppDelegate的注册逻辑,只需要替换原来XIB加载广告视图的部分即可。


步骤1:编写纯SwiftUI的广告内容视图

这部分完全用SwiftUI写布局,同时把需要被Admob SDK识别的广告控件实例桥接出来,用于后续绑定SDK要求的属性:

import SwiftUI
import GoogleMobileAds

// 纯SwiftUI实现的广告UI布局
struct NativeAdContentView: View {
    let nativeAd: GADNativeAd
    // 保留UIKit控件引用,供SDK绑定识别
    let headlineRef = UILabel()
    let bodyRef = UILabel()
    let iconRef = UIImageView()
    let ctaRef = UIButton(type: .system)
    
    init(nativeAd: GADNativeAd) {
        self.nativeAd = nativeAd
        // 提前给控件填充广告数据
        headlineRef.text = nativeAd.headline
        bodyRef.text = nativeAd.body
        bodyRef.isHidden = nativeAd.body == nil
        iconRef.image = nativeAd.icon?.image
        iconRef.isHidden = nativeAd.icon == nil
        ctaRef.setTitle(nativeAd.callToAction, for: .normal)
        ctaRef.isHidden = nativeAd.callToAction == nil
        ctaRef.isUserInteractionEnabled = false // 关闭按钮自身交互,由SDK统一处理点击
    }
    
    var body: some View {
        HStack(spacing: 12) {
            IconView(iconRef: iconRef)
                .frame(width: 40, height: 40)
                .clipped()
            
            VStack(alignment: .leading, spacing: 4) {
                HeadlineView(labelRef: headlineRef)
                    .font(.system(size: 16, weight: .medium))
                    .lineLimit(1)
                if nativeAd.body != nil {
                    BodyView(labelRef: bodyRef)
                        .font(.system(size: 14))
                        .lineLimit(2)
                        .foregroundColor(.secondary)
                }
            }
            
            Spacer()
            
            CTAView(buttonRef: ctaRef)
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .background(Color.accentColor)
                .foregroundColor(.white)
                .cornerRadius(6)
        }
        .padding(12)
    }
}

// 以下为UIKit控件桥接到SwiftUI的轻量包装,用于获取固定的UIView实例
struct HeadlineView: UIViewRepresentable {
    let labelRef: UILabel
    func makeUIView(context: Context) -> UILabel { labelRef }
    func updateUIView(_ uiView: UILabel, context: Context) {}
}

struct BodyView: UIViewRepresentable {
    let labelRef: UILabel
    func makeUIView(context: Context) -> UILabel { labelRef }
    func updateUIView(_ uiView: UILabel, context: Context) {}
}

struct IconView: UIViewRepresentable {
    let iconRef: UIImageView
    func makeUIView(context: Context) -> UIImageView {
        iconRef.contentMode = .scaleAspectFill
        return iconRef
    }
    func updateUIView(_ uiView: UIImageView, context: Context) {}
}

struct CTAView: UIViewRepresentable {
    let buttonRef: UIButton
    func makeUIView(context: Context) -> UIButton { buttonRef }
    func updateUIView(_ uiView: UIButton, context: Context) {}
}

步骤2:实现桥接用的GADNativeAdView子类

这个类是UIKit类型,替代原来XIB加载的广告视图,负责承载SwiftUI布局,同时完成SDK要求的控件绑定、广告数据挂载:

class SwiftUINativeAdView: GADNativeAdView {
    private var hostingController: UIHostingController<NativeAdContentView>?
    
    func setup(with nativeAd: GADNativeAd) {
        let adContentView = NativeAdContentView(nativeAd: nativeAd)
        
        // 挂载SwiftUI视图到当前UIKit视图
        if hostingController == nil {
            let host = UIHostingController(rootView: adContentView)
            hostingController = host
            guard let hostView = host.view else { return }
            hostView.translatesAutoresizingMaskIntoConstraints = false
            hostView.backgroundColor = .clear
            addSubview(hostView)
            // 让SwiftUI视图铺满整个广告容器
            NSLayoutConstraint.activate([
                hostView.topAnchor.constraint(equalTo: topAnchor),
                hostView.leadingAnchor.constraint(equalTo: leadingAnchor),
                hostView.trailingAnchor.constraint(equalTo: trailingAnchor),
                hostView.bottomAnchor.constraint(equalTo: bottomAnchor)
            ])
        } else {
            hostingController?.rootView = adContentView
        }
        
        // 核心:必须绑定对应控件到SDK规定的属性,否则无法触发曝光、点击上报
        self.headlineView = adContentView.headlineRef
        self.bodyView = adContentView.bodyRef
        self.iconView = adContentView.iconRef
        self.callToActionView = adContentView.ctaRef
        // 挂载广告数据对象
        self.nativeAd = nativeAd
    }
}

步骤3:修改广告工厂类

替换原来XIB加载的逻辑,直接初始化桥接的广告视图即可:

class PageAd: FLTNativeAdFactory {
    func createNativeAd(_ nativeAd: GADNativeAd,
                        customOptions: [AnyHashable : Any]? = nil) -> GADNativeAdView? {
        let nativeAdView = SwiftUINativeAdView()
        nativeAdView.setup(with: nativeAd)
        return nativeAdView
    }
}

步骤4:AppDelegate注册逻辑

原有注册代码不需要做任何修改,和XIB版本完全一致:

let listTileFactory = PageAd()
FLTGoogleMobileAdsPlugin.registerNativeAdFactory(self, 
                                                 factoryId: "listTile", 
                                                 nativeAdFactory: listTileFactory)

注意事项

  • 如果项目最低支持iOS 16及以上,可以简化桥接逻辑:不用UIHostingController,直接在SwiftUINativeAdView中通过UIHostingConfiguration挂载SwiftUI视图即可
  • 广告样式调整直接修改NativeAdContentView里的SwiftUI布局代码即可,和普通SwiftUI页面开发逻辑完全一致
  • 如果需要接入媒体素材(视频/大图),只需要在SwiftUI布局中新增对应的UIImageView/UIView桥接,赋值给GADNativeAdView的mediaView属性即可

内容的提问来源于stack exchange,提问作者Lalit Rawat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 00:09:14