├── .gitignore ├── Example ├── Gemfile ├── Gemfile.lock ├── MultiSelectionTable-Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── nunogoncalves.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── MultiSelectionTable-Example.xcscheme │ │ ├── MultiSelectionTable.xcscheme │ │ └── xcschememanagement.plist ├── MultiSelectionTable-Example.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── MultiSelectionTable-Example │ ├── Albums │ │ ├── Album+DictInit.swift │ │ ├── Album.swift │ │ ├── AlbumCell.swift │ │ ├── AlbumCell.xib │ │ ├── AlbunsViewController.swift │ │ └── Band.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── IronManMask.imageset │ │ │ ├── Contents.json │ │ │ └── IronManMask.png │ │ ├── Music.imageset │ │ │ ├── Contents.json │ │ │ └── music-notes.png │ │ ├── MusicNote.imageset │ │ │ ├── Contents.json │ │ │ └── MusicNote.png │ │ ├── SupermanFlyLeft.imageset │ │ │ ├── Contents.json │ │ │ └── SuperManFlyLeft.png │ │ └── SupermanFlyRight.imageset │ │ │ ├── Contents.json │ │ │ └── SuperManFlyRight.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── BridgingHeader.h │ ├── Heroes │ │ ├── Hero+DictInit.swift │ │ ├── Hero.swift │ │ ├── HeroCell.swift │ │ ├── HeroCell.xib │ │ ├── HeroCellMover.swift │ │ ├── HeroDetailsViewController.swift │ │ ├── HeroesList.swift │ │ ├── HeroesViewController.swift │ │ └── SuperManAnimator.swift │ ├── Info.plist │ ├── Models │ │ ├── NetworkError.swift │ │ └── Result.swift │ ├── UseCases │ │ ├── Albums │ │ │ └── Albums.Fetcher.swift │ │ ├── Cache │ │ │ └── ImageLoader.swift │ │ ├── Heroes │ │ │ └── Heroes.Fetcher.swift │ │ └── Network │ │ │ └── Network.Get.swift │ ├── albums.json │ └── covers │ │ ├── 1000x1000.jpg │ │ ├── Dark_Side_of_the_Moon.png │ │ ├── Eurythmics_-_Touch.jpg │ │ ├── PuddleOfMudd_-_ComeClean.jpg │ │ ├── Thisisacting_albumcover.png │ │ ├── YouveComeALongWayBaby2.jpg │ │ ├── appetite-for-destruction.jpg │ │ ├── badadchaseylane.jpg │ │ ├── blackice.jpg │ │ ├── californication.jpg │ │ ├── funhouse.png │ │ ├── ghoststoriesfull.jpg │ │ ├── imnotdead.png │ │ ├── itsmylife.jpg │ │ ├── joyride.jpg │ │ ├── nevermind.jpg │ │ ├── queengreatest2.jpg │ │ ├── rosenrot.jpg │ │ ├── s&m.jpg │ │ ├── switch.jpg │ │ ├── threedollarbillyall.jpg │ │ ├── thriller.jpg │ │ ├── truthaboutlove.png │ │ └── u2bestof.jpg ├── MultiSelectionTable.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── MultiSelectionTableTests │ ├── Info.plist │ └── MultiSelectionTableTests.swift ├── MultiSelectionTableUITests │ ├── Info.plist │ └── MultiSelectionTableUITests.swift ├── Podfile ├── Podfile.lock └── Pods │ ├── Local Podspecs │ └── MultiSelectionTableView.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── nunogoncalves.xcuserdatad │ │ └── xcschemes │ │ ├── MultiSelectionTableView.xcscheme │ │ ├── Pods-MultiSelectionTable-Example.xcscheme │ │ ├── Pods-MultiSelectionTable-ExampleTests.xcscheme │ │ ├── Pods-MultiSelectionTable-ExampleUITests.xcscheme │ │ └── xcschememanagement.plist │ └── Target Support Files │ ├── MultiSelectionTableView │ ├── Info.plist │ ├── MultiSelectionTableView-Info.plist │ ├── MultiSelectionTableView-dummy.m │ ├── MultiSelectionTableView-prefix.pch │ ├── MultiSelectionTableView-umbrella.h │ ├── MultiSelectionTableView.modulemap │ └── MultiSelectionTableView.xcconfig │ ├── Pods-MultiSelectionTable-Example │ ├── Info.plist │ ├── Pods-MultiSelectionTable-Example-Info.plist │ ├── Pods-MultiSelectionTable-Example-acknowledgements.markdown │ ├── Pods-MultiSelectionTable-Example-acknowledgements.plist │ ├── Pods-MultiSelectionTable-Example-dummy.m │ ├── Pods-MultiSelectionTable-Example-frameworks.sh │ ├── Pods-MultiSelectionTable-Example-resources.sh │ ├── Pods-MultiSelectionTable-Example-umbrella.h │ ├── Pods-MultiSelectionTable-Example.debug.xcconfig │ ├── Pods-MultiSelectionTable-Example.modulemap │ └── Pods-MultiSelectionTable-Example.release.xcconfig │ ├── Pods-MultiSelectionTable-ExampleTests │ ├── Info.plist │ ├── Pods-MultiSelectionTable-ExampleTests-Info.plist │ ├── Pods-MultiSelectionTable-ExampleTests-acknowledgements.markdown │ ├── Pods-MultiSelectionTable-ExampleTests-acknowledgements.plist │ ├── Pods-MultiSelectionTable-ExampleTests-dummy.m │ ├── Pods-MultiSelectionTable-ExampleTests-frameworks.sh │ ├── Pods-MultiSelectionTable-ExampleTests-resources.sh │ ├── Pods-MultiSelectionTable-ExampleTests-umbrella.h │ ├── Pods-MultiSelectionTable-ExampleTests.debug.xcconfig │ ├── Pods-MultiSelectionTable-ExampleTests.modulemap │ └── Pods-MultiSelectionTable-ExampleTests.release.xcconfig │ └── Pods-MultiSelectionTable-ExampleUITests │ ├── Info.plist │ ├── Pods-MultiSelectionTable-ExampleUITests-Info.plist │ ├── Pods-MultiSelectionTable-ExampleUITests-acknowledgements.markdown │ ├── Pods-MultiSelectionTable-ExampleUITests-acknowledgements.plist │ ├── Pods-MultiSelectionTable-ExampleUITests-dummy.m │ ├── Pods-MultiSelectionTable-ExampleUITests-frameworks.sh │ ├── Pods-MultiSelectionTable-ExampleUITests-resources.sh │ ├── Pods-MultiSelectionTable-ExampleUITests-umbrella.h │ ├── Pods-MultiSelectionTable-ExampleUITests.debug.xcconfig │ ├── Pods-MultiSelectionTable-ExampleUITests.modulemap │ └── Pods-MultiSelectionTable-ExampleUITests.release.xcconfig ├── LICENSE.md ├── MultiSelectionTableView.podspec ├── README.md ├── Resources ├── MultiSelectionTableView1.gif ├── MultiselectionSupermanAnimation.gif ├── StyleBlack.png ├── StyleGreenBlue.png ├── StyleRed.png └── StyleWhite.png └── Source ├── Animators ├── Selection │ ├── CellSelectionAnimator.swift │ └── CellSelectionPulseAnimator.swift └── Translation │ ├── CellMover.swift │ ├── CellReplacer.swift │ └── CellTransitionAnimator.swift ├── DataSource.swift ├── ItemIndex.swift ├── MultiSelectionDataSource.swift ├── MultiSelectionDelegate.swift ├── MultiSelectionTableView.swift ├── TableViewHeader.swift └── UIControlEvents.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xcuserstate 3 | 4 | Example/MultiSelectionTable.xcworkspace/xcuserdata/nunogoncalves.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist 5 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.8.3' 4 | -------------------------------------------------------------------------------- /Example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.1) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.8.4) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.8.4) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.11.1, < 2.0) 34 | cocoapods-core (1.8.4) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | cocoapods-deintegrate (1.0.4) 41 | cocoapods-downloader (1.3.0) 42 | cocoapods-plugins (1.0.0) 43 | nap 44 | cocoapods-search (1.0.0) 45 | cocoapods-stats (1.1.0) 46 | cocoapods-trunk (1.4.1) 47 | nap (>= 0.8, < 2.0) 48 | netrc (~> 0.11) 49 | cocoapods-try (1.1.0) 50 | colored2 (3.1.2) 51 | concurrent-ruby (1.1.5) 52 | escape (0.0.4) 53 | fourflusher (2.3.1) 54 | fuzzy_match (2.0.4) 55 | gh_inspector (1.1.3) 56 | httpclient (2.8.3) 57 | i18n (0.9.5) 58 | concurrent-ruby (~> 1.0) 59 | json (2.2.0) 60 | minitest (5.13.0) 61 | molinillo (0.6.6) 62 | nanaimo (0.2.6) 63 | nap (1.1.0) 64 | netrc (0.11.0) 65 | ruby-macho (1.4.0) 66 | thread_safe (0.3.6) 67 | tzinfo (1.2.5) 68 | thread_safe (~> 0.1) 69 | xcodeproj (1.13.0) 70 | CFPropertyList (>= 2.3.3, < 4.0) 71 | atomos (~> 0.1.3) 72 | claide (>= 1.0.2, < 2.0) 73 | colored2 (~> 3.1) 74 | nanaimo (~> 0.2.6) 75 | 76 | PLATFORMS 77 | ruby 78 | 79 | DEPENDENCIES 80 | cocoapods (~> 1.8.3) 81 | 82 | BUNDLED WITH 83 | 2.0.2 84 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/MultiSelectionTable-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/MultiSelectionTable.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 89 | 90 | 91 | 92 | 93 | 94 | 100 | 102 | 108 | 109 | 110 | 111 | 113 | 114 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MultiSelectionTable-Example.xcscheme 8 | 9 | orderHint 10 | 6 11 | 12 | MultiSelectionTable.xcscheme 13 | 14 | isShown 15 | 16 | orderHint 17 | 0 18 | 19 | 20 | SuppressBuildableAutocreation 21 | 22 | 85C315111DECEFF30095AFA1 23 | 24 | primary 25 | 26 | 27 | 85C315281DECEFF30095AFA1 28 | 29 | primary 30 | 31 | 32 | 85C315331DECEFF30095AFA1 33 | 34 | primary 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/Album+DictInit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album+DictInit.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 10/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Album { 12 | 13 | init?(dictionary: [String : Any]) { 14 | guard let name = dictionary["name"] as? String, 15 | let year = dictionary["year"] as? Int, 16 | let coverImageName = dictionary["coverImageUrl"] as? String, 17 | let bandDic = dictionary["band"] as? [String: Any], 18 | let bandName = bandDic["name"] as? String 19 | else { 20 | return nil 21 | } 22 | 23 | self.band = Band(name: bandName) 24 | self.name = name 25 | self.coverImageName = coverImageName 26 | print(coverImageName) 27 | self.year = year 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/Album.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 29/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Album : Equatable { 12 | 13 | let band: Band 14 | let name: String 15 | let coverImageName: String 16 | let year: Int 17 | 18 | static func all(finished: @escaping ([Album]) -> ()) { 19 | Albums.Fetcher.fetch(got: finished) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/AlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumCell.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 30/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumCell: UITableViewCell { 12 | 13 | @IBOutlet weak var albumImageView: UIImageView! 14 | @IBOutlet weak var nameLabel: UILabel! 15 | @IBOutlet weak var subtitleLabel: UILabel! 16 | @IBOutlet weak var yearLabel: UILabel! 17 | 18 | @IBOutlet weak var bottomLineHeightConstraint: NSLayoutConstraint! 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | 23 | bottomLineHeightConstraint.constant = 0.5 24 | } 25 | 26 | override func prepareForReuse() { 27 | albumImageView.image = nil 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/AlbumCell.xib: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 47 | 48 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/AlbunsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 28/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MultiSelectionTableView 11 | 12 | class AlbunsViewController: UIViewController { 13 | 14 | @IBOutlet weak var multiSelectionTableContainer: UIStackView! 15 | @IBOutlet weak var searchTextField: UITextField! 16 | 17 | override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } 18 | 19 | private var multiSelectionDataSource: MultiSelectionDataSource! 20 | @IBOutlet fileprivate weak var multiSelectionTableView: MultiSelectionTableView! 21 | 22 | fileprivate var filteredAlbuns: [Album] = [] { 23 | didSet { 24 | multiSelectionDataSource.allItems = filteredAlbuns 25 | } 26 | } 27 | fileprivate var allAlbums: [Album] = [] 28 | 29 | @IBAction func clearSearchText() { 30 | searchTextField.text = nil 31 | searchTextField.resignFirstResponder() 32 | filteredAlbuns = allAlbums 33 | } 34 | 35 | @IBAction func textUpdated(_ sender: UITextField) { 36 | if let searchText = sender.text, 37 | searchText.count > 0 { 38 | filteredAlbuns = allAlbums.filter { $0.name.lowercased().contains(searchText.lowercased()) } 39 | } else { 40 | filteredAlbuns = allAlbums 41 | } 42 | } 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | 47 | multiSelectionDataSource = MultiSelectionDataSource(multiSelectionTableView: multiSelectionTableView) 48 | multiSelectionDataSource.delegate = self 49 | multiSelectionDataSource.register(nib: UINib(nibName: "AlbumCell", bundle: nil), for: "AlbumCell") 50 | 51 | Album.all { [weak self] albums in 52 | self?.allAlbums = albums 53 | self?.multiSelectionDataSource.allItems = albums 54 | } 55 | 56 | multiSelectionTableView.dataSource = multiSelectionDataSource 57 | multiSelectionTableView.allItemsContentInset = UIEdgeInsets(top: 110, left: 0, bottom: 0, right: 0) 58 | multiSelectionTableView.selectedItemsContentInset = UIEdgeInsets(top: 110, left: 0, bottom: 0, right: 0) 59 | multiSelectionTableView.addTarget(self, action: #selector(selectedItem(multiSelectionTableView:)), for: .itemSelected) 60 | multiSelectionTableView.addTarget(self, action: #selector(unselectedItem(multiSelectionTableView:)), for: .itemUnselected) 61 | } 62 | 63 | @objc private func selectedItem(multiSelectionTableView: MultiSelectionTableView) { 64 | print("selected item") 65 | } 66 | 67 | @objc private func unselectedItem(multiSelectionTableView: MultiSelectionTableView) { 68 | print("unselected item") 69 | } 70 | } 71 | 72 | extension AlbunsViewController : MultiSelectionTableDelegate { 73 | 74 | func paint(_ cell: UITableViewCell, at indexPath: IndexPath, in tableView: UITableView, with item: Any) { 75 | if let cell = cell as? AlbumCell, 76 | let album = item as? Album { 77 | cell.nameLabel.text = album.band.name 78 | cell.subtitleLabel.text = album.name 79 | cell.yearLabel.text = "\(album.year)" 80 | 81 | let image = UIImage(named: album.coverImageName) 82 | cell.albumImageView.image = image 83 | } 84 | } 85 | } 86 | 87 | extension AlbunsViewController : UITextFieldDelegate { 88 | 89 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 90 | textField.resignFirstResponder() 91 | return true 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Albums/Band.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Band.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 29/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | struct Band: Equatable { 10 | 11 | let name: String 12 | 13 | static func ==(left: Band, right: Band) -> Bool { 14 | return left.name == right.name 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 28/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application( 18 | _ application: UIApplication, 19 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 20 | ) -> Bool { 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) {} 25 | func applicationDidEnterBackground(_ application: UIApplication) {} 26 | func applicationWillEnterForeground(_ application: UIApplication) {} 27 | func applicationDidBecomeActive(_ application: UIApplication) {} 28 | func applicationWillTerminate(_ application: UIApplication) {} 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/IronManMask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "IronManMask.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/IronManMask.imageset/IronManMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/Assets.xcassets/IronManMask.imageset/IronManMask.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/Music.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "music-notes.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/Music.imageset/music-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/Assets.xcassets/Music.imageset/music-notes.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/MusicNote.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "MusicNote.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/MusicNote.imageset/MusicNote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/Assets.xcassets/MusicNote.imageset/MusicNote.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyLeft.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "SuperManFlyLeft.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyLeft.imageset/SuperManFlyLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyLeft.imageset/SuperManFlyLeft.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "SuperManFlyRight.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyRight.imageset/SuperManFlyRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/Assets.xcassets/SupermanFlyRight.imageset/SuperManFlyRight.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-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 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingHeader.h 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | #ifndef BridgingHeader_h 10 | #define BridgingHeader_h 11 | 12 | 13 | #endif /* BridgingHeader_h */ 14 | 15 | #import 16 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/Hero+DictInit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hero+DictInit.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 10/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Hero { 12 | 13 | init?(dictionary: [String : Any]) { 14 | guard let id = dictionary["id"] as? Int, 15 | let name = dictionary["name"] as? String, 16 | let thumb = dictionary["thumbnail"] as? [String : String], 17 | let thumbPath = thumb["path"], 18 | let thumbExtension = thumb["extension"], 19 | let url = URL(string: "\(thumbPath).\(thumbExtension)") 20 | else { 21 | return nil 22 | } 23 | 24 | self.id = id 25 | self.name = name 26 | self.imageURL = url 27 | self.description = dictionary["description"] as? String 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/Hero.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hero.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Hero : Equatable { 12 | let id: Int 13 | let name: String 14 | let imageURL: URL 15 | let description: String? 16 | 17 | static func all(named name: String? = nil, in page: Int, finished: @escaping (HeroesList) -> ()) { 18 | Heroes.Fetcher.fetch(named: name, in: page, got: finished) 19 | } 20 | 21 | static func ==(leftHero: Hero, rightHero: Hero) -> Bool { 22 | return leftHero.id == rightHero.id && leftHero.name == rightHero.name 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeroCell.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HeroCell: UITableViewCell { 12 | 13 | @IBOutlet weak var heroImageView: UIImageView! 14 | @IBOutlet weak var heroNameLabel: UILabel! 15 | @IBOutlet weak var bottomLine: UIView! 16 | 17 | @IBAction func infoTapped() { 18 | showInfo?() 19 | } 20 | 21 | var showInfo: (() -> ())? 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | } 26 | 27 | override func prepareForReuse() { 28 | heroImageView.image = nil 29 | bottomLine.isHidden = false 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroCell.xib: -------------------------------------------------------------------------------- 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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroCellMover.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeroCellMover.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MultiSelectionTableView 11 | 12 | class HeroCellMover : CellTransitionAnimator { 13 | 14 | func selectionTransition(in containerView: UIView, 15 | fromTableView: UITableView, 16 | fromIndexPath: IndexPath, 17 | toTableView: UITableView, 18 | toIndexPath: IndexPath) { 19 | 20 | toTableView.insertRows(at: [toIndexPath], with: .left) 21 | fromTableView.deleteRows(at: [fromIndexPath], with: .right) 22 | } 23 | 24 | func unselectionTransition(in containerView: UIView, 25 | fromTableView: UITableView, 26 | fromIndexPath: IndexPath, 27 | toTableView: UITableView, 28 | toIndexPath: IndexPath) { 29 | 30 | toTableView.insertRows(at: [toIndexPath], with: .right) 31 | fromTableView.deleteRows(at: [fromIndexPath], with: .left) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeroDetailsViewController.swift 3 | // MultiSelectionTable-Example 4 | // 5 | // Created by Nuno Gonçalves on 17/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HeroDetailsViewController : UIViewController { 12 | 13 | @IBOutlet weak var descriptionLabel: UILabel! 14 | @IBOutlet weak var heroImageView: UIImageView! 15 | 16 | @IBAction func closeTapped() { 17 | dismiss(animated: true, completion: nil) 18 | } 19 | 20 | var hero: Hero! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | descriptionLabel.text = hero.description 25 | 26 | Cache.ImageLoader().image(with: hero.imageURL) { image in 27 | self.heroImageView.image = image 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroesList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeroesGroup.swift 3 | // MultiSelectionTable-Example 4 | // 5 | // Created by Nuno Gonçalves on 16/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct HeroesList { 12 | static let emptyHeroesList = HeroesList(heroes: [], totalCount: 0, currentPage: 0, totalPages: 0) 13 | 14 | let heroes: [Hero] 15 | 16 | let totalCount: Int 17 | let currentPage: Int 18 | let totalPages: Int 19 | 20 | var hasMorePages: Bool { 21 | return currentPage < totalPages 22 | } 23 | 24 | var isFirstPage: Bool { 25 | return currentPage == 0 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/HeroesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeroesViewController.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 08/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MultiSelectionTableView 11 | 12 | class HeroesViewController : UIViewController { 13 | 14 | @IBOutlet weak var multiSelectionTableContainer: UIStackView! 15 | @IBOutlet weak var searchTextField: UITextField! 16 | 17 | override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } 18 | 19 | private var multiSelectionDataSource: MultiSelectionDataSource! 20 | @IBOutlet fileprivate weak var multiSelectionTableView: MultiSelectionTableView! 21 | 22 | fileprivate lazy var mainStoryboard: UIStoryboard = { 23 | return UIStoryboard(name: "Main", bundle: nil) 24 | }() 25 | 26 | @IBAction func clearSearchText() { 27 | searchTextField.text = nil 28 | searchTextField.resignFirstResponder() 29 | searchText = "" 30 | } 31 | 32 | fileprivate var searchText = "" { 33 | didSet { 34 | multiSelectionDataSource.allItems = [] 35 | searchHeroes() 36 | } 37 | } 38 | 39 | fileprivate func searchHeroes(in page: Int = 0) { 40 | guard !isLoading else { return } 41 | 42 | if page == 0 { 43 | let loadingView = UIActivityIndicatorView() 44 | loadingView.transform = CGAffineTransform.init(scaleX: 2, y: 2) 45 | loadingView.startAnimating() 46 | multiSelectionTableView.stateView = loadingView 47 | } 48 | isLoading = true 49 | Hero.all(named: searchText, in: page) { [weak self] heroesList in 50 | self?.multiSelectionTableView.stateView = nil 51 | self?.heroesList = heroesList 52 | self?.isLoading = false 53 | } 54 | } 55 | 56 | fileprivate var heroesList: HeroesList = HeroesList.emptyHeroesList { 57 | didSet { 58 | if heroesList.isFirstPage { 59 | multiSelectionDataSource.allItems = heroesList.heroes 60 | } else { 61 | multiSelectionDataSource.allItems.append(contentsOf: heroesList.heroes) 62 | } 63 | if heroesList.heroes.count <= 1 { 64 | let label = UILabel() 65 | label.text = "No heroes" 66 | label.textColor = .white 67 | label.textAlignment = .center 68 | multiSelectionTableView.stateView = label 69 | } 70 | } 71 | } 72 | 73 | fileprivate let imageLoader = Cache.ImageLoader.shared 74 | 75 | fileprivate var isLoading = false 76 | 77 | override func viewDidLoad() { 78 | super.viewDidLoad() 79 | 80 | multiSelectionDataSource = MultiSelectionDataSource(multiSelectionTableView: multiSelectionTableView) 81 | multiSelectionDataSource.delegate = self 82 | multiSelectionDataSource.register(nib: UINib(nibName: "HeroCell", bundle: nil), for: "HeroCell") 83 | 84 | multiSelectionDataSource.allItems = heroesList.heroes 85 | searchHeroes() 86 | 87 | multiSelectionTableView.dataSource = multiSelectionDataSource 88 | multiSelectionTableView.allItemsContentInset = UIEdgeInsets(top: 105, left: 0, bottom: 0, right: 0) 89 | multiSelectionTableView.selectedItemsContentInset = UIEdgeInsets(top: 105, left: 0, bottom: 0, right: 0) 90 | multiSelectionTableView.supportsPagination = true 91 | multiSelectionTableView.paginationNotificationRowIndex = 20 92 | multiSelectionTableView.cellAnimator = CellSelectionPulseAnimator(pulseColor: .black) 93 | multiSelectionTableView.cellTransitioner = SuperManAnimator() 94 | 95 | multiSelectionTableView.addTarget(self, action: #selector(loadMoreHeroes(multiSelectionTableView:)), for: .scrollReachingEnd) 96 | } 97 | 98 | @objc private func loadMoreHeroes(multiSelectionTableView: MultiSelectionTableView) { 99 | if heroesList.hasMorePages { 100 | searchHeroes(in: heroesList.currentPage + 1) 101 | } 102 | } 103 | 104 | } 105 | 106 | extension HeroesViewController : MultiSelectionTableDelegate { 107 | 108 | func paint( 109 | _ cell: UITableViewCell, 110 | at indexPath: IndexPath, 111 | in tableView: UITableView, with item: Any 112 | ) { 113 | if let cell = cell as? HeroCell, 114 | let hero = item as? Hero { 115 | cell.heroNameLabel.text = hero.name 116 | if let image = imageLoader.cachedImage(with: hero.imageURL) { 117 | cell.heroImageView.image = image 118 | } else { 119 | imageLoader.image(with: hero.imageURL) { image in 120 | if let cell = tableView.cellForRow(at: indexPath) as? HeroCell { 121 | cell.heroImageView.image = image 122 | } 123 | } 124 | } 125 | 126 | cell.showInfo = { [weak self] in 127 | if let heroViewController = self?.mainStoryboard.instantiateViewController(withIdentifier: "HeroDetailsViewController") as? HeroDetailsViewController { 128 | heroViewController.hero = hero 129 | self?.show(heroViewController, sender: self) 130 | } 131 | } 132 | } 133 | } 134 | 135 | } 136 | 137 | extension HeroesViewController : UITextFieldDelegate { 138 | 139 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 140 | searchText = textField.text ?? "" 141 | textField.resignFirstResponder() 142 | return true 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Heroes/SuperManAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuperManAnimator.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // 7 | // 8 | 9 | import MultiSelectionTableView 10 | 11 | public class SuperManAnimator : CellTransitionAnimator { 12 | 13 | public init() {} 14 | 15 | private let duration: TimeInterval = 0.5 16 | 17 | public func selectionTransition(in containerView: UIView, 18 | fromTableView: UITableView, 19 | fromIndexPath: IndexPath, 20 | toTableView: UITableView, 21 | toIndexPath: IndexPath) { 22 | 23 | toTableView.insertRows(at: [toIndexPath], with: .top) 24 | 25 | var _newCellAdded: UITableViewCell? 26 | 27 | if let cell = toTableView.cellForRow(at: toIndexPath) { 28 | _newCellAdded = cell 29 | } else if let cell = toTableView.visibleCells.last { 30 | _newCellAdded = cell 31 | } 32 | 33 | guard let newCellAdded = _newCellAdded else { return } 34 | 35 | newCellAdded.contentView.isHidden = true 36 | let newCellConvertedFrame = newCellAdded.convert(newCellAdded.contentView.frame, to: containerView) 37 | 38 | guard let cellToDelete = fromTableView.cellForRow(at: fromIndexPath) else { return } 39 | (cellToDelete as? HeroCell)?.bottomLine.isHidden = true 40 | 41 | if let movingCell = cellToDelete.contentView.snapshotView(afterScreenUpdates: true) { 42 | let viewToHide = UIView(frame: CGRect(x: 0, y: 0, width: movingCell.frame.width, height: movingCell.frame.height)) 43 | movingCell.addSubview(viewToHide) 44 | let superman = UIImageView(image: #imageLiteral(resourceName: "SupermanFlyRight")) 45 | superman.frame = CGRect(x: -90, y: movingCell.frame.midY - 20, width: 100, height: 40) 46 | movingCell.addSubview(superman) 47 | 48 | cellToDelete.contentView.isHidden = true 49 | containerView.addSubview(movingCell) 50 | movingCell.frame = fromTableView.convert(cellToDelete.frame, to: containerView) 51 | 52 | fromTableView.deleteRows(at: [fromIndexPath], with: .top) 53 | 54 | UIView.animate(withDuration: duration, animations: { 55 | movingCell.frame = newCellConvertedFrame 56 | }, completion: { _ in 57 | newCellAdded.contentView.isHidden = false 58 | cellToDelete.contentView.isHidden = false 59 | UIView.animate(withDuration: self.duration, 60 | animations: { 61 | superman.frame = superman.frame.offsetBy(dx: movingCell.frame.width + superman.frame.width, 62 | dy: 0) 63 | }, 64 | completion: { _ in 65 | movingCell.removeFromSuperview() 66 | }) 67 | viewToHide.isHidden = true 68 | }) 69 | } 70 | 71 | } 72 | 73 | public func unselectionTransition(in containerView: UIView, 74 | fromTableView: UITableView, 75 | fromIndexPath: IndexPath, 76 | toTableView: UITableView, 77 | toIndexPath: IndexPath) { 78 | 79 | let newIndexPath = toIndexPath 80 | let allItemsTable = toTableView 81 | 82 | let indexPath = fromIndexPath 83 | let selectedItemsTable = fromTableView 84 | 85 | allItemsTable.insertRows(at: [newIndexPath], with: .bottom) 86 | 87 | var _newCellAdded: UITableViewCell? 88 | 89 | if let cell = allItemsTable.cellForRow(at: newIndexPath) { 90 | _newCellAdded = cell 91 | } else if let cell = allItemsTable.visibleCells.last { 92 | _newCellAdded = cell 93 | } 94 | 95 | guard let newCellAdded = _newCellAdded else { return } 96 | 97 | newCellAdded.contentView.isHidden = true 98 | let newCellConvertedFrame = newCellAdded.convert(newCellAdded.contentView.frame, to: containerView) 99 | 100 | guard let cellToDelete = selectedItemsTable.cellForRow(at: indexPath) else { return } 101 | 102 | if let movingCell = cellToDelete.contentView.snapshotView(afterScreenUpdates: false) { 103 | let viewToHide = UIView(frame: CGRect(x: 0, y: 0, width: movingCell.frame.width, height: movingCell.frame.height)) 104 | movingCell.addSubview(viewToHide) 105 | let superman = UIImageView(image: #imageLiteral(resourceName: "SupermanFlyLeft")) 106 | superman.frame = CGRect(x: movingCell.frame.width - 5, y: movingCell.frame.midY - 20, width: 100, height: 40) 107 | movingCell.addSubview(superman) 108 | 109 | cellToDelete.contentView.isHidden = true 110 | containerView.addSubview(movingCell) 111 | movingCell.frame = selectedItemsTable.convert(cellToDelete.frame, to: containerView) 112 | 113 | selectedItemsTable.deleteRows(at: [indexPath], with: .top) 114 | 115 | UIView.animate(withDuration: duration, animations: { 116 | movingCell.frame = newCellConvertedFrame 117 | }, completion: { _ in 118 | newCellAdded.contentView.isHidden = false 119 | UIView.animate(withDuration: self.duration, 120 | animations: { 121 | superman.frame = superman.frame.offsetBy(dx: -(movingCell.frame.width + superman.frame.width), 122 | dy: 0) 123 | }, 124 | completion: { _ in 125 | movingCell.removeFromSuperview() 126 | }) 127 | viewToHide.isHidden = true 128 | }) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-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 | CFBundleVersion 20 | 1 21 | LSApplicationCategoryType 22 | 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarStyle 39 | UIStatusBarStyleLightContent 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Models/NetworkError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 11/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum NetworkError : Error { 12 | case parse 13 | case data 14 | } 15 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/Models/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 11/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | enum Result { 10 | case success(T) 11 | case failure(NetworkError) 12 | } 13 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/UseCases/Albums/Albums.Fetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Albums.Fetcher.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 10/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Albums { 12 | 13 | struct Fetcher { 14 | 15 | static func fetch(got: @escaping ([Album]) -> ()) { 16 | 17 | guard let path = Bundle.main.path(forResource: "albums", ofType: "json"), 18 | let data = try? Data(contentsOf: URL(fileURLWithPath: path)), 19 | let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] 20 | else { return } 21 | 22 | if let dicAlbums = json["albums"] as? [[String : Any]] { 23 | let albums = dicAlbums.compactMap { Album(dictionary: $0) } 24 | got(albums) 25 | } else { 26 | //We should really propagate the Result the the caller 27 | got([]) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/UseCases/Cache/ImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // MultiSelectionTable-Example 4 | // 5 | // Created by Nuno Gonçalves on 12/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | struct Cache { 12 | 13 | final class ImageLoader { 14 | 15 | static let shared = ImageLoader() 16 | 17 | fileprivate let cache: NSCache = { 18 | let cache = NSCache() 19 | cache.name = "ImageCache" 20 | return cache 21 | }() 22 | 23 | fileprivate let physicalCacheURL: URL = { 24 | var url = try! FileManager.default.url( 25 | for: .cachesDirectory, 26 | in: .userDomainMask, 27 | appropriateFor: nil, 28 | create: true 29 | ) 30 | url = url.appendingPathComponent("ImageCache", isDirectory: true) 31 | 32 | try! FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) 33 | 34 | return url 35 | }() 36 | 37 | func cachedImage(with url: URL) -> UIImage? { 38 | let hash = "\(url.hashValue)" as NSString 39 | 40 | print(hash) 41 | // Check if image exists on volatile cache 42 | if let image = cache.object(forKey: hash) { 43 | return image 44 | } 45 | 46 | // Check if image exists on physical cache 47 | if let image = UIImage(contentsOfFile: pathOnPhysicalCacheForFile(with: hash)) { 48 | cache.setObject(image, forKey: hash) 49 | return image 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func image(with url: URL, completionHandler: @escaping (UIImage) -> Void){ 56 | let request = URLRequest(url: url) 57 | image(with: request, completionHandler: completionHandler) 58 | } 59 | 60 | private func image(with request: URLRequest, completionHandler: @escaping (UIImage) -> Void) { 61 | let hash = String(describing: request.url.hash) as NSString 62 | 63 | if let image = cache.object(forKey: hash) { 64 | completionHandler(image) 65 | return 66 | } 67 | 68 | if let image = UIImage(contentsOfFile: pathOnPhysicalCacheForFile(with: hash)) { 69 | cache.setObject(image, forKey: hash) 70 | completionHandler(image) 71 | return 72 | } 73 | 74 | // Download 75 | let task = URLSession.shared.downloadTask(with: request) { url, response, error in 76 | guard let tempUrl = url else { 77 | print("Error downloading \(request)") 78 | print("Error \(error)") 79 | print("Response \(response)") 80 | return 81 | } 82 | let finalUrl = self.URLOnPhysicalCacheForFile(with: hash) 83 | 84 | self.saveFile(in: finalUrl, with: hash, oldUrl: tempUrl, completionHandler: completionHandler) 85 | 86 | } 87 | task.resume() 88 | } 89 | 90 | private func saveFile( 91 | in url: URL, 92 | with hash: NSString, 93 | oldUrl: URL, 94 | completionHandler: @escaping (UIImage) -> Void 95 | ) { 96 | do { 97 | if FileManager.default.fileExists(atPath: url.path) { 98 | try FileManager.default.removeItem(at: url) 99 | } 100 | 101 | try FileManager.default.moveItem(at: oldUrl, to: url) 102 | if let image = UIImage(contentsOfFile: url.path) { 103 | self.cache.setObject(image, forKey: hash) 104 | OperationQueue.main.addOperation { 105 | completionHandler(image) 106 | } 107 | } 108 | 109 | } catch { 110 | print("Error saving image to final location: \(error)") 111 | } 112 | } 113 | 114 | private func URLOnPhysicalCacheForFile(with hash: NSString) -> URL { 115 | let URL = physicalCacheURL.appendingPathComponent(hash as String) 116 | return URL 117 | } 118 | 119 | private func pathOnPhysicalCacheForFile(with hash: NSString) -> String { 120 | return URLOnPhysicalCacheForFile(with: hash).path 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/UseCases/Heroes/Heroes.Fetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Heroes.Fetcher.swift 3 | // MultiSelectionTable 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Heroes { 12 | 13 | struct Fetcher { 14 | 15 | static func fetch(named name: String? = nil, in page: Int = 0, got: @escaping (HeroesList) -> ()) { 16 | let url = buildUrl(with: page, and: name) 17 | Network.get(from: url) { result in 18 | switch result { 19 | case .success(let json): 20 | guard let dataDic = json["data"] as? [String : Any] else { 21 | return got(HeroesList(heroes: [], 22 | totalCount: 0, 23 | currentPage: 0, 24 | totalPages: 0)) 25 | } 26 | 27 | let totalCount = dataDic["total"] as? Int ?? 0 28 | let offset = dataDic["offset"] as? Int ?? 0 29 | let limit = dataDic["limit"] as? Int ?? 0 30 | 31 | let totalPages = limit == 0 ? 0 : Int(ceil(Double(totalCount / limit))) 32 | let currentPage = totalCount == 0 ? 0 : Int(ceil(Double(totalPages) * Double(offset) / Double(totalCount))) 33 | 34 | let dicCharacters = dataDic["results"] as! [[String : Any]] 35 | let heroes = dicCharacters.flatMap { Hero(dictionary: $0) } 36 | 37 | let heroesList = HeroesList(heroes: heroes, 38 | totalCount: totalCount, 39 | currentPage: currentPage, 40 | totalPages: totalPages) 41 | got(heroesList) 42 | case .failure(_): 43 | //We should really propagate the Result the the caller 44 | return got(HeroesList(heroes: [], 45 | totalCount: 0, 46 | currentPage: 0, 47 | totalPages: 0)) 48 | } 49 | } 50 | } 51 | 52 | static let ts = "1" 53 | static let pub = "ab781b828ce0d61d8d053bde7d8c3fde" 54 | static let priv = "f9490b9557c2e8e7c52b72a632898b63658dcf5b" 55 | static let charactersUrl = "http://gateway.marvel.com:80/v1/public/characters" 56 | 57 | static var hash : String { 58 | return "\(ts)\(priv)\(pub)".md5ed 59 | } 60 | 61 | fileprivate static func buildUrl(with page: Int, and name: String? = nil) -> URL { 62 | let limit = 25 63 | var urlStr = "\(charactersUrl)?limit=\(limit)&offset=\(page * limit)&apikey=\(pub)&ts=1&hash=\(hash)" 64 | if let name = name, 65 | !name.isEmpty { 66 | urlStr.append("&nameStartsWith=\(name)") 67 | urlStr = urlStr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! 68 | } 69 | return URL(string: urlStr)! 70 | } 71 | } 72 | } 73 | 74 | fileprivate extension String { 75 | var md5ed: String! { 76 | let str = self.cString(using: String.Encoding.utf8) 77 | let strLen = CC_LONG(self.lengthOfBytes(using: String.Encoding.utf8)) 78 | let digestLen = Int(CC_MD5_DIGEST_LENGTH) 79 | let result = UnsafeMutablePointer.allocate(capacity: digestLen) 80 | 81 | CC_MD5(str!, strLen, result) 82 | 83 | let hash = NSMutableString() 84 | for i in 0..) -> ()) { 18 | DispatchQueue.global(qos: .userInteractive).async { 19 | let task = urlSession.dataTask(with: url, completionHandler: { data, response, error in 20 | if let data = data { 21 | do { 22 | let json = try dictionary(from: data) 23 | DispatchQueue.main.async { 24 | result(.success(json)) 25 | } 26 | } catch { 27 | DispatchQueue.main.async { 28 | result(.failure(.parse)) 29 | } 30 | 31 | } 32 | } else { 33 | DispatchQueue.main.async { 34 | result(.failure(.data)) 35 | } 36 | } 37 | }) 38 | task.resume() 39 | } 40 | } 41 | 42 | private static func dictionary(from data: Data) throws -> JSONDictionary { 43 | let dataJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) 44 | if let dataDictionary = dataJSON as? JSONDictionary { 45 | return dataDictionary 46 | } 47 | throw NetworkError.parse 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/albums.json: -------------------------------------------------------------------------------- 1 | { 2 | "albums" : [{ 3 | "name" : "Nevermind", 4 | "coverImageUrl" : "covers/nevermind.jpg", 5 | "year": 1991, 6 | "band" : { 7 | "name" : "Nirvana" 8 | 9 | } 10 | }, { 11 | "name" : "Californication", 12 | "coverImageUrl" : "covers/californication.jpg", 13 | "year": 1999, 14 | "band" : { 15 | "name" : "Red Hot Chili Peppers" 16 | 17 | } 18 | }, { 19 | "name" : "Hoorray for boobies", 20 | "coverImageUrl" : "covers/badadchaseylane.jpg", 21 | "year": 1999, 22 | "band" : { 23 | "name" : "BloodHound Gang" 24 | 25 | } 26 | }, { 27 | "name" : "Three Dollar Bill Yall", 28 | "coverImageUrl" : "covers/threedollarbillyall.jpg", 29 | "year": 1997, 30 | "band" : { 31 | "name" : "Limp Bizkit" 32 | 33 | } 34 | }, { 35 | "name" : "S&M", 36 | "coverImageUrl" : "covers/s&m.jpg", 37 | "year": 1999, 38 | "band" : { 39 | "name" : "Metallica" 40 | 41 | } 42 | }, { 43 | "name" : "Joyride", 44 | "coverImageUrl" : "covers/joyride.jpg", 45 | "year": 1991, 46 | "band" : { 47 | "name" : "Roxette" 48 | 49 | } 50 | }, { 51 | "name" : "Best of", 52 | "coverImageUrl" : "covers/u2bestof.jpg", 53 | "year": 1998, 54 | "band" : { 55 | "name" : "U2" 56 | 57 | } 58 | }, { 59 | "name" : "Greatest Hits 2", 60 | "coverImageUrl" : "covers/queengreatest2.jpg", 61 | "year": 1991, 62 | "band" : { 63 | "name" : "Queen" 64 | 65 | } 66 | }, { 67 | "name" : "I'm not dead", 68 | "coverImageUrl" : "covers/imnotdead.png", 69 | "year": 2006, 70 | "band" : { 71 | "name" : "Pink" 72 | 73 | } 74 | }, { 75 | "name" : "Appetite for Destruction", 76 | "coverImageUrl" : "covers/appetite-for-destruction.jpg", 77 | "year": 1987, 78 | "band" : { 79 | "name" : "Guns N Roses" 80 | 81 | } 82 | }, { 83 | "name" : "Black Ice", 84 | "coverImageUrl" : "covers/blackice.jpg", 85 | "year": 2008, 86 | "band" : { 87 | "name" : "AC/DC" 88 | 89 | } 90 | }, { 91 | "name" : "Dark Side of the Moon", 92 | "coverImageUrl" : "covers/Dark_Side_of_the_Moon.png", 93 | "year": 1973, 94 | "band" : { 95 | "name" : "Pink Floyd" 96 | 97 | } 98 | }, { 99 | "name" : "Touch", 100 | "coverImageUrl" : "covers/Eurythmics_-_Touch.jpg", 101 | "year": 1983, 102 | "band" : { 103 | "name" : "Eurytmics" 104 | 105 | } 106 | }, { 107 | "name" : "Ghosts of Stories", 108 | "coverImageUrl" : "covers/ghoststoriesfull.jpg", 109 | "year": 2014, 110 | "band" : { 111 | "name" : "Coldplay" 112 | 113 | } 114 | }, { 115 | "name" : "Funhouse", 116 | "coverImageUrl" : "covers/funhouse.png", 117 | "year": 2008, 118 | "band" : { 119 | "name" : "Pink" 120 | 121 | } 122 | }, { 123 | "name" : "It's My Life", 124 | "coverImageUrl" : "covers/itsmylife.jpg", 125 | "year": 2000, 126 | "band" : { 127 | "name" : "Bon Jovi" 128 | 129 | } 130 | }, { 131 | "name" : "Rosenrot", 132 | "coverImageUrl" : "covers/rosenrot.jpg", 133 | "year": 2005, 134 | "band" : { 135 | "name" : "Ramstein" 136 | 137 | } 138 | }, { 139 | "name" : "Thriller", 140 | "coverImageUrl" : "covers/thriller.jpg", 141 | "year": 1982, 142 | "band" : { 143 | "name" : "Michael Jackson" 144 | 145 | } 146 | }, { 147 | "name" : "Switch", 148 | "coverImageUrl" : "covers/switch.jpg", 149 | "year": 2005, 150 | "band" : { 151 | "name" : "INXS" 152 | 153 | } 154 | }, { 155 | "name" : "This Is Acting", 156 | "coverImageUrl" : "covers/Thisisacting_albumcover.png", 157 | "year": 2016, 158 | "band" : { 159 | "name" : "Sia" 160 | 161 | } 162 | }, { 163 | "name" : "Come Clean", 164 | "coverImageUrl" : "covers/PuddleOfMudd_-_ComeClean.jpg", 165 | "year": 2001, 166 | "band" : { 167 | "name" : "Puddle of Mudd" 168 | 169 | } 170 | }, { 171 | "name" : "Proud Like a God", 172 | "coverImageUrl" : "covers/1000x1000.jpg", 173 | "year": 1997, 174 | "band" : { 175 | "name" : "Guano Apes" 176 | 177 | } 178 | }, { 179 | "name" : "You've Come a Long Way Baby", 180 | "coverImageUrl" : "covers/YouveComeALongWayBaby2.jpg", 181 | "year": 1998, 182 | "band" : { 183 | "name" : "Fat Boy Slim" 184 | 185 | } 186 | } 187 | ] 188 | } 189 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/1000x1000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/1000x1000.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/Dark_Side_of_the_Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/Dark_Side_of_the_Moon.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/Eurythmics_-_Touch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/Eurythmics_-_Touch.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/PuddleOfMudd_-_ComeClean.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/PuddleOfMudd_-_ComeClean.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/Thisisacting_albumcover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/Thisisacting_albumcover.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/YouveComeALongWayBaby2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/YouveComeALongWayBaby2.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/appetite-for-destruction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/appetite-for-destruction.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/badadchaseylane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/badadchaseylane.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/blackice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/blackice.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/californication.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/californication.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/funhouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/funhouse.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/ghoststoriesfull.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/ghoststoriesfull.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/imnotdead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/imnotdead.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/itsmylife.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/itsmylife.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/joyride.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/joyride.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/nevermind.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/nevermind.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/queengreatest2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/queengreatest2.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/rosenrot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/rosenrot.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/s&m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/s&m.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/switch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/switch.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/threedollarbillyall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/threedollarbillyall.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/thriller.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/thriller.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/truthaboutlove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/truthaboutlove.png -------------------------------------------------------------------------------- /Example/MultiSelectionTable-Example/covers/u2bestof.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Example/MultiSelectionTable-Example/covers/u2bestof.jpg -------------------------------------------------------------------------------- /Example/MultiSelectionTable.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MultiSelectionTable.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/MultiSelectionTableTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/MultiSelectionTableTests/MultiSelectionTableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectionTableTests.swift 3 | // MultiSelectionTableTests 4 | // 5 | // Created by Nuno Gonçalves on 28/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MultiSelectionTable 11 | 12 | class MultiSelectionTableTests: XCTestCase { 13 | 14 | var multiSelectionDataSource: MultiSelectionDataSource! 15 | let multiSelectionTableView = MultiSelectionTableView() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | multiSelectionDataSource = MultiSelectionDataSource(multiSelectionTableView: multiSelectionTableView) 21 | multiSelectionTableView.dataSource = multiSelectionDataSource 22 | 23 | multiSelectionDataSource.allItems = [1, 2, 3] 24 | 25 | let view = MultiSelectionTableView() 26 | view.dataSource = multiSelectionDataSource 27 | 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testAllItemsCount() { 36 | XCTAssert(multiSelectionDataSource.allItemsCount == 3, "All items should be three") 37 | } 38 | 39 | func testInitialSelectedItemsCount() { 40 | XCTAssert(multiSelectionDataSource.selectedItemsCount == 0, "Selected Items should be zero at first") 41 | } 42 | 43 | func testSelectedItemsCountAfterOneSelection() { 44 | multiSelectionDataSource.selectedItem(at: 0) 45 | let selectedItems = multiSelectionDataSource.selectedItems 46 | XCTAssert(selectedItems.count == 1, "Selected Items should be one after selecting one item") 47 | XCTAssert(selectedItems.first == 1, "First item should be 1") 48 | } 49 | 50 | func testAllItemsCountAfterOneSelection() { 51 | multiSelectionDataSource.selectedItem(at: 0) 52 | 53 | let allItems = multiSelectionDataSource.allItems 54 | XCTAssert(allItems.count == 3, "All items should remain the same") 55 | } 56 | 57 | func testPerformanceExample() { 58 | // This is an example of a performance test case. 59 | self.measure { 60 | // Put the code you want to measure the time of here. 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Example/MultiSelectionTableUITests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/MultiSelectionTableUITests/MultiSelectionTableUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectionTableUITests.swift 3 | // MultiSelectionTableUITests 4 | // 5 | // Created by Nuno Gonçalves on 28/11/16. 6 | // Copyright © 2016 Nuno Gonçalves. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MultiSelectionTableUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // 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. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'MultiSelectionTable-Example' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | pod 'MultiSelectionTableView', :path => '../' 8 | 9 | # Pods for MultiSelectionTable 10 | 11 | target 'MultiSelectionTable-ExampleTests' do 12 | inherit! :search_paths 13 | # Pods for testing 14 | end 15 | 16 | target 'MultiSelectionTable-ExampleUITests' do 17 | inherit! :search_paths 18 | # Pods for testing 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MultiSelectionTableView (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - MultiSelectionTableView (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | MultiSelectionTableView: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | MultiSelectionTableView: 7db06fbd1de16cfdd9fcf9783bc0c55cc2e85cf4 13 | 14 | PODFILE CHECKSUM: c1f5b0b3d32d622b8f70229b856cfc9b42548a01 15 | 16 | COCOAPODS: 1.8.4 17 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/MultiSelectionTableView.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MultiSelectionTableView", 3 | "version": "0.1.0", 4 | "summary": "Beautiful multi-selection table for iOS written in Swift 3", 5 | "description": "MultiSelectionTableView is a custom view that allows displaying a list of items and select as many as one wants. One sees two lists, the first lists all items the user might select, and the second lists the selected items. The table allows pagination and search, and the selected items will not leave context making it easy for the user to interact with your view.", 6 | "homepage": "https://github.com/nunogoncalves/iOS-MultiSelectionTable", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE.md" 10 | }, 11 | "authors": { 12 | "Nuno Gonçalves": "" 13 | }, 14 | "source": { 15 | "git": "https://github.com/nunogoncalves/MultiSelectionTable.git", 16 | "tag": "0.1.0" 17 | }, 18 | "social_media_url": "https://twitter.com/goncalvescmnuno", 19 | "platforms": { 20 | "ios": "9.0" 21 | }, 22 | "source_files": "Source/**/*" 23 | } 24 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MultiSelectionTableView (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - MultiSelectionTableView (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | MultiSelectionTableView: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | MultiSelectionTableView: 7db06fbd1de16cfdd9fcf9783bc0c55cc2e85cf4 13 | 14 | PODFILE CHECKSUM: c1f5b0b3d32d622b8f70229b856cfc9b42548a01 15 | 16 | COCOAPODS: 1.8.4 17 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/MultiSelectionTableView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/Pods-MultiSelectionTable-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/Pods-MultiSelectionTable-ExampleTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/Pods-MultiSelectionTable-ExampleUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/nunogoncalves.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MultiSelectionTableView.xcscheme 8 | 9 | isShown 10 | 11 | 12 | Pods-MultiSelectionTable-Example.xcscheme 13 | 14 | isShown 15 | 16 | 17 | Pods-MultiSelectionTable-ExampleTests.xcscheme 18 | 19 | isShown 20 | 21 | 22 | Pods-MultiSelectionTable-ExampleUITests.xcscheme 23 | 24 | isShown 25 | 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/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 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView-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 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_MultiSelectionTableView : NSObject 3 | @end 4 | @implementation PodsDummy_MultiSelectionTableView 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double MultiSelectionTableViewVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char MultiSelectionTableViewVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView.modulemap: -------------------------------------------------------------------------------- 1 | framework module MultiSelectionTableView { 2 | umbrella header "MultiSelectionTableView-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/MultiSelectionTableView/MultiSelectionTableView.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## MultiSelectionTableView 5 | 6 | Licensed under the **MIT** license 7 | 8 | > Copyright (c) 2016 Nuno Gonçalves 9 | > 10 | > Permission is hereby granted, free of charge, to any person obtaining 11 | > a copy of this software and associated documentation files (the 12 | > "Software"), to deal in the Software without restriction, including 13 | > without limitation the rights to use, copy, modify, merge, publish, 14 | > distribute, sublicense, and/or sell copies of the Software, and to 15 | > permit persons to whom the Software is furnished to do so, subject to 16 | > the following conditions: 17 | > 18 | > The above copyright notice and this permission notice shall be 19 | > included in all copies or substantial portions of the Software. 20 | > 21 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | Generated by CocoaPods - https://cocoapods.org 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Licensed under the **MIT** license 18 | 19 | > Copyright (c) 2016 Nuno Gonçalves 20 | > 21 | > Permission is hereby granted, free of charge, to any person obtaining 22 | > a copy of this software and associated documentation files (the 23 | > "Software"), to deal in the Software without restriction, including 24 | > without limitation the rights to use, copy, modify, merge, publish, 25 | > distribute, sublicense, and/or sell copies of the Software, and to 26 | > permit persons to whom the Software is furnished to do so, subject to 27 | > the following conditions: 28 | > 29 | > The above copyright notice and this permission notice shall be 30 | > included in all copies or substantial portions of the Software. 31 | > 32 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 34 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 35 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 36 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 37 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 38 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | 40 | License 41 | MIT 42 | Title 43 | MultiSelectionTableView 44 | Type 45 | PSGroupSpecifier 46 | 47 | 48 | FooterText 49 | Generated by CocoaPods - https://cocoapods.org 50 | Title 51 | 52 | Type 53 | PSGroupSpecifier 54 | 55 | 56 | StringsTable 57 | Acknowledgements 58 | Title 59 | Acknowledgements 60 | 61 | 62 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_MultiSelectionTable_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_MultiSelectionTable_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | if [ -r "$source" ]; then 88 | # Copy the dSYM into a the targets temp dir. 89 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 90 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 91 | 92 | local basename 93 | basename="$(basename -s .framework.dSYM "$source")" 94 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 95 | 96 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 97 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 98 | strip_invalid_archs "$binary" 99 | fi 100 | 101 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 102 | # Move the stripped file into its final destination. 103 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 104 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 105 | else 106 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 107 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 108 | fi 109 | fi 110 | } 111 | 112 | # Copies the bcsymbolmap files of a vendored framework 113 | install_bcsymbolmap() { 114 | local bcsymbolmap_path="$1" 115 | local destination="${BUILT_PRODUCTS_DIR}" 116 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 117 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 118 | } 119 | 120 | # Signs a framework with the provided identity 121 | code_sign_if_enabled() { 122 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 123 | # Use the current code_sign_identity 124 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 125 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 126 | 127 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 128 | code_sign_cmd="$code_sign_cmd &" 129 | fi 130 | echo "$code_sign_cmd" 131 | eval "$code_sign_cmd" 132 | fi 133 | } 134 | 135 | # Strip invalid architectures 136 | strip_invalid_archs() { 137 | binary="$1" 138 | # Get architectures for current target binary 139 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 140 | # Intersect them with the architectures we are building for 141 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 142 | # If there are no archs supported by this binary then warn the user 143 | if [[ -z "$intersected_archs" ]]; then 144 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 145 | STRIP_BINARY_RETVAL=0 146 | return 147 | fi 148 | stripped="" 149 | for arch in $binary_archs; do 150 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 151 | # Strip non-valid architectures in-place 152 | lipo -remove "$arch" -output "$binary" "$binary" 153 | stripped="$stripped $arch" 154 | fi 155 | done 156 | if [[ "$stripped" ]]; then 157 | echo "Stripped $binary of architectures:$stripped" 158 | fi 159 | STRIP_BINARY_RETVAL=1 160 | } 161 | 162 | 163 | if [[ "$CONFIGURATION" == "Debug" ]]; then 164 | install_framework "${BUILT_PRODUCTS_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework" 165 | fi 166 | if [[ "$CONFIGURATION" == "Release" ]]; then 167 | install_framework "${BUILT_PRODUCTS_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework" 168 | fi 169 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 170 | wait 171 | fi 172 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_MultiSelectionTable_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_MultiSelectionTable_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_MultiSelectionTable_Example { 2 | umbrella header "Pods-MultiSelectionTable-Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-Example/Pods-MultiSelectionTable-Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_MultiSelectionTable_ExampleTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_MultiSelectionTable_ExampleTests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 91 | wait 92 | fi 93 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_MultiSelectionTable_ExampleTestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_MultiSelectionTable_ExampleTestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_MultiSelectionTable_ExampleTests { 2 | umbrella header "Pods-MultiSelectionTable-ExampleTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleTests/Pods-MultiSelectionTable-ExampleTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_MultiSelectionTable_ExampleUITests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_MultiSelectionTable_ExampleUITests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 91 | wait 92 | fi 93 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_MultiSelectionTable_ExampleUITestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_MultiSelectionTable_ExampleUITestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_MultiSelectionTable_ExampleUITests { 2 | umbrella header "Pods-MultiSelectionTable-ExampleUITests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-MultiSelectionTable-ExampleUITests/Pods-MultiSelectionTable-ExampleUITests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MultiSelectionTableView/MultiSelectionTableView.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "MultiSelectionTableView" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2016 Nuno Gonçalves 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MultiSelectionTableView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MultiSelectionTableView' 3 | s.version = '0.1.0' 4 | s.summary = 'Beautiful multi-selection table for iOS written in Swift 3' 5 | s.description = <<-DESC 6 | MultiSelectionTableView is a custom view that allows displaying a list of items and select as many as one wants. One sees two lists, the first lists all items the user might select, and the second lists the selected items. The table allows pagination and search, and the selected items will not leave context making it easy for the user to interact with your view. 7 | DESC 8 | 9 | s.homepage = 'https://github.com/nunogoncalves/iOS-MultiSelectionTable' 10 | s.license = { :type => 'MIT', :file => 'LICENSE.md' } 11 | s.author = { 'Nuno Gonçalves' => '' } 12 | s.source = { :git => 'https://github.com/nunogoncalves/MultiSelectionTable.git', :tag => s.version.to_s } 13 | s.social_media_url = 'https://twitter.com/goncalvescmnuno' 14 | 15 | s.ios.deployment_target = '9.0' 16 | 17 | s.source_files = 'Source/**/*' 18 | 19 | # s.resource_bundles = { 20 | # 'MultiSelectionTable' => ['MultiSelectionTable/Assets/*.png'] 21 | # } 22 | 23 | end 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS-MultiSelectionTable 2 | Beautifull way of having a multi-selection table on iOS 3 | 4 | [![Swift 5.1](https://img.shields.io/badge/Swift-5.1-orange.svg?style=flat)](https://developer.apple.com/swift/) 5 | [![Xcode 11.2.1+](https://img.shields.io/badge/Xcode-11.2+-blue.svg?style=flat)](https://developer.apple.com/swift/) 6 | [![Platforms iOS](https://img.shields.io/badge/Platforms-iOS%2013+-blue.svg?style=flat)](https://developer.apple.com/swift/) 7 | [![Licence MIT](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://opensource.org/licenses/MIT) 8 | 9 |

10 | 11 | 12 | 13 | 14 |

15 | 16 | Based on [this](https://dribbble.com/shots/2904577-Multi-Selection-Experiment) dribbble by [Vitaly Rubtsov](https://dribbble.com/Vitwai) 17 | 18 | ## How it works: 19 | ```MultiSelectionTable``` underneath is composed of a view and a data source, much like the ```UITableView```'s ```UITableViewDataSource/Delegate```. They both know each other and communicate between themselves. 20 | The view is is composed by two configurable ```UITableView``` and a line seperating them. The DataSource keeps the data the ```UITableView```s display. 21 | 22 | ## Considerations: 23 | (before Usage, pay attention to the following considerations) 24 | - In order to achieve a nice effect when transitioning, cells on the right (selected cells) must be equal to the cells on the left (all items cells). 25 | - The item object you are displaying, must conform with the ```Equatable``` protocol so the control can know where to move the items when unselecting items. 26 | -You can also paginate and use search on your items list. The table keeps a reference to the selected items. 27 | - The Marvel developers API has a 3000 requests limit per day. If this is reached and you can't try the Marvel example, you need to create a developers account to get credentials. Then replace them in ```Heroes.Fetcher.swift``` file 28 | 29 | ## Usage: 30 | 31 | ### Most basic usage: 32 | 33 | Considering you are using MultiSelectionTableView in ViewController: 34 | 35 | ```swift 36 | 37 | var multiSelectionDataSource: MultiSelectionDataSource! //MyItems must be Equatable 38 | var multiSelectionTableView: MultiSelectionTableView! 39 | 40 | var allItems: [MyItem] = [] //MyItem must be Equatable 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | multiSelectionTableView = MultiSelectionTableView() 46 | view.addSubview(multiSelectionTableView) 47 | 48 | multiSelectionDataSource = MultiSelectionDataSource(multiSelectionTableView: multiSelectionTableView) 49 | multiSelectionDataSource.delegate = self 50 | let cellReuseIdentifier = "MyCell" 51 | multiSelectionDataSource.register(nib: UINib(nibName: "MyCustomCellNibName", bundle: nil), for: cellReuseIdentifier) 52 | 53 | multiSelectionDataSource.allItems = allItems 54 | 55 | multiSelectionTableView.dataSource = multiSelectionDataSource 56 | } 57 | 58 | extension ViewController : MultiSelectionTableDelegate { 59 | 60 | func paint(_ cell: UITableViewCell, for indexPath: IndexPath, with item: Any) { 61 | if let cell = cell as? MyCustomCell, 62 | let myItem = item as? MyItem { 63 | //configureCellWithMyItem 64 | } 65 | } 66 | 67 | } 68 | ``` 69 | 70 | ### Costumization 71 | #### Colors style 72 | ```swift 73 | multiSelectionTableView.controlBackgroundColor = .black 74 | multiSelectionTableView.allItemsTableBackgroundColor = .black 75 | multiSelectionTableView.selectedItemsTableBackgroundColor = .black 76 | ``` 77 | #### Horizontal movement width: 78 | Depending on your cell, you might want to set the horizontal width the line moves. This value is based on the center X anchor. 79 | ```swift 80 | multiSelectionTableView.seperatorWidthOffset = 100 //will move 100 point on both directions from the center 81 | ``` 82 | #### Animations 83 | There are two animation types. The selection and the transition. You can customize your animations for both types. 84 | The default selection animation is a pulse starting on the tap point on the cell. 85 | The default transition animation moves a snapshot view of the selected cell to the corresponding side (depending on selection or unselection events) 86 | ```swift 87 | 88 | multiSelectionTableView.cellAnimator = CellSelectionPulseAnimator(pulseColor: .black) // Must conform to CellSelectionAnimator 89 | multiSelectionTableView.cellTransitioner = CellFlyerAnimator() // Must conform to CellTransitionAnimator 90 | 91 | ``` 92 | You can check out the animator examples. 93 | 94 | ### Pagination 95 | If you want MultiSelectionTableView to handle pagination you need to set: 96 | ```swift 97 | multiSelectionTableView.supportsPagination = true 98 | ``` 99 | and you can add a target action to the control. 100 | 101 | ```swift 102 | multiSelectionTableView.addTarget(self, action: #selector(loadMoreData(sender:)), for: .scrollReachingEnd) 103 | ``` 104 | 105 | Aditionally, you can have some control of when to get more data setting 106 | ```swift 107 | multiSelectionTableView.paginationNotificationRowIndex = 5 108 | ``` 109 | this will call .scrollReachingEnd action 5 rows before reaching the end of the table, so you can pre fetch next page data. 110 | 111 | ### Empty State View 112 | It's common for results to come from the web, take some time loading, and/or be empty, and/or display an error. `MultiSelectionTable` has got you covered. 113 | If you want to display a custom empty view, just set the `stateView` with your view. For example a loading indicator: 114 | 115 | ```swift 116 | let loadingView = UIActivityIndicatorView( 117 | loadingView.transform = CGAffineTransform.init(scaleX: 2, y: 2) 118 | loadingView.startAnimating() 119 | multiSelectionTableView.stateView = loadingView 120 | ``` 121 | 122 | ### Target Actions 123 | ```swift``` 124 | ... 125 | multiSelectionTableView.addTarget(self, action: #selector(selectedItem(sender:)), for: .itemSelected) 126 | multiSelectionTableView.addTarget(self, action: #selector(unselectedItem(sender:)), for: .itemUnselected) 127 | 128 | //only called if supportsPagination is set to true 129 | multiSelectionTableView.addTarget(self, action: #selector(loadMoreData(sender:)), for: .scrollReachingEnd) 130 | ... 131 | 132 | @objc private func selectedItem(sender: MultiSelectionTableView) { 133 | print("selected item") 134 | } 135 | 136 | @objc private func unselectedItem(sender: MultiSelectionTableView) { 137 | print("unselected item") 138 | } 139 | ... 140 | ``` 141 | ## Requirements 142 | 143 | - iOS 9.0+ 144 | - Xcode 8.0+ 145 | 146 | ## Installation 147 | 148 |
149 | Cocoapods 150 | 151 | MultiSelectionTable is available through [CocoaPods](http://cocoapods.org). To install 152 | it, simply add the following line to your Podfile: 153 | 154 | ```ruby 155 | platform :ios, '9.0' 156 | use_frameworks! 157 | 158 | pod 'MultiSelectionTable', git: 'https://github.com/nunogoncalves/iOS-MultiSelectionTable' 159 | ``` 160 | (Currently **MultiSelectionTable** is still not yet published to Cocoapods, so for now you need to add ```swift git: 'https://github.com/nunogoncalves/iOS-MultiSelectionTable'```. 161 |
162 | 163 |
164 | Manually 165 | Copy the contents of [Source](https://github.com/nunogoncalves/iOS-MultiSelectionTable/tree/master/Source) folder into your project and you're ready to go. 166 |
167 | 168 | ## TODOs 169 | Missing features and/or bugs can be found in the [Issues](https://github.com/nunogoncalves/iOS-MultiSelectionTable/issues) section. 170 | 171 | ## Author 172 | 173 | Nuno Gonçalves 174 | 175 | || 176 | |:-------------:|:-------------:| 177 | | nunogoncalves | @goncalvescmnuno | 178 | 179 | ## Contribute 180 | Feel free to contribute to **MultiSelectionTable**. 181 | Check [Issues](https://github.com/nunogoncalves/iOS-MultiSelectionTable/issues) before asking something or adding some contribuition that's already being done. 182 | 183 | ## Licence 184 | 185 | **iOS-MultiSelectionTable** is available under the MIT license. See the [LICENSE](https://github.com/nunogoncalves/iOS-MultiSelectionTable/blob/master/LICENSE.md) file for more info. 186 | 187 | ## Final note 188 | If you use `MultiSelectionTable` in a production app, let me know. I'll be very flattered and pleased and sure want to be aware of it. :) 189 | -------------------------------------------------------------------------------- /Resources/MultiSelectionTableView1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/MultiSelectionTableView1.gif -------------------------------------------------------------------------------- /Resources/MultiselectionSupermanAnimation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/MultiselectionSupermanAnimation.gif -------------------------------------------------------------------------------- /Resources/StyleBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/StyleBlack.png -------------------------------------------------------------------------------- /Resources/StyleGreenBlue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/StyleGreenBlue.png -------------------------------------------------------------------------------- /Resources/StyleRed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/StyleRed.png -------------------------------------------------------------------------------- /Resources/StyleWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nunogoncalves/iOS-MultiSelectionTable/f5ceb6a15e1eb5db71308384fddcb4e817021024/Resources/StyleWhite.png -------------------------------------------------------------------------------- /Source/Animators/Selection/CellSelectionAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellSelectionAnimator.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 07/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol CellSelectionAnimator { 12 | func animate(_ cell: UITableViewCell, 13 | startingAt origin: CGPoint?, 14 | finish: (() -> ())?) 15 | } 16 | -------------------------------------------------------------------------------- /Source/Animators/Selection/CellSelectionPulseAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellSelectionPulseAnimator.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 07/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class CellSelectionPulseAnimator : CellSelectionAnimator { 12 | 13 | private let pathLayer = CAShapeLayer() 14 | private let pathAnimation = CABasicAnimation(keyPath: "path") 15 | private let opacityAnimation = CABasicAnimation(keyPath: "opacity") 16 | 17 | private let pulseColor: UIColor 18 | private let initialPulseOpacityAlpha = 1.0 19 | private let finalPulseOpacityAlpha = 0.3 20 | 21 | private let animationDuration: TimeInterval = 0.3 22 | 23 | public init(pulseColor: UIColor) { 24 | self.pulseColor = pulseColor 25 | } 26 | 27 | public func animate(_ cell: UITableViewCell, 28 | startingAt origin: CGPoint?, 29 | finish: (() -> ())?) { 30 | 31 | let startingPoint = origin ?? cell.contentView.center 32 | 33 | let smallCircle = UIBezierPath(ovalIn: CGRect(x: startingPoint.x - 1, 34 | y: startingPoint.y - 1, 35 | width: 2, 36 | height: 2)) 37 | 38 | let maxRadius = maxDistance(between: startingPoint, andCornersIn: cell.contentView.frame) 39 | 40 | let bigCircle = UIBezierPath(arcCenter: startingPoint, 41 | radius: maxRadius, 42 | startAngle: 0, 43 | endAngle: 2 * CGFloat.pi, 44 | clockwise: true) 45 | 46 | pathLayer.lineWidth = 0 47 | cell.contentView.layer.masksToBounds = true 48 | pathLayer.fillColor = pulseColor.cgColor 49 | cell.contentView.layer.addSublayer(pathLayer) 50 | 51 | CATransaction.begin() 52 | 53 | pathAnimation.fromValue = smallCircle.cgPath 54 | pathAnimation.toValue = bigCircle.cgPath 55 | 56 | CATransaction.setCompletionBlock { 57 | finish?() 58 | } 59 | 60 | opacityAnimation.fromValue = initialPulseOpacityAlpha 61 | opacityAnimation.toValue = finalPulseOpacityAlpha 62 | 63 | let animationGroup = CAAnimationGroup() 64 | animationGroup.duration = animationDuration 65 | animationGroup.animations = [pathAnimation, opacityAnimation] 66 | 67 | pathLayer.add(animationGroup, forKey: "animation") 68 | 69 | CATransaction.commit() 70 | } 71 | 72 | func maxDistance(between point: CGPoint, andCornersIn rect: CGRect) -> CGFloat { 73 | let px = point.x 74 | let py = point.y 75 | 76 | let corners = [ 77 | CGPoint(x: rect.origin.x, y: rect.origin.y), 78 | CGPoint(x: rect.width, y: rect.origin.y), 79 | CGPoint(x: rect.origin.x, y: rect.height), 80 | CGPoint(x: rect.width, y: rect.height) 81 | ] 82 | 83 | var maxDistance: CGFloat = 0 84 | 85 | for corner in corners { 86 | let dx = abs(px - corner.x) 87 | let dy = abs(py - corner.y) 88 | let length = sqrt(dx * dx + dy * dy) 89 | maxDistance = max(length, maxDistance) 90 | } 91 | return maxDistance 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Source/Animators/Translation/CellMover.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellMover.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class CellMover : CellTransitionAnimator { 12 | 13 | public func selectionTransition(in containerView: UIView, 14 | fromTableView: UITableView, 15 | fromIndexPath: IndexPath, 16 | toTableView: UITableView, 17 | toIndexPath: IndexPath) { 18 | 19 | toTableView.insertRows(at: [toIndexPath], with: .fade) 20 | 21 | animateTransition(defaultRemovingAnimation: .right, 22 | sourceTableView: fromTableView, 23 | sourceIndexPath: fromIndexPath, 24 | destinationTableView: toTableView, 25 | destinationIndexPath: toIndexPath, 26 | containerView: containerView) 27 | } 28 | 29 | public func unselectionTransition(in containerView: UIView, 30 | fromTableView: UITableView, 31 | fromIndexPath: IndexPath, 32 | toTableView: UITableView, 33 | toIndexPath: IndexPath) { 34 | 35 | toTableView.insertRows(at: [toIndexPath], with: .bottom) 36 | 37 | animateTransition(defaultRemovingAnimation: .left, 38 | sourceTableView: fromTableView, 39 | sourceIndexPath: fromIndexPath, 40 | destinationTableView: toTableView, 41 | destinationIndexPath: toIndexPath, 42 | containerView: containerView) 43 | } 44 | 45 | private func animateTransition(defaultRemovingAnimation: UITableView.RowAnimation, 46 | sourceTableView: UITableView, 47 | sourceIndexPath: IndexPath, 48 | destinationTableView: UITableView, 49 | destinationIndexPath: IndexPath, 50 | containerView: UIView) { 51 | 52 | guard let oldCell = sourceTableView.cellForRow(at: sourceIndexPath), 53 | let newCell = destinationTableView.cellForRow(at: destinationIndexPath), 54 | let movingCell = oldCell.contentView.snapshotView(afterScreenUpdates: false) 55 | else { 56 | sourceTableView.deleteRows(at: [sourceIndexPath], with: defaultRemovingAnimation) 57 | return 58 | } 59 | 60 | newCell.contentView.isHidden = true 61 | oldCell.contentView.isHidden = true 62 | 63 | containerView.addSubview(movingCell) 64 | movingCell.frame = sourceTableView.convert(oldCell.frame, to: containerView) 65 | 66 | sourceTableView.deleteRows(at: [sourceIndexPath], with: .fade) 67 | 68 | let newCellConvertedFrame = newCell.convert(newCell.contentView.frame, to: containerView) 69 | 70 | UIView.animate(withDuration: 0.4, animations: { 71 | movingCell.frame = newCellConvertedFrame 72 | }, completion: { _ in 73 | movingCell.removeFromSuperview() 74 | newCell.contentView.isHidden = false 75 | oldCell.contentView.isHidden = false // because of reusage. 76 | }) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Source/Animators/Translation/CellReplacer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellReplacer.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class CellReplacer : CellTransitionAnimator { 12 | 13 | public init() {} 14 | 15 | public func selectionTransition(in containerView: UIView, 16 | fromTableView: UITableView, 17 | fromIndexPath: IndexPath, 18 | toTableView: UITableView, 19 | toIndexPath: IndexPath) { 20 | 21 | toTableView.insertRows(at: [toIndexPath], with: .left) 22 | fromTableView.deleteRows(at: [fromIndexPath], with: .right) 23 | } 24 | 25 | public func unselectionTransition(in containerView: UIView, 26 | fromTableView: UITableView, 27 | fromIndexPath: IndexPath, 28 | toTableView: UITableView, 29 | toIndexPath: IndexPath) { 30 | toTableView.insertRows(at: [toIndexPath], with: .right) 31 | fromTableView.deleteRows(at: [fromIndexPath], with: .left) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Source/Animators/Translation/CellTransitionAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellTransitionAnimator.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 09/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol CellTransitionAnimator { 12 | func selectionTransition(in containerView: UIView, 13 | fromTableView: UITableView, 14 | fromIndexPath: IndexPath, 15 | toTableView: UITableView, 16 | toIndexPath: IndexPath) 17 | 18 | func unselectionTransition(in containerView: UIView, 19 | fromTableView: UITableView, 20 | fromIndexPath: IndexPath, 21 | toTableView: UITableView, 22 | toIndexPath: IndexPath) 23 | } 24 | -------------------------------------------------------------------------------- /Source/DataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSource.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 04/12/16. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol DataSource : class { 11 | 12 | var allItemsCount: Int { get } 13 | var selectedItemsCount: Int { get } 14 | func cell(for indexPath: IndexPath, inAllItemsTable tableView: UITableView) -> UITableViewCell 15 | func cell(for indexPath: IndexPath, inSelectedItemsTable tableView: UITableView) -> UITableViewCell 16 | 17 | func unselectedItem(at index: Int) 18 | func selectedItem(at index: Int) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Source/ItemIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemIndex.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 01/12/16. 6 | // 7 | 8 | internal struct ItemIndex : Equatable { 9 | 10 | let item: T 11 | var index: Int 12 | 13 | static func ==(lhs: ItemIndex, rhs: ItemIndex) -> Bool { 14 | return lhs.item == rhs.item 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/MultiSelectionDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectionDataSource.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 04/12/16. 6 | // 7 | 8 | import UIKit 9 | 10 | final public class MultiSelectionDataSource : DataSource { 11 | 12 | fileprivate let multiSelectionTableView: MultiSelectionTableView! 13 | 14 | public weak var delegate: MultiSelectionTableDelegate? 15 | 16 | fileprivate var allItemsIndexes: [ItemIndex] = [] 17 | public var allItems: [T] = [] { 18 | didSet { 19 | mapToItemIndexesAndUpdateOriginSelectedIndexesIfNecessary() 20 | multiSelectionTableView.reloadAllItemsTable() 21 | } 22 | } 23 | 24 | public var allItemsCount: Int { 25 | return allItemsIndexes.count 26 | } 27 | 28 | fileprivate var selectedItemsIndexes: [ItemIndex] = [] 29 | public var selectedItems: [T] { 30 | return selectedItemsIndexes.map { $0.item } 31 | } 32 | 33 | public var selectedItemsCount: Int { 34 | return selectedItemsIndexes.count 35 | } 36 | 37 | private func mapToItemIndexesAndUpdateOriginSelectedIndexesIfNecessary() { 38 | let _selectedItems = selectedItemsIndexes.map{ $0.item } 39 | allItemsIndexes = allItems.enumerated().compactMap { index, item in 40 | if let indexOfItem = _selectedItems.firstIndex(of: item) { 41 | selectedItemsIndexes[indexOfItem].index = index 42 | return nil 43 | } 44 | return ItemIndex(item: item, index: index) 45 | } 46 | } 47 | 48 | public init(multiSelectionTableView: MultiSelectionTableView) { 49 | self.multiSelectionTableView = multiSelectionTableView 50 | } 51 | 52 | fileprivate var cellReuseId = "Cell" 53 | public func register(nib: UINib, for cellReuseIdentifier: String) { 54 | cellReuseId = cellReuseIdentifier 55 | multiSelectionTableView.register(nib: nib, for: cellReuseId) 56 | } 57 | 58 | public func register(anyClass: AnyClass?, for cellReuseIdentifier: String) { 59 | cellReuseId = cellReuseIdentifier 60 | multiSelectionTableView.register(anyClass: anyClass, for: cellReuseId) 61 | } 62 | 63 | public func selectedItem(at index: Int) { 64 | let item = allItemsIndexes.remove(at: index) 65 | selectedItemsIndexes.append(item) 66 | 67 | multiSelectionTableView.addToSelectedItemsTable(at: index) 68 | } 69 | 70 | public func unselectedItem(at index: Int) { 71 | 72 | let item = selectedItemsIndexes.remove(at: index) 73 | 74 | guard let indexToAdd = findIndexToPutBack(item, in: allItemsIndexes) else { 75 | multiSelectionTableView.removeFromSelected(at: index) 76 | return 77 | } 78 | 79 | allItemsIndexes.insert(item, at: indexToAdd) 80 | 81 | multiSelectionTableView.putBackInAllItemsTable(at: indexToAdd, selectedItemAt: index) 82 | 83 | if selectedItemsIndexes.isEmpty { 84 | multiSelectionTableView.displayAllItems() 85 | } 86 | 87 | } 88 | 89 | private func findIndexToPutBack(_ item: ItemIndex, in list: [ItemIndex]) -> Int? { 90 | guard allItems.contains(item.item) else { return nil } 91 | 92 | for (index, iteratedItemIndex) in list.enumerated() { 93 | if iteratedItemIndex.index >= item.index { 94 | return index 95 | } 96 | } 97 | 98 | return item.index 99 | } 100 | 101 | public func cell(for indexPath: IndexPath, inAllItemsTable tableView: UITableView) -> UITableViewCell { 102 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) 103 | let item = allItemsIndexes[indexPath.row] 104 | delegate?.paint(cell, at: indexPath, in: tableView, with: item.item) 105 | 106 | return cell 107 | } 108 | 109 | public func cell(for indexPath: IndexPath, inSelectedItemsTable tableView: UITableView) -> UITableViewCell { 110 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) 111 | let item = selectedItemsIndexes[indexPath.row] 112 | delegate?.paint(cell, at: indexPath, in: tableView, with: item.item) 113 | return cell 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Source/MultiSelectionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectionTableDelegate.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 01/12/16. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol MultiSelectionTableDelegate : class { 11 | 12 | func paint(_ cell: UITableViewCell, at indexPath: IndexPath, in tableView: UITableView, with object: Any) 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Source/MultiSelectionTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectionTableControl.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 29/11/16. 6 | // 7 | 8 | import UIKit 9 | 10 | @IBDesignable 11 | final public class MultiSelectionTableView : UIControl { 12 | 13 | public weak var dataSource: DataSource? 14 | 15 | fileprivate let allItemsTable = UITableView() 16 | fileprivate let seperator = UIView() 17 | fileprivate let selectedItemsTable = UITableView() 18 | 19 | fileprivate var seperatorCenterXConstraint: NSLayoutConstraint! 20 | fileprivate var allItemsTableLeadingConstraint: NSLayoutConstraint! 21 | fileprivate var selectedItemsTableTrailingConstraint: NSLayoutConstraint! 22 | 23 | fileprivate var isSelectingMode = false 24 | @IBInspectable var seperatorWidthOffset: CGFloat = 100 25 | 26 | public var cellAnimator: CellSelectionAnimator = CellSelectionPulseAnimator(pulseColor: .defaultCellPulseColor) 27 | public var cellTransitioner: CellTransitionAnimator = CellMover() 28 | 29 | fileprivate var stateViewContainer = UIView() 30 | 31 | override public func awakeFromNib() { 32 | super.awakeFromNib() 33 | 34 | allItemsTable.backgroundColor = allItemsTableBackgroundColor 35 | allItemsTable.separatorColor = .clear 36 | 37 | blackLine.backgroundColor = seperatorColor 38 | 39 | selectedItemsTable.backgroundColor = selectedItemsTableBackgroundColor 40 | selectedItemsTable.separatorColor = .clear 41 | } 42 | 43 | @IBInspectable 44 | public var controlBackgroundColor: UIColor = .black { 45 | didSet { 46 | backgroundColor = controlBackgroundColor 47 | } 48 | } 49 | 50 | @IBInspectable 51 | public var seperatorColor: UIColor = .black { 52 | didSet { 53 | blackLine.backgroundColor = seperatorColor 54 | } 55 | } 56 | 57 | @IBInspectable 58 | public var allItemsTableBackgroundColor: UIColor = .defaultTableBackground { 59 | didSet { 60 | allItemsTable.backgroundColor = allItemsTableBackgroundColor 61 | } 62 | } 63 | 64 | @IBInspectable 65 | public var selectedItemsTableBackgroundColor: UIColor = .defaultTableBackground { 66 | didSet { 67 | selectedItemsTable.backgroundColor = selectedItemsTableBackgroundColor 68 | } 69 | } 70 | 71 | public var allItemsContentInset: UIEdgeInsets = .zero { 72 | didSet { 73 | allItemsTable.contentInset = allItemsContentInset 74 | allItemsTable.scrollIndicatorInsets = UIEdgeInsets(top: allItemsContentInset.top, 75 | left: 0, 76 | bottom: 0, 77 | right: 0) 78 | } 79 | } 80 | 81 | public var selectedItemsContentInset: UIEdgeInsets = .zero { 82 | didSet { 83 | selectedItemsTable.contentInset = selectedItemsContentInset 84 | selectedItemsTable.scrollIndicatorInsets = UIEdgeInsets(top: selectedItemsContentInset.top, 85 | left: 0, 86 | bottom: 0, 87 | right: 0) 88 | } 89 | } 90 | 91 | @IBInspectable 92 | public var supportsPagination: Bool = false 93 | 94 | //If you want to provide pagination, and don't want the next page of data to be fetch on the 95 | //last item to be displayed, set this var to be the higher than the last item. 96 | //A value of 1 would mean the table would signal the caller that 97 | //it's displaying the second to last row and therefor send a .scrollReachingEnd action. 98 | @IBInspectable 99 | public var paginationNotificationRowIndex: Int = 0 100 | 101 | public var stateView: UIView? { 102 | didSet { 103 | stateViewContainer.subviews.forEach { $0.removeFromSuperview() } 104 | if let view = stateView { 105 | stateViewContainer.addSubview(view) 106 | view.pinToEdges(of: stateViewContainer, top: allItemsContentInset.top) 107 | view.translatesAutoresizingMaskIntoConstraints = false 108 | stateViewContainer.isHidden = false 109 | } else { 110 | stateViewContainer.isHidden = true 111 | } 112 | } 113 | } 114 | 115 | override init(frame: CGRect) { 116 | super.init(frame: frame) 117 | initialize() 118 | } 119 | 120 | required public init?(coder aDecoder: NSCoder) { 121 | super.init(coder: aDecoder) 122 | initialize() 123 | } 124 | 125 | private func initialize() { 126 | backgroundColor = controlBackgroundColor 127 | 128 | buildSeperator() 129 | buildAllItemsTable() 130 | buildSelectedItemsTable() 131 | 132 | buildStateViewContainer() 133 | 134 | displayAllItems() 135 | } 136 | 137 | fileprivate let blackLine = UIView() 138 | private func buildSeperator() { 139 | addSubview(blackLine) 140 | blackLine.backgroundColor = seperatorColor 141 | 142 | blackLine.topAnchor.constraint(equalTo: topAnchor).isActive = true 143 | blackLine.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 144 | seperatorCenterXConstraint = blackLine.centerXAnchor.constraint(equalTo: centerXAnchor) 145 | seperatorCenterXConstraint.isActive = true 146 | 147 | blackLine.widthAnchor.constraint(equalToConstant: 4).isActive = true 148 | blackLine.translatesAutoresizingMaskIntoConstraints = false 149 | 150 | let grayIndicator = UIView() 151 | blackLine.addSubview(grayIndicator) 152 | grayIndicator.layer.cornerRadius = 2 153 | grayIndicator.backgroundColor = .lightGray 154 | 155 | grayIndicator.centerXAnchor.constraint(equalTo: blackLine.centerXAnchor).isActive = true 156 | grayIndicator.centerYAnchor.constraint(equalTo: blackLine.centerYAnchor).isActive = true 157 | grayIndicator.widthAnchor.constraint(equalToConstant: 4).isActive = true 158 | grayIndicator.heightAnchor.constraint(equalToConstant: 50).isActive = true 159 | grayIndicator.translatesAutoresizingMaskIntoConstraints = false 160 | 161 | addSubview(seperator) 162 | seperator.backgroundColor = .clear 163 | 164 | seperator.topAnchor.constraint(equalTo: topAnchor).isActive = true 165 | seperator.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 166 | seperator.leadingAnchor.constraint(equalTo: blackLine.leadingAnchor, constant: -10).isActive = true 167 | seperator.trailingAnchor.constraint(equalTo: blackLine.trailingAnchor, constant: 10).isActive = true 168 | seperator.translatesAutoresizingMaskIntoConstraints = false 169 | 170 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(swipped(gesture:))) 171 | seperator.addGestureRecognizer(panGesture) 172 | } 173 | 174 | private func buildAllItemsTable() { 175 | addSubview(allItemsTable) 176 | configure(allItemsTable) 177 | 178 | allItemsTableLeadingConstraint = allItemsTable.leadingAnchor.constraint(equalTo: leadingAnchor) 179 | allItemsTableLeadingConstraint.isActive = true 180 | allItemsTable.topAnchor.constraint(equalTo: topAnchor).isActive = true 181 | allItemsTable.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 182 | allItemsTable.trailingAnchor.constraint(equalTo: seperator.leadingAnchor, constant: 5).isActive = true 183 | 184 | allItemsTable.translatesAutoresizingMaskIntoConstraints = false 185 | } 186 | 187 | private func buildSelectedItemsTable() { 188 | addSubview(selectedItemsTable) 189 | configure(selectedItemsTable) 190 | 191 | selectedItemsTable.tableHeaderView = TableViewHeader(title: "SELECTED") 192 | 193 | selectedItemsTable.leadingAnchor.constraint(equalTo: seperator.trailingAnchor, constant: -5).isActive = true 194 | selectedItemsTable.topAnchor.constraint(equalTo: topAnchor).isActive = true 195 | selectedItemsTable.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 196 | selectedItemsTableTrailingConstraint = selectedItemsTable.trailingAnchor.constraint(equalTo: trailingAnchor) 197 | selectedItemsTableTrailingConstraint.isActive = true 198 | selectedItemsTable.translatesAutoresizingMaskIntoConstraints = false 199 | } 200 | 201 | private func configure(_ tableView: UITableView) { 202 | tableView.tableFooterView = UIView() 203 | 204 | tableView.backgroundView = nil 205 | tableView.backgroundColor = self.allItemsTableBackgroundColor 206 | tableView.separatorColor = .clear 207 | tableView.keyboardDismissMode = .interactive 208 | 209 | tableView.delegate = self 210 | tableView.dataSource = self 211 | 212 | tableView.estimatedRowHeight = 100 213 | tableView.rowHeight = UITableView.automaticDimension 214 | 215 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapTable(gestureRecognizer:))) 216 | tableView.addGestureRecognizer(tapGestureRecognizer) 217 | 218 | } 219 | 220 | private func buildStateViewContainer() { 221 | addSubview(stateViewContainer) 222 | stateViewContainer.isHidden = true 223 | stateViewContainer.pinToEdges(of: allItemsTable) 224 | stateViewContainer.translatesAutoresizingMaskIntoConstraints = false 225 | } 226 | 227 | //Used this method instead of `tableView(tableView:didSelectRowAt:)` because we need the 228 | //location on the cell where the cell was tapped. the delegate method won't give us that. 229 | @objc private func didTapTable(gestureRecognizer: UITapGestureRecognizer) { 230 | guard let tableView = gestureRecognizer.view as? UITableView else { return } 231 | 232 | let location = gestureRecognizer.location(in: tableView) 233 | guard let indexPath = tableView.indexPathForRow(at: location), 234 | let cell = tableView.cellForRow(at: indexPath) 235 | else { 236 | return 237 | } 238 | 239 | let origin = tableView.convert(location, to: cell.contentView) 240 | 241 | let actionAnimation: () -> () 242 | if tableView == selectedItemsTable { 243 | actionAnimation = { [weak self] in 244 | self?.dataSource?.unselectedItem(at: indexPath.row) 245 | } 246 | sendActions(for: .itemUnselected) 247 | } else { 248 | actionAnimation = { [weak self] in 249 | self?.dataSource?.selectedItem(at: indexPath.row) 250 | } 251 | sendActions(for: .itemSelected) 252 | } 253 | 254 | cellAnimator.animate(cell, startingAt: origin) { 255 | actionAnimation() 256 | } 257 | } 258 | 259 | @objc private func swipped(gesture: UIPanGestureRecognizer) { 260 | if gesture.translation(in: seperator).x > 0 { 261 | displayAllItems() 262 | } else { 263 | displaySelectedItems() 264 | } 265 | } 266 | 267 | func displayAllItems() { 268 | if !isSelectingMode { 269 | seperatorCenterXConstraint.constant = seperatorWidthOffset 270 | 271 | allItemsTableLeadingConstraint.constant = 0 272 | selectedItemsTableTrailingConstraint.constant += seperatorCenterXConstraint.constant * 2 273 | 274 | animateStateTransition() 275 | isSelectingMode = true 276 | } 277 | } 278 | 279 | fileprivate func displaySelectedItems() { 280 | if isSelectingMode { 281 | seperatorCenterXConstraint.constant = -seperatorWidthOffset 282 | 283 | allItemsTableLeadingConstraint.constant += seperatorCenterXConstraint.constant * 2 284 | selectedItemsTableTrailingConstraint.constant = 5 285 | 286 | animateStateTransition() 287 | isSelectingMode = false 288 | } 289 | } 290 | 291 | private func animateStateTransition() { 292 | UIView.animate(withDuration: 0.3, 293 | delay: 0, 294 | usingSpringWithDamping: 1, 295 | initialSpringVelocity: 1, 296 | options: .curveEaseInOut, 297 | animations: { 298 | self.layoutIfNeeded() 299 | }, 300 | completion: nil) 301 | } 302 | 303 | fileprivate var cellReuseId = "Cell" 304 | func register(nib: UINib, for cellReuseIdentifier: String) { 305 | cellReuseId = cellReuseIdentifier 306 | allItemsTable.register(nib, forCellReuseIdentifier: cellReuseId) 307 | selectedItemsTable.register(nib, forCellReuseIdentifier: cellReuseId) 308 | } 309 | 310 | func register(anyClass: AnyClass?, for cellReuseIdentifier: String) { 311 | cellReuseId = cellReuseIdentifier 312 | allItemsTable.register(anyClass, forCellReuseIdentifier: cellReuseId) 313 | selectedItemsTable.register(anyClass, forCellReuseIdentifier: cellReuseId) 314 | } 315 | 316 | func reloadAllItemsTable() { 317 | allItemsTable.reloadData() 318 | } 319 | 320 | func putBackInAllItemsTable(at index: Int, selectedItemAt selectedItemIndex: Int) { 321 | 322 | let indexPathToRemove = IndexPath(item: selectedItemIndex, section: 0) 323 | let newIndexPath = IndexPath(item: index, section: 0) 324 | 325 | cellTransitioner.unselectionTransition(in: self, 326 | fromTableView: selectedItemsTable, 327 | fromIndexPath: indexPathToRemove, 328 | toTableView: allItemsTable, 329 | toIndexPath: newIndexPath) 330 | } 331 | 332 | func removeFromSelected(at index: Int) { 333 | let indexPath = IndexPath(item: index, section: 0) 334 | selectedItemsTable.deleteRows(at: [indexPath], with: .right) 335 | } 336 | 337 | func addToSelectedItemsTable(at index: Int) { 338 | let count = selectedItemsTable.numberOfRows(inSection: 0) 339 | let newIndexPath = IndexPath(item: count, section: 0) 340 | let indexPath = IndexPath(item: index, section: 0) 341 | cellTransitioner.selectionTransition(in: self, 342 | fromTableView: allItemsTable, 343 | fromIndexPath: indexPath, 344 | toTableView: selectedItemsTable, 345 | toIndexPath: newIndexPath) 346 | } 347 | } 348 | 349 | extension MultiSelectionTableView : UITableViewDataSource { 350 | 351 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 352 | guard let dataSource = dataSource else { return 0 } 353 | 354 | if tableView == allItemsTable { 355 | return dataSource.allItemsCount 356 | } else { 357 | return dataSource.selectedItemsCount 358 | } 359 | } 360 | 361 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 362 | let cell: UITableViewCell? 363 | 364 | if tableView == allItemsTable { 365 | cell = dataSource?.cell(for: indexPath, inAllItemsTable: tableView) 366 | } else { 367 | cell = dataSource?.cell(for: indexPath, inSelectedItemsTable: tableView) 368 | } 369 | return cell ?? tableView.dequeueReusableCell(withIdentifier: cellReuseId) ?? UITableViewCell() 370 | } 371 | } 372 | 373 | extension MultiSelectionTableView : UITableViewDelegate { 374 | 375 | 376 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 377 | guard supportsPagination, 378 | let dataSource = dataSource 379 | else { return } 380 | 381 | if tableView == allItemsTable { 382 | if indexPath.row == dataSource.allItemsCount - 1 - paginationNotificationRowIndex { 383 | sendActions(for: .scrollReachingEnd) 384 | } 385 | } 386 | } 387 | } 388 | 389 | fileprivate extension UIColor { 390 | static var defaultTableBackground: UIColor { 391 | UIColor(red: 25/255, green: 25/255, blue: 25/255, alpha: 1) 392 | } 393 | 394 | static var defaultCellPulseColor: UIColor { 395 | UIColor(red: 121/255, green: 2/255, blue: 188/255, alpha: 0.3) 396 | } 397 | } 398 | 399 | fileprivate extension UIView { 400 | 401 | typealias EdgesMargin = (top: CGFloat?, right: CGFloat?, bottom: CGFloat?, left: CGFloat?) 402 | 403 | func pinToEdges( 404 | of view: UIView, 405 | top: CGFloat = 0, 406 | left: CGFloat = 0, 407 | bottom: CGFloat = 0, 408 | right: CGFloat = 0 409 | ) { 410 | let boundAttributes: [NSLayoutConstraint.Attribute] = [.top, .right, .bottom, .left] 411 | let constants: [CGFloat] = [top, bottom, right, left] 412 | 413 | for i in 0..<4 { 414 | anchor(view, with: boundAttributes[i], withConstant: constants[i]) 415 | } 416 | } 417 | 418 | private func anchor( 419 | _ view : UIView, 420 | with attribute: NSLayoutConstraint.Attribute, 421 | withConstant constant: CGFloat = 0 422 | ) { 423 | 424 | view.topAnchor.constraint(equalTo: topAnchor, constant: constant).isActive = true 425 | view.leftAnchor.constraint(equalTo: leftAnchor, constant: constant).isActive = true 426 | view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: constant).isActive = true 427 | view.rightAnchor.constraint(equalTo: rightAnchor, constant: constant).isActive = true 428 | 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /Source/TableViewHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewHeader.swift 3 | // MultiSelectionTableView 4 | // 5 | // Created by Nuno Gonçalves on 08/12/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | final class TableViewHeader : UIView { 12 | 13 | let title: String 14 | 15 | init(title: String) { 16 | self.title = title 17 | 18 | super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 30)) 19 | 20 | let label = UILabel() 21 | addSubview(label) 22 | label.text = title 23 | label.textColor = .gray 24 | label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 25 | label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true 26 | label.translatesAutoresizingMaskIntoConstraints = false 27 | } 28 | 29 | @available(*, unavailable) 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Source/UIControlEvents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControlEvents.swift 3 | // Pods 4 | // 5 | // Created by Nuno Gonçalves on 15/12/16. 6 | // 7 | // 8 | 9 | public extension UIControl.Event { 10 | 11 | static var itemSelected: UIControl.Event { 12 | return UIControl.Event(rawValue: 1 << 30) 13 | } 14 | 15 | static var itemUnselected: UIControl.Event { 16 | return UIControl.Event(rawValue: 1 << 31) 17 | } 18 | 19 | static var scrollReachingEnd: UIControl.Event { 20 | return UIControl.Event(rawValue: 1 << 29) 21 | } 22 | 23 | } 24 | --------------------------------------------------------------------------------