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

Swift/Xcode借助Firebase实现Instagram式应用内弹窗通知

实现类似Instagram的全局应用内通知弹窗(iOS Swift)

嘿,针对你想要在任意页面弹出应用内通知的需求,我给你整理了一套完整的实现方案,结合你已经用Firebase搞定的事件检测逻辑,直接就能对接上!

1. 先搞一个全局的通知管理器(单例模式)

这个管理器负责统一处理通知的显示、队列管理,确保在应用的任何页面都能调用。

import UIKit

class InAppNotificationManager {
    static let shared = InAppNotificationManager()
    private var notificationQueue: [NotificationData] = []
    private var isShowingNotification = false
    
    // 自定义通知数据模型,根据你的业务需求调整字段
    struct NotificationData {
        let senderAvatarURL: URL?
        let senderName: String
        let actionType: NotificationActionType
        let targetID: String // 跳转目标ID(比如帖子ID、用户ID)
        let extraText: String? // 可选:评论内容等额外信息
    }
    
    enum NotificationActionType {
        case like
        case comment
        case follow
    }
    
    private init() {}
    
    // 对外暴露的通知触发方法
    func triggerNotification(with data: NotificationData) {
        guard !isShowingNotification else {
            // 如果当前正在显示通知,将新通知加入队列等待
            notificationQueue.append(data)
            return
        }
        DispatchQueue.main.async {
            self.displayNotification(data: data)
        }
    }
    
    private func displayNotification(data: NotificationData) {
        isShowingNotification = true
        
        // 创建自定义通知弹窗View
        let notificationView = InAppNotificationView(data: data)
        notificationView.onTap = { [weak self] in
            // 处理弹窗点击跳转逻辑
            self?.handleNotificationTap(actionType: data.actionType, targetID: data.targetID)
            self?.dismissNotification(notificationView: notificationView)
        }
        
        // 获取当前应用的顶层ViewController,确保弹窗能显示在最上层
        guard let keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .compactMap({$0 as? UIWindowScene})
                .first?.windows
                .first(where: {$0.isKeyWindow}),
              let rootVC = keyWindow.rootViewController else {
            isShowingNotification = false
            processNextNotification()
            return
        }
        
        // 将弹窗添加到顶层View
        rootVC.view.addSubview(notificationView)
        notificationView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            notificationView.topAnchor.constraint(equalTo: rootVC.view.safeAreaLayoutGuide.topAnchor, constant: 16),
            notificationView.leadingAnchor.constraint(equalTo: rootVC.view.leadingAnchor, constant: 16),
            notificationView.trailingAnchor.constraint(equalTo: rootVC.view.trailingAnchor, constant: -16),
            notificationView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60)
        ])
        
        // 弹窗滑入动画
        notificationView.transform = CGAffineTransform(translationX: 0, y: -notificationView.bounds.height - 16)
        UIView.animate(withDuration: 0.3) {
            notificationView.transform = .identity
        } completion: { _ in
            // 自动停留3秒后滑出消失
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                self.dismissNotification(notificationView: notificationView)
            }
        }
    }
    
    private func dismissNotification(notificationView: InAppNotificationView) {
        UIView.animate(withDuration: 0.3) {
            notificationView.transform = CGAffineTransform(translationX: 0, y: -notificationView.bounds.height - 16)
            notificationView.alpha = 0
        } completion: { [weak self] _ in
            notificationView.removeFromSuperview()
            self?.isShowingNotification = false
            // 处理队列中的下一条通知
            self?.processNextNotification()
        }
    }
    
    private func processNextNotification() {
        guard !notificationQueue.isEmpty else { return }
        let nextData = notificationQueue.removeFirst()
        displayNotification(data: nextData)
    }
    
    private func handleNotificationTap(actionType: NotificationActionType, targetID: String) {
        // 根据不同动作类型跳转对应页面,替换成你自己的ViewController跳转逻辑
        guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { return }
        
        switch actionType {
        case .like:
            let postVC = PostDetailViewController(postID: targetID)
            rootVC.present(UINavigationController(rootViewController: postVC), animated: true)
        case .comment:
            let commentVC = CommentListViewController(postID: targetID)
            rootVC.present(UINavigationController(rootViewController: commentVC), animated: true)
        case .follow:
            let profileVC = UserProfileViewController(userID: targetID)
            rootVC.present(UINavigationController(rootViewController: profileVC), animated: true)
        }
    }
}

2. 设计Instagram风格的弹窗UI(InAppNotificationView)

这个View就是实际显示的通知弹窗,包含头像、消息文本等元素,你可以根据喜好调整样式:

import UIKit

class InAppNotificationView: UIView {
    var onTap: (() -> Void)?
    
    private let avatarImageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        iv.clipsToBounds = true
        iv.layer.cornerRadius = 20 // 圆形头像
        iv.backgroundColor = .systemGray4
        return iv
    }()
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 2
        label.font = UIFont.systemFont(ofSize: 14)
        label.textColor = .label
        return label
    }()
    
    init(data: InAppNotificationManager.NotificationData) {
        super.init(frame: .zero)
        setupUI()
        configureContent(with: data)
        addTapGesture()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        backgroundColor = .systemBackground
        layer.cornerRadius = 12
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.15
        layer.shadowOffset = CGSize(width: 0, height: 2)
        layer.shadowRadius = 6
        
        addSubview(avatarImageView)
        addSubview(messageLabel)
        
        avatarImageView.translatesAutoresizingMaskIntoConstraints = false
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
            avatarImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 40),
            avatarImageView.heightAnchor.constraint(equalToConstant: 40),
            
            messageLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 12),
            messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
            messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
            messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12)
        ])
    }
    
    private func configureContent(with data: InAppNotificationManager.NotificationData) {
        // 加载用户头像(推荐用Kingfisher/SDWebImage做异步缓存加载)
        if let avatarURL = data.senderAvatarURL {
            avatarImageView.kf.setImage(with: avatarURL) // 需要先导入Kingfisher
        } else {
            avatarImageView.image = UIImage(systemName: "person.circle.fill")
        }
        
        // 根据动作类型组装消息文本
        switch data.actionType {
        case .like:
            messageLabel.text = "\(data.senderName) 赞了你的帖子"
        case .comment:
            let commentText = data.extraText ?? "一条评论"
            messageLabel.text = "\(data.senderName) 评论了你的帖子:\(commentText)"
        case .follow:
            messageLabel.text = "\(data.senderName) 关注了你"
        }
    }
    
    private func addTapGesture() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        addGestureRecognizer(tap)
        isUserInteractionEnabled = true
    }
    
    @objc private func handleTap() {
        onTap?()
    }
}

3. 和Firebase事件逻辑对接

你已经实现了点赞、评论、关注的检测逻辑,只需要在事件触发时,调用通知管理器的方法即可:

// 示例:Firebase监听点赞事件的回调
func handleLikeEvent(senderUID: String, senderAvatarURL: URL?, postID: String) {
    // 从Firebase数据库获取发送者用户名
    fetchUserName(uid: senderUID) { userName in
        let notificationData = InAppNotificationManager.NotificationData(
            senderAvatarURL: senderAvatarURL,
            senderName: userName,
            actionType: .like,
            targetID: postID,
            extraText: nil
        )
        // 触发全局通知
        InAppNotificationManager.shared.triggerNotification(with: notificationData)
    }
}

评论、关注事件的调用方式完全类似,只需要修改对应的actionType和参数即可。

4. 额外优化建议

  • 通知合并:如果短时间内收到多个同类型通知(比如多个点赞),可以合并成一条“张三、李四和另外3人赞了你的帖子”,提升用户体验。
  • 样式自定义:可以根据App主题调整弹窗的背景色、字体、动画速度,让它更贴合你的产品风格。
  • 用户开关:可以在设置页面添加应用内通知的开关,允许用户自主选择是否接收。
  • 缓存优化:头像图片一定要用第三方库做缓存,避免重复请求网络,提升弹窗加载速度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:33:13