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

iOS应用中如何自定义Firebase应用内消息?含弹窗定制需求

iOS 自定义Firebase应用内消息(双按钮弹窗)实现方案

核心结论

可以通过Firebase In-App Messaging实现带标题、副标题及双按钮的自定义弹窗,核心是自定义消息显示库,拦截Firebase的消息回调,用自定义UI展示内容并处理按钮事件。

实现步骤

1. 基础环境配置

确保已集成Firebase核心SDK及In-App Messaging组件:

  • 在Podfile中添加依赖:
    pod 'Firebase/Core'
    pod 'Firebase/InAppMessagingDisplay'
    
  • 执行pod install后,在AppDelegate中初始化Firebase:
    import Firebase
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        return true
    }
    

2. 实现自定义弹窗UI

创建一个独立的ViewController作为弹窗容器,包含标题、副标题和两个操作按钮:

import UIKit

class CustomInAppMessageVC: UIViewController {
    // MARK: UI Components
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .boldSystemFont(ofSize: 18)
        label.textAlignment = .center
        label.numberOfLines = 0
        return label
    }()
    
    private let subtitleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 14)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.textColor = .gray
        return label
    }()
    
    private let primaryButton: UIButton = {
        let btn = UIButton(type: .system)
        btn.layer.cornerRadius = 8
        btn.backgroundColor = .systemBlue
        btn.setTitleColor(.white, for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
        return btn
    }()
    
    private let secondaryButton: UIButton = {
        let btn = UIButton(type: .system)
        btn.layer.cornerRadius = 8
        btn.layer.borderWidth = 1
        btn.layer.borderColor = UIColor.systemBlue.cgColor
        btn.setTitleColor(.systemBlue, for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
        return btn
    }()
    
    // MARK: Callback Handlers
    var primaryActionHandler: (() -> Void)?
    var secondaryActionHandler: (() -> Void)?
    
    // MARK: Initializer
    init(title: String, subtitle: String, primaryBtnText: String, secondaryBtnText: String) {
        super.init(nibName: nil, bundle: nil)
        titleLabel.text = title
        subtitleLabel.text = subtitle
        primaryButton.setTitle(primaryBtnText, for: .normal)
        secondaryButton.setTitle(secondaryBtnText, for: .normal)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupButtonActions()
    }
    
    // MARK: UI Setup
    private func setupUI() {
        view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        let containerView = UIView()
        containerView.backgroundColor = .white
        containerView.layer.cornerRadius = 12
        containerView.clipsToBounds = true
        
        view.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        
        [titleLabel, subtitleLabel, primaryButton, secondaryButton].forEach {
            containerView.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // Layout Constraints
        NSLayoutConstraint.activate([
            containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            containerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85),
            
            titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 24),
            titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
            titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
            
            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
            subtitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
            subtitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
            
            secondaryButton.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 24),
            secondaryButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
            secondaryButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
            secondaryButton.heightAnchor.constraint(equalToConstant: 44),
            
            primaryButton.topAnchor.constraint(equalTo: secondaryButton.bottomAnchor, constant: 12),
            primaryButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
            primaryButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
            primaryButton.heightAnchor.constraint(equalToConstant: 44),
            
            primaryButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -24)
        ])
    }
    
    // MARK: Button Actions
    private func setupButtonActions() {
        primaryButton.addTarget(self, action: #selector(primaryBtnTapped), for: .touchUpInside)
        secondaryButton.addTarget(self, action: #selector(secondaryBtnTapped), for: .touchUpInside)
    }
    
    @objc private func primaryBtnTapped() {
        primaryActionHandler?()
        dismiss(animated: true)
    }
    
    @objc private func secondaryBtnTapped() {
        secondaryActionHandler?()
        dismiss(animated: true)
    }
}

3. 拦截Firebase消息并显示自定义弹窗

实现FIRInAppMessagingDisplayDelegate协议,拦截默认消息显示逻辑,解析自定义参数并展示弹窗:

import FirebaseInAppMessagingDisplay

extension YourRootViewController: FIRInAppMessagingDisplayDelegate {
    func viewDidLoad() {
        super.viewDidLoad()
        // 设置代理拦截消息
        FirebaseInAppMessagingDisplay.inAppMessagingDisplay().delegate = self
    }
    
    func messageDisplayWillDisplay(_ message: FIRInAppMessagingDisplayMessage) {
        // 解析Firebase消息中的自定义数据
        guard let customData = message.customData else { return }
        
        let title = customData["title"] as? String ?? ""
        let subtitle = customData["subtitle"] as? String ?? ""
        let primaryBtnText = customData["primary_button_text"] as? String ?? "确认"
        let secondaryBtnText = customData["secondary_button_text"] as? String ?? "取消"
        let primaryAction = customData["primary_action"] as? String ?? ""
        
        // 实例化自定义弹窗
        let messageVC = CustomInAppMessageVC(
            title: title,
            subtitle: subtitle,
            primaryBtnText: primaryBtnText,
            secondaryBtnText: secondaryBtnText
        )
        
        // 配置按钮回调
        messageVC.primaryActionHandler = {
            // 处理主按钮动作(比如跳转deeplink、触发业务逻辑)
            if let url = URL(string: primaryAction) {
                UIApplication.shared.open(url)
            }
            // 上报Firebase点击事件
            FirebaseInAppMessaging.inAppMessaging().logEvent(.messageClicked, withMessage: message)
        }
        
        messageVC.secondaryActionHandler = {
            // 处理次要按钮动作
            FirebaseInAppMessaging.inAppMessaging().logEvent(.messageDismissed, withMessage: message)
        }
        
        // 展示弹窗
        messageVC.modalPresentationStyle = .overFullScreen
        messageVC.modalTransitionStyle = .crossDissolve
        present(messageVC, animated: true)
    }
    
    func messageDisplayDidDismiss(_ message: FIRInAppMessagingDisplayMessage) {
        // 可选:处理弹窗消失后的逻辑
    }
}

4. Firebase控制台配置自定义消息

在Firebase控制台创建应用内消息时:

  1. 选择自定义消息类型
  2. 在自定义数据模块添加以下键值对:
    • title: 弹窗标题内容
    • subtitle: 弹窗副标题内容
    • primary_button_text: 主按钮文字
    • secondary_button_text: 次要按钮文字
    • primary_action: 主按钮触发的动作(比如deeplink地址)
  3. 设置消息触发条件(如用户进入特定页面、满足特定用户属性等)

关键注意事项

  • 必须设置FIRInAppMessagingDisplayDelegate才能拦截默认UI,否则会显示Firebase原生弹窗
  • 调用logEvent上报事件,确保Firebase控制台能统计消息的点击/关闭数据
  • 自定义弹窗的布局可根据需求调整(比如按钮横向排列、修改配色等)
  • 测试时可使用Firebase的"测试消息"功能,直接推送自定义消息到测试设备

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:42:02