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
相关产品推荐
相关产品推荐

