[Swift/Firebase] In-App Messagingのカスタマイズ

デフォルトのモーダルをそのまま配信するとiOSのデザインぽくないのでカスタマイズを施すようにしてみる。

Firebase In-App Messagingのセットアップ自体は↓を参考にしてください。

[Swift/Firebase] Firebase In-App Messaging を使ってみる

実装

(@mainのview.swift)

全パターン実装は時間かかるので、今回はモーダルタイプの配信を受け取って表示させるようにします。

import SwiftUI
import FirebaseInAppMessaging

@main
struct SwiftUI_PlaygroundApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            HomeView()
                .modalInAppMessage { modalMessage, delegate in
                    InAppMessagingModalView(modalMessage: modalMessage, delegate: delegate)
                }
//                .imageOnlyInAppMessage { imageOnlyMessage, delegate in
//                    ImageOnlyInAppMessageView(imageOnlyMessage: imageOnlyMessage, delegate: delegate)
//                }
//                .cardInAppMessage { cardInAppMessage, delegate in
//                    CardInAppMessageView(cardInAppMessage: cardInAppMessage, delegate: delegate)
//                }
//                .bannerInAppMessage { bannerInAppMessage, delegate in
//                    BannerInAppMessageView(bannerInAppMessage: bannerInAppMessage, delegate: delegate)
//                }
        }
    }
}

modalMessageにFirebaseコンソールで指定した値 (URL, Text, Boyd,,,etc)が諸々渡ってくるのでそれをViewで受け取るようにしています、delegateも同様。

InAppMessagingModalView

実際にモーダルとして表示するためのView, バツボタンの画像データは各々好きなものに変えてください。

import SwiftUI
import FirebaseInAppMessaging

struct InAppMessagingModalView: View {
    // MARK: - Properties
    let modalMessage: InAppMessagingModalDisplay
    let delegate: InAppMessagingDisplayDelegate

    // MARK: - Body
    var body: some View {
        VStack(spacing: 4) {
            pullCloseButton
            inAppMessagingModalView
        }
        .padding(.horizontal, 20)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        .background(
            Color.black
                .opacity(0.5)
                .ignoresSafeArea()
                .onTapGesture {
                    delegate.messageDismissed?(modalMessage, dismissType: .typeUserTapClose)
                }
        )
    }

    // 右上閉じるボタン
    var pullCloseButton: some View {
        HStack {
            Button {
                delegate.messageDismissed?(modalMessage, dismissType: .typeUserTapClose)
            } label: {
                Image("img_headback")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            .frame(width: 40, height: 40)
            .padding(2)
        }
        .frame(maxWidth: .infinity, alignment: .trailing)
    }

    // モーダル内容
    var inAppMessagingModalView: some View {
        VStack(spacing: 20) {
            inAppTitleText
            inAppImage
            inAppBodyText
            inAppButton
        }
        .padding(24)
        .background(.white)
        .frame(maxWidth: .infinity)
        .cornerRadius(12)
    }

    // メイン画像
    var inAppImage: some View {
        let imageURL = modalMessage.imageData?.imageURL
        return AsyncImage(url: URL(string: imageURL ?? "")) { image in
            image.resizable()
                .aspectRatio(contentMode: .fit)
        } placeholder: {
            ProgressView()
        }
    }

    // タイトル
    var inAppTitleText: some View {
        Text(modalMessage.title)
            .font(.system(size: 16, weight: .bold))
            .foregroundColor(.black)
    }

    // 内容
    var inAppBodyText: some View {
        Text(modalMessage.bodyText ?? "")
            .font(.system(size: 14))
            .foregroundColor(.gray)
            .multilineTextAlignment(.center)
    }

    // 下部ボタン
    var inAppButton: some View {
        Button(action: {
            guard let url = modalMessage.actionURL else { return }
            UIApplication.shared.open(url)
            delegate.messageDismissed?(modalMessage, dismissType: .typeUserTapClose)
        }, label: {
            Text(modalMessage.actionButton?.buttonText ?? "")
                .font(.system(size: 16, weight: .bold))
                .foregroundColor(.black)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        })
        .overlay(
            RoundedRectangle(cornerRadius: 80)
                .stroke(.black, lineWidth: 3)
        )
        .background(.white)
        .frame(height: 48)
        .padding(.horizontal, 6)
    }
}

検証

序盤に貼った関連記事内にもありますが、In-Appの本番配信だと1日1回?くらいしか検証できないので検証用にIDを取得してそちらを使って表示の検証をするようにします。

AppDelegateとか最初に表示されるViewとかに以下を入れる。

Installations.installations().installationID { id, error in
    if let error = error {
        print("Error retrieving installation ID: \(error.localizedDescription)")
    } else if let id = id {
        print("Firebase Installation ID: \(id)")
    }
}

これでIDを確認できるようになるので、↑の関連記事のフローを参考にテスト配信して表示されるか確認してください。