SwiftUI APP 集成 Google 移动广告 Admob (一)

@高效码农  December 8, 2021

简介:

随着越来越多的应用程序使用 SwiftUI 构建,我想提供一种简单的方法将 Admob 直接集成到您的应用程序中。为了让大家集成的更容易,我计划将它作为 Cocoapod 发布。

先看一下集成后的效果:

1_owJpkqpXu6_xYPPcrBRXXQ.gif

设置 Google 移动广告 (Admob) SDK

申请应用,请参考 Google 的官方文档地址为:https://developers.google.com/admob/ios/quick-start

使用 Cocoapods 导入 SDK

pod 'Google-Mobile-Ads-SDK'

然后使用命令行运行

pod install --repo-update

更新 Info.plist

请更新应用的 Info.plist 文件以添加以下两个键:

  • 一个字符串值为您的 AdMob 应用 ID 的 GADApplicationIdentifier 键(在 AdMob 界面中标识)。
  • 一个 SKAdNetworkIdentifier 值为 Google(cstr6suwn9.skadnetwork),并选择向 Google 提供了这些值的其他买家的 SKAdNetworkItems 键。
 <key>GADApplicationIdentifier</key>
 <string>ca-app-pub-3940256099942544~2435281174</string>

初始化 Google 移动广告 (Admob) SDK

import SwiftUI
import AppTrackingTransparency
import GoogleMobileAds

@main
struct ExampleApp: App {
    
    //在 App Delegate 中使用 init() 代替 ApplicationDidFinishLaunchWithOptions
    init() {
        if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
            // TODO 这里可以弹出隐私协议
        } else {
            ATTrackingManager.requestTrackingAuthorization { status in                                
                GADMobileAds.sharedInstance().start(completionHandler: nil)
            }
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

横幅广告(自适应)

创建一个 BannerAd继承 UIViewController

class BannerAdVC: UIViewController {
    let adUnitId: String
    
    //Initialize variable
    init(adUnitId: String) {
        self.adUnitId = adUnitId
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    var bannerView: GADBannerView = GADBannerView() //Creates your BannerView
    override func viewDidLoad() {
        bannerView.adUnitID = adUnitId
        bannerView.rootViewController = self
      
        //Add our BannerView to the VC
        view.addSubview(bannerView)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        loadBannerAd()
    }

    //Allows the banner to resize when transition from portrait to landscape orientation
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate { _ in
            self.bannerView.isHidden = true //So banner doesn't disappear in middle of animation
        } completion: { _ in
            self.bannerView.isHidden = false
            self.loadBannerAd()
        }
    }

    func loadBannerAd() {
        let frame = view.frame.inset(by: view.safeAreaInsets)
        let viewWidth = frame.size.width

        //Updates the BannerView size relative to the current safe area of device (This creates the adaptive banner)
        bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)

        bannerView.load(GADRequest())
    }
}

要在 SwiftUI 中使用新创建的UIViewController,您需要创建一个UIViewControllerRepresentable

final class BannerAd: UIViewControllerRepresentable {
    let adUnitId: String
    
    init(adUnitId: String) {
        self.adUnitId = adUnitId
    }
    
    
    func makeUIViewController(context: Context) -> BannerAdVC {
        return BannerAdVC(adUnitId: adUnitId)
    }

    func updateUIViewController(_ uiViewController: BannerAdVC, context: Context) {
        
    }
}

现在你的横幅广告是一个 SwiftUI 视图,但我们仍然需要设置一些东西,以便你可以将它添加到你的应用程序中。让我们创建一个名为SwiftUIBannerAd的新 SwiftUI 视图。此视图将管理您的广告的位置和框架。

struct SwiftUIBannerAd: View {
    @State var height: CGFloat = 0 //Height of ad
    @State var width: CGFloat = 0 //Width of ad
    @State var adPosition: AdPosition
    let adUnitId: String
    
    init(adPosition: AdPosition, adUnitId: String) {
        self.adPosition = adPosition
        self.adUnitId = adUnitId
    }
    
    enum AdPosition {
        case top
        case bottom
    }
    
    public var body: some View {
        VStack {
            if adPosition == .bottom {
                Spacer() //Pushes ad to bottom
            }
            
            //Ad
            BannerAd(adUnitId: adUnitId)
                .frame(width: width, height: height, alignment: .center)
                .onAppear {
                    //Call this in .onAppear() b/c need to load the initial frame size
                    //.onReceive() will not be called on initial load
                    setFrame()
                }
                //Changes the frame of the ad whenever the device is rotated.
                //This is what creates the adaptive ad
                .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                    setFrame()
                }
            
            if adPosition == .top {
                Spacer() //Pushes ad to top
            }
        }
    }
    
    func setFrame() {
      
        //Get the frame of the safe area
        let safeAreaInsets = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets ?? .zero
        let frame = UIScreen.main.bounds.inset(by: safeAreaInsets)
        
        //Use the frame to determine the size of the ad
        let adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(frame.width)
        
        //Set the ads frame
        self.width = adSize.size.width
        self.height = adSize.size.height
    }
}

就是这样!您可以将广告添加到您的应用中。我建议您通过将其放置在ZStack的最高级别来执行此操作。(参考开头的例子)

测试您的广告时,请务必将adUnitId设置为横幅测试广告单元 ID:ca-app-pub-3940256099942544/2934735716

您可能已经注意到,我使用NotificationCenter和UIApplication来获取安全区域和方向。虽然您可以使用GeometryReader来执行此操作,但我不建议将其用于生产应用程序。在测试我自己的 SwiftUI 应用程序WidgeTube 时,我发现GeometryReader对于生产来说根本不可靠。很多时候,它是 SwiftUI 的属性图崩溃的根源。虽然有些人已经找到了避免这种情况的方法,但通过试验视图堆栈的顺序,我决定最好完全避免使用GeometryReader。

插页式广告

首先,我们需要创建一个InterstitialAdObject。此对象将处理您的应用程序中的插页式广告的加载。

class InterstitialAd: NSObject {
    var interstitialAd: GADInterstitialAd?
    
    //Want to have one instance of the ad for the entire app
    //We can do this b/c you will never show more than 1 ad at once so only 1 ad needs to be loaded
    static let shared = InterstitialAd()
    
    func loadAd(withAdUnitId id: String) {
        let req = GADRequest()
        GADInterstitialAd.load(withAdUnitID: id, request: req) { interstitialAd, err in
            if let err = err {
                print("Failed to load ad with error: \(err)")
                return
            }
            
            self.interstitialAd = interstitialAd
        }
    }
}

我们现在将使用这个对象来创建一个InterstitialAdView。该视图将是一个UIViewControllerRepresentable以及一个GADFullScreenContentDelegate。

class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {
    
    //Here's the Ad Object we just created
    let interstitialAd = InterstitialAd.shared.interstitialAd
    @Binding var isPresented: Bool
    var adUnitId: String
    
    init(isPresented: Binding<Bool>, adUnitId: String) {
        self._isPresented = isPresented
        self.adUnitId = adUnitId
        super.init()
        
        interstitialAd?.fullScreenContentDelegate = self //Set this view as the delegate for the ad
    }
    
    //Make's a SwiftUI View from a UIViewController
    func makeUIViewController(context: Context) -> UIViewController {
        let view = UIViewController()
        
        //Show the ad after a slight delay to ensure the ad is loaded and ready to present
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
            self.showAd(from: view)
        }
        
        return view
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        
    }
    
    //Presents the ad if it can, otherwise dismisses so the user's experience is not interrupted
    func showAd(from root: UIViewController) {
        
        if let ad = interstitialAd {
            ad.present(fromRootViewController: root)
        } else {
            print("Ad not ready")
            self.isPresented.toggle()
        }
    }
    
    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
        //Prepares another ad for the next time view presented
        InterstitialAd.shared.loadAd(withAdUnitId: adUnitId)
        
        //Dismisses the view once ad dismissed
        isPresented.toggle()
    }
}

稍后,我们将创建全屏修改器来呈现此视图。现在,我们将保留现有内容并创建激励广告(如果您不创建激励广告,请跳至全屏修改器)。
测试时,使用插页式测试广告单元 ID:ca-app-pub-3940256099942544/4411468910



添加新评论