{"id":25932687,"url":"https://github.com/socketmobile/clubkit-ios","last_synced_at":"2025-09-24T16:52:51.796Z","repository":{"id":46350246,"uuid":"270703662","full_name":"SocketMobile/clubkit-ios","owner":"SocketMobile","description":null,"archived":false,"fork":false,"pushed_at":"2023-07-20T13:11:52.000Z","size":265,"stargazers_count":0,"open_issues_count":5,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-08-24T08:14:31.956Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SocketMobile.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-08T14:39:59.000Z","updated_at":"2021-10-22T01:01:29.000Z","dependencies_parsed_at":"2022-08-02T19:00:09.775Z","dependency_job_id":null,"html_url":"https://github.com/SocketMobile/clubkit-ios","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/SocketMobile/clubkit-ios","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SocketMobile%2Fclubkit-ios","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SocketMobile%2Fclubkit-ios/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SocketMobile%2Fclubkit-ios/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SocketMobile%2Fclubkit-ios/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SocketMobile","download_url":"https://codeload.github.com/SocketMobile/clubkit-ios/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SocketMobile%2Fclubkit-ios/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276787034,"owners_count":25704726,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-24T02:00:09.776Z","response_time":97,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-04T00:38:54.284Z","updated_at":"2025-09-24T16:52:51.780Z","avatar_url":"https://github.com/SocketMobile.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ClubKit\n\n[![CI Status](https://img.shields.io/travis/Chrishon/ClubKit.svg?style=flat)](https://travis-ci.org/Chrishon/ClubKit)\n[![Version](https://img.shields.io/cocoapods/v/ClubKit.svg?style=flat)](https://cocoapods.org/pods/ClubKit)\n[![License](https://img.shields.io/cocoapods/l/ClubKit.svg?style=flat)](https://cocoapods.org/pods/ClubKit)\n[![Platform](https://img.shields.io/cocoapods/p/ClubKit.svg?style=flat)](https://cocoapods.org/pods/ClubKit)\n\nClubKit provides Membership/Loyalty functionality when paired with our Socket Mobile S550 NFC reader.\nDevelopers can use the S550 NFC reader to scan appropriate Mobile Pass and/or RFID cards carried by end users to update\ntheir local record. Examples include maintaining number of visits, time of last visit and much more when configured.\n\n* [Usage](#usage)\n* [Documentation](#documentation)\n    * [Subclassing User class](#subclassing-membership-user)\n    * [Displaying User Records](#displaying-user-list)\n    * [Syncing User Records Between Devices](#syncing-users-between-devices)\n    * [Delegate Events](#receiving-delegate-events)\n* [Example App](#example-app)\n* [Requirements](#requirements)\n* [Installation](#installation)\n\n\u003ca name=\"usage\"/\u003e\n\n## Usage\n\nUnder the hood, ClubKit is an umbrella for our iOS Capture SDK. So naturally, you need to provide credentials to get started. \n\nYou may provide your own [subclass of `MembershipUser`](#subclassing-membership-user) class, to maintain more than the default information\non end users\n\n```swift\n\noverride func viewDidLoad() {\n    super.viewDidLoad()\n\n    setupClub()\n}\n\nprivate func setupClub() {\n    let appKey =        \u003cYour App Key\u003e\n    let appId =         \u003cYour App ID\u003e\n    let developerId =   \u003cYour Developer ID\u003e\n    \n    Club.shared.setDelegate(to: self)\n        .setCustomMembershipUser(classType: CustomMembershipUser.self)\n        .setDispatchQueue(DispatchQueue.main)\n        .setDebugMode(isActivated: true)\n        .open(withAppKey:   appKey,\n              appId:        appID,\n              developerId:  developerID,\n              completion: { (result) in\n                \n                if result != CaptureLayerResult.E_NOERROR {\n                    // Open failed due to internal error.\n                    // Display an alert to the user suggesting to restart the app\n                    // or perform some other action.\n                }\n         })\n    \n}\n```\n\n\u003ca name=\"documentation\" /\u003e\n\n## Documentation\n\n\u003ca name=\"subclassing-membership-user\"/\u003e\n\n## Creating User class\n\nBy default, ClubKit offers an out-of-the-box user class: `MembershipUser`\n\nThis provides 5 basic values for each user record:\n\n- `userId`: A string that uniquely identifiers the user record.\n- `username`: A string for the user's name. \n- `timeStampAdded`: The time interval (since UTC Jan 1 1970) of the user record's creation date\n- `numVisits`: Number of times the user has scanned their mobile pass/RFID card.\n- `timeStampOfLastVisit`: The time interval (since UTC Jan 1 1970) of the last time the user has scanned their mobile pass/RFID card.\n\n\nThe `MembershipUser` superclass encodes and decodes its variables (and their values) into and from Data. This allows the record to be synced across different devices.\n\nUsing the example of `CustomMembershipUser` which, aside from the 5 basic values, adds a 6th value: Email Address:\n\n```swift\n@objcMembers class CustomMembershipUser: MembershipUser {\n    // More code coming\n}\n```\nUse the modifier `@objcMembers` in the class declaration to signify that the class will be using Objective C objects in our subclass.\nYou will \u003cb\u003eNOT\u003c/b\u003e be writing Objective C code here. It is merely a requirement for observing `dynamic` variables\n\n\n\n### Step 1/3\n\n- First, define a variable you would like to observe in this record. As noted before, in this example, you will add an Email Address to this User class.\n- Then, define an enum which conforms to `String`, `CodingKey` and `CaseIterable`. Then define cases for all of your variables\n\u003cb\u003eNOTE: The case name must match the name of the variable it represents.\u003c/b\u003e camelCase, lowercased, UPPERCASED, etc. It must match exactly\n\n```swift\n@objcMembers class CustomMembershipUser: MembershipUser {\n\n    // Step 1/3\n    dynamic var userEmailAddress: String?\n\n    enum CodingKeys: String, CodingKey, CaseIterable {\n        case userEmailAddress\n    }\n    \n    // More code coming\n}\n\n```\n\n### Step 2/3\n- Next, override an aptly named function called `variableNamesAsStrings() -\u003e [String]` and return all the case values you created in Step 1, plus the superclass values.\nThis allows your subclass to be synced between different devices. More on that [later](#syncing-users-between-devices)\n\n```swift\n\n@objcMembers class CustomMembershipUser: MembershipUser {\n\n    // Step 1/3\n    dynamic var userEmailAddress: String?\n\n    enum CodingKeys: String, CodingKey, CaseIterable {\n        case userEmailAddress\n    }\n    \n    // Step 2/3\n    override class func variableNamesAsStrings() -\u003e [String] {\n\n        let superclassVariableNames: [String] = super.variableNamesAsStrings()\n        \n        // Using CaseIterable, map through all CodingKeys enum and return its rawValue\n        let mySubclassVariableNames: [String] = CodingKeys.allCases.map { $0.rawValue }\n        \n        return superclassVariableNames + mySubclassVariableNames\n    }\n    \n    // More code coming\n}\n\n```\n\n### Step 3\nFinally, provide implementation to the overriden [Encodabe](https://developer.apple.com/documentation/swift/encodable)  and  [Decodable](https://developer.apple.com/documentation/swift/decodable) functions:\n\n```swift\n\n@objcMembers class CustomMembershipUser: MembershipUser {\n\n    // Step 1/3\n    dynamic var userEmailAddress: String?\n\n    enum CodingKeys: String, CodingKey, CaseIterable {\n        case userEmailAddress\n    }\n    \n    // Step 2/3\n    override class func variableNamesAsStrings() -\u003e [String] {\n\n        let superclassVariableNames: [String] = super.variableNamesAsStrings()\n        \n        // Using CaseIterable, map through all CodingKeys enum and return its rawValue\n        let mySubclassVariableNames: [String] = CodingKeys.allCases.map { $0.rawValue }\n        \n        return superclassVariableNames + mySubclassVariableNames\n    }\n    \n    // Step 3/3\n    // Encodable\n    public override func encode(to encoder: Encoder) throws {\n        try super.encode(to: encoder)\n        \n        // Create container to encode your variables\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        \n        // TRY to encode your variable using the key that matches\n        try container.encode(emailAddress, forKey: .emailAddress)\n        \n        // ... Other variables if necessary\n    }\n\n    // Decodable\n    public required init(from decoder: Decoder) throws  {\n        try super.init(from: decoder)\n        \n        // Create container, again, but this time for decoding your variables\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        \n        // TRY to decode the data into your original variable type and value\n        emailAddress = try container.decode(String.self, forKey: .emailAddress)\n    }\n\n}\n\n```\nFor encoding variables, it uses the matching Key you created in the `CodingKeys` enum, and encodes the variable to Data.\nFor decoding, its reversed. The Data in the container which matches the specific key is decoded to its original variable.\n\n\u003ca name=\"displaying-user-list\"/\u003e\n\n## Displaying User Records\n\nUsing the `MembershipUserCollection` you can display user records in a UITableView or UICollectionView.\nIt accepts a generic parameter in its initializer. Pass in your custom membership user class:\n\n```swift\nprivate let usersCollection = MembershipUserCollection\u003cCustomMembershipUser\u003e()\n```\n\n\n\n### Step 1/2\n\nFirst, you need to begin observing changes to user records.\nChanges include new additions, updates and deletions.\nYou can observe or stop observing changes for user records like so:\n\n```swift\n\nvar tableView: UITableView...\n\nprivate let usersCollection = MembershipUserCollection\u003cCustomMembershipUser\u003e()\n\n// ...\n\nprivate func observeChanges() {\n    \n    usersCollection.observeAllRecords({ [weak self] (changes: MembershipUserChanges) in\n        switch changes {\n        case .initial(_):\n            \n            // Reload tableView with initial data once\n            self?.tableView.reloadData()\n            \n        case let .update(_, deletions, insertions, modifications):\n            self?.tableView.performBatchUpdates({\n            \n                // Reload rows for updated user records\n                self?.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                \n                // Insert newly created records\n                self?.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                \n                // Delete rows for records that have been deleted\n                self?.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                \n            }, completion: { (completed: Bool) in\n                self?.tableView.reloadData()\n            })\n            break\n        case let .error(error):\n        \n            // Handle error...\n        \n        }\n        \n    })\n}\n\nprivate func stopObserving() {\n    // Will stop observing changes.\n    // Call on viewWillDisappear, etc.\n    userCollection.stopObserving()\n}\n```\n\n### Step 2/2\n\nNext, implement the usual `UITableViewDelegate` and `UITableViewDataSource` functions to display the user records:\n\n```swift\n\nextension UserListViewController: UITableViewDelegate, UITableViewDataSource {\n\n    func numberOfSections(in tableView: UITableView) -\u003e Int {\n        return 1\n    }\n\n    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n        return usersCollection.count\n    }\n\n    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)\n        \n        if let user = usersCollection.user(at: indexPath.item) {\n            \n            // Configure your cell with all of the data in this user record\n            \n        }\n        \n        return cell\n    }\n\n    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        if let user = usersCollection.user(at: indexPath.item) {\n            // Perform action with selected user\n        }\n    }\n}\n\n```\n\n\u003ca name=\"syncing-users-between-devices\"/\u003e\n\n## Syncing User Records Between Devices\n\nSyncing user records between devices is as simple as airdropping a file containing user records between the two.\nUsing the function below, you can generate a file containing the locally stored user records and export that file to wherever necessary\n\n```swift\nfunc getExportableURLForDataSource\u003cT: MembershipUser\u003e(ofType objectType: T.Type, fileType: IOFileType) -\u003e URL?\n```\n\n`objectType` will be the `CustomMembershipUser` class or any other MembershipUser subclass\n\n`IOFileType` provides two kinds of files:\n\n- UserList\n- CSV\n\nThe UserList file should only be used between two applications using ClubKit. It may be difficult opening it in other environments\nThe CSV file (comma separated values) can be exported to other environments however.\n\n### Step 1/3 (Exporting)\n\n```swift\nif let exportableURL = Club.shared.getExportableURLForDataSource(ofType: CustomMembershipUser.self, fileType: .userList) {\n    // Show UIActivityViewController with exportableURL\n    \n    let activityItems: [Any] = [\n        exportableURL\n    ]\n    \n    let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)\n    \n    present(activityController, animated: true, completion: nil)\n}\n```\n\n### Step 2/3 (Importing)\n\nImporting requires that the application \"catches\" incoming URLs and decodes user records from the URL\n\nFor pre-iOS 13.0 applications, implement the function below in `AppDelegate` to handle incoming URLs:\n\n```swift\nfunc application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -\u003e Bool {\n    \n    Club.shared.getImportedDataSource(ofType: CustomMembershipUser.self, from: url)\n    \n    return true\n}\n```\n\nFor applications that support iOS 13.0 and onward, implement the function below in `SceneDelegate`:\n\n```swift\nfunc scene(_ scene: UIScene, openURLContexts URLContexts: Set\u003cUIOpenURLContext\u003e) {\n    \n    if let url = URLContexts.first?.url {\n    \n        Club.shared.getImportedDataSource(ofType: CustomMembershipUser.self, from: url)\n    \n    }\n}\n```\n\n### Step 3/3 (Info.plist)\n\nLastly, you need to configure your application to import and export this custom exportable URL from Step 1. \nTo do so, you will need to add entries to your `Info.plist` file that let the application know how to handle the\ncustom file types that ClubKit provides for exporting user records.\n\nThe simplest method would be to follow these steps:\n- \"Right+Click\" on your `Info.plist` file\n- `Open as-\u003e`\n- `Source code`\n- Then paste these entries in between the `\u003cdict\u003e` tags\n\n```swift\n\u003ckey\u003eCFBundleDocumentTypes\u003c/key\u003e\n\u003carray\u003e\n    \u003cdict\u003e\n        \u003ckey\u003eCFBundleTypeIconFiles\u003c/key\u003e\n        \u003carray/\u003e\n        \u003ckey\u003eCFBundleTypeName\u003c/key\u003e\n        \u003cstring\u003eMembership User List Document\u003c/string\u003e\n        \u003ckey\u003eCFBundleTypeRole\u003c/key\u003e\n        \u003cstring\u003eEditor\u003c/string\u003e\n        \u003ckey\u003eLSHandlerRank\u003c/key\u003e\n        \u003cstring\u003eOwner\u003c/string\u003e\n        \u003ckey\u003eLSItemContentTypes\u003c/key\u003e\n        \u003carray\u003e\n            \u003cstring\u003ecom.socketmobile.MemberPass.MembershipUserListDocument\u003c/string\u003e\n        \u003c/array\u003e\n    \u003c/dict\u003e\n    \u003cdict\u003e\n        \u003ckey\u003eCFBundleTypeIconFiles\u003c/key\u003e\n        \u003carray/\u003e\n        \u003ckey\u003eCFBundleTypeName\u003c/key\u003e\n        \u003cstring\u003eMembership User CSV Document\u003c/string\u003e\n        \u003ckey\u003eCFBundleTypeRole\u003c/key\u003e\n        \u003cstring\u003eEditor\u003c/string\u003e\n        \u003ckey\u003eLSHandlerRank\u003c/key\u003e\n        \u003cstring\u003eOwner\u003c/string\u003e\n        \u003ckey\u003eLSItemContentTypes\u003c/key\u003e\n        \u003carray\u003e\n            \u003cstring\u003ecom.socketmobile.MemberPass.MembershipUserCSVDocument\u003c/string\u003e\n        \u003c/array\u003e\n    \u003c/dict\u003e\n\u003c/array\u003e\n\n\u003ckey\u003eLSSupportsOpeningDocumentsInPlace\u003c/key\u003e\n\u003cfalse/\u003e\n\n\u003ckey\u003eUISupportsDocumentBrowser\u003c/key\u003e\n\u003cfalse/\u003e\n\n\u003ckey\u003eUTExportedTypeDeclarations\u003c/key\u003e\n\u003carray\u003e\n    \u003cdict\u003e\n        \u003ckey\u003eUTTypeConformsTo\u003c/key\u003e\n        \u003carray\u003e\n            \u003cstring\u003epublic.data\u003c/string\u003e\n        \u003c/array\u003e\n        \u003ckey\u003eUTTypeDescription\u003c/key\u003e\n        \u003cstring\u003eMembership User List Document\u003c/string\u003e\n        \u003ckey\u003eUTTypeIconFiles\u003c/key\u003e\n        \u003carray/\u003e\n        \u003ckey\u003eUTTypeIdentifier\u003c/key\u003e\n        \u003cstring\u003ecom.socketmobile.MemberPass.MembershipUserListDocument\u003c/string\u003e\n        \u003ckey\u003eUTTypeTagSpecification\u003c/key\u003e\n        \u003cdict\u003e\n            \u003ckey\u003epublic.filename-extension\u003c/key\u003e\n            \u003carray\u003e\n                \u003cstring\u003eMUSRL\u003c/string\u003e\n                \u003cstring\u003emusrl\u003c/string\u003e\n            \u003c/array\u003e\n        \u003c/dict\u003e\n    \u003c/dict\u003e\n    \u003cdict\u003e\n        \u003ckey\u003eUTTypeConformsTo\u003c/key\u003e\n        \u003carray\u003e\n            \u003cstring\u003epublic.data\u003c/string\u003e\n        \u003c/array\u003e\n        \u003ckey\u003eUTTypeDescription\u003c/key\u003e\n        \u003cstring\u003eMembership User CSV Document\u003c/string\u003e\n        \u003ckey\u003eUTTypeIconFiles\u003c/key\u003e\n        \u003carray/\u003e\n        \u003ckey\u003eUTTypeIdentifier\u003c/key\u003e\n        \u003cstring\u003ecom.socketmobile.MemberPass.MembershipUserCSVDocument\u003c/string\u003e\n        \u003ckey\u003eUTTypeTagSpecification\u003c/key\u003e\n        \u003cdict\u003e\n            \u003ckey\u003epublic.filename-extension\u003c/key\u003e\n            \u003carray\u003e\n                \u003cstring\u003eMUCSV\u003c/string\u003e\n                \u003cstring\u003emucsv\u003c/string\u003e\n            \u003c/array\u003e\n        \u003c/dict\u003e\n    \u003c/dict\u003e\n\u003c/array\u003e\n```\n\nIf you'd like a detailed description about this process, there's a good article on that [here](https://www.raywenderlich.com/8413525-universal-type-identifiers-tutorial-for-ios-importing-and-exporting-app-data)\n\nThe next step is to implement a ClubKit [delegate](#import-users-delegate) which provides the opportunity to approve or deny merging the incoming user records with the local store.\n\n\u003ca name=\"receiving-delegate-events\"/\u003e\n\n## Delegate Events\n\nClubKit provides notifications on other events through delegate calls. Conform to `ClubMiddlewareDelegate` to receive these events.\n\nNotifies receiver of errors\n\n```swift\n@objc optional func club(_ clubMiddleware: Club, didReceive error: Error)\n```\n\nNotifies receiver that a new MembershipUser object has been created.\nUse this to show popup views, or update the UI, etc. if desired.\n\u003cb\u003eNOTE\u003c/b\u003e If displaying list of records in UITableView or UICollectionView, refer to this [section](#displaying-user-list) for updating the list\n\n```swift\n@objc optional func club(_ clubMiddleware: Club, didCreateNewMembership user: MembershipUser)\n```\n\nNotifies the delegate that a MembershipUser object has been updated.\nUse this to show popup views, or update the UI, etc. if desired.\n\u003cb\u003eNOTE\u003c/b\u003e If displaying list of records in UITableView or UICollectionView, refer to this [section](#displaying-user-list) for updating the list\n\n```swift\n@objc optional func club(_ clubMiddleware: Club, didUpdateMembership user: MembershipUser)\n```\n\nNotifies the delegate that a MembershipUser object has been deleted.\nUse this to show popup views, or update the UI, etc. if desired.\n\u003cb\u003eNOTE\u003c/b\u003e If displaying list of records in UITableView or UICollectionView, refer to this [section](#displaying-user-list) for updating the list\n\n```swift\n@objc optional func club(_ clubMiddleware: Club, didDeleteMembership user: MembershipUser)\n```\n\n\u003ca name=\"import-users-delegate\" /\u003e\n\nNotifies the delegate that an array of `MembershipUser` objects have been imported from another device\nUse this to store new list of transferred data in local Realm\n\n```swift\n@objc optional func club(_ clubMiddleware: Club, didReceiveImported users: [MembershipUser])\n```\nThis function can be used to determine if the incoming list of user records should be merged with the existing local store\nFor example, an alert is displayed giving the developer, clerk, etc. the opportunity to accept or decline the imported user records.\n\n```swift\nfunc club(_ clubMiddleware: Club, didReceiveImported users: [MembershipUser]) {\n\n    var alertStyle = UIAlertController.Style.actionSheet\n    if (UIDevice.current.userInterfaceIdiom == .pad) {\n        alertStyle = UIAlertController.Style.alert\n    }\n    \n    let title = \"Import\"\n    let message = \"Received \\(users.count) users to import. Would you like to save them?\"\n    \n    let alertController = UIAlertController(title: title,\n                                            message: message,\n                                            preferredStyle: alertStyle)\n                                            \n    let yesAction = UIAlertAction(title: \"Yes\", style: UIAlertAction.Style.default) { (_) in\n        Club.shared.merge(importedUsers: users)\n    }\n    let noAction = UIAlertAction(title: \"No\", style: UIAlertAction.Style.cancel) { (_) in\n        // Just decline and do nothing\n    }\n    \n    alertController.addAction(yesAction)\n    alertController.addAction(noAction)\n    \n    present(alertController, animated: true, completion: nil)\n}\n```\n\n\u003ca name=\"example-app\" /\u003e\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\n\n\u003ca name=\"requirements\" /\u003e\n\n## Requirements\n\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eXcode 7.3\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eiOS 9.3\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ca name=\"installation\" /\u003e\n\n## Installation\n\nClubKit is available through [CocoaPods](https://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'ClubKit'\n```\n\n## Author\n\nChrishon, chrishon@socketmobile.com\n\n## License\n\nClubKit is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsocketmobile%2Fclubkit-ios","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsocketmobile%2Fclubkit-ios","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsocketmobile%2Fclubkit-ios/lists"}