MagicBell iOS SDK

这是官方的 MagicBell iOS SDK。

此 SDK 提供:

它需要:

快速开始

首先,从您的 MagicBell 仪表盘 获取您的 API 密钥。然后,初始化客户端并设置当前用户

import MagicBell

// Create the MagicBell client with your project's API key
let client = MagicBellClient(apiKey: "[MAGICBELL_API_KEY]")

// Set the MagicBell user
let user = client.connectUser(email: "richard@example.com")

// Create a store of notifications
let store = user.store.build()

// Fetch the first page of notifications
store.fetch { result in
    if let notifications = try? result.get() {
        // Print the unread count
        print("Count: \(store.unreadCount)")

        // Print the fetched notifications
        print("notifications: \(notifications)")
    }
}

此代码仓库还包含一个完整的示例。要运行该项目:

目录

安装

CocoaPods

要使用 CocoaPods 安装 MagicBell,请将以下条目添加到您的 Podfile 中:

pod 'MagicBell', '>=3.0.0'

重要提示:请确保在您的 Podfile 中指定 use_frameworks!

然后,运行 pod install

Swift Package Manager

要使用 Swift Package Manager 安装 MagicBell,只需将依赖项添加到您的项目,如下所示:

dependencies: [
    .package(url: "https://github.com/magicbell/magicbell-swift", .upToNextMajor(from: "3.0.0"))
]

Carthage

要使用 Carthage 安装 MagicBell,请将以下依赖项添加到 Carfile:

github "magicbell/magicbell-swift" "3.0.0"

然后,运行 carthage update --use-xcframeworks --platform [iOS|macOS] --no-use-binaries (选择所需的平台) 以解析依赖项。

MagicBell.xcframework 与 Carthage 解析的其他依赖项一起添加到您的项目链接的框架中。

MagicBell 客户端

第一步是创建一个 MagicBellClient 实例。它将为您管理用户和其他功能。初始化它需要您的 MagicBell 项目的 API 密钥。

let magicbell = MagicBellClient(apiKey: "[MAGICBELL_API_KEY]")

您可以在初始化客户端时提供其他选项:

let magicbell = MagicBellClient(
    apiKey: "[MAGICBELL_API_KEY]"
    logLevel: .debug
)
参数 默认值 描述
apiKey - 您的 MagicBell 的 API 密钥
apiSecret nil 您的 MagicBell 的 API 密钥
logLevel .none 将其设置为 .debug 以启用日志

虽然 API 密钥旨在发布,但您不应分发 API 密钥。而是为您的项目启用 HMAC,并在分发您的应用程序之前在您的后端生成用户密钥。

集成到您的应用中

您应该在应用程序中尽早创建客户端实例,并确保您的应用程序中只使用一个实例。

import MagicBell

// Store the instance at a place of your convenience
let magicbell = MagicBellClient(apiKey: "[MAGICBELL_API_KEY]")

或者,您可以将唯一实例作为静态共享实例分配到 MagicBellClient 中。

import MagicBell

extension MagicBellClient {
    static var shared = MagicBellClient(apiKey: "[MAGICBELL_API_KEY]")
}

用户

对 MagicBell API 的请求需要您识别 MagicBell 用户。 这可以通过使用用户的电子邮件或外部 ID 在 MagicBellClient 实例上调用 connectUser(...) 方法来完成

// Identify the user by its email
let user = magicbell.connectUser(email: "richard@example.com")

// Identify the user by its external id
let user = magicbell.connectUser(externalId: "001")

// Identify the user by both, email and external id
let user = magicbell.connectUser(email: "richard@example.com", externalId: "001")

connectUser 的每个变体都支持一个可选的 hmac 参数,该参数应在为项目启用 HMAC 安全性时发送。

您可以连接 您需要的许多用户

重要提示: User 实例是单例。因此,使用相同参数调用 connectUser 方法将产生相同的用户

let userOne = magicbell.connectUser(email: "mary@example.com")
let userTwo = magicbell.connectUser(email: "mary@example.com")

assert(userOne === userTwo, "Both users reference to the same instance")

多用户支持

如果您的应用支持多个登录,您可能希望同时显示所有已登录用户的通知状态。MagicBell SDK 允许您这样做。

您可以根据需要多次使用已登录用户的电子邮件或外部 ID 调用 connectUser(:) 方法。

let userOne = magicbell.connectUser(email: "richard@example.com")
let userTwo = magicbell.connectUser(email: "mary@example.com")
let userThree = magicbell.connectUser(externalId: "001")

注销用户

当用户从您的应用程序注销时,您希望:

这可以使用 MagicBell 客户端实例的 disconnectUser 方法来实现

// Remove by email
magicbell.disconnectUser(email: "richard@example.com")

// Remove by external id
magicbell.disconnectUser(externalId: "001")

// Remove by email and external id
magicbell.disconnectUser(email: "richard@example.com", externalId: "001")

集成到您的应用中

MagicBell User 实例需要可以在您的应用程序中使用。这里有一些选择:

扩展您自己的用户对象

如果您在您的应用程序中有一个用户对象,此方法会很有帮助。MagicBell 将保证给定电子邮件/externalId 的 User 实例是唯一的,您只需要提供对该实例的访问权限。例如:

import MagicBell

// Your own user
struct User {
    let name: String
    let email: String
}

extension User {
    /// Returns the logged in MagicBell user
    func magicBell() -> MagicBell.User {
        return magicbell.connectUser(email: email)
    }
}

定义一个全局属性

这是如何定义一个可空的全局变量,它将代表您的 MagicBell 用户:

import MagicBell

let magicbell = MagicBellClient(apiKey: "[MAGICBELL_API_KEY]")
var magicbellUser: MagicBell.User? = nil

一旦您执行登录,就为此变量分配一个值。请记住,您必须检查 magicbellUser 变量是否已实际设置,然后才能在您的代码中访问它。

使用您自己的依赖注入图

您还可以将 MagicBell User 实例注入到您自己的图中,并使用您喜欢的模式跟踪它。

NotificationStore

NotificationStore 类表示 MagicBell 通知的集合。您可以通过用户存储对象上的 .build(...) 方法创建此类的实例。

例如:

let allNotifications = user.store.build()

let readNotifications = user.store.build(read: true)

let unreadNotifications = user.store.build(read: false)

let archviedNotifications = user.store.build(archived: true)

let billingNotifications = user.store.build(category: "billing")

let firstOrderNotifications = user.store.build(topic: "order:001")

以下是通知存储的属性:

属性 类型 描述
totalCount Int 通知的总数
unreadCount Int 未读通知的数量
unseenCount Int 未查看通知的数量
hasNextPage Bool 向前分页时是否有更多项目
count Int 商店中当前通知的数量
predicate StorePredicate 用于过滤通知的谓词

以下是可用的方法:

方法 描述
refresh 重置商店并获取第一页通知
fetch 获取下一页通知
subscript(index:) 用于访问通知的下标:store[index]
delete 删除通知
delete 删除通知
markAsRead 将通知标记为已读
markAsUnread 将通知标记为未读
archive 存档通知
unarchive 取消存档通知
markAllRead 将所有通知标记为已读
markAllUnseen 将所有通知标记为已查看

大多数方法都有两种实现:

// Delete notification
store.delete(notification) { result in
    switch result {
    case .success:
        print("Notification deleted")
    case .failure(error):
        print("Failed: \(error)")
    }
}

// Read a notification
store.markAsRead(notification)
    .sink { error in
        print("Failed: \(error)")
    } receiveValue: { notification in
        print("Notification marked as read")
    }

这些方法确保当通知更改时,商店的状态是一致的。例如,当读取通知时,谓词为 read: .unread 的商店将从自身中删除该通知,并通知通知商店的所有观察者。

高级过滤器

您还可以使用更高级的过滤器创建商店。为此,使用带有 StorePredicate.build(...) 方法获取商店。

let predicate = StorePredicate()
let notifications = user.store.build(predicate: predicate)

以下是可用的选项:

参数 选项 默认 描述
read truefalsenil nil read 状态过滤(nil 表示未指定)
seen truefalsenil nil seen 状态过滤(nil 表示未指定)
archived truefalse false archived 状态过滤
category String nil 按类别过滤
topic String nil 按主题过滤

例如,使用此谓词获取类别为 "important" 的未读通知

let predicate = StorePredicate(read: .unread, category: "important")
let store = user.store.build(predicate: predicate)

通知商店是单例。两次使用相同的谓词创建商店将产生相同的实例。

注意:一旦获取了商店,它将被保存在内存中,以便可以实时更新。您可以使用 .dispose 方法强制删除商店。

let predicate = StorePredicate()
user.store.dispose(with: predicate)

当您 删除用户实例 时,会自动为您完成此操作。

观察变更

当调用 fetchrefresh 时,商店将通知内容观察者新添加的通知(请在 此处 阅读有关观察者的信息)。

// Obtaining a new notification store (first time)
let store = user.store.build()

// First loading
store.fetch { result in
    if let notifications = try? result.get() {
        print("Notifications: \(notifications))")

        // If store has next page available
        if store.hasNextPage {
            // Load next page
            store.fetch { result in
                if let notifications = try? result.get() {
                    print("Notifications: \(notifications))")
                }
            }
        }
    }
}

要重置和获取商店:

store.refresh { result in
    if let notifications = try? result.get() {
        print("Notifications: \(notifications))")
    }
}

访问通知

NotificationStore 是一个可迭代的集合。因此,可以按预期方式访问通知

for i in 0..<store.count {
    let notification = store[i]
    print("notification: \(notification)")
}

// forEach
store.forEach { notification in
    print("notification: \(notification)")
}

// for in
for notification in store {
    print("notification: \(notification)")
}

// As an array
let notifications = store.notifications

枚举也可用

// forEach
store.enumerated().forEach { idx, notification in
    print("notification[\(idx)] = \(notification)")
}

// for in
for (idx, notification) in store.enumerated() {
    print("notification[\(idx)] = \(notification)")
}

观察通知商店更改

经典观察者方法

当新通知到达或通知状态更改(标记为已读、已存档等)时,NotificationStore 的实例会自动更新。

要观察通知商店的更改,您的观察者必须实现以下协议

// Get notified when the list of notifications of a notification store changes
protocol NotificationStoreContentObserver: AnyObject {
    func didReloadStore(_ store: NotificationStore)
    func store(_ store: NotificationStore, didInsertNotificationsAt indexes: [Int])
    func store(_ store: NotificationStore, didChangeNotificationAt indexes: [Int])
    func store(_ store: NotificationStore, didDeleteNotificationAt indexes: [Int])
    func store(_ store: NotificationStore, didChangeHasNextPage hasNextPage: Bool)
}

// Get notified when the counters of a notification store change
protocol NotificationStoreCountObserver: AnyObject {
    func store(_ store: NotificationStore, didChangeTotalCount count: Int)
    func store(_ store: NotificationStore, didChangeUnreadCount count: Int)
    func store(_ store: NotificationStore, didChangeUnseenCount count: Int)
}

要观察更改,请实现这些协议(或其中之一),并将自己注册为通知商店的观察者。

let store = user.store.build()
let observer = myObserverClassInstance

store.addContentObserver(observer)
store.addCountObserver(observer)

响应式方法 (iOS 13)

使用类 NotificationStorePublisher 创建一个能够发布 NotificaitonStore 的主要属性更改的 ObservableObject

每当需要时,都必须由用户创建并保留此对象。

属性 类型 描述
totalCount @Published Int 总数
unreadCount @Published Int 未读数
unseenCount @Published Int 未查看数
hasNextPage @Published Bool 指示是否还有更多内容要获取的 Bool 值。
notifications @Published [Notification] 通知数组。

一个典型的用法是在 SwiftUI 的 View 中,充当可以直接从视图引用的视图模型

import SwiftUI
import MagicBell

class Notifications: View {
    let store: NotificationStore
    @ObservedObject var bell: NotificationStorePublisher

    init(store: NotificationStore) {
        self.store = store
        self.bell = NotificationStorePublisher(store)
    }

    var body: some View {
        List(bell.notifications, id: \.id) { notification in
            VStack(alignment: .leading) {
                Text(notification.title)
                Text(notification.content ?? "-")
            }
        }
        .navigationBarTitle("Notifications - \(bell.totalCount)")
    }
}

通知首选项

您可以获取和设置 MagicBell 频道和类别的通知首选项。

public struct Channel {
    public let label: String
    public let slug: String
    public let enabled: Bool
}

public struct Category {
    public let channels: [Channel]
    public let label: String
    public let slug: String
}

public struct NotificationPreferences {
    public let categories: [Category]
}

要获取通知首选项,请按如下方式使用 fetch 方法:

user.preferences.fetch { result in
    if let preferences = try? result.get() {
        print("Notification Preferences: \(preferences)")
    }
}

要更新首选项,请使用 update

// Updating notification preferences.
// The update can be partial and only will affect the categories included in the object being sent
user.preferences.update(preferences) { result in }

要更新单个频道,您可以使用提供的便捷函数 updateChannel

user.preferences.update(categorySlug: "new_comment", channelSlug: "in_app", enabled: true) { result in }

推送通知

您可以向 MagicBell 注册设备令牌,以便接收移动推送通知。 要做到这一点,请在 iOS 提供设备令牌后立即设置它。

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    // Storing device token when refreshed
    magicbell.setDeviceToken(deviceToken: deviceToken)
}

MagicBell 会将设备令牌临时存储在内存中,并在通过 MagicBellClient.connectUser 声明新用户时立即发送该令牌。

当用户断开连接(MagicBellClient.disconnectUser)时,设备令牌将自动取消注册该用户。

贡献

我们欢迎各种形式的贡献。 为此,请克隆存储库,通过在根文件夹中运行命令 carthage bootstrap --use-xcframeworks --no-use-binaries 使用 Carthage 解决依赖关系,然后打开 MagicBell.xcodeproj