├── .gitignore ├── .swift-version ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TokenRow.podspec └── TokenRow ├── Example ├── Alamofire.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── profile_empty.imageset │ │ ├── Contents.json │ │ ├── oval@2x.png │ │ └── oval@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Example.xcscheme ├── Example.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── ExampleUITests │ ├── ExampleUITests.swift │ └── Info.plist ├── Info.plist ├── Podfile ├── Podfile.lock ├── TokenRowTests │ ├── Info.plist │ └── TokenRowTests.swift ├── User.swift └── ViewController.swift ├── Info.plist ├── Media ├── TokenAccessoryView.gif ├── TokenTableView.gif └── TokenTableViewCustom.gif ├── Sources ├── CollectionViewTokenCell.swift ├── String+TokenRow.swift ├── TRCollectionViewCell.swift ├── TRTableViewCell.swift ├── TableViewTokenCell.swift ├── TokenCell.swift └── TokenRow.swift └── TokenRow.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X Finder 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | ## Playgrounds 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | # Packages/ 37 | .build/ 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | TokenRow/Example/Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | 51 | Carthage/ 52 | Cartfile.resolved 53 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to TokenRow will be documented in this file. 3 | 4 | ### [1.6.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.6.0) 5 | 6 | 7 | * Updates for Xcode 14. 8 | * Minimum iOS version pushed to 11.0. 9 | * Fix disabled setting (#27) 10 | 11 | ### [1.5.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.5.0) 12 | 13 | 14 | * Migrated to Swift 5.0. 15 | * Changing the value of the row will now update the tokens correctly. 16 | 17 | ### [1.4.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.4.0) 18 | 19 | 20 | * Migrated to Swift 4.2 21 | 22 | ### [1.3.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.3.0) 23 | 24 | 25 | * Added Eureka 4.0 support 26 | * Migrated to Swift 4 27 | 28 | ### [1.2.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.2.0) 29 | 30 | 31 | * Added Eureka 3.0.0 support 32 | 33 | ### [1.1.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.0.0) 34 | 35 | 36 | * Swift 3 compatibility. 37 | 38 | ### [1.0.0](https://github.com/EurekaCommunity/TokenRow/releases/tag/1.0.0) 39 | 40 | 41 | * This is the initial version. 42 | 43 | [xmartlabs]: https://xmartlabs.com 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Guidelines 2 | -------------------------------------------------- 3 | 4 | This document provides general guidelines about how to contribute to the project. Keep in mind these important things before you start contributing. 5 | 6 | ### Reporting issues 7 | 8 | * Use [github issues](https://github.com/EurekaCommunity/TokenRow/issues) to report a bug. 9 | * Before creating a new issue: 10 | * Make sure you are using the [latest release](https://github.com/EurekaCommunity/TokenRow/releases). 11 | * Check if the issue was [already reported or fixed](https://github.com/EurekaCommunity/TokenRow/issues?utf8=%E2%9C%93&q=is%3Aissue). Notice that it may not be released yet. 12 | * If you found a match add a brief comment "I have the same problem" or "+1". This helps prioritize the issues addressing the most common and critical first. If possible add additional information to help us reproduce and fix the issue. Please use your best judgement. 13 | * Reporting issues: 14 | * Please include the following information to help maintainers to fix the problem faster: 15 | * Xcode version you are using. 16 | * iOS version you are targeting. 17 | * Full Xcode console output of stack trace or code compilation error. 18 | * Any other additional detail you think it would be useful to understand and solve the problem. 19 | 20 | 21 | ### Pull requests 22 | 23 | The easiest way to start contributing is searching open issues by `help wanted` tag. We also add a `difficulty` tag (difficulty: easy, difficulty: moderate, difficulty: hard) in order to give an idea of how complex it can be to implement the feature according maintainers project experience. 24 | 25 | * Add test coverage to the feature or fix. We only accept new feature pull requests that have related test coverage. This allows us to keep the library stable as we move forward. 26 | * Remember to document the new feature. We do not accept new feature pull requests without its associated documentation. 27 | * In case of a new feature please update the example project showing the feature. 28 | * Please only one fix or feature per pull request. This will increase the chances your feature will be merged. 29 | 30 | 31 | ###### Suggested git workflow to contribute 32 | 33 | 1. Fork the TokenRow repository. 34 | 2. Clone your forked project into your developer machine: `git clone git@github.com:/TokenRow.git` 35 | 3. Add the original project repo as upstream repository in your forked project: `git remote add upstream git@github.com:EurekaCommunity/TokenRow.git` 36 | 4. Before starting a new feature make sure your forked master branch is synchronized upstream master branch. Considering you do not mere your pull request into master you can run: `git checkout master` and then `git pull upstream master`. Optionally `git push origin master`. 37 | 5. Create a new branch. Note that the starting point is the upstream master branch HEAD. `git checkout -b my-feature-name` 38 | 6. Stage all your changes `git add .` and commit them `git commit -m "Your commit message"` 39 | 7. Make sure your branch is up to date with upstream master, `git pull --rebase upstream master`, resolve conflicts if necessary. This will move your commit to the top of git stack. 40 | 8. Squash your commits into one commit. `git rebase -i HEAD~6` considering you did 6 commits. 41 | 9. Push your branch into your forked remote repository. 42 | 10. Create a new pull request adding any useful comment. 43 | 44 | 45 | ###### Code style and conventions 46 | 47 | We try to follow our [swift style guide](https://github.com/EurekaCommunity/Swift-Style-Guide). Following it is not strictly necessary to contribute and to have a pull request accepted but project maintainers try to follow it. We would love to hear your ideas to improve our code style and conventions. Feel free to contribute. 48 | 49 | 50 | ### Feature proposal 51 | 52 | We would love to hear your ideas and make a discussions about it. 53 | 54 | * Use github issues to make feature proposals. 55 | * We use `type: feature request` label to mark all [feature request issues](https://github.com/EurekaCommunity/TokenRow/labels/type%3A%20feature%20request). 56 | * Before submitting your proposal make sure there is no similar feature request. If you found a match feel free to join the discussion or just add a brief "+1" if you think the feature is worth implementing. 57 | * Be as specific as possible providing a precise explanation of feature request so anyone can understand the problem and the benefits of solving it. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Xmartlabs SRL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TokenRow 2 | 3 |

4 | Platform iOS 5 | Swift 4 compatible 6 | CocoaPods compatible 7 | License: MIT 8 |

9 | 10 | By [Xmartlabs SRL](http://EurekaCommunity.com). 11 | 12 | 13 | ## Contents 14 | 15 | * [Introduction](#introduction) 16 | * [Usage](#usage) 17 | * [Dependencies](#dependencies) 18 | * [Requirements](#requirements) 19 | * [Getting involved](#getting-involved) 20 | * [Examples](#examples) 21 | * [Installation](#installation) 22 | * [Customization](#customization) 23 | 24 | ## Introduction 25 | 26 | TokenRow is a row extension for Eureka. It includes a [CLTokenInputView] which allows the user to select, add and remove tokens. 27 | 28 | 29 | 30 | 31 | TokenRow includes two rows with similar functionality but their options are displayed differently: 32 | * TokenAccessoryRow: displays a collection view as the `inputAccessoryView` of the cell. The user will be able to scroll horizontally to select the desired token 33 | * TokenTableRow: displays a `UITableView` directly below the cell for the user to choose the desired option. 34 | 35 | 36 | 37 | ## Usage 38 | 39 | ```swift 40 | form +++ Section() 41 | <<< TokenAccessoryRow() { 42 | $0.placeholder = "Choose from collection view" 43 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 44 | } 45 | +++ Section() 46 | <<< TokenTableRow() { 47 | $0.placeholder = "Choose from table" 48 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 49 | } 50 | ``` 51 | 52 | To see what you can customize have a look at the [Customization](#customization) section. 53 | 54 | ## Dependencies 55 | * [Eureka] 5.x 56 | * [CLTokenInputView] which is a token view pod 57 | 58 | ## Requirements 59 | 60 | * iOS 9.3+ 61 | * Xcode 10.2+ 62 | 63 | ## Getting involved 64 | 65 | * If you **want to contribute** please feel free to **submit pull requests**. 66 | * If you **have a feature request** please **open an issue**. 67 | * If you **found a bug** or **need help** please **check older issues before submitting an issue.**. 68 | 69 | Before contributing check the [CONTRIBUTING](https://github.com/EurekaCommunity/TokenRow/blob/master/CONTRIBUTING.md) file for more info. 70 | 71 | If you use **TokenRow** in your app we would love to hear about it! Drop us a line on [twitter](https://twitter.com/EurekaCommunity). 72 | 73 | ## Examples 74 | 75 | Follow these steps to run Example project: 76 | * Clone TokenRow repository 77 | * Run `pod install` in the `TokenRow/Example` folder 78 | * Open Example workspace in that folder 79 | 80 | 81 | ## Installation 82 | 83 | #### CocoaPods 84 | 85 | [CocoaPods](https://cocoapods.org/) is a dependency manager for Cocoa projects. 86 | 87 | To install TokenRow, simply add the following line to your Podfile: 88 | 89 | ```ruby 90 | pod 'TokenRow' 91 | ``` 92 | 93 | ## TokenSearchable 94 | 95 | The value of a token row (i.e. TokenAccessoryRow or TokenTableRow) must conform to the TokenSearchable protocol. This way you can have classes or structs as the values of this rows and not just Strings. The TokenSearchable protocol is defined as follows: 96 | ```swift 97 | public protocol TokenSearchable: Hashable { 98 | var displayString: String { get } 99 | func contains(token: String) -> Bool 100 | var identifier: NSObject { get } 101 | } 102 | ``` 103 | 104 | And here is a brief explanation for each of them: 105 | * `displayString`: Will be used to get the String to be displayed as token in the token view 106 | * `contains(token: String) -> Bool` is used to filter the options for a given search string. 107 | * `identifier`: Is an identifier for the token and **should be unique among all options** 108 | 109 | > Note that the value also has to conform to `Hashable` protocol which in turn requires `Equatable` 110 | 111 | TokenRow includes an extension on `String` that makes it conform to `TokenSearchable` so that you can use any row with String as value. To make a class conform to this protocol you could do something like the following: 112 | ```swift 113 | final class User { 114 | var id: Int = 0 115 | var name: String = "" 116 | var avatar: String? 117 | 118 | // conform to Hashable 119 | var hashValue: Int { 120 | return id 121 | } 122 | } 123 | 124 | // conform to Equatable 125 | func == (lhs: User, rhs: User) -> Bool { 126 | return lhs.id == rhs.id 127 | } 128 | 129 | extension User: TokenSearchable { 130 | func contains(token: String) -> Bool { 131 | return name.contains(token) 132 | } 133 | 134 | var identifier: NSObject { 135 | return id 136 | } 137 | 138 | var displayString: String { 139 | return name 140 | } 141 | } 142 | ``` 143 | 144 | ## Customization 145 | 146 | Many things of this row are very similar to the [SuggestionRow] and the [GooglePlacesRow]. You might find useful examples in those projects as well. 147 | 148 | ### General customization 149 | 150 | There are several parts of the TokenRow that you can change. First of all, if you want to change the view that contains the tokens then you should have a look at [CLTokenInputView]. 151 | 152 | A thing you must do for each of these rows is provide the options from which the user can choose a token. The conventional way is to specify an array of options to the `options` variable of the row: 153 | 154 | ```swift 155 | << TokenAccessoryRow() { 156 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 157 | } 158 | ``` 159 | 160 | But you could also provide the options overriding `getTokensForString`. For example you could get the tokens asynchronously over a network call and reload the options after the response comes in. 161 | For example: 162 | ```swift 163 | row.getTokensForString = { [weak self, row] string in 164 | guard let me = self else { return nil } 165 | Alamofire.SessionManager.default.request("https://api.github.com/search/users?q=\(text)&per_page=5") 166 | .responseCollection(completionHandler: { (response: DataResponse<[User]>) in 167 | switch response.result { 168 | case let .success(value): 169 | row.cell.filteredTokens = value 170 | row.cell.reloadOptions() 171 | case let .failure(error): 172 | print(error) 173 | } 174 | }) 175 | return [] 176 | } 177 | ``` 178 | 179 | It is as simple as that (at least if you are familiar with Alamofire). You can see a working example of this in the [Examples](#examples) project. 180 | 181 | 182 | 183 | ### TokenAccessoryRow 184 | TokenAccessoryRow uses a generic `TokenCollectionCell` cell whose generic parameter is the UICollectionViewCell class used in the inputAccessoryView. 185 | 186 | * If you just want to change minor things of the collection view cells (you most probably will want to) then the `customizeCollectionViewCell` callback is for you. This block is called in the delegate's `collectionView:cellForItemAtIndexPath:` method. 187 | 188 | * If you want to change the **layout of the collectionView** then you can use/modify/override the `collectionViewLayout` attribute in the `cellSetup` method when declaring the row. Have a look at the examples for this. 189 | 190 | * If you want to change something about the **collectionView** (e.g. its height, backgroundColor) then you can also do that in the `cellSetup` method. Just edit anything on the `collectionView` variable of your cell. 191 | 192 | * If you want to **change the collection view cell of the inputAccessoryView** drastically then there is nothing easier than creating your own row (`CustomAccessoryRow`) with your own `MyCollectionViewCell`: 193 | ```swift 194 | final class CustomAccessoryRow: _TokenRow>>, RowType { 195 | required init(tag: String?) { 196 | super.init(tag: tag) 197 | } 198 | } 199 | ``` 200 | 201 | > Note: Your custom `MyCollectionViewCell` has to conform to `EurekaTokenCollectionViewCell` 202 | 203 | ### TokenTableRow 204 | TokenTableRow uses a generic `TokenTableCell` cell whose generic parameter is the UITableViewCell class used to create the cells displayed in a UITableView with the suggested options. 205 | 206 | * If you just want to change minor things of the cells that display the options then you can use the `customizeTableViewCell` callback on the cell. You should define it in the `cellSetup` method. This callback will be called in the corresponding `tableView:cellForRowAtIndexPath:` method. 207 | 208 | * You can customize attributes of the `tableView` that is displayed with the options. You should do this in `cellSetup` and keep in mind that the frame of the tableView is reset each time the tableView is displayed. 209 | 210 | * If you want to change the cells of the options table view then there is nothing easier than creating your own row (`MyTokenTableRow`) with your own `MyTableViewCell`: 211 | ```swift 212 | final class MyTokenTableRow: _TokenRow>>, RowType { 213 | required public init(tag: String?) { 214 | super.init(tag: tag) 215 | } 216 | } 217 | 218 | ``` 219 | 220 | You could also change `TableTokenCell` for any class you want to represent the TokenRow cell. 221 | 222 | > Note: Make sure your cell conforms to `EurekaTokenTableViewCell` 223 | 224 | 225 | ## Author 226 | 227 | * [Mathias Claassen](https://github.com/mats-claassen) ([@mClaassen26](https://twitter.com/mClaassen26)) ([@EurekaCommunity](https://twitter.com/EurekaCommunity)) 228 | 229 | # Change Log 230 | 231 | This can be found in the [CHANGELOG.md](CHANGELOG.md) file. 232 | 233 | [CLTokenInputView]: https://github.com/clusterinc/CLTokenInputView 234 | [Eureka]: https://github.com/xmartlabs/eureka 235 | [SuggestionRow]: https://github.com/EurekaCommunity/SuggestionRow 236 | [GooglePlacesRow]: https://github.com/EurekaCommunity/GooglePlacesRow 237 | -------------------------------------------------------------------------------- /TokenRow.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TokenRow" 3 | s.version = "1.6.0" 4 | s.summary = "An Eureka row that allows the user to select options into a token view." 5 | s.homepage = "https://github.com/EurekaCommunity/TokenRow" 6 | s.license = { type: 'MIT', file: 'LICENSE' } 7 | s.author = { "Xmartlabs SRL" => "swift@xmartlabs.com" } 8 | s.source = { git: "https://github.com/EurekaCommunity/TokenRow.git", tag: s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/EurekaCommunity' 10 | s.ios.deployment_target = '11.0' 11 | s.requires_arc = true 12 | s.ios.source_files = 'TokenRow/Sources/**/*.{swift}' 13 | s.ios.frameworks = 'UIKit', 'Foundation' 14 | s.dependency 'Eureka', '~> 5.4' 15 | s.dependency 'CLTokenInputView', '~> 2.3' 16 | s.swift_version = "5.0" 17 | end 18 | -------------------------------------------------------------------------------- /TokenRow/Example/Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alamofire.swift 3 | // Example 4 | // 5 | // Created by Mathias Claassen on 9/8/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | 13 | /** 14 | 15 | NOTE: This code has been copied from the Alamofire Readme. This are just helper methods to show the other functionalities 16 | */ 17 | 18 | 19 | public enum BackendError: Error { 20 | case network(error: NSError) 21 | case dataSerialization(reason: String) 22 | case jsonSerialization(error: NSError) 23 | case objectSerialization(reason: String) 24 | case xmlSerialization(error: NSError) 25 | } 26 | 27 | public protocol ResponseObjectSerializable { 28 | init?(response: HTTPURLResponse, representation: AnyObject) 29 | } 30 | 31 | extension DataRequest { 32 | @discardableResult 33 | public func responseObject(queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse) -> Void) -> Self { 34 | let responseSerializer = DataResponseSerializer { request, response, data, error in 35 | guard error == nil else { return .failure(BackendError.network(error: error! as NSError)) } 36 | 37 | let JSONResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments) 38 | let result = JSONResponseSerializer.serializeResponse(request, response, data, error) 39 | 40 | switch result { 41 | case .success(let value): 42 | if let 43 | response = response, 44 | let responseObject = T(response: response, representation: value as AnyObject) 45 | { 46 | return .success(responseObject) 47 | } else { 48 | return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized into response object: \(value)")) 49 | } 50 | case .failure(let error): 51 | return .failure(BackendError.jsonSerialization(error: error as NSError)) 52 | } 53 | } 54 | 55 | return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) 56 | } 57 | } 58 | 59 | public protocol ResponseCollectionSerializable { 60 | static func collection(response: HTTPURLResponse, representation: AnyObject) -> [Self] 61 | } 62 | 63 | extension ResponseCollectionSerializable where Self: ResponseObjectSerializable { 64 | static func collection(response: HTTPURLResponse, representation: AnyObject) -> [Self] { 65 | var collection = [Self]() 66 | 67 | // check in items path 68 | if let representation = representation.value(forKeyPath: "items") as? [[String: AnyObject]] { 69 | for itemRepresentation in representation { 70 | if let item = Self(response: response, representation: itemRepresentation as AnyObject) { 71 | collection.append(item) 72 | } 73 | } 74 | } 75 | 76 | return collection 77 | } 78 | } 79 | 80 | extension DataRequest { 81 | @discardableResult 82 | public func responseCollection(queue: DispatchQueue? = nil, 83 | completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self { 84 | let responseSerializer = DataResponseSerializer<[T]> { request, response, data, error in 85 | guard error == nil else { return .failure(BackendError.network(error: error! as NSError)) } 86 | 87 | let JSONResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments) 88 | let result = JSONResponseSerializer.serializeResponse(request, response, data, error) 89 | 90 | switch result { 91 | case .success(let value): 92 | if let response = response { 93 | return .success(T.collection(response: response, representation: value as AnyObject)) 94 | } else { 95 | return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized into response object: \(value)")) 96 | } 97 | case .failure(let error): 98 | return .failure(BackendError.jsonSerialization(error: error as NSError)) 99 | } 100 | } 101 | 102 | return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /TokenRow/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Copyright © 2016 Xmartlabs SRL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /TokenRow/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /TokenRow/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TokenRow/Example/Assets.xcassets/profile_empty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "oval@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "oval@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TokenRow/Example/Assets.xcassets/profile_empty.imageset/oval@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EurekaCommunity/TokenRow/8411f2bddaae87b75cf038f5871272584a2f4d4c/TokenRow/Example/Assets.xcassets/profile_empty.imageset/oval@2x.png -------------------------------------------------------------------------------- /TokenRow/Example/Assets.xcassets/profile_empty.imageset/oval@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EurekaCommunity/TokenRow/8411f2bddaae87b75cf038f5871272584a2f4d4c/TokenRow/Example/Assets.xcassets/profile_empty.imageset/oval@3x.png -------------------------------------------------------------------------------- /TokenRow/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TokenRow/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TokenRow/Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 28F828D01C4B714D00330CF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F828CF1C4B714D00330CF4 /* AppDelegate.swift */; }; 11 | 28F828D21C4B714D00330CF4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F828D11C4B714D00330CF4 /* ViewController.swift */; }; 12 | 28F828D71C4B714D00330CF4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 28F828D61C4B714D00330CF4 /* Assets.xcassets */; }; 13 | 8E655004036B88C52E2FDC8E /* Pods_TokenRowTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F00C0D45BC6CBD8472EA660 /* Pods_TokenRowTests.framework */; }; 14 | 8F7B2E3D1D82E2C500D5AD90 /* TokenRowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7B2E3C1D82E2C500D5AD90 /* TokenRowTests.swift */; }; 15 | 8FDA507B1D7F191800AF6514 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8FDA50771D7F191800AF6514 /* Main.storyboard */; }; 16 | 8FDA507C1D7F191800AF6514 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8FDA50791D7F191800AF6514 /* LaunchScreen.storyboard */; }; 17 | 8FDA50831D7F39F800AF6514 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F828E61C4B714D00330CF4 /* ExampleUITests.swift */; }; 18 | 8FDACD881D81D6A7004FEA1D /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDACD871D81D6A7004FEA1D /* User.swift */; }; 19 | 8FDACD8A1D81D9C8004FEA1D /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDACD891D81D9C8004FEA1D /* Alamofire.swift */; }; 20 | FA1B5EE76E1660889FFB77A5 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BED93B457ED01E4D96853FE /* Pods_Example.framework */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 28F828E11C4B714D00330CF4 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 28F828C41C4B714D00330CF4 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 28F828CB1C4B714D00330CF4; 29 | remoteInfo = Example; 30 | }; 31 | 8F7B2E3F1D82E2C500D5AD90 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 28F828C41C4B714D00330CF4 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 28F828CB1C4B714D00330CF4; 36 | remoteInfo = Example; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 0F00C0D45BC6CBD8472EA660 /* Pods_TokenRowTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TokenRowTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 287D0A711C4B7877004566D6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ExampleUITests/Info.plist; sourceTree = SOURCE_ROOT; }; 43 | 28F828CC1C4B714D00330CF4 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 28F828CF1C4B714D00330CF4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 28F828D11C4B714D00330CF4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 46 | 28F828D61C4B714D00330CF4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 28F828DB1C4B714D00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 28F828E01C4B714D00330CF4 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 28F828E61C4B714D00330CF4 /* ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; }; 50 | 8BED93B457ED01E4D96853FE /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 8F7B2E3A1D82E2C500D5AD90 /* TokenRowTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TokenRowTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 8F7B2E3C1D82E2C500D5AD90 /* TokenRowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRowTests.swift; sourceTree = ""; }; 53 | 8F7B2E3E1D82E2C500D5AD90 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 8FDA50781D7F191800AF6514 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 8FDA507A1D7F191800AF6514 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 8FDACD871D81D6A7004FEA1D /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 57 | 8FDACD891D81D9C8004FEA1D /* Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alamofire.swift; sourceTree = ""; }; 58 | 95E39C43A4E429314B1C3D63 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 59 | A58DE564DA68C5AC77D89095 /* Pods-TokenRowTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokenRowTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-TokenRowTests/Pods-TokenRowTests.release.xcconfig"; sourceTree = ""; }; 60 | B19E3C5587C8B604A9173418 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; 61 | C724B3E3226F520D00DEB999 /* TokenRow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TokenRow.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | F3317486620E38E7A4823BCF /* Pods-TokenRowTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokenRowTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokenRowTests/Pods-TokenRowTests.debug.xcconfig"; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 28F828C91C4B714D00330CF4 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | FA1B5EE76E1660889FFB77A5 /* Pods_Example.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 28F828DD1C4B714D00330CF4 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 8F7B2E371D82E2C500D5AD90 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 8E655004036B88C52E2FDC8E /* Pods_TokenRowTests.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 2639BAE4A4AEE426D71C1F02 /* Pods */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | B19E3C5587C8B604A9173418 /* Pods-Example.debug.xcconfig */, 96 | 95E39C43A4E429314B1C3D63 /* Pods-Example.release.xcconfig */, 97 | F3317486620E38E7A4823BCF /* Pods-TokenRowTests.debug.xcconfig */, 98 | A58DE564DA68C5AC77D89095 /* Pods-TokenRowTests.release.xcconfig */, 99 | ); 100 | name = Pods; 101 | sourceTree = ""; 102 | }; 103 | 28F828C31C4B714D00330CF4 = { 104 | isa = PBXGroup; 105 | children = ( 106 | C724B3E3226F520D00DEB999 /* TokenRow.framework */, 107 | 28F828CE1C4B714D00330CF4 /* Example */, 108 | 28F828E31C4B714D00330CF4 /* ExampleUITests */, 109 | 8F7B2E3B1D82E2C500D5AD90 /* TokenRowTests */, 110 | 28F828CD1C4B714D00330CF4 /* Products */, 111 | 2639BAE4A4AEE426D71C1F02 /* Pods */, 112 | C7AE64D42B2B235DA7A9CD60 /* Frameworks */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 28F828CD1C4B714D00330CF4 /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 28F828CC1C4B714D00330CF4 /* Example.app */, 120 | 28F828E01C4B714D00330CF4 /* ExampleUITests.xctest */, 121 | 8F7B2E3A1D82E2C500D5AD90 /* TokenRowTests.xctest */, 122 | ); 123 | name = Products; 124 | sourceTree = ""; 125 | }; 126 | 28F828CE1C4B714D00330CF4 /* Example */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 8FDA50771D7F191800AF6514 /* Main.storyboard */, 130 | 8FDA50791D7F191800AF6514 /* LaunchScreen.storyboard */, 131 | 28F828CF1C4B714D00330CF4 /* AppDelegate.swift */, 132 | 28F828D11C4B714D00330CF4 /* ViewController.swift */, 133 | 8FDACD871D81D6A7004FEA1D /* User.swift */, 134 | 28F828D61C4B714D00330CF4 /* Assets.xcassets */, 135 | 28F828DB1C4B714D00330CF4 /* Info.plist */, 136 | 8FDACD891D81D9C8004FEA1D /* Alamofire.swift */, 137 | ); 138 | name = Example; 139 | sourceTree = ""; 140 | }; 141 | 28F828E31C4B714D00330CF4 /* ExampleUITests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 28F828E61C4B714D00330CF4 /* ExampleUITests.swift */, 145 | 287D0A711C4B7877004566D6 /* Info.plist */, 146 | ); 147 | path = ExampleUITests; 148 | sourceTree = ""; 149 | }; 150 | 8F7B2E3B1D82E2C500D5AD90 /* TokenRowTests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 8F7B2E3C1D82E2C500D5AD90 /* TokenRowTests.swift */, 154 | 8F7B2E3E1D82E2C500D5AD90 /* Info.plist */, 155 | ); 156 | path = TokenRowTests; 157 | sourceTree = ""; 158 | }; 159 | C7AE64D42B2B235DA7A9CD60 /* Frameworks */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 8BED93B457ED01E4D96853FE /* Pods_Example.framework */, 163 | 0F00C0D45BC6CBD8472EA660 /* Pods_TokenRowTests.framework */, 164 | ); 165 | name = Frameworks; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 28F828CB1C4B714D00330CF4 /* Example */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 28F828E91C4B714D00330CF4 /* Build configuration list for PBXNativeTarget "Example" */; 174 | buildPhases = ( 175 | 6FEE7A2956C9764B58D94F5D /* [CP] Check Pods Manifest.lock */, 176 | 28F828C81C4B714D00330CF4 /* Sources */, 177 | 28F828C91C4B714D00330CF4 /* Frameworks */, 178 | 28F828CA1C4B714D00330CF4 /* Resources */, 179 | E8354FD93BFCEE73C23850C7 /* [CP] Embed Pods Frameworks */, 180 | ); 181 | buildRules = ( 182 | ); 183 | dependencies = ( 184 | ); 185 | name = Example; 186 | productName = Example; 187 | productReference = 28F828CC1C4B714D00330CF4 /* Example.app */; 188 | productType = "com.apple.product-type.application"; 189 | }; 190 | 28F828DF1C4B714D00330CF4 /* ExampleUITests */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 28F828EC1C4B714D00330CF4 /* Build configuration list for PBXNativeTarget "ExampleUITests" */; 193 | buildPhases = ( 194 | 28F828DC1C4B714D00330CF4 /* Sources */, 195 | 28F828DD1C4B714D00330CF4 /* Frameworks */, 196 | 28F828DE1C4B714D00330CF4 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | 28F828E21C4B714D00330CF4 /* PBXTargetDependency */, 202 | ); 203 | name = ExampleUITests; 204 | productName = ExampleUITests; 205 | productReference = 28F828E01C4B714D00330CF4 /* ExampleUITests.xctest */; 206 | productType = "com.apple.product-type.bundle.ui-testing"; 207 | }; 208 | 8F7B2E391D82E2C500D5AD90 /* TokenRowTests */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = 8F7B2E411D82E2C500D5AD90 /* Build configuration list for PBXNativeTarget "TokenRowTests" */; 211 | buildPhases = ( 212 | 9D01DBBB5723FF157F4C53EB /* [CP] Check Pods Manifest.lock */, 213 | 8F7B2E361D82E2C500D5AD90 /* Sources */, 214 | 8F7B2E371D82E2C500D5AD90 /* Frameworks */, 215 | 8F7B2E381D82E2C500D5AD90 /* Resources */, 216 | 6BB818B4241595B40EB6DAF7 /* [CP] Embed Pods Frameworks */, 217 | ); 218 | buildRules = ( 219 | ); 220 | dependencies = ( 221 | 8F7B2E401D82E2C500D5AD90 /* PBXTargetDependency */, 222 | ); 223 | name = TokenRowTests; 224 | productName = TokenRowTests; 225 | productReference = 8F7B2E3A1D82E2C500D5AD90 /* TokenRowTests.xctest */; 226 | productType = "com.apple.product-type.bundle.unit-test"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | 28F828C41C4B714D00330CF4 /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastSwiftUpdateCheck = 0730; 235 | LastUpgradeCheck = 1300; 236 | TargetAttributes = { 237 | 28F828CB1C4B714D00330CF4 = { 238 | CreatedOnToolsVersion = 7.2; 239 | LastSwiftMigration = 1020; 240 | }; 241 | 28F828DF1C4B714D00330CF4 = { 242 | CreatedOnToolsVersion = 7.2; 243 | LastSwiftMigration = 1020; 244 | TestTargetID = 28F828CB1C4B714D00330CF4; 245 | }; 246 | 8F7B2E391D82E2C500D5AD90 = { 247 | CreatedOnToolsVersion = 7.3.1; 248 | LastSwiftMigration = 1020; 249 | TestTargetID = 28F828CB1C4B714D00330CF4; 250 | }; 251 | }; 252 | }; 253 | buildConfigurationList = 28F828C71C4B714D00330CF4 /* Build configuration list for PBXProject "Example" */; 254 | compatibilityVersion = "Xcode 3.2"; 255 | developmentRegion = en; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | Base, 260 | ); 261 | mainGroup = 28F828C31C4B714D00330CF4; 262 | productRefGroup = 28F828CD1C4B714D00330CF4 /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 28F828CB1C4B714D00330CF4 /* Example */, 267 | 28F828DF1C4B714D00330CF4 /* ExampleUITests */, 268 | 8F7B2E391D82E2C500D5AD90 /* TokenRowTests */, 269 | ); 270 | }; 271 | /* End PBXProject section */ 272 | 273 | /* Begin PBXResourcesBuildPhase section */ 274 | 28F828CA1C4B714D00330CF4 /* Resources */ = { 275 | isa = PBXResourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 8FDA507C1D7F191800AF6514 /* LaunchScreen.storyboard in Resources */, 279 | 28F828D71C4B714D00330CF4 /* Assets.xcassets in Resources */, 280 | 8FDA507B1D7F191800AF6514 /* Main.storyboard in Resources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | 28F828DE1C4B714D00330CF4 /* Resources */ = { 285 | isa = PBXResourcesBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | 8F7B2E381D82E2C500D5AD90 /* Resources */ = { 292 | isa = PBXResourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXResourcesBuildPhase section */ 299 | 300 | /* Begin PBXShellScriptBuildPhase section */ 301 | 6BB818B4241595B40EB6DAF7 /* [CP] Embed Pods Frameworks */ = { 302 | isa = PBXShellScriptBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | ); 306 | inputPaths = ( 307 | "${PODS_ROOT}/Target Support Files/Pods-TokenRowTests/Pods-TokenRowTests-frameworks.sh", 308 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 309 | "${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework", 310 | "${BUILT_PRODUCTS_DIR}/CLTokenInputView/CLTokenInputView.framework", 311 | "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", 312 | "${BUILT_PRODUCTS_DIR}/TokenRow/TokenRow.framework", 313 | ); 314 | name = "[CP] Embed Pods Frameworks"; 315 | outputPaths = ( 316 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 317 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework", 318 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CLTokenInputView.framework", 319 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", 320 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TokenRow.framework", 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | shellPath = /bin/sh; 324 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TokenRowTests/Pods-TokenRowTests-frameworks.sh\"\n"; 325 | showEnvVarsInLog = 0; 326 | }; 327 | 6FEE7A2956C9764B58D94F5D /* [CP] Check Pods Manifest.lock */ = { 328 | isa = PBXShellScriptBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | ); 332 | inputPaths = ( 333 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 334 | "${PODS_ROOT}/Manifest.lock", 335 | ); 336 | name = "[CP] Check Pods Manifest.lock"; 337 | outputPaths = ( 338 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | shellPath = /bin/sh; 342 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 343 | showEnvVarsInLog = 0; 344 | }; 345 | 9D01DBBB5723FF157F4C53EB /* [CP] Check Pods Manifest.lock */ = { 346 | isa = PBXShellScriptBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | ); 350 | inputPaths = ( 351 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 352 | "${PODS_ROOT}/Manifest.lock", 353 | ); 354 | name = "[CP] Check Pods Manifest.lock"; 355 | outputPaths = ( 356 | "$(DERIVED_FILE_DIR)/Pods-TokenRowTests-checkManifestLockResult.txt", 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | shellPath = /bin/sh; 360 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 361 | showEnvVarsInLog = 0; 362 | }; 363 | E8354FD93BFCEE73C23850C7 /* [CP] Embed Pods Frameworks */ = { 364 | isa = PBXShellScriptBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | ); 368 | inputPaths = ( 369 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", 370 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 371 | "${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework", 372 | "${BUILT_PRODUCTS_DIR}/CLTokenInputView/CLTokenInputView.framework", 373 | "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", 374 | "${BUILT_PRODUCTS_DIR}/TokenRow/TokenRow.framework", 375 | ); 376 | name = "[CP] Embed Pods Frameworks"; 377 | outputPaths = ( 378 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 379 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework", 380 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CLTokenInputView.framework", 381 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", 382 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TokenRow.framework", 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | shellPath = /bin/sh; 386 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; 387 | showEnvVarsInLog = 0; 388 | }; 389 | /* End PBXShellScriptBuildPhase section */ 390 | 391 | /* Begin PBXSourcesBuildPhase section */ 392 | 28F828C81C4B714D00330CF4 /* Sources */ = { 393 | isa = PBXSourcesBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | 28F828D21C4B714D00330CF4 /* ViewController.swift in Sources */, 397 | 28F828D01C4B714D00330CF4 /* AppDelegate.swift in Sources */, 398 | 8FDACD8A1D81D9C8004FEA1D /* Alamofire.swift in Sources */, 399 | 8FDACD881D81D6A7004FEA1D /* User.swift in Sources */, 400 | ); 401 | runOnlyForDeploymentPostprocessing = 0; 402 | }; 403 | 28F828DC1C4B714D00330CF4 /* Sources */ = { 404 | isa = PBXSourcesBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | 8FDA50831D7F39F800AF6514 /* ExampleUITests.swift in Sources */, 408 | ); 409 | runOnlyForDeploymentPostprocessing = 0; 410 | }; 411 | 8F7B2E361D82E2C500D5AD90 /* Sources */ = { 412 | isa = PBXSourcesBuildPhase; 413 | buildActionMask = 2147483647; 414 | files = ( 415 | 8F7B2E3D1D82E2C500D5AD90 /* TokenRowTests.swift in Sources */, 416 | ); 417 | runOnlyForDeploymentPostprocessing = 0; 418 | }; 419 | /* End PBXSourcesBuildPhase section */ 420 | 421 | /* Begin PBXTargetDependency section */ 422 | 28F828E21C4B714D00330CF4 /* PBXTargetDependency */ = { 423 | isa = PBXTargetDependency; 424 | target = 28F828CB1C4B714D00330CF4 /* Example */; 425 | targetProxy = 28F828E11C4B714D00330CF4 /* PBXContainerItemProxy */; 426 | }; 427 | 8F7B2E401D82E2C500D5AD90 /* PBXTargetDependency */ = { 428 | isa = PBXTargetDependency; 429 | target = 28F828CB1C4B714D00330CF4 /* Example */; 430 | targetProxy = 8F7B2E3F1D82E2C500D5AD90 /* PBXContainerItemProxy */; 431 | }; 432 | /* End PBXTargetDependency section */ 433 | 434 | /* Begin PBXVariantGroup section */ 435 | 8FDA50771D7F191800AF6514 /* Main.storyboard */ = { 436 | isa = PBXVariantGroup; 437 | children = ( 438 | 8FDA50781D7F191800AF6514 /* Base */, 439 | ); 440 | name = Main.storyboard; 441 | sourceTree = ""; 442 | }; 443 | 8FDA50791D7F191800AF6514 /* LaunchScreen.storyboard */ = { 444 | isa = PBXVariantGroup; 445 | children = ( 446 | 8FDA507A1D7F191800AF6514 /* Base */, 447 | ); 448 | name = LaunchScreen.storyboard; 449 | sourceTree = ""; 450 | }; 451 | /* End PBXVariantGroup section */ 452 | 453 | /* Begin XCBuildConfiguration section */ 454 | 28F828E71C4B714D00330CF4 /* Debug */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | ALWAYS_SEARCH_USER_PATHS = NO; 458 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 459 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 460 | CLANG_CXX_LIBRARY = "libc++"; 461 | CLANG_ENABLE_MODULES = YES; 462 | CLANG_ENABLE_OBJC_ARC = YES; 463 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 464 | CLANG_WARN_BOOL_CONVERSION = YES; 465 | CLANG_WARN_COMMA = YES; 466 | CLANG_WARN_CONSTANT_CONVERSION = YES; 467 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 468 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INFINITE_RECURSION = YES; 472 | CLANG_WARN_INT_CONVERSION = YES; 473 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 477 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 478 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 479 | CLANG_WARN_STRICT_PROTOTYPES = YES; 480 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 481 | CLANG_WARN_UNREACHABLE_CODE = YES; 482 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 483 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 484 | COPY_PHASE_STRIP = NO; 485 | DEBUG_INFORMATION_FORMAT = dwarf; 486 | ENABLE_STRICT_OBJC_MSGSEND = YES; 487 | ENABLE_TESTABILITY = YES; 488 | GCC_C_LANGUAGE_STANDARD = gnu99; 489 | GCC_DYNAMIC_NO_PIC = NO; 490 | GCC_NO_COMMON_BLOCKS = YES; 491 | GCC_OPTIMIZATION_LEVEL = 0; 492 | GCC_PREPROCESSOR_DEFINITIONS = ( 493 | "DEBUG=1", 494 | "$(inherited)", 495 | ); 496 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 497 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 498 | GCC_WARN_UNDECLARED_SELECTOR = YES; 499 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 500 | GCC_WARN_UNUSED_FUNCTION = YES; 501 | GCC_WARN_UNUSED_VARIABLE = YES; 502 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 503 | MTL_ENABLE_DEBUG_INFO = YES; 504 | ONLY_ACTIVE_ARCH = YES; 505 | SDKROOT = iphoneos; 506 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 507 | SWIFT_VERSION = 4.2; 508 | }; 509 | name = Debug; 510 | }; 511 | 28F828E81C4B714D00330CF4 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_SEARCH_USER_PATHS = NO; 515 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 516 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 517 | CLANG_CXX_LIBRARY = "libc++"; 518 | CLANG_ENABLE_MODULES = YES; 519 | CLANG_ENABLE_OBJC_ARC = YES; 520 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 521 | CLANG_WARN_BOOL_CONVERSION = YES; 522 | CLANG_WARN_COMMA = YES; 523 | CLANG_WARN_CONSTANT_CONVERSION = YES; 524 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 525 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 526 | CLANG_WARN_EMPTY_BODY = YES; 527 | CLANG_WARN_ENUM_CONVERSION = YES; 528 | CLANG_WARN_INFINITE_RECURSION = YES; 529 | CLANG_WARN_INT_CONVERSION = YES; 530 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 531 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 532 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 533 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 534 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 535 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 536 | CLANG_WARN_STRICT_PROTOTYPES = YES; 537 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 538 | CLANG_WARN_UNREACHABLE_CODE = YES; 539 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 540 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 541 | COPY_PHASE_STRIP = NO; 542 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 543 | ENABLE_NS_ASSERTIONS = NO; 544 | ENABLE_STRICT_OBJC_MSGSEND = YES; 545 | GCC_C_LANGUAGE_STANDARD = gnu99; 546 | GCC_NO_COMMON_BLOCKS = YES; 547 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 548 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 549 | GCC_WARN_UNDECLARED_SELECTOR = YES; 550 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 551 | GCC_WARN_UNUSED_FUNCTION = YES; 552 | GCC_WARN_UNUSED_VARIABLE = YES; 553 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 554 | MTL_ENABLE_DEBUG_INFO = NO; 555 | SDKROOT = iphoneos; 556 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 557 | SWIFT_VERSION = 4.2; 558 | VALIDATE_PRODUCT = YES; 559 | }; 560 | name = Release; 561 | }; 562 | 28F828EA1C4B714D00330CF4 /* Debug */ = { 563 | isa = XCBuildConfiguration; 564 | baseConfigurationReference = B19E3C5587C8B604A9173418 /* Pods-Example.debug.xcconfig */; 565 | buildSettings = { 566 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 567 | DEVELOPMENT_TEAM = ""; 568 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 569 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 570 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 571 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.Example; 572 | PRODUCT_NAME = "$(TARGET_NAME)"; 573 | SWIFT_VERSION = 5.0; 574 | }; 575 | name = Debug; 576 | }; 577 | 28F828EB1C4B714D00330CF4 /* Release */ = { 578 | isa = XCBuildConfiguration; 579 | baseConfigurationReference = 95E39C43A4E429314B1C3D63 /* Pods-Example.release.xcconfig */; 580 | buildSettings = { 581 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 582 | DEVELOPMENT_TEAM = ""; 583 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 584 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 585 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 586 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.Example; 587 | PRODUCT_NAME = "$(TARGET_NAME)"; 588 | SWIFT_VERSION = 5.0; 589 | }; 590 | name = Release; 591 | }; 592 | 28F828ED1C4B714D00330CF4 /* Debug */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | CLANG_ENABLE_MODULES = YES; 596 | INFOPLIST_FILE = ExampleUITests/Info.plist; 597 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 598 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.ExampleUITests; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 601 | SWIFT_VERSION = 5.0; 602 | TEST_TARGET_NAME = Example; 603 | USES_XCTRUNNER = YES; 604 | }; 605 | name = Debug; 606 | }; 607 | 28F828EE1C4B714D00330CF4 /* Release */ = { 608 | isa = XCBuildConfiguration; 609 | buildSettings = { 610 | CLANG_ENABLE_MODULES = YES; 611 | INFOPLIST_FILE = ExampleUITests/Info.plist; 612 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 613 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.ExampleUITests; 614 | PRODUCT_NAME = "$(TARGET_NAME)"; 615 | SWIFT_VERSION = 5.0; 616 | TEST_TARGET_NAME = Example; 617 | USES_XCTRUNNER = YES; 618 | }; 619 | name = Release; 620 | }; 621 | 8F7B2E421D82E2C500D5AD90 /* Debug */ = { 622 | isa = XCBuildConfiguration; 623 | baseConfigurationReference = F3317486620E38E7A4823BCF /* Pods-TokenRowTests.debug.xcconfig */; 624 | buildSettings = { 625 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 626 | BUNDLE_LOADER = "$(TEST_HOST)"; 627 | CLANG_ANALYZER_NONNULL = YES; 628 | INFOPLIST_FILE = TokenRowTests/Info.plist; 629 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 630 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 631 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.TokenRowTests; 632 | PRODUCT_NAME = "$(TARGET_NAME)"; 633 | SWIFT_VERSION = 5.0; 634 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 635 | }; 636 | name = Debug; 637 | }; 638 | 8F7B2E431D82E2C500D5AD90 /* Release */ = { 639 | isa = XCBuildConfiguration; 640 | baseConfigurationReference = A58DE564DA68C5AC77D89095 /* Pods-TokenRowTests.release.xcconfig */; 641 | buildSettings = { 642 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 643 | BUNDLE_LOADER = "$(TEST_HOST)"; 644 | CLANG_ANALYZER_NONNULL = YES; 645 | INFOPLIST_FILE = TokenRowTests/Info.plist; 646 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 647 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 648 | PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.TokenRowTests; 649 | PRODUCT_NAME = "$(TARGET_NAME)"; 650 | SWIFT_VERSION = 5.0; 651 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 652 | }; 653 | name = Release; 654 | }; 655 | /* End XCBuildConfiguration section */ 656 | 657 | /* Begin XCConfigurationList section */ 658 | 28F828C71C4B714D00330CF4 /* Build configuration list for PBXProject "Example" */ = { 659 | isa = XCConfigurationList; 660 | buildConfigurations = ( 661 | 28F828E71C4B714D00330CF4 /* Debug */, 662 | 28F828E81C4B714D00330CF4 /* Release */, 663 | ); 664 | defaultConfigurationIsVisible = 0; 665 | defaultConfigurationName = Release; 666 | }; 667 | 28F828E91C4B714D00330CF4 /* Build configuration list for PBXNativeTarget "Example" */ = { 668 | isa = XCConfigurationList; 669 | buildConfigurations = ( 670 | 28F828EA1C4B714D00330CF4 /* Debug */, 671 | 28F828EB1C4B714D00330CF4 /* Release */, 672 | ); 673 | defaultConfigurationIsVisible = 0; 674 | defaultConfigurationName = Release; 675 | }; 676 | 28F828EC1C4B714D00330CF4 /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { 677 | isa = XCConfigurationList; 678 | buildConfigurations = ( 679 | 28F828ED1C4B714D00330CF4 /* Debug */, 680 | 28F828EE1C4B714D00330CF4 /* Release */, 681 | ); 682 | defaultConfigurationIsVisible = 0; 683 | defaultConfigurationName = Release; 684 | }; 685 | 8F7B2E411D82E2C500D5AD90 /* Build configuration list for PBXNativeTarget "TokenRowTests" */ = { 686 | isa = XCConfigurationList; 687 | buildConfigurations = ( 688 | 8F7B2E421D82E2C500D5AD90 /* Debug */, 689 | 8F7B2E431D82E2C500D5AD90 /* Release */, 690 | ); 691 | defaultConfigurationIsVisible = 0; 692 | defaultConfigurationName = Release; 693 | }; 694 | /* End XCConfigurationList section */ 695 | }; 696 | rootObject = 28F828C41C4B714D00330CF4 /* Project object */; 697 | } 698 | -------------------------------------------------------------------------------- /TokenRow/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TokenRow/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 86 | 88 | 94 | 95 | 96 | 97 | 103 | 105 | 111 | 112 | 113 | 114 | 116 | 117 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /TokenRow/Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TokenRow/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TokenRow/Example/ExampleUITests/ExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleUITests.swift 3 | // ExampleUITests 4 | // 5 | // Copyright © 2016 Xmartlabs SRL. All rights reserved. 6 | // 7 | 8 | import XCTest 9 | 10 | class ExampleUITests: XCTestCase { 11 | 12 | override func setUp() { 13 | super.setUp() 14 | 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | 17 | // In UI tests it is usually best to stop immediately when a failure occurs. 18 | continueAfterFailure = false 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | super.tearDown() 28 | } 29 | 30 | func testExample() { 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /TokenRow/Example/ExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TokenRow/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TokenRow/Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '11.0' 3 | 4 | inhibit_all_warnings! 5 | use_frameworks! 6 | 7 | def pods 8 | pod 'Alamofire', '~> 4.0' 9 | pod 'AlamofireImage' 10 | pod 'CLTokenInputView', '~> 2.3' 11 | pod 'TokenRow', :path => '../../' 12 | end 13 | 14 | target 'Example' do 15 | pods 16 | end 17 | 18 | target 'TokenRowTests' do 19 | pods 20 | end 21 | 22 | post_install do |installer| 23 | installer.pods_project.targets.each do |target| 24 | if target.name == 'Eureka' 25 | target.build_configurations.each do |config| 26 | config.build_settings['SWIFT_VERSION'] = '5.0' 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /TokenRow/Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.9.1) 3 | - AlamofireImage (3.6.0): 4 | - Alamofire (~> 4.9) 5 | - CLTokenInputView (2.3.0) 6 | - Eureka (5.4.0) 7 | - TokenRow (1.6.0): 8 | - CLTokenInputView (~> 2.3) 9 | - Eureka (~> 5.4) 10 | 11 | DEPENDENCIES: 12 | - Alamofire (~> 4.0) 13 | - AlamofireImage 14 | - CLTokenInputView (~> 2.3) 15 | - TokenRow (from `../../`) 16 | 17 | SPEC REPOS: 18 | https://github.com/CocoaPods/Specs.git: 19 | - Alamofire 20 | - AlamofireImage 21 | - CLTokenInputView 22 | - Eureka 23 | 24 | EXTERNAL SOURCES: 25 | TokenRow: 26 | :path: "../../" 27 | 28 | SPEC CHECKSUMS: 29 | Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 30 | AlamofireImage: be9963c6582d68b39e89191f64c82a7d7bf40fdd 31 | CLTokenInputView: 9dc1ffb7c9d2d81787e69a63c75bd97b0b2d1e76 32 | Eureka: fadaa9fa3d6e402d3c60f78f24edf3d7bafc9c29 33 | TokenRow: e4cb4747f65949bc8443d2af05a556567a2b3c37 34 | 35 | PODFILE CHECKSUM: 931c2335c8e2c07a39e69588e3df33524eb0bb35 36 | 37 | COCOAPODS: 1.11.3 38 | -------------------------------------------------------------------------------- /TokenRow/Example/TokenRowTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TokenRow/Example/TokenRowTests/TokenRowTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenRowTests.swift 3 | // TokenRowTests 4 | // 5 | // Created by Mathias Claassen on 9/9/16. 6 | // Copyright © 2016 Xmartlabs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import TokenRow 11 | 12 | class TokenRowTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testAddRemoveToken() { 25 | let token1 = "First token" 26 | let token2 = "Second token" 27 | 28 | let tokenRow = TokenAccessoryRow() { 29 | $0.options = [token1, token2] 30 | } 31 | tokenRow.addToken(token1) 32 | tokenRow.addToken(NSString(string: token2)) 33 | 34 | XCTAssertEqual(tokenRow.value?.count, 2) 35 | 36 | tokenRow.removeToken(NSString(string: token1)) 37 | 38 | XCTAssertEqual(tokenRow.value?.count, 1) 39 | 40 | tokenRow.removeToken("something else") 41 | 42 | XCTAssertEqual(tokenRow.value?.count, 1) 43 | } 44 | 45 | func testStringSearchable() { 46 | let string = "SoMe VeRy RarE StrINg" 47 | 48 | XCTAssertTrue(string.contains(token: "very")) 49 | XCTAssertTrue(string.contains(token: "STRING")) 50 | XCTAssertFalse(string.contains(token: "strange")) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /TokenRow/Example/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Example 4 | // 5 | // Created by Mathias Claassen on 9/8/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import TokenRow 11 | import Alamofire 12 | 13 | final class User: ResponseObjectSerializable, ResponseCollectionSerializable { 14 | var id: Int = 0 15 | var name: String = "" 16 | var avatar: String? 17 | 18 | func hash(into hasher: inout Hasher) { 19 | hasher.combine(id) 20 | } 21 | 22 | required init?(response: HTTPURLResponse, representation: AnyObject) { 23 | self.name = representation.value(forKeyPath: "login") as! String 24 | self.avatar = representation.value(forKeyPath: "avatar_url") as? String 25 | self.id = representation.value(forKeyPath: "id") as! Int 26 | } 27 | } 28 | 29 | func == (lhs: User, rhs: User) -> Bool { 30 | return lhs.id == rhs.id 31 | } 32 | 33 | extension User: TokenSearchable { 34 | func contains(token: String) -> Bool { 35 | return name.contains(token) 36 | } 37 | 38 | var identifier: NSObject { 39 | return id as NSObject 40 | } 41 | 42 | var displayString: String { 43 | return name 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TokenRow/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Copyright © 2016 Xmartlabs SRL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import Eureka 10 | import TokenRow 11 | import CLTokenInputView 12 | import Alamofire 13 | import AlamofireImage 14 | 15 | class ViewController: FormViewController { 16 | 17 | var timer: Timer? 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | form +++ Section() 22 | <<< TokenAccessoryRow() { 23 | $0.placeholder = "Choose from collection view" 24 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 25 | } 26 | +++ Section() 27 | <<< TokenTableRow() { 28 | $0.placeholder = "Choose from table" 29 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 30 | } 31 | 32 | +++ Section("Customized Collection View") 33 | <<< TokenAccessoryRow() { 34 | $0.title = "A title:" 35 | $0.options = ["Peter Schmeichel", "David de Gea", "Oliver Kahn", "Fabien Barthez", "Tim Howard", "Gianluigi Buffon"] 36 | }.cellSetup({ (cell, row) in 37 | (cell.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset = .zero 38 | (cell.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing = 40 39 | cell.customizeCollectionViewCell = { _, cvcell in 40 | cvcell.label.textColor = UIColor.red 41 | cvcell.layer.borderColor = UIColor.red.cgColor 42 | cvcell.layer.borderWidth = 1 43 | cvcell.layer.cornerRadius = 4 44 | } 45 | }) 46 | 47 | //Let's implement a row that asychronously fetches results from Github User search API 48 | +++ Section("Loading tokens asynchronously") 49 | <<< TokenTableRow() { row in 50 | row.placeholder = "Enter Github user name" 51 | row.getTokensForString = { [weak self, row] string in 52 | guard let me = self else { return nil } 53 | if me.timer != nil { 54 | me.invalidateTimer() 55 | } 56 | me.timer = Timer.scheduledTimer(timeInterval: 0.5, target: me, selector: #selector(ViewController.timerFired(_:)), userInfo: ["text": string, "row": row], repeats: false) 57 | 58 | return [] 59 | } 60 | }.cellSetup({ (cell, row) in 61 | cell.customizeTableViewCell = { (user: User, cell: TRTableViewCell) -> Void in 62 | if let avatar = user.avatar, let url = URL(string: avatar) { 63 | cell.imageView?.af_setImage(withURL: url, placeholderImage: UIImage(named: "profile_empty")) 64 | } 65 | } 66 | }) 67 | } 68 | 69 | func invalidateTimer() { 70 | timer?.invalidate() 71 | timer = nil 72 | } 73 | 74 | @objc func timerFired(_ timer: Timer) { 75 | if let dict = (timer.userInfo as? Dictionary), 76 | let text = dict["text"] as? String, 77 | let row = dict["row"] as? TokenTableRow { 78 | 79 | Alamofire.SessionManager.default.request("https://api.github.com/search/users?q=\(text)&per_page=5") 80 | .responseCollection(completionHandler: { (response: DataResponse<[User]>) in 81 | switch response.result { 82 | case let .success(value): 83 | row.cell.filteredTokens = value 84 | row.cell.reloadOptions() 85 | case let .failure(error): 86 | print(error) 87 | } 88 | }) 89 | } 90 | } 91 | 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /TokenRow/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TokenRow/Media/TokenAccessoryView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EurekaCommunity/TokenRow/8411f2bddaae87b75cf038f5871272584a2f4d4c/TokenRow/Media/TokenAccessoryView.gif -------------------------------------------------------------------------------- /TokenRow/Media/TokenTableView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EurekaCommunity/TokenRow/8411f2bddaae87b75cf038f5871272584a2f4d4c/TokenRow/Media/TokenTableView.gif -------------------------------------------------------------------------------- /TokenRow/Media/TokenTableViewCustom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EurekaCommunity/TokenRow/8411f2bddaae87b75cf038f5871272584a2f4d4c/TokenRow/Media/TokenTableViewCustom.gif -------------------------------------------------------------------------------- /TokenRow/Sources/CollectionViewTokenCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewTokenCell.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CLTokenInputView 11 | 12 | /** 13 | * Protocol that the UICollectionViewCells that show options in a TokenRow have to conform to 14 | */ 15 | public protocol EurekaTokenCollectionViewCell { 16 | associatedtype T: TokenSearchable 17 | 18 | func setupForToken(_ token: T) 19 | func sizeThatFits() -> CGSize 20 | } 21 | 22 | /** 23 | Cell that is used in a TokenTableRow. shows a UITableView with options. Generic parameters are: Value of Row and Type of the Cell to be shown in the UITableView that shows the options 24 | */ 25 | open class CollectionTokenCell: TokenCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout where CollectionViewCell: EurekaTokenCollectionViewCell, CollectionViewCell.T == T { 26 | 27 | /// callback that can be used to cuustomize the appearance of the UICollectionViewCell in the inputAccessoryView 28 | public var customizeCollectionViewCell: ((T, CollectionViewCell) -> Void)? 29 | 30 | /// UICollectionView that acts as inputAccessoryView. 31 | public lazy var collectionView: UICollectionView? = { 32 | let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.contentView.frame.width, height: 50), collectionViewLayout: self.collectionViewLayout) 33 | collectionView.showsHorizontalScrollIndicator = false 34 | collectionView.delegate = self 35 | collectionView.dataSource = self 36 | collectionView.backgroundColor = UIColor.white 37 | collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: self.cellReuseIdentifier) 38 | return collectionView 39 | }() 40 | 41 | public var collectionViewLayout: UICollectionViewLayout = { 42 | var layout = UICollectionViewFlowLayout() 43 | layout.scrollDirection = .horizontal 44 | layout.minimumLineSpacing = 10 45 | layout.minimumInteritemSpacing = 10 46 | layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) 47 | return layout 48 | }() 49 | 50 | required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 51 | super.init(style: style, reuseIdentifier: reuseIdentifier) 52 | } 53 | 54 | required public init?(coder aDecoder: NSCoder) { 55 | fatalError("init(coder:) has not been implemented") 56 | } 57 | 58 | open override var inputAccessoryView: UIView? { 59 | return self.collectionView 60 | } 61 | 62 | override open func reloadOptions() { 63 | collectionView?.reloadData() 64 | } 65 | 66 | @objc open func tokenInputView(_ aView: CLTokenInputView, didChangeText text: String?) { 67 | if let text = text , !text.isEmpty { 68 | if let newTokens = (row as! _TokenRow>).getTokensForString(text) { 69 | filteredTokens = newTokens 70 | } 71 | showOptions() 72 | } 73 | else { 74 | filteredTokens = [] 75 | hideOptions() 76 | } 77 | reloadOptions() 78 | } 79 | 80 | //MARK: UICollectionViewDelegate and Datasource 81 | open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 | return filteredTokens.count 83 | } 84 | 85 | open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 86 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) as! CollectionViewCell 87 | if filteredTokens.count > (indexPath as NSIndexPath).row { 88 | let token = filteredTokens[(indexPath as NSIndexPath).row] 89 | cell.setupForToken(token) 90 | customizeCollectionViewCell?(token, cell) 91 | } 92 | return cell 93 | } 94 | 95 | open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 96 | if filteredTokens.count > (indexPath as NSIndexPath).row { 97 | let token = filteredTokens[(indexPath as NSIndexPath).row] 98 | (row as! _TokenRow).addToken(token) 99 | _ = cellResignFirstResponder() 100 | } 101 | } 102 | 103 | open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 104 | let cell = CollectionViewCell(frame: CGRect.zero) 105 | if filteredTokens.count > (indexPath as NSIndexPath).row { 106 | let token = filteredTokens[(indexPath as NSIndexPath).row] 107 | cell.setupForToken(token) 108 | } 109 | return cell.sizeThatFits() 110 | } 111 | 112 | open func numberOfSections(in collectionView: UICollectionView) -> Int { 113 | return 1 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /TokenRow/Sources/String+TokenRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+TokenRow.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension String: TokenSearchable { 12 | 13 | public var displayString: String { return self } 14 | public var identifier: NSObject { return self as NSObject } 15 | 16 | public func contains(token: String) -> Bool { 17 | return self.lowercased().contains(token.lowercased()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TokenRow/Sources/TRCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TRCollectionViewCell.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Eureka 11 | 12 | /// Default cell for the inputAccessoryView of the TokenAccessoryRow 13 | open class TRCollectionViewCell: UICollectionViewCell, EurekaTokenCollectionViewCell { 14 | public typealias T = Token 15 | 16 | public var label = UILabel() 17 | 18 | override public init(frame: CGRect) { 19 | super.init(frame: frame) 20 | initialize() 21 | } 22 | 23 | required public init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | initialize() 26 | } 27 | 28 | func initialize() { 29 | label.font = UIFont.systemFont(ofSize: 13) 30 | label.numberOfLines = 2 31 | label.minimumScaleFactor = 0.8 32 | label.adjustsFontSizeToFitWidth = true 33 | label.textColor = UIColor.blue 34 | contentView.addSubview(label) 35 | contentView.backgroundColor = UIColor.white 36 | label.translatesAutoresizingMaskIntoConstraints = false 37 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-(4)-[label]-(4)-|", options: [], metrics: nil, views: ["label": label])) 38 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[label]|", options: [], metrics: nil, views: ["label": label])) 39 | } 40 | 41 | open func setupForToken(_ token: T) { 42 | label.text = token.displayString 43 | } 44 | 45 | open func sizeThatFits() -> CGSize { 46 | label.frame = CGRect(x: 0, y: 0, width: 180, height: 40) 47 | label.sizeToFit() 48 | return CGSize(width: label.frame.width + 8, height: 40) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /TokenRow/Sources/TRTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TRTableViewCell.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Eureka 11 | 12 | /// Default cell for the table of the TokenTableCell 13 | open class TRTableViewCell: UITableViewCell, EurekaTokenTableViewCell { 14 | public typealias T = Token 15 | 16 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 17 | super.init(style: style, reuseIdentifier: reuseIdentifier) 18 | initialize() 19 | } 20 | 21 | required public init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | initialize() 24 | } 25 | 26 | func initialize() { 27 | textLabel?.font = UIFont.systemFont(ofSize: 16) 28 | textLabel?.minimumScaleFactor = 0.8 29 | textLabel?.adjustsFontSizeToFitWidth = true 30 | textLabel?.textColor = UIColor.blue 31 | contentView.backgroundColor = UIColor.white 32 | } 33 | 34 | open func setupForToken(_ token: T) { 35 | textLabel?.text = token.displayString 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TokenRow/Sources/TableViewTokenCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewTokenCell.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import CLTokenInputView 12 | 13 | /** 14 | * Protocol that the UITableViewCells that show options in a TokenRow have to conform to 15 | */ 16 | public protocol EurekaTokenTableViewCell { 17 | associatedtype T: TokenSearchable 18 | 19 | func setupForToken(_ token: T) 20 | } 21 | 22 | /// Cell that is used in a TokenTableRow. shows a UITableView with options. Generic parameters are: Value of Row and Type of the Cell to be shown in the UITableView that shows the options 23 | open class TableTokenCell: TokenCell, UITableViewDelegate, UITableViewDataSource where TableViewCell: EurekaTokenTableViewCell, TableViewCell.T == T { 24 | 25 | /// callback that can be used to cuustomize the appearance of the UICollectionViewCell in the inputAccessoryView 26 | public var customizeTableViewCell: ((T, TableViewCell) -> Void)? 27 | 28 | /// UICollectionView that acts as inputAccessoryView. 29 | public var tableView: UITableView? 30 | 31 | /// Maximum number of options to be shown as candidates 32 | public var numberOfOptions: Int = 5 33 | 34 | required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | } 37 | 38 | required public init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | open override func setup() { 43 | super.setup() 44 | tableView = UITableView(frame: CGRect.zero, style: .plain) 45 | tableView?.autoresizingMask = .flexibleHeight 46 | tableView?.isHidden = true 47 | tableView?.delegate = self 48 | tableView?.dataSource = self 49 | tableView?.backgroundColor = UIColor.white 50 | tableView?.register(TableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier) 51 | } 52 | 53 | open override func showOptions() { 54 | if let controller = formViewController() { 55 | if tableView?.superview == nil { 56 | controller.view.addSubview(tableView!) 57 | } 58 | let frame = controller.tableView?.convert(self.frame, to: controller.view) ?? self.frame 59 | tableView?.frame = CGRect(x: 0, y: frame.origin.y + frame.height, width: contentView.frame.width, height: 44 * CGFloat(numberOfOptions)) 60 | tableView?.isHidden = false 61 | } 62 | } 63 | 64 | override open func hideOptions() { 65 | tableView?.isHidden = true 66 | } 67 | 68 | override open func reloadOptions() { 69 | tableView?.reloadData() 70 | } 71 | 72 | @objc open func tokenInputView(_ aView: CLTokenInputView, didChangeText text: String?) { 73 | if let text = text , !text.isEmpty { 74 | if let newTokens = (row as! _TokenRow>).getTokensForString(text) { 75 | filteredTokens = newTokens 76 | } 77 | showOptions() 78 | } 79 | else { 80 | filteredTokens = [] 81 | hideOptions() 82 | } 83 | reloadOptions() 84 | } 85 | 86 | //MARK: UITableViewDelegate and Datasource 87 | open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 88 | return filteredTokens.count 89 | } 90 | 91 | open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 92 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! TableViewCell 93 | if filteredTokens.count > (indexPath as NSIndexPath).row { 94 | let token = filteredTokens[(indexPath as NSIndexPath).row] 95 | cell.setupForToken(token) 96 | customizeTableViewCell?(token, cell) 97 | } 98 | return cell 99 | } 100 | 101 | open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 102 | if filteredTokens.count > (indexPath as NSIndexPath).row { 103 | let token = filteredTokens[(indexPath as NSIndexPath).row] 104 | (row as! _TokenRow).addToken(token) 105 | _ = cellResignFirstResponder() 106 | } 107 | } 108 | 109 | open func numberOfSections(in tableView: UITableView) -> Int { 110 | return 1 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TokenRow/Sources/TokenCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenCell.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Eureka 11 | import CLTokenInputView 12 | 13 | // This protocol exists because of a Swift 2 limitation (cannot add generic type constraint parameters into generic declaration) 14 | public protocol TokenCellProtocol { 15 | var tokenView: CLTokenInputView { get } 16 | } 17 | 18 | open class TokenCell: Cell>, CLTokenInputViewDelegate, TokenCellProtocol, CellType { 19 | 20 | /// View that contains the tokens of this row 21 | lazy public var tokenView: CLTokenInputView = { [weak self] in 22 | guard let me = self else { return CLTokenInputView() } 23 | let tokenView = CLTokenInputView(frame: me.frame) 24 | tokenView.translatesAutoresizingMaskIntoConstraints = false 25 | tokenView.accessoryView = nil 26 | tokenView.delegate = me 27 | return tokenView 28 | }() 29 | 30 | /// Reuse identifier for collection view or table view that shows the options 31 | public var cellReuseIdentifier: String = "EurekaTokenCellReuseIdentifier" 32 | 33 | /// The options that matched the search string entered by the user 34 | public var filteredTokens: [T] = [] 35 | 36 | required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 37 | super.init(style: style, reuseIdentifier: reuseIdentifier) 38 | } 39 | 40 | required public init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | } 43 | 44 | open override func cellCanBecomeFirstResponder() -> Bool { 45 | return !row.isDisabled 46 | } 47 | 48 | open override var canBecomeFirstResponder: Bool { 49 | if row.isDisabled { return false } 50 | return tokenView.canBecomeFirstResponder 51 | } 52 | 53 | open override func becomeFirstResponder() -> Bool { 54 | if row.isDisabled { return false } 55 | return tokenView.becomeFirstResponder() 56 | } 57 | 58 | open override func cellResignFirstResponder() -> Bool { 59 | hideOptions() 60 | return tokenView.resignFirstResponder() 61 | } 62 | 63 | var tokenRow: TokenRowProtocol { 64 | return row as! TokenRowProtocol 65 | } 66 | 67 | open override func setup() { 68 | super.setup() 69 | contentView.addSubview(tokenView) 70 | setupConstraints() 71 | selectionStyle = .none 72 | tokenView.backgroundColor = .clear 73 | } 74 | 75 | open override func update() { 76 | // Not calling super on purpose as we do not want to use textlabel nor detailTextLabel 77 | tokenView.fieldName = row.title 78 | tokenView.placeholderText = tokenRow.placeholder 79 | 80 | // check if row.value contains other tokens than tokenView 81 | let valueIdentifiers = Set(row.value?.compactMap { $0.identifier } ?? []) 82 | var valuesToAdd = valueIdentifiers 83 | if Set(tokenView.allTokens.compactMap { $0.context }) != valueIdentifiers { 84 | tokenView.allTokens.forEach { (token) in 85 | if !valueIdentifiers.contains(token.context!) { 86 | tokenView.remove(token) 87 | } else { 88 | valuesToAdd.remove(token.context!) 89 | } 90 | } 91 | row.value?.forEach({ (tokenSearchable) in 92 | if valuesToAdd.contains(tokenSearchable.identifier) { 93 | tokenView.add(tokenSearchable.getCLToken()) 94 | } 95 | }) 96 | } 97 | tokenView.isUserInteractionEnabled = !row.isDisabled 98 | } 99 | 100 | /** 101 | Constrinats for this cell should be set up here. Custom constraints should be added here in an override 102 | */ 103 | open func setupConstraints() { 104 | let views = ["tokenView": tokenView] 105 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tokenView]|", options: .alignAllLastBaseline, metrics: nil, views: views)) 106 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tokenView]|", options: .alignAllLastBaseline, metrics: nil, views: views)) 107 | } 108 | 109 | /// Should show the list of options 110 | func showOptions() {} 111 | 112 | /// Should hide the list of options 113 | func hideOptions() {} 114 | 115 | /// Should reload the list of options 116 | open func reloadOptions() {} 117 | 118 | open func tokenInputView(_ aView: CLTokenInputView, didAdd token: CLToken) { 119 | tokenRow.addToken(token.context!) 120 | } 121 | 122 | open func tokenInputView(_ aView: CLTokenInputView, didRemove token: CLToken) { 123 | tokenRow.removeToken(token.context!) 124 | } 125 | 126 | open func tokenInputView(_ aView: CLTokenInputView, tokenForText text: String) -> CLToken? { 127 | if filteredTokens.count > 0 { 128 | let matchingToken = filteredTokens[0] 129 | return matchingToken.getCLToken() 130 | } 131 | return nil 132 | } 133 | 134 | open func tokenInputViewDidBeginEditing(_ view: CLTokenInputView) { 135 | formViewController()?.beginEditing(of: self) 136 | } 137 | 138 | open func tokenInputViewDidEndEditing(_ view: CLTokenInputView) { 139 | formViewController()?.endEditing(of: self) 140 | hideOptions() 141 | } 142 | 143 | open func tokenInputView(_ view: CLTokenInputView, didChangeHeightTo height: CGFloat) { 144 | self.height = { height } 145 | formViewController()?.tableView?.beginUpdates() 146 | formViewController()?.tableView?.endUpdates() 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /TokenRow/Sources/TokenRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenRow.swift 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/6/16. 6 | // 7 | 8 | import Foundation 9 | import Eureka 10 | import CLTokenInputView 11 | 12 | 13 | /** 14 | * Values of a TokenRow have to conform to this protocol 15 | */ 16 | public protocol TokenSearchable: Hashable { 17 | var displayString: String { get } 18 | func contains(token: String) -> Bool 19 | var identifier: NSObject { get } 20 | } 21 | 22 | extension TokenSearchable { 23 | 24 | func getCLToken() -> CLToken { 25 | let token = CLToken() 26 | token.displayText = displayString 27 | token.context = identifier 28 | return token 29 | } 30 | 31 | } 32 | 33 | /// Generic TokenRow. Concrete classes should subclass this one and specify generic parameters 34 | open class _TokenRow : Row where Cell: CellType, Cell: TokenCellProtocol, Cell.Value == Set { 35 | public var options: [T] = [] 36 | public var placeholder: String? 37 | 38 | required public init(tag: String?) { 39 | super.init(tag: tag) 40 | value = Set() 41 | displayValueFor = nil 42 | } 43 | 44 | /// Get tokens that match a given search text. Useful when you want to get those tokens asynchronously 45 | open lazy var getTokensForString: (String) -> [T]? = { [weak self] searchString in 46 | guard let me = self else { return nil } 47 | 48 | // return options that have not been chosen and that contain the searchString 49 | return me.options.filter { 50 | return me.value == nil || !me.value!.contains($0) 51 | }.filter { $0.contains(token: searchString) } 52 | } 53 | 54 | /// remove a token by its identifier 55 | open func removeToken(_ tokenIdentifier: NSObject) { 56 | if let token = value?.filter({ $0.identifier == tokenIdentifier }).first { 57 | removeToken(token) 58 | } 59 | } 60 | 61 | /// remove a token from the list of chosen tokens 62 | open func removeToken(_ token: T) { 63 | value?.remove(token) 64 | if let cltoken = cell.tokenView.allTokens.filter({ $0.context == token.identifier }).first { 65 | cell.tokenView.remove(cltoken) 66 | } 67 | } 68 | 69 | /// add a token to the list of chosen tokens by identifier 70 | open func addToken(_ tokenIdentifier: NSObject) { 71 | if let token = options.filter({$0.identifier == tokenIdentifier}).first { 72 | addToken(token) 73 | } 74 | } 75 | 76 | /// add a token from the list of chosen tokens 77 | open func addToken(_ token: T) { 78 | value?.insert(token) 79 | cell.tokenView.add(CLToken(displayText: token.displayString, context: token.identifier)) 80 | } 81 | } 82 | 83 | /// TokenRow that shows its options in the inputAccessoryView 84 | public final class TokenAccessoryRow: _TokenRow>>, RowType { 85 | required public init(tag: String?) { 86 | super.init(tag: tag) 87 | } 88 | } 89 | 90 | /// TokenRow that shows its options in a table below the cell 91 | public final class TokenTableRow: _TokenRow>>, RowType { 92 | required public init(tag: String?) { 93 | super.init(tag: tag) 94 | } 95 | } 96 | 97 | 98 | /** 99 | * Protocol to access _TokenRow from TokenCell 100 | */ 101 | protocol TokenRowProtocol { 102 | var placeholder: String? { get } 103 | func addToken(_ tokenIdentifier: NSObject) 104 | func removeToken(_ tokenIdentifier: NSObject) 105 | } 106 | 107 | extension _TokenRow: TokenRowProtocol {} 108 | -------------------------------------------------------------------------------- /TokenRow/TokenRow.h: -------------------------------------------------------------------------------- 1 | // 2 | // TokenRow.h 3 | // TokenRow 4 | // 5 | // Created by Mathias Claassen on 9/8/16. 6 | // Copyright © 2016 Xmartlabs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TokenRow. 12 | FOUNDATION_EXPORT double TokenRowVersionNumber; 13 | 14 | //! Project version string for TokenRow. 15 | FOUNDATION_EXPORT const unsigned char TokenRowVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | --------------------------------------------------------------------------------