Repository: ralcr/ConsentKit Branch: master Commit: e196c4c3b145 Files: 31 Total size: 66.2 KB Directory structure: gitextract_6bh629f5/ ├── Consent.podspec ├── LICENSE ├── README.md ├── demo-ios/ │ ├── Gdpr/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── InMemoryDataSource.swift │ │ ├── Info.plist │ │ ├── NavigationViewController.swift │ │ ├── SimpleViewController.swift │ │ ├── TableViewController.swift │ │ └── ViewController.swift │ └── Gdpr.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata/ │ │ ├── cristi.xcuserdatad/ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── cristianbaluta.xcuserdatad/ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata/ │ ├── cristi.xcuserdatad/ │ │ ├── xcdebugger/ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes/ │ │ └── xcschememanagement.plist │ └── cristianbaluta.xcuserdatad/ │ └── xcschemes/ │ └── xcschememanagement.plist └── src/ ├── CloudKitViewControllerHeader.swift ├── ConsentKit.swift ├── ConsentKitCell.swift ├── ConsentKitCell.xib ├── ConsentKitCellProtocol.swift ├── ConsentKitServices.swift ├── ConsentKitTableViewDataSource.swift ├── ConsentKitUserDefaultsDataSource.swift └── ConsentKitViewController.swift ================================================ FILE CONTENTS ================================================ ================================================ FILE: Consent.podspec ================================================ Pod::Spec.new do |s| s.name = 'ConsentKit' s.version = '0.9' s.summary = 'Keep track of GDPR consents in your app.' s.description = <<-DESC Keep track of GDPR consents in your app. DESC s.module_name = "ConsentKit" s.homepage = 'https://github.com/ralcr/Consent' s.license = 'MIT' s.authors = { 'Cristian Baluta' => 'cristi.baluta@gmail.com' } s.ios.deployment_target = '10.0' s.source = { :git => 'https://github.com/ralcr/Consent', :tag => s.version } s.source_files = 'src/*.{h,swift}' end ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Cristian Baluta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ConsentKit Note: This library only helps you collect consents for the services in your app, actually disabling those services is developer's job. ![Screenshot](https://image.ibb.co/iuASQy/Screen_Shot_2018_05_28_at_23_07_10.png) ## Usage ### Define the services in your app Note: Some generic services can be found in ConsentKitServices ```swift enum Services: String, ConsentKitItem { case icloud = "iCloud" case analytics = "analytics" func title() -> String { switch self { case .icloud: return "iCloud" case .analytics: return "Google analytics" } } func description() -> String { switch self { case .icloud: return "Wether to accept iCloud or not" case .analytics: return "Google analytics" } } func alertMessage() -> String? { switch self { case .icloud: return nil case .analytics: return "I accept this app to store anonymous analytics in Google Analytics!" } } } ``` ### Instantiate the lib, preferably once in the AppDelegate, but can be anywhere and as many times as you like. ```swift let gdpr = ConsentKit() ``` ### Check if you have missing consents If yes, add the default ConsentKitViewController to handle all the switches for you. ```swift if gdpr.needsReviewing([Services.icloud, Services.analytics]) { let vc = ConsentKitViewController() vc.items = [Services.icloud, Services.analytics, ConsentKitServices.location] self.present(vc, animated: true) } ``` ### Custom storage By default ConsentKit keeps values in UserDefaults, but you can change that with the gdpr.dataSource property. Just assign or pass through constructor a class implementing the ConsentKitDataSource ```swift let gdpr = ConsentKit(dataSource: InMemoryDataSource())// InMemoryDataSource implements ConsentKitDataSource protocol ConsentKitDataSource { func isAccepted (_ item: ConsentKitItem) -> Bool func isReviewed (_ item: ConsentKitItem) -> Bool func setAccepted (_ value: Bool, for item: ConsentKitItem) func reset (_ item: ConsentKitItem)// Optional } ``` ## Contribution Help me create a complete list of services that the developers can use. Thanks. ================================================ FILE: demo-ios/Gdpr/AppDelegate.swift ================================================ // // AppDelegate.swift // Gdpr // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Cristian Baluta. All rights reserved. // import UIKit enum Services: String, ConsentKitItem { case icloud = "iCloud" case analytics = "analytics" func title() -> String { switch self { case .icloud: return "iCloud" case .analytics: return "Google analytics" } } func description() -> String { switch self { case .icloud: return "Store data to the Apple's iCloud. This will allow the data to by synced across all your devices." case .analytics: return "Help developer understand how the app is used by sharing usage with Google analytics." } } func alertMessage() -> String? { switch self { case .icloud: return nil case .analytics: return nil } } } let gdpr = ConsentKit() //let gdpr = ConsentKit(dataSource: InMemoryDataSource()) @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // For testing purposes we reset all values from previous run gdpr.reset(Services.icloud) gdpr.reset(Services.analytics) gdpr.reset(ConsentKitServices.location) return true } } ================================================ FILE: demo-ios/Gdpr/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: demo-ios/Gdpr/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: demo-ios/Gdpr/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: demo-ios/Gdpr/Base.lproj/Main.storyboard ================================================ ================================================ FILE: demo-ios/Gdpr/InMemoryDataSource.swift ================================================ // // InMemorydataSource.swift // Gdpr // // Created by Cristian Baluta on 23/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class InMemoryDataSource: ConsentKitDataSource { private var dict = [String: Bool?]() func isAccepted (_ item: ConsentKitItem) -> Bool { return dict[item.rawValue] == true } func isReviewed (_ item: ConsentKitItem) -> Bool { return dict[item.rawValue] != nil } func setAccepted (_ value: Bool, for item: ConsentKitItem) { dict[item.rawValue] = value } } ================================================ FILE: demo-ios/Gdpr/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: demo-ios/Gdpr/NavigationViewController.swift ================================================ // // NavigationViewController.swift // Gdpr // // Created by Cristian Baluta on 23/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import UIKit class NavigationViewController: UIViewController { @IBOutlet var label: UILabel! override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if gdpr.needsReviewing([Services.icloud, Services.analytics, ConsentKitServices.location]) { label.text = "Gdpr needs reviewing!" } else { label.text = "All gdpr items already reviewed" } } @IBAction func handleGdpr() { let vc = ConsentKitViewController() vc.items = [Services.icloud, Services.analytics, ConsentKitServices.location] vc.didFinishReview = { self.navigationController?.popViewController(animated: true) } self.navigationController?.pushViewController(vc, animated: true) } } ================================================ FILE: demo-ios/Gdpr/SimpleViewController.swift ================================================ // // SimpleViewController.swift // Gdpr // // Created by Cristian Baluta on 22/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import UIKit class SimpleViewController: UIViewController { @IBOutlet var label: UILabel! override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if gdpr.needsReviewing([Services.icloud, Services.analytics, ConsentKitServices.location]) { label.text = "Gdpr needs reviewing!" } else { label.text = "All gdpr items already reviewed" } } @IBAction func handleGdpr() { let vc = ConsentKitViewController() vc.items = [Services.icloud, Services.analytics, ConsentKitServices.location] self.present(vc, animated: true) } } ================================================ FILE: demo-ios/Gdpr/TableViewController.swift ================================================ // // TableViewController.swift // Gdpr // // Created by Cristian Baluta on 04/06/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import UIKit class TableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 3 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return section == 1 ? 1 : 2 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 1 { let cell = UITableViewCell(style: .default, reuseIdentifier: nil) return cell } else { let cell = UITableViewCell(style: .default, reuseIdentifier: nil) cell.textLabel?.text = "IndePath \(indexPath)" return cell } } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch section { case 0: return "Section 1" case 1: return "GDPR section" default: return "Section 3" } } } ================================================ FILE: demo-ios/Gdpr/ViewController.swift ================================================ // // ViewController.swift // Gdpr // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Cristian Baluta. All rights reserved. // import UIKit class ViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() } } ================================================ FILE: demo-ios/Gdpr.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 283DB05C20BADECD00A8BC14 /* ConsentKitCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 283DB05B20BADECD00A8BC14 /* ConsentKitCell.xib */; }; 283DB05E20BC77D200A8BC14 /* ConsentKitCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283DB05D20BC77D200A8BC14 /* ConsentKitCellProtocol.swift */; }; 2845157D20B3528B009F3EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845157C20B3528B009F3EE1 /* AppDelegate.swift */; }; 2845157F20B3528B009F3EE1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845157E20B3528B009F3EE1 /* ViewController.swift */; }; 2845158220B3528B009F3EE1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2845158020B3528B009F3EE1 /* Main.storyboard */; }; 2845158420B3528F009F3EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2845158320B3528F009F3EE1 /* Assets.xcassets */; }; 2845158720B3528F009F3EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2845158520B3528F009F3EE1 /* LaunchScreen.storyboard */; }; 2845159320B352F4009F3EE1 /* CloudKitViewControllerHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845158F20B352F4009F3EE1 /* CloudKitViewControllerHeader.swift */; }; 2845159420B352F4009F3EE1 /* ConsentKitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159020B352F4009F3EE1 /* ConsentKitCell.swift */; }; 2845159520B352F4009F3EE1 /* ConsentKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159120B352F4009F3EE1 /* ConsentKit.swift */; }; 2845159620B352F4009F3EE1 /* ConsentKitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159220B352F4009F3EE1 /* ConsentKitViewController.swift */; }; 2845159820B4BB95009F3EE1 /* SimpleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159720B4BB95009F3EE1 /* SimpleViewController.swift */; }; 2845159A20B4BDBC009F3EE1 /* NavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159920B4BDBC009F3EE1 /* NavigationViewController.swift */; }; 2845159C20B5EA1C009F3EE1 /* InMemoryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159B20B5EA1C009F3EE1 /* InMemoryDataSource.swift */; }; 2845159E20B5EDD6009F3EE1 /* ConsentKitUserDefaultsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159D20B5EDD6009F3EE1 /* ConsentKitUserDefaultsDataSource.swift */; }; 284515A020B5F738009F3EE1 /* ConsentKitServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2845159F20B5F738009F3EE1 /* ConsentKitServices.swift */; }; 5684412C20C5A4DB00D9A471 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5684412B20C5A4DB00D9A471 /* TableViewController.swift */; }; 5684413220C6D62E00D9A471 /* ConsentKitTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5684413120C6D62E00D9A471 /* ConsentKitTableViewDataSource.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 283DB05B20BADECD00A8BC14 /* ConsentKitCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConsentKitCell.xib; sourceTree = ""; }; 283DB05D20BC77D200A8BC14 /* ConsentKitCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentKitCellProtocol.swift; sourceTree = ""; }; 2845157920B3528B009F3EE1 /* Gdpr.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gdpr.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2845157C20B3528B009F3EE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 2845157E20B3528B009F3EE1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 2845158120B3528B009F3EE1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 2845158320B3528F009F3EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2845158620B3528F009F3EE1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 2845158820B3528F009F3EE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2845158F20B352F4009F3EE1 /* CloudKitViewControllerHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitViewControllerHeader.swift; sourceTree = ""; }; 2845159020B352F4009F3EE1 /* ConsentKitCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsentKitCell.swift; sourceTree = ""; }; 2845159120B352F4009F3EE1 /* ConsentKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsentKit.swift; sourceTree = ""; }; 2845159220B352F4009F3EE1 /* ConsentKitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsentKitViewController.swift; sourceTree = ""; }; 2845159720B4BB95009F3EE1 /* SimpleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleViewController.swift; sourceTree = ""; }; 2845159920B4BDBC009F3EE1 /* NavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewController.swift; sourceTree = ""; }; 2845159B20B5EA1C009F3EE1 /* InMemoryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryDataSource.swift; sourceTree = ""; }; 2845159D20B5EDD6009F3EE1 /* ConsentKitUserDefaultsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentKitUserDefaultsDataSource.swift; sourceTree = ""; }; 2845159F20B5F738009F3EE1 /* ConsentKitServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentKitServices.swift; sourceTree = ""; }; 5684412B20C5A4DB00D9A471 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 5684413120C6D62E00D9A471 /* ConsentKitTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentKitTableViewDataSource.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2845157620B3528B009F3EE1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2845157020B3528B009F3EE1 = { isa = PBXGroup; children = ( 2845157B20B3528B009F3EE1 /* Gdpr */, 2845157A20B3528B009F3EE1 /* Products */, ); sourceTree = ""; }; 2845157A20B3528B009F3EE1 /* Products */ = { isa = PBXGroup; children = ( 2845157920B3528B009F3EE1 /* Gdpr.app */, ); name = Products; sourceTree = ""; }; 2845157B20B3528B009F3EE1 /* Gdpr */ = { isa = PBXGroup; children = ( 2845157C20B3528B009F3EE1 /* AppDelegate.swift */, 2845157E20B3528B009F3EE1 /* ViewController.swift */, 2845159720B4BB95009F3EE1 /* SimpleViewController.swift */, 2845159920B4BDBC009F3EE1 /* NavigationViewController.swift */, 5684412B20C5A4DB00D9A471 /* TableViewController.swift */, 2845159B20B5EA1C009F3EE1 /* InMemoryDataSource.swift */, 2845158020B3528B009F3EE1 /* Main.storyboard */, 2845158320B3528F009F3EE1 /* Assets.xcassets */, 2845158520B3528F009F3EE1 /* LaunchScreen.storyboard */, 2845158820B3528F009F3EE1 /* Info.plist */, 2845158E20B352F4009F3EE1 /* src */, ); path = Gdpr; sourceTree = ""; }; 2845158E20B352F4009F3EE1 /* src */ = { isa = PBXGroup; children = ( 2845159120B352F4009F3EE1 /* ConsentKit.swift */, 2845159F20B5F738009F3EE1 /* ConsentKitServices.swift */, 2845159D20B5EDD6009F3EE1 /* ConsentKitUserDefaultsDataSource.swift */, 2845159220B352F4009F3EE1 /* ConsentKitViewController.swift */, 5684413120C6D62E00D9A471 /* ConsentKitTableViewDataSource.swift */, 283DB05D20BC77D200A8BC14 /* ConsentKitCellProtocol.swift */, 2845159020B352F4009F3EE1 /* ConsentKitCell.swift */, 283DB05B20BADECD00A8BC14 /* ConsentKitCell.xib */, 2845158F20B352F4009F3EE1 /* CloudKitViewControllerHeader.swift */, ); name = src; path = ../../src; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2845157820B3528B009F3EE1 /* Gdpr */ = { isa = PBXNativeTarget; buildConfigurationList = 2845158B20B3528F009F3EE1 /* Build configuration list for PBXNativeTarget "Gdpr" */; buildPhases = ( 2845157520B3528B009F3EE1 /* Sources */, 2845157620B3528B009F3EE1 /* Frameworks */, 2845157720B3528B009F3EE1 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Gdpr; productName = Gdpr; productReference = 2845157920B3528B009F3EE1 /* Gdpr.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2845157120B3528B009F3EE1 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0930; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "Imagin soft"; TargetAttributes = { 2845157820B3528B009F3EE1 = { CreatedOnToolsVersion = 9.3; }; }; }; buildConfigurationList = 2845157420B3528B009F3EE1 /* Build configuration list for PBXProject "Gdpr" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2845157020B3528B009F3EE1; productRefGroup = 2845157A20B3528B009F3EE1 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2845157820B3528B009F3EE1 /* Gdpr */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2845157720B3528B009F3EE1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2845158720B3528F009F3EE1 /* LaunchScreen.storyboard in Resources */, 283DB05C20BADECD00A8BC14 /* ConsentKitCell.xib in Resources */, 2845158420B3528F009F3EE1 /* Assets.xcassets in Resources */, 2845158220B3528B009F3EE1 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2845157520B3528B009F3EE1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2845159A20B4BDBC009F3EE1 /* NavigationViewController.swift in Sources */, 2845159E20B5EDD6009F3EE1 /* ConsentKitUserDefaultsDataSource.swift in Sources */, 5684412C20C5A4DB00D9A471 /* TableViewController.swift in Sources */, 284515A020B5F738009F3EE1 /* ConsentKitServices.swift in Sources */, 2845157F20B3528B009F3EE1 /* ViewController.swift in Sources */, 2845159C20B5EA1C009F3EE1 /* InMemoryDataSource.swift in Sources */, 2845159420B352F4009F3EE1 /* ConsentKitCell.swift in Sources */, 2845159620B352F4009F3EE1 /* ConsentKitViewController.swift in Sources */, 5684413220C6D62E00D9A471 /* ConsentKitTableViewDataSource.swift in Sources */, 283DB05E20BC77D200A8BC14 /* ConsentKitCellProtocol.swift in Sources */, 2845157D20B3528B009F3EE1 /* AppDelegate.swift in Sources */, 2845159320B352F4009F3EE1 /* CloudKitViewControllerHeader.swift in Sources */, 2845159820B4BB95009F3EE1 /* SimpleViewController.swift in Sources */, 2845159520B352F4009F3EE1 /* ConsentKit.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 2845158020B3528B009F3EE1 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 2845158120B3528B009F3EE1 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 2845158520B3528F009F3EE1 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 2845158620B3528F009F3EE1 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 2845158920B3528F009F3EE1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2845158A20B3528F009F3EE1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 2845158C20B3528F009F3EE1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = Gdpr/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = ro.imagin.Gdpr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2845158D20B3528F009F3EE1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 5NHDC5EV44; INFOPLIST_FILE = Gdpr/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = ro.imagin.Gdpr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2845157420B3528B009F3EE1 /* Build configuration list for PBXProject "Gdpr" */ = { isa = XCConfigurationList; buildConfigurations = ( 2845158920B3528F009F3EE1 /* Debug */, 2845158A20B3528F009F3EE1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2845158B20B3528F009F3EE1 /* Build configuration list for PBXNativeTarget "Gdpr" */ = { isa = XCConfigurationList; buildConfigurations = ( 2845158C20B3528F009F3EE1 /* Debug */, 2845158D20B3528F009F3EE1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 2845157120B3528B009F3EE1 /* Project object */; } ================================================ FILE: demo-ios/Gdpr.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: demo-ios/Gdpr.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: demo-ios/Gdpr.xcodeproj/xcuserdata/cristi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ================================================ ================================================ FILE: demo-ios/Gdpr.xcodeproj/xcuserdata/cristi.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Gdpr.xcscheme orderHint 0 ================================================ FILE: demo-ios/Gdpr.xcodeproj/xcuserdata/cristianbaluta.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState Gdpr.xcscheme orderHint 0 ================================================ FILE: src/CloudKitViewControllerHeader.swift ================================================ // // CloudKitViewControllerHeader.swift // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Cristian Baluta. All rights reserved. // import UIKit class CloudKitViewControllerHeader: UIView { var didDone: (() -> Void)? private let padding = CGFloat(16) override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor(white: 0.9, alpha: 1.0) let label = UILabel() label.text = "Review services!" label.font = UIFont.boldSystemFont(ofSize: 16) label.sizeToFit() label.center = CGPoint(x: label.frame.size.width/2 + padding, y: 30) self.addSubview(label) let button = UIButton() button.setTitle("Done", for: .normal) button.setTitleColor(UIColor.orange, for: .normal) button.addTarget(self, action: #selector(handleButton), for: .touchUpInside) button.sizeToFit() button.center = CGPoint(x: frame.size.width - button.frame.size.width/2 - padding, y: 30) self.addSubview(button) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func handleButton() { didDone?() } } ================================================ FILE: src/ConsentKit.swift ================================================ // // ConsentKit.swift // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation protocol ConsentKitItem { var rawValue: String {get} /// Title used in the cell and alert func title() -> String /// Description used in the cell func description() -> String /// Message displayed in an alert when the switch is turned on. func alertMessage() -> String? } protocol ConsentKitDataSource { /// Returns if the item has a value of true func isAccepted (_ item: ConsentKitItem) -> Bool /// Returns if the item's value is different than nil, which means it was reviewed func isReviewed (_ item: ConsentKitItem) -> Bool /// Set a value for the item func setAccepted (_ value: Bool, for item: ConsentKitItem) /// Resets the value of an item, which means that isReviewed will return false after that func reset (_ item: ConsentKitItem) } extension ConsentKitDataSource { func reset (_ item: ConsentKitItem) { // This is a empty implementation to allow this method to be optional } } class ConsentKit { /// Assign a custom dataSource. By default UserDefaults is used and each key is prefixed with "ConsentKit-" var dataSource: ConsentKitDataSource = ConsentKitUserDefaultsDataSource() convenience init (dataSource: ConsentKitDataSource) { self.init() self.dataSource = dataSource } /// Returns if there's any value that was not reviewed /// Used to check if the ConsentKitViewController should be presented or not /// This will also be useful to present the controller if any new service added later in the app needs reviewing func needsReviewing(_ items: [ConsentKitItem]) -> Bool { for item in items { if !isReviewed(item) { return true } } return false } } extension ConsentKit: ConsentKitDataSource { func isAccepted (_ item: ConsentKitItem) -> Bool { return dataSource.isAccepted(item) } func isReviewed (_ item: ConsentKitItem) -> Bool { return dataSource.isReviewed(item) } func setAccepted (_ value: Bool, for item: ConsentKitItem) { dataSource.setAccepted(value, for: item) } func reset (_ item: ConsentKitItem) { dataSource.reset(item) } } ================================================ FILE: src/ConsentKitCell.swift ================================================ // // ConsentKitCell.swift // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Cristian Baluta. All rights reserved. // import UIKit class ConsentKitCell: UITableViewCell, ConsentKitCellProtocol { @IBOutlet private var titleLabel: UILabel! @IBOutlet private var subtitleLabel: UILabel! @IBOutlet private var switchButton: UISwitch! var title: String = "" { didSet { titleLabel.text = title } } var subtitle: String = "" { didSet { subtitleLabel.text = subtitle } } var value: Bool = false { didSet { switchButton.isOn = value } } var valueDidChange: ((Bool) -> Void)? override func awakeFromNib() { super.awakeFromNib() switchButton.onTintColor = UIColor.orange } static func instantiateFromXib() -> ConsentKitCell { let arrNib: Array = Bundle.main.loadNibNamed("ConsentKitCell", owner: self, options: nil)! return arrNib.first as! ConsentKitCell } @IBAction func switchChanged(_ sender: UISwitch) { valueDidChange?(sender.isOn) } } ================================================ FILE: src/ConsentKitCell.xib ================================================ ================================================ FILE: src/ConsentKitCellProtocol.swift ================================================ // // ConsentKitCellProtocol.swift // // Created by Cristian Baluta on 28/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation protocol ConsentKitCellProtocol { var title: String {get set} var subtitle: String {get set} var value: Bool {get set} var valueDidChange: ((Bool) -> Void)? {get set} } ================================================ FILE: src/ConsentKitServices.swift ================================================ // // ConsentKitServices.swift // // Created by Cristian Baluta on 23/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation enum ConsentKitServices: String, ConsentKitItem { case icloud = "ck-icloud" case analytics = "ck-analytics" case location = "ck-location" func title() -> String { switch self { case .icloud: return "iCloud" case .analytics: return "Google analytics" case .location: return "Location" } } func description() -> String { switch self { case .icloud: return "Store data to the Apple's iCloud. This will allow the data to by synced across all your devices." case .analytics: return "Help developer understand how the app is used by sharing usage with Google analytics." case .location: return "Store location on server" } } func alertMessage() -> String? { switch self { case .icloud: return "I accept that this app will store my data in the Apple's iCloud for the purpose of syncing it across multiple devices and backup" case .analytics: return "I accept that this app will store anonymous usage data in Google analytics for the purpose of improving the app" case .location: return "I accept to store my location on the server" } } } ================================================ FILE: src/ConsentKitTableViewDataSource.swift ================================================ // // ConsentKitTableViewDataSource.swift // Gdpr // // Created by Cristian Baluta on 05/06/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import UIKit class ConsentKitTableViewDataSource: NSObject, UITableViewDelegate, UITableViewDataSource { var items: [ConsentKitItem] = [] func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = items[indexPath.row] var cell: ConsentKitCellProtocol = ConsentKitCell.instantiateFromXib() cell.title = item.title() cell.subtitle = item.description() cell.value = gdpr.isAccepted(item) cell.valueDidChange = { isOn in self.item(item, didChangeValue: isOn, in: cell) } return cell as! ConsentKitCell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } func item(_ item: ConsentKitItem, didChangeValue value: Bool, in cell: ConsentKitCellProtocol) { if value { guard let message = item.alertMessage() else { self.gdpr.setAccepted(true, for: item) self.didAccept?(item) return } let alert = UIAlertController(title: item.title(), message: message, preferredStyle: .alert) alert.addAction( UIAlertAction(title: "Accept", style: .default, handler: { _ in self.gdpr.setAccepted(true, for: item) self.didAccept?(item) }) ) alert.addAction( UIAlertAction(title: "Decline", style: .cancel, handler: { _ in var cell = cell cell.value = false self.gdpr.setAccepted(false, for: item) self.didReject?(item) }) ) self.present(alert, animated: true, completion: nil) } else { self.gdpr.setAccepted(false, for: item) didReject?(item) } } } ================================================ FILE: src/ConsentKitUserDefaultsDataSource.swift ================================================ // // ConsentKitUserDefaultsDataSource.swift // // Created by Cristian Baluta on 23/05/2018. // Copyright © 2018 Imagin soft. All rights reserved. // import Foundation class ConsentKitUserDefaultsDataSource { fileprivate let prefix = "ConsentKit-" fileprivate let userDefaults = UserDefaults.standard fileprivate func get (_ key: String) -> Any? { return userDefaults.object(forKey: prefix + key) } fileprivate func set (_ value: Bool?, forKey key: String) { if let v = value { userDefaults.set(v, forKey: prefix + key) } else { userDefaults.removeObject(forKey: prefix + key) } userDefaults.synchronize() } } extension ConsentKitUserDefaultsDataSource: ConsentKitDataSource { func isAccepted (_ item: ConsentKitItem) -> Bool { return (get(item.rawValue) as? Bool) == true } func isReviewed (_ item: ConsentKitItem) -> Bool { return get(item.rawValue) != nil } func setAccepted (_ value: Bool, for item: ConsentKitItem) { set(value, forKey: item.rawValue) } func reset (_ item: ConsentKitItem) { set(nil, forKey: item.rawValue) } } ================================================ FILE: src/ConsentKitViewController.swift ================================================ // // ConsentKitViewController.swift // // Created by Cristian Baluta on 21/05/2018. // Copyright © 2018 Cristian Baluta. All rights reserved. // import UIKit class ConsentKitViewController: UITableViewController { var didAccept: ((ConsentKitItem) -> Void)? var didReject: ((ConsentKitItem) -> Void)? var didFinishReview: (() -> Void)? var items: [ConsentKitItem] = [] { didSet { tableView.reloadData() } } fileprivate let gdpr = ConsentKit() override func viewDidLoad() { super.viewDidLoad() tableView.tableFooterView = UIView() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.navigationController == nil { // Add a custom header only if the VC is not pushed into a navigationController let header = CloudKitViewControllerHeader(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 80)) header.didDone = { self.handleDone() self.dismiss(animated: true, completion: nil) } tableView.tableHeaderView = header } else { self.title = "Review services!" self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(handleDone)) } } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = items[indexPath.row] var cell: ConsentKitCellProtocol = ConsentKitCell.instantiateFromXib() cell.title = item.title() cell.subtitle = item.description() cell.value = gdpr.isAccepted(item) cell.valueDidChange = { isOn in self.item(item, didChangeValue: isOn, in: cell) } return cell as! ConsentKitCell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } // MARK: - Switch changed func item(_ item: ConsentKitItem, didChangeValue value: Bool, in cell: ConsentKitCellProtocol) { if value { guard let message = item.alertMessage() else { self.gdpr.setAccepted(true, for: item) self.didAccept?(item) return } let alert = UIAlertController(title: item.title(), message: message, preferredStyle: .alert) alert.addAction( UIAlertAction(title: "Accept", style: .default, handler: { _ in self.gdpr.setAccepted(true, for: item) self.didAccept?(item) }) ) alert.addAction( UIAlertAction(title: "Decline", style: .cancel, handler: { _ in var cell = cell cell.value = false self.gdpr.setAccepted(false, for: item) self.didReject?(item) }) ) self.present(alert, animated: true, completion: nil) } else { self.gdpr.setAccepted(false, for: item) didReject?(item) } } @objc func handleDone() { // Set to false the untouched switches, to prevent gdpr being called again for item in items { if !gdpr.isReviewed(item) { gdpr.setAccepted(false, for: item) } } didFinishReview?() } }