ClubKit
ClubKit与我们公司的Socket Mobile S550 NFC读取器配合使用时,提供会员/忠诚度功能。开发者可以使用S550 NFC读取器来扫描用户携带的相应移动通行证和/或RFID卡片,以更新其本地记录。例如,在配置后,维护访问次数、上次访问时间等信息。
用法
在内部,ClubKit是我们的iOS捕获SDK的一个整体。因此,您自然需要提供凭证才能开始。
您可能需要提供自己的MembershipUser
类子类,以维护关于最终用户信息更多的默认信息
override func viewDidLoad() {
super.viewDidLoad()
setupClub()
}
private func setupClub() {
let appKey = <Your App Key>
let appId = <Your App ID>
let developerId = <Your Developer ID>
Club.shared.setDelegate(to: self)
.setCustomMembershipUser(classType: CustomMembershipUser.self)
.setDispatchQueue(DispatchQueue.main)
.setDebugMode(isActivated: true)
.open(withAppKey: appKey,
appId: appID,
developerId: developerID,
completion: { (result) in
if result != CaptureLayerResult.E_NOERROR {
// Open failed due to internal error.
// Display an alert to the user suggesting to restart the app
// or perform some other action.
}
})
}
文档
创建User类
默认情况下,ClubKit提供了一个现成的用户类:MembershipUser
这为每个用户记录提供了5个基本值
userId
:一个唯一的字符串,用于标识用户记录。username
:一个字符串,用于表示用户的名称。timeStampAdded
:用户记录创建日期的间隔时间(自UTC 1970年1月1日起)numVisits
:用户扫描其移动通行证/RFID卡的次数。timeStampOfLastVisit
:用户上次扫描其移动通行证/RFID卡的间隔时间(自UTC 1970年1月1日起)
MembershipUser
超类将变量(及其值)编码和解码为数据。这使得记录可以跨不同设备同步。
以CustomMembershipUser
为例,它除了5个基本值外,还增加了一个第6个值:电子邮件地址
@objcMembers class CustomMembershipUser: MembershipUser {
// More code coming
}
使用类声明中的修饰符@objcMembers
来表示我们的子类将使用Objective C对象。您不会在此处编写Objective C代码。这只是观察dynamic
变量的要求
第1/3步
- 首先,定义您想观察此记录的变量。如前所述,在此示例中,您将向此用户类添加电子邮件地址。
- 然后,定义一个符合
String
、CodingKey
和CaseIterable
的枚举。然后定义所有变量的案例注意:案例名称必须与它表示的变量名称相匹配。骆驼(camelCase)、小写(lowercased)、大写(UPPERCASED)等。必须完全匹配
@objcMembers class CustomMembershipUser: MembershipUser {
// Step 1/3
dynamic var userEmailAddress: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case userEmailAddress
}
// More code coming
}
第2/3步
- 接下来,重写一个叫作
variableNamesAsStrings() -> [String]
的适当命名的函数,并返回您在步骤1中创建的所有案例值,加上超类值。这使得您的子类可以在不同设备之间同步。稍后再说 有关同步用户的更多内容
@objcMembers class CustomMembershipUser: MembershipUser {
// Step 1/3
dynamic var userEmailAddress: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case userEmailAddress
}
// Step 2/3
override class func variableNamesAsStrings() -> [String] {
let superclassVariableNames: [String] = super.variableNamesAsStrings()
// Using CaseIterable, map through all CodingKeys enum and return its rawValue
let mySubclassVariableNames: [String] = CodingKeys.allCases.map { $0.rawValue }
return superclassVariableNames + mySubclassVariableNames
}
// More code coming
}
第3步
最后,为重写的Encodabe和Decodable函数提供实现
@objcMembers class CustomMembershipUser: MembershipUser {
// Step 1/3
dynamic var userEmailAddress: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case userEmailAddress
}
// Step 2/3
override class func variableNamesAsStrings() -> [String] {
let superclassVariableNames: [String] = super.variableNamesAsStrings()
// Using CaseIterable, map through all CodingKeys enum and return its rawValue
let mySubclassVariableNames: [String] = CodingKeys.allCases.map { $0.rawValue }
return superclassVariableNames + mySubclassVariableNames
}
// Step 3/3
// Encodable
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
// Create container to encode your variables
var container = encoder.container(keyedBy: CodingKeys.self)
// TRY to encode your variable using the key that matches
try container.encode(emailAddress, forKey: .emailAddress)
// ... Other variables if necessary
}
// Decodable
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
// Create container, again, but this time for decoding your variables
let container = try decoder.container(keyedBy: CodingKeys.self)
// TRY to decode the data into your original variable type and value
emailAddress = try container.decode(String.self, forKey: .emailAddress)
}
}
编码变量时,使用在CodingKeys
枚举中创建的匹配键,并将变量编码为数据。解码时相反。与容器中匹配特定键的数据解码为其原始变量。
显示用户记录
使用MembershipUserCollection
,您可以在UITableView或UICollectionView中显示用户记录。它在初始化器中接受一个泛型参数。传入您的自定义会员用户类
private let usersCollection = MembershipUserCollection<CustomMembershipUser>()
第一步/两步
首先,需要开始观察用户记录的变化。变化包括新添加、更新和删除。您可以像这样观察或停止观察用户记录的变化:
var tableView: UITableView...
private let usersCollection = MembershipUserCollection<CustomMembershipUser>()
// ...
private func observeChanges() {
usersCollection.observeAllRecords({ [weak self] (changes: MembershipUserChanges) in
switch changes {
case .initial(_):
// Reload tableView with initial data once
self?.tableView.reloadData()
case let .update(_, deletions, insertions, modifications):
self?.tableView.performBatchUpdates({
// Reload rows for updated user records
self?.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
// Insert newly created records
self?.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
// Delete rows for records that have been deleted
self?.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
}, completion: { (completed: Bool) in
self?.tableView.reloadData()
})
break
case let .error(error):
// Handle error...
}
})
}
private func stopObserving() {
// Will stop observing changes.
// Call on viewWillDisappear, etc.
userCollection.stopObserving()
}
第二步/两步
接下来,实现通常的UITableViewDelegate
和UITableViewDataSource
方法来显示用户记录
extension UserListViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersCollection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
if let user = usersCollection.user(at: indexPath.item) {
// Configure your cell with all of the data in this user record
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let user = usersCollection.user(at: indexPath.item) {
// Perform action with selected user
}
}
}
在设备间同步用户记录
在设备间同步用户记录就像在两个设备之间发送一个包含用户记录的文件一样简单。使用以下函数,您可以生成包含本地存储的用户记录的文件,并将该文件导出到任何必要的位置
func getExportableURLForDataSource<T: MembershipUser>(ofType objectType: T.Type, fileType: IOFileType) -> URL?
objectType
将是指定CustomMembershipUser
类或其他 MembershipUser子类的
IOFileType
提供两种类型的文件
- 用户列表
- CSV
用户列表文件应仅用于使用ClubKit的两个应用程序之间。在其他环境中可能很难打开它。然而,CSV文件(逗号分隔值)可导出到其他环境。
步骤 1/3(导出)
if let exportableURL = Club.shared.getExportableURLForDataSource(ofType: CustomMembershipUser.self, fileType: .userList) {
// Show UIActivityViewController with exportableURL
let activityItems: [Any] = [
exportableURL
]
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
present(activityController, animated: true, completion: nil)
}
步骤 2/3(导入)
导入需要应用程序“捕获”传入的URL并从URL解码用户记录
对于iOS 13.0之前的预应用程序,请在AppDelegate中实现以下功能来处理传入的URL
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
Club.shared.getImportedDataSource(ofType: CustomMembershipUser.self, from: url)
return true
}
对于支持iOS 13.0及以后的应用程序,请在SceneDelegate中实现以下功能
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
Club.shared.getImportedDataSource(ofType: CustomMembershipUser.self, from: url)
}
}
步骤 3/3(Info.plist)
最后,您需要配置应用程序以导入和导出自步骤1中的自定义可导出URL。为此,您需要在Info.plist文件中添加条目,以便应用程序知道如何处理ClubKit提供的用于导出用户记录的自定义文件类型。
最简单的方法是按照以下步骤操作
- "右键单击"您的Info.plist文件
打开为->
源代码
- 然后在
<dict>
标签之间粘贴这些条目
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Membership User List Document</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.socketmobile.MemberPass.MembershipUserListDocument</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Membership User CSV Document</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.socketmobile.MemberPass.MembershipUserCSVDocument</string>
</array>
</dict>
</array>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>UISupportsDocumentBrowser</key>
<false/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Membership User List Document</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>com.socketmobile.MemberPass.MembershipUserListDocument</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>MUSRL</string>
<string>musrl</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Membership User CSV Document</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>com.socketmobile.MemberPass.MembershipUserCSVDocument</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>MUCSV</string>
<string>mucsv</string>
</array>
</dict>
</dict>
</array>
下一步是实现ClubKit的代理,这提供了批准或拒绝将传入的用户记录与本地存储合并的机会。
代理事件
ClubKit通过代理调用提供对其他事件的通知。符合ClubMiddlewareDelegate以接收这些事件。
通知接收器错误
@objc optional func club(_ clubMiddleware: Club, didReceive error: Error)
@objc optional func club(_ clubMiddleware: Club, didCreateNewMembership user: MembershipUser)
通知代理 MembershipUser 对象已更新。如果需要,可以使用此功能显示弹出视图或更新 UI 等。 注意 如果在 UITableView 或 UICollectionView 中显示记录列表,请参阅此 部分 以更新列表
@objc optional func club(_ clubMiddleware: Club, didUpdateMembership user: MembershipUser)
通知代理 MembershipUser 对象已删除。如果需要,可以使用此功能显示弹出视图或更新 UI 等。 注意 如果在 UITableView 或 UICollectionView 中显示记录列表,请参阅此 部分 以更新列表
@objc optional func club(_ clubMiddleware: Club, didDeleteMembership user: MembershipUser)
通知代理从另一设备导入的 MembershipUser 对象数组。使用此功能将传送数据的新的列表存储在本地的 Realm 中
@objc optional func club(_ clubMiddleware: Club, didReceiveImported users: [MembershipUser])
此函数可用于确定传入的用户记录列表是否应与现有本地存储合并。例如,会显示一个警告,让开发人员、职员等有机会接受或拒绝导入的用户记录。
func club(_ clubMiddleware: Club, didReceiveImported users: [MembershipUser]) { var alertStyle = UIAlertController.Style.actionSheet if (UIDevice.current.userInterfaceIdiom == .pad) { alertStyle = UIAlertController.Style.alert } let title = "Import" let message = "Received \(users.count) users to import. Would you like to save them?" let alertController = UIAlertController(title: title, message: message, preferredStyle: alertStyle) let yesAction = UIAlertAction(title: "Yes", style: UIAlertAction.Style.default) { (_) in Club.shared.merge(importedUsers: users) } let noAction = UIAlertAction(title: "No", style: UIAlertAction.Style.cancel) { (_) in // Just decline and do nothing } alertController.addAction(yesAction) alertController.addAction(noAction) present(alertController, animated: true, completion: nil) }
示例
要运行示例项目,请克隆仓库,然后首先从 Example 目录运行 pod install
需求
Xcode 7.3
iOS 9.3
安装
ClubKit可以通过CocoaPods获得。要安装它,只需将以下行添加到您的Podfile中
pod 'ClubKit'
作者
Chrishon, [email protected]
许可证
ClubKit以MIT许可证提供。有关更多信息,请参阅LICENSE文件。