Upgrade to Pro — share decks privately, control downloads, hide ads and more …

overlayPreferenceValue で実現する ピュア SwiftUI な AdMo...

Avatar for Takashi Takashi
October 03, 2025

overlayPreferenceValue で実現する ピュア SwiftUI な AdMob ネイティブ広告

extension DC 2025 Day 3

AdMob のネイティブ広告を SwiftUI だけで作れるようにするライブラリを作ったよ、というお話

Avatar for Takashi

Takashi

October 03, 2025
Tweet

Other Decks in Programming

Transcript

  1. Copyright ©︎ 2025 Takashi Ushikoshi ڇӽɹਹ (Takashi Ushikoshi) @Ukokkei95Toyama ݸਓࣄۀओ

    (2025೥2݄ - ݱࡏ) - iOS / Android ΤϯδχΞ աڈͷ࡞඼
 ূ໌ࣸਅΞϓϦ ࡳຈੜ·Εࡳຈҭͪ, ΠϯυωγΞ޷͖
  2. Copyright ©︎ 2025 Takashi Ushikoshi SwiftUI ϦϦʔε͔Β6೥ɺࠓ΋ UIViewRepresent a ble

    Λ࢖ͬ ͨ࡞ΓํͰҊ಺͞Ε͍ͯΔ https://developers.google.com/admob/ios/native/advanced?hl=ja#display_the_ad
  3. Copyright ©︎ 2025 Takashi Ushikoshi ໨ࢦͨ͠APIσβΠϯ NativeAdvertisement(adUnitId: "ca-pub-xxxx") { loadedAd

    in VStack { if let headline = loadedAd?.headline { Text(headline) } if let icon = loadedAd?.icon { Image(uiImage: icon.image) } } }
  4. NativeAdvertisement(adUnitId: "ca-pub-xxxx") { loadedAd in VStack { if let headline

    = loadedAd?.headline { Text(headline) } if let icon = loadedAd?.icon { Image(uiImage: icon.image) } } } Copyright ©︎ 2025 Takashi Ushikoshi ໨ࢦͨ͠APIσβΠϯ ޿ࠂϢχοτIDΛୈҰҾ਺ʹͱΔ
  5. NativeAdvertisement(adUnitId: "ca-pub-xxxx") { loadedAd in VStack { if let headline

    = loadedAd?.headline { Text(headline) } if let icon = loadedAd?.icon { Image(uiImage: icon.image) } } } Copyright ©︎ 2025 Takashi Ushikoshi ໨ࢦͨ͠APIσβΠϯ ୈೋҾ਺ͷΫϩʔδϟͰɺ޿ࠂͷ ςϯϓϨʔτΛߏ੒
  6. Copyright ©︎ 2025 Takashi Ushikoshi private struct NativeAdViewContainer: UIViewRepresentable {

    typealias UIViewType = NativeAdView // Observer to update the UIView when the native ad value changes. @ObservedObject var nativeViewModel: NativeAdViewModel func makeUIView(context: Context) -> NativeAdView { return Bundle.main.loadNibNamed( "NativeAdView", owner: nil, options: nil)?.first as! NativeAdView } func updateUIView(_ nativeAdView: NativeAdView, context: Context) { guard let nativeAd = nativeViewModel.nativeAd else { return } // Each UI property is configurable using your native ad. (nativeAdView.headlineView as? UILabel)?.text = nativeAd.headline nativeAdView.mediaView?.mediaContent = nativeAd.mediaContent (nativeAdView.bodyView as? UILabel)?.text = nativeAd.body (nativeAdView.iconView as? UIImageView)?.image = nativeAd.icon?.image (nativeAdView.starRatingView as? UIImageView)?.image = imageOfStars(from: nativeAd.starRating) (nativeAdView.storeView as? UILabel)?.text = nativeAd.store (nativeAdView.priceView as? UILabel)?.text = nativeAd.price (nativeAdView.advertiserView as? UILabel)?.text = nativeAd.advertiser (nativeAdView.callToActionView as? UIButton)?.setTitle(nativeAd.callToAction, for: .normal) // For the SDK to process touch events properly, user interaction should be disabled. nativeAdView.callToActionView?.isUserInteractionEnabled = false // Associate the native ad view with the native ad object. This is required to make the ad // clickable. // Note: this should always be done after populating the ad views. nativeAdView.nativeAd = nativeAd } } ࢠViewΛҾͬுͬͯ͜͜ʹೖΕࠐΉɺ͕Ͱ͖ͳ͍ https://developers.google.com/admob/ios/native/advanced?hl=ja#display_the_ad
  7. Copyright ©︎ 2025 Takashi Ushikoshi overlayPreferenceValue ͱ͸ - overlay ͱ

    onPreferenceChange ͕߹Θͬͨ͞Α͏ͳAPI - PreferenceKey ͷ࢓૊ΈΛ࢖͍ɺࢠ View ্͕͖͛ͯͨαΠζ΍࠲ඪͷ஋Λ਌ଆͰಡΈऔΓɺ ॏͶ߹ΘͤΔ͜ͱ͕Ͱ͖ΔAPI
  8. Copyright ©︎ 2025 Takashi Ushikoshi struct ContentView: View { var

    body: some View { NativeAdvertisement(adUnitId: "ca-pub-xxxx") { loadedAd in VStack { if let headline = loadedAd?.headline { Text(headline) } if let icon = loadedAd?.icon { Image(uiImage: icon.image) } } } } }
  9. Copyright ©︎ 2025 Takashi Ushikoshi struct ContentView: View { var

    body: some View { NativeAdvertisement(adUnitId: "ca-pub-xxxx") { loadedAd in VStack { if let headline = loadedAd?.headline { Text(headline) .nativeAdElement("headline") } if let icon = loadedAd?.icon { Image(uiImage: icon.image) .nativeAdElement("icon") } } } } }
  10. Copyright ©︎ 2025 Takashi Ushikoshi public struct NativeAdvertisement<AdContent: View>: View

    { @StateObject private var nativeAdLoader: NativeAdLoader private let adUnitId: String @ViewBuilder private let adContent: (_ advertisementPhase: NativeAdvertisementPhase) -> AdContent // தུ public var body: some View { let loadedAd = nativeAdLoader.loadedAd adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) { namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() } } }
  11. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() }
  12. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlay(alignment: .center) { _RepresentedUINativeAdView(

    nativeAd: loadedAd, elementFrames: [] ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } .onAppear { nativeAdLoader.loadAd() } }
  13. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() }
  14. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() }
  15. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() } → CGRect
  16. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() }
  17. Copyright ©︎ 2025 Takashi Ushikoshi adContent(nativeAdLoader.nativeAdvertisementPhase) .overlayPreferenceValue(TypedAnchorBoundsPreferenceKey.self, alignment: .center) {

    namedAnchors in GeometryReader { overlayGeometry in let elementFrames: [ElementFrame] = namedAnchors.map { .init( elementType: $0.viewType, frame: overlayGeometry[$0.anchor] ) } _RepresentedUINativeAdView( nativeAd: loadedAd, elementFrames: elementFrames ) .frame(width: overlayGeometry.size.width, height: overlayGeometry.size.height) } } .onAppear { nativeAdLoader.loadAd() }