├── .gitignore ├── Example ├── AppDelegate.swift ├── BasicViewController.swift ├── CustomCellsViewController.swift ├── EffectsViewController.swift ├── GroupedViewController.swift ├── Info.plist ├── LongListViewController.swift ├── NonMovableViewController.swift └── RootViewController.swift ├── LICENSE ├── Package.swift ├── README.md ├── Resources └── demo.gif ├── Source ├── ReorderController+AutoScroll.swift ├── ReorderController+DestinationRow.swift ├── ReorderController+GestureRecognizer.swift ├── ReorderController+SnapshotView.swift ├── ReorderController.swift └── UITableView+Reorder.swift ├── SwiftReorder.podspec ├── SwiftReorder.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── SwiftReorder.xcscheme └── SwiftReorder ├── Info.plist └── SwiftReorder.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | .idea/ 16 | 17 | ## Other 18 | *.moved-aside 19 | *.xccheckout 20 | *.xcscmblueprint 21 | *.DS_Store 22 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? 29 | 30 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 31 | window = UIWindow(frame: UIScreen.main.bounds) 32 | window?.makeKeyAndVisible() 33 | 34 | let navController = UINavigationController() 35 | let viewController = RootViewController() 36 | navController.pushViewController(viewController, animated: false) 37 | window?.rootViewController = navController 38 | 39 | return true 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Example/BasicViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class BasicViewController: UITableViewController { 27 | 28 | var items = (1...10).map { "Item \($0)" } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | init() { 35 | super.init(style: .plain) 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | title = "Basic" 42 | 43 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 44 | tableView.allowsSelection = false 45 | tableView.reorder.delegate = self 46 | } 47 | 48 | } 49 | 50 | extension BasicViewController { 51 | 52 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return items.count 54 | } 55 | 56 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 58 | return spacer 59 | } 60 | 61 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 62 | cell.textLabel?.text = items[indexPath.row] 63 | 64 | return cell 65 | } 66 | 67 | } 68 | 69 | extension BasicViewController: TableViewReorderDelegate { 70 | 71 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 72 | let item = items[sourceIndexPath.row] 73 | items.remove(at: sourceIndexPath.row) 74 | items.insert(item, at: destinationIndexPath.row) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Example/CustomCellsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class CustomCell: UITableViewCell { 27 | 28 | let titleLabel = UILabel() 29 | 30 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 31 | super.init(style: style, reuseIdentifier: reuseIdentifier) 32 | 33 | contentView.addSubview(titleLabel) 34 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 35 | 36 | NSLayoutConstraint.activate([ 37 | titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), 38 | titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), 39 | titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16), 40 | ]) 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { fatalError() } 44 | 45 | } 46 | 47 | class CustomCellsViewController: UITableViewController { 48 | 49 | var items = (1...22).map { "Item \($0)" } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | 54 | title = "Custom Cells" 55 | 56 | tableView.register(CustomCell.self, forCellReuseIdentifier: "cell") 57 | tableView.reorder.delegate = self 58 | } 59 | 60 | } 61 | 62 | extension CustomCellsViewController { 63 | 64 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 65 | return items.count 66 | } 67 | 68 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 69 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 70 | return spacer 71 | } 72 | 73 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell 74 | cell.titleLabel.text = items[indexPath.row] 75 | 76 | return cell 77 | } 78 | } 79 | 80 | extension CustomCellsViewController: TableViewReorderDelegate { 81 | 82 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 83 | let item = items[sourceIndexPath.row] 84 | items.remove(at: sourceIndexPath.row) 85 | items.insert(item, at: destinationIndexPath.row) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Example/EffectsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class EffectsViewController: UITableViewController { 27 | 28 | var items = (1...5).map { "Item \($0)" } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | init() { 35 | super.init(style: .grouped) 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | title = "Effects" 42 | 43 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 44 | tableView.allowsSelection = false 45 | 46 | tableView.reorder.delegate = self 47 | tableView.reorder.cellOpacity = 0.7 48 | tableView.reorder.cellScale = 1.05 49 | tableView.reorder.shadowOpacity = 0.5 50 | tableView.reorder.shadowRadius = 20 51 | tableView.reorder.shadowOffset = CGSize(width: 0, height: 10) 52 | } 53 | 54 | } 55 | 56 | extension EffectsViewController { 57 | 58 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 59 | return items.count 60 | } 61 | 62 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 63 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 64 | return spacer 65 | } 66 | 67 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 68 | cell.textLabel?.text = items[indexPath.row] 69 | 70 | return cell 71 | } 72 | 73 | } 74 | 75 | extension EffectsViewController: TableViewReorderDelegate { 76 | 77 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 78 | let item = items[sourceIndexPath.row] 79 | items.remove(at: sourceIndexPath.row) 80 | items.insert(item, at: destinationIndexPath.row) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Example/GroupedViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class GroupedViewController: UITableViewController { 27 | 28 | var sectionedItems: [(title: String, items: [String])] = [ 29 | ("Foo", (1...5).map { "Foo \($0)" }), 30 | ("Bar", (1...5).map { "Bar \($0)" }) 31 | ] 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | init() { 38 | super.init(style: .grouped) 39 | } 40 | 41 | override func viewDidLoad() { 42 | super.viewDidLoad() 43 | 44 | title = "Grouped" 45 | 46 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 47 | tableView.allowsSelection = false 48 | tableView.reorder.delegate = self 49 | } 50 | 51 | } 52 | 53 | extension GroupedViewController { 54 | 55 | override func numberOfSections(in tableView: UITableView) -> Int { 56 | return sectionedItems.count 57 | } 58 | 59 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 60 | return sectionedItems[section].items.count 61 | } 62 | 63 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 64 | return sectionedItems[section].title 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 69 | return spacer 70 | } 71 | 72 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 73 | cell.backgroundColor = .white 74 | cell.textLabel?.text = sectionedItems[indexPath.section].items[indexPath.row] 75 | 76 | return cell 77 | } 78 | 79 | } 80 | 81 | extension GroupedViewController: TableViewReorderDelegate { 82 | 83 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 84 | let item = sectionedItems[sourceIndexPath.section].items[sourceIndexPath.row] 85 | sectionedItems[sourceIndexPath.section].items.remove(at: sourceIndexPath.row) 86 | sectionedItems[destinationIndexPath.section].items.insert(item, at: destinationIndexPath.row) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/LongListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class LongListViewController: UITableViewController { 27 | 28 | var items = (1...50).map { "Item \($0)" } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | init() { 35 | super.init(style: .plain) 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | title = "Long List" 42 | 43 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 44 | tableView.allowsSelection = false 45 | tableView.reorder.delegate = self 46 | } 47 | 48 | } 49 | 50 | extension LongListViewController { 51 | 52 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return items.count 54 | } 55 | 56 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 58 | return spacer 59 | } 60 | 61 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 62 | cell.textLabel?.text = items[indexPath.row] 63 | 64 | return cell 65 | } 66 | 67 | } 68 | 69 | extension LongListViewController: TableViewReorderDelegate { 70 | 71 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 72 | let item = items[sourceIndexPath.row] 73 | items.remove(at: sourceIndexPath.row) 74 | items.insert(item, at: destinationIndexPath.row) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Example/NonMovableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | import SwiftReorder 25 | 26 | class NonMovableViewController: UITableViewController { 27 | 28 | var sectionedItems: [(title: String, items: [String])] = [ 29 | ("Movable", (1...3).map { "Spam \($0)" }), 30 | ("Not Movable", (1...3).map { "Ham \($0)" }), 31 | ("Movable", (1...3).map { "Eggs \($0)" }) 32 | ] 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | init() { 39 | super.init(style: .grouped) 40 | } 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | title = "Non-Movable" 46 | 47 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 48 | tableView.allowsSelection = false 49 | tableView.reorder.delegate = self 50 | } 51 | 52 | } 53 | 54 | extension NonMovableViewController { 55 | 56 | override func numberOfSections(in tableView: UITableView) -> Int { 57 | return sectionedItems.count 58 | } 59 | 60 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 61 | return sectionedItems[section].items.count 62 | } 63 | 64 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 65 | return sectionedItems[section].title 66 | } 67 | 68 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 69 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 70 | return spacer 71 | } 72 | 73 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 74 | cell.backgroundColor = .white 75 | cell.textLabel?.text = sectionedItems[indexPath.section].items[indexPath.row] 76 | 77 | return cell 78 | } 79 | 80 | } 81 | 82 | extension NonMovableViewController: TableViewReorderDelegate { 83 | 84 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 85 | let item = sectionedItems[sourceIndexPath.section].items[sourceIndexPath.row] 86 | sectionedItems[sourceIndexPath.section].items.remove(at: sourceIndexPath.row) 87 | sectionedItems[destinationIndexPath.section].items.insert(item, at: destinationIndexPath.row) 88 | } 89 | 90 | func tableView(_ tableView: UITableView, canReorderRowAt indexPath: IndexPath) -> Bool { 91 | return indexPath.section != 1 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Example/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | class RootViewController: UITableViewController { 26 | 27 | enum Row: Int { 28 | case basic = 0 29 | case grouped 30 | case longList 31 | case nonMovable 32 | case effects 33 | case customCells 34 | 35 | case count 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | init() { 43 | super.init(style: .grouped) 44 | } 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | title = "SwiftReorder" 50 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 51 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") 52 | } 53 | 54 | } 55 | 56 | extension RootViewController { 57 | 58 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 59 | return Row.count.rawValue 60 | } 61 | 62 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 63 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 64 | 65 | switch Row(rawValue: indexPath.row) ?? .count { 66 | case .basic: 67 | cell.textLabel?.text = "Basic" 68 | case .grouped: 69 | cell.textLabel?.text = "Grouped" 70 | case .longList: 71 | cell.textLabel?.text = "Long List" 72 | case .nonMovable: 73 | cell.textLabel?.text = "Non-Movable" 74 | case .effects: 75 | cell.textLabel?.text = "Effects" 76 | case .customCells: 77 | cell.textLabel?.text = "Custom Cells" 78 | case .count: 79 | break 80 | } 81 | 82 | cell.accessoryType = .disclosureIndicator 83 | return cell 84 | } 85 | 86 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 87 | switch Row(rawValue: indexPath.row) ?? .count { 88 | case .basic: 89 | navigationController?.pushViewController(BasicViewController(), animated: true) 90 | case .grouped: 91 | navigationController?.pushViewController(GroupedViewController(), animated: true) 92 | case .longList: 93 | navigationController?.pushViewController(LongListViewController(), animated: true) 94 | case .nonMovable: 95 | navigationController?.pushViewController(NonMovableViewController(), animated: true) 96 | case .effects: 97 | navigationController?.pushViewController(EffectsViewController(), animated: true) 98 | case .customCells: 99 | navigationController?.pushViewController(CustomCellsViewController(), animated: true) 100 | case .count: 101 | break 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Adam Shin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftReorder", 7 | platforms: [.iOS(.v9)], 8 | products: [ 9 | .library(name: "SwiftReorder", targets: ["SwiftReorder"]) 10 | ], 11 | targets: [ 12 | .target(name: "SwiftReorder", path: "Source") 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftReorder 2 | 3 | **NOTE: Some users have encountered compatibility issues when using this library with recent versions of iOS. For apps targeting iOS 11 and up, it's recommended to use the built-in [UITableView drag and drop API](https://developer.apple.com/documentation/uikit/views_and_controls/table_views/supporting_drag_and_drop_in_table_views) instead.** 4 | 5 | SwiftReorder is a UITableView extension that lets you add long-press drag-and-drop reordering to any table view. It's robust, lightweight, and fully customizable. 6 | 7 | ![Demo](Resources/demo.gif) 8 | 9 | ## Features 10 | 11 | - Smooth animations 12 | - Automatic edge scrolling 13 | - Works with multiple table sections 14 | - Customizable shadow, scaling, and transparency effects 15 | 16 | ## Installation 17 | 18 | ### CocoaPods 19 | 20 | To integrate SwiftReorder into your Xcode project using CocoaPods, specify it in your `Podfile`: 21 | 22 | ```ruby 23 | pod 'SwiftReorder', '~> 7.2' 24 | ``` 25 | 26 | ### Carthage 27 | 28 | To integrate SwiftReorder into your Xcode project using Carthage, specify it in your `Cartfile`: 29 | 30 | ``` 31 | github "adamshin/SwiftReorder" ~> 7.2 32 | ``` 33 | 34 | Remember to [add SwiftReorder to your Carthage build phase](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos): 35 | 36 | ``` 37 | $(SRCROOT)/Carthage/Build/iOS/SwiftReorder.framework 38 | ``` 39 | 40 | and 41 | 42 | ``` 43 | $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftReorder.framework 44 | ``` 45 | 46 | ### Manually 47 | 48 | You can integrate SwiftReorder into your project manually by copying the contents of the `Source` folder into your project. 49 | 50 | ## Usage 51 | 52 | ### Setup 53 | 54 | * Add the following line to your table view setup. 55 | ```swift 56 | override func viewDidLoad() { 57 | // ... 58 | tableView.reorder.delegate = self 59 | } 60 | ``` 61 | * Add this code to the beginning of your `tableView(_:cellForRowAt:)`. 62 | ```swift 63 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 64 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 65 | return spacer 66 | } 67 | // ... 68 | } 69 | ``` 70 | * Implement the `tableView(_:reorderRowAt:to:)` delegate method, and others as necessary. 71 | ```swift 72 | extension MyViewController: TableViewReorderDelegate { 73 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 74 | // Update data model 75 | } 76 | } 77 | ``` 78 | This method is analogous to the `UITableViewDataSource` method `tableView(_:moveRowAt:to:)`. However, it may be called multiple times in the course of one drag-and-drop action. 79 | 80 | ### Customization 81 | SwiftReorder exposes several properties for adjusting the style of the reordering effect. For example, you can add a scaling effect to the selected cell: 82 | ```swift 83 | tableView.reorder.cellScale = 1.05 84 | ``` 85 | Or adjust the shadow: 86 | ```swift 87 | tableView.reorder.shadowOpacity = 0.5 88 | tableView.reorder.shadowRadius = 20 89 | ``` 90 | -------------------------------------------------------------------------------- /Resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamshin/SwiftReorder/9553a0d805c68d2bdd1bc6e5385cf1d5e9c44a4c/Resources/demo.gif -------------------------------------------------------------------------------- /Source/ReorderController+AutoScroll.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | private let autoScrollThreshold: CGFloat = 30 26 | private let autoScrollMinVelocity: CGFloat = 60 27 | private let autoScrollMaxVelocity: CGFloat = 280 28 | 29 | private func mapValue(_ value: CGFloat, inRangeWithMin minA: CGFloat, max maxA: CGFloat, toRangeWithMin minB: CGFloat, max maxB: CGFloat) -> CGFloat { 30 | return (value - minA) * (maxB - minB) / (maxA - minA) + minB 31 | } 32 | 33 | extension ReorderController { 34 | 35 | func autoScrollVelocity() -> CGFloat { 36 | guard let tableView = tableView, let snapshotView = snapshotView else { return 0 } 37 | 38 | guard autoScrollEnabled else { return 0 } 39 | 40 | let safeAreaFrame: CGRect 41 | if #available(iOS 11, *) { 42 | safeAreaFrame = tableView.frame.inset(by: tableView.adjustedContentInset) 43 | } else { 44 | safeAreaFrame = tableView.frame.inset(by: tableView.scrollIndicatorInsets) 45 | } 46 | 47 | let distanceToTop = max(snapshotView.frame.minY - safeAreaFrame.minY, 0) 48 | let distanceToBottom = max(safeAreaFrame.maxY - snapshotView.frame.maxY, 0) 49 | 50 | if distanceToTop < autoScrollThreshold { 51 | return mapValue(distanceToTop, inRangeWithMin: autoScrollThreshold, max: 0, toRangeWithMin: -autoScrollMinVelocity, max: -autoScrollMaxVelocity) 52 | } 53 | if distanceToBottom < autoScrollThreshold { 54 | return mapValue(distanceToBottom, inRangeWithMin: autoScrollThreshold, max: 0, toRangeWithMin: autoScrollMinVelocity, max: autoScrollMaxVelocity) 55 | } 56 | return 0 57 | } 58 | 59 | func activateAutoScrollDisplayLink() { 60 | autoScrollDisplayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLinkUpdate)) 61 | autoScrollDisplayLink?.add(to: .main, forMode: .default) 62 | lastAutoScrollTimeStamp = nil 63 | } 64 | 65 | func clearAutoScrollDisplayLink() { 66 | autoScrollDisplayLink?.invalidate() 67 | autoScrollDisplayLink = nil 68 | lastAutoScrollTimeStamp = nil 69 | } 70 | 71 | @objc func handleDisplayLinkUpdate(_ displayLink: CADisplayLink) { 72 | guard let tableView = tableView else { return } 73 | 74 | if let lastAutoScrollTimeStamp = lastAutoScrollTimeStamp { 75 | let scrollVelocity = autoScrollVelocity() 76 | 77 | if scrollVelocity != 0 { 78 | let elapsedTime = displayLink.timestamp - lastAutoScrollTimeStamp 79 | let scrollDelta = CGFloat(elapsedTime) * scrollVelocity 80 | 81 | let contentOffset = tableView.contentOffset 82 | tableView.contentOffset = CGPoint(x: contentOffset.x, y: contentOffset.y + CGFloat(scrollDelta)) 83 | 84 | let contentInset: UIEdgeInsets 85 | if #available(iOS 11, *) { 86 | contentInset = tableView.adjustedContentInset 87 | } else { 88 | contentInset = tableView.contentInset 89 | } 90 | 91 | let minContentOffset = -contentInset.top 92 | let maxContentOffset = tableView.contentSize.height - tableView.bounds.height + contentInset.bottom 93 | 94 | tableView.contentOffset.y = min(tableView.contentOffset.y, maxContentOffset) 95 | tableView.contentOffset.y = max(tableView.contentOffset.y, minContentOffset) 96 | 97 | updateSnapshotViewPosition() 98 | updateDestinationRow() 99 | } 100 | } 101 | lastAutoScrollTimeStamp = displayLink.timestamp 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Source/ReorderController+DestinationRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | extension CGRect { 26 | 27 | init(center: CGPoint, size: CGSize) { 28 | self.init(x: center.x - (size.width / 2), y: center.y - (size.height / 2), width: size.width, height: size.height) 29 | } 30 | 31 | } 32 | 33 | extension ReorderController { 34 | 35 | func updateDestinationRow() { 36 | guard case .reordering(let context) = reorderState, 37 | let tableView = tableView, 38 | let proposedNewDestinationRow = proposedNewDestinationRow(), 39 | let newDestinationRow = delegate?.tableView(tableView, targetIndexPathForReorderFromRowAt: context.destinationRow, to: proposedNewDestinationRow), 40 | newDestinationRow != context.destinationRow 41 | else { return } 42 | 43 | var newContext = context 44 | newContext.destinationRow = newDestinationRow 45 | reorderState = .reordering(context: newContext) 46 | 47 | delegate?.tableView(tableView, reorderRowAt: context.destinationRow, to: newContext.destinationRow) 48 | 49 | tableView.beginUpdates() 50 | tableView.deleteRows(at: [context.destinationRow], with: .fade) 51 | tableView.insertRows(at: [newContext.destinationRow], with: .fade) 52 | tableView.endUpdates() 53 | } 54 | 55 | func proposedNewDestinationRow() -> IndexPath? { 56 | guard case .reordering(let context) = reorderState, 57 | let tableView = tableView, 58 | let superview = tableView.superview, 59 | let snapshotView = snapshotView 60 | else { return nil } 61 | 62 | let snapshotFrameInSuperview = CGRect(center: snapshotView.center, size: snapshotView.bounds.size) 63 | let snapshotFrame = superview.convert(snapshotFrameInSuperview, to: tableView) 64 | 65 | let visibleCells = tableView.visibleCells.filter { 66 | // Workaround for an iOS 11 bug. 67 | 68 | // When adding a row using UITableView.insertRows(...), if the new 69 | // row's frame will be partially or fully outside the table view's 70 | // bounds, and the new row is not the first row in the table view, 71 | // it's inserted without animation. 72 | 73 | let cellOverlapsTopBounds = $0.frame.minY < tableView.bounds.minY + 5 74 | let cellIsFirstCell = tableView.indexPath(for: $0) == IndexPath(row: 0, section: 0) 75 | 76 | return !cellOverlapsTopBounds || cellIsFirstCell 77 | } 78 | 79 | let rowSnapDistances = visibleCells.map { cell -> (path: IndexPath, distance: CGFloat) in 80 | let path = tableView.indexPath(for: cell) ?? IndexPath(row: 0, section: 0) 81 | 82 | if context.destinationRow.compare(path) == .orderedAscending { 83 | return (path, abs(snapshotFrame.maxY - cell.frame.maxY)) 84 | } else { 85 | return (path, abs(snapshotFrame.minY - cell.frame.minY)) 86 | } 87 | } 88 | 89 | let sectionIndexes = 0.. (path: IndexPath, distance: CGFloat)? in 91 | let rowsInSection = tableView.numberOfRows(inSection: section) 92 | 93 | if section > context.destinationRow.section { 94 | let rect: CGRect 95 | if rowsInSection == 0 { 96 | rect = rectForEmptySection(section) 97 | } else { 98 | rect = tableView.rectForRow(at: IndexPath(row: 0, section: section)) 99 | } 100 | 101 | let path = IndexPath(row: 0, section: section) 102 | return (path, abs(snapshotFrame.maxY - rect.minY)) 103 | 104 | } else if section < context.destinationRow.section { 105 | let rect: CGRect 106 | if rowsInSection == 0 { 107 | rect = rectForEmptySection(section) 108 | } else { 109 | rect = tableView.rectForRow(at: IndexPath(row: rowsInSection - 1, section: section)) 110 | } 111 | 112 | let path = IndexPath(row: rowsInSection, section: section) 113 | return (path, abs(snapshotFrame.minY - rect.maxY)) 114 | 115 | } else { 116 | return nil 117 | } 118 | } 119 | 120 | let snapDistances = rowSnapDistances + sectionSnapDistances 121 | return snapDistances.min(by: { $0.distance < $1.distance })?.path 122 | } 123 | 124 | func rectForEmptySection(_ section: Int) -> CGRect { 125 | guard let tableView = tableView else { return .zero } 126 | 127 | let sectionRect = tableView.rectForHeader(inSection: section) 128 | return sectionRect.inset(by: UIEdgeInsets(top: sectionRect.height, left: 0, bottom: 0, right: 0)) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /Source/ReorderController+GestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | extension ReorderController { 26 | 27 | @objc internal func handleReorderGesture(_ gestureRecognizer: UIGestureRecognizer) { 28 | guard let superview = tableView?.superview else { return } 29 | 30 | let touchPosition = gestureRecognizer.location(in: superview) 31 | 32 | switch gestureRecognizer.state { 33 | case .began: 34 | beginReorder(touchPosition: touchPosition) 35 | 36 | case .changed: 37 | updateReorder(touchPosition: touchPosition) 38 | 39 | case .ended, .cancelled, .failed, .possible: 40 | endReorder() 41 | @unknown default: break 42 | } 43 | } 44 | 45 | } 46 | 47 | extension ReorderController: UIGestureRecognizerDelegate { 48 | 49 | public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 50 | guard let tableView = tableView else { return false } 51 | 52 | let gestureLocation = gestureRecognizer.location(in: tableView) 53 | guard let indexPath = tableView.indexPathForRow(at: gestureLocation) else { return false } 54 | 55 | return delegate?.tableView(tableView, canReorderRowAt: indexPath) ?? true 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Source/ReorderController+SnapshotView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | extension ReorderController { 26 | 27 | func createSnapshotViewForCell(at indexPath: IndexPath) { 28 | guard let tableView = tableView, let superview = tableView.superview else { return } 29 | 30 | removeSnapshotView() 31 | tableView.reloadRows(at: [indexPath], with: .none) 32 | 33 | guard let cell = tableView.cellForRow(at: indexPath) else { return } 34 | let cellFrame = tableView.convert(cell.frame, to: superview) 35 | 36 | UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0) 37 | cell.layer.render(in: UIGraphicsGetCurrentContext()!) 38 | let cellImage = UIGraphicsGetImageFromCurrentImageContext() 39 | UIGraphicsEndImageContext() 40 | 41 | let view = UIImageView(image: cellImage) 42 | view.frame = cellFrame 43 | 44 | view.layer.masksToBounds = false 45 | view.layer.opacity = Float(cellOpacity) 46 | view.layer.transform = CATransform3DMakeScale(cellScale, cellScale, 1) 47 | 48 | view.layer.shadowColor = shadowColor.cgColor 49 | view.layer.shadowOpacity = Float(shadowOpacity) 50 | view.layer.shadowRadius = shadowRadius 51 | view.layer.shadowOffset = shadowOffset 52 | 53 | superview.addSubview(view) 54 | snapshotView = view 55 | } 56 | 57 | func removeSnapshotView() { 58 | snapshotView?.removeFromSuperview() 59 | snapshotView = nil 60 | } 61 | 62 | func updateSnapshotViewPosition() { 63 | guard case .reordering(let context) = reorderState, let tableView = tableView else { return } 64 | 65 | var newCenterY = context.touchPosition.y + context.snapshotOffset 66 | 67 | let safeAreaFrame: CGRect 68 | if #available(iOS 11, *) { 69 | safeAreaFrame = tableView.frame.inset(by: tableView.safeAreaInsets) 70 | } else { 71 | safeAreaFrame = tableView.frame.inset(by: tableView.scrollIndicatorInsets) 72 | } 73 | 74 | newCenterY = min(newCenterY, safeAreaFrame.maxY) 75 | newCenterY = max(newCenterY, safeAreaFrame.minY) 76 | 77 | snapshotView?.center.y = newCenterY 78 | } 79 | 80 | func animateSnapshotViewIn() { 81 | guard let snapshotView = snapshotView else { return } 82 | 83 | let opacityAnimation = CABasicAnimation(keyPath: "opacity") 84 | opacityAnimation.fromValue = 1 85 | opacityAnimation.toValue = cellOpacity 86 | opacityAnimation.duration = animationDuration 87 | 88 | let shadowAnimation = CABasicAnimation(keyPath: "shadowOpacity") 89 | shadowAnimation.fromValue = 0 90 | shadowAnimation.toValue = shadowOpacity 91 | shadowAnimation.duration = animationDuration 92 | 93 | let transformAnimation = CABasicAnimation(keyPath: "transform.scale") 94 | transformAnimation.fromValue = 1 95 | transformAnimation.toValue = cellScale 96 | transformAnimation.duration = animationDuration 97 | transformAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 98 | 99 | snapshotView.layer.add(opacityAnimation, forKey: nil) 100 | snapshotView.layer.add(shadowAnimation, forKey: nil) 101 | snapshotView.layer.add(transformAnimation, forKey: nil) 102 | } 103 | 104 | func animateSnapshotViewOut() { 105 | guard let snapshotView = snapshotView else { return } 106 | 107 | let opacityAnimation = CABasicAnimation(keyPath: "opacity") 108 | opacityAnimation.fromValue = cellOpacity 109 | opacityAnimation.toValue = 1 110 | opacityAnimation.duration = animationDuration 111 | 112 | let shadowAnimation = CABasicAnimation(keyPath: "shadowOpacity") 113 | shadowAnimation.fromValue = shadowOpacity 114 | shadowAnimation.toValue = 0 115 | shadowAnimation.duration = animationDuration 116 | 117 | let transformAnimation = CABasicAnimation(keyPath: "transform.scale") 118 | transformAnimation.fromValue = cellScale 119 | transformAnimation.toValue = 1 120 | transformAnimation.duration = animationDuration 121 | transformAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 122 | 123 | snapshotView.layer.add(opacityAnimation, forKey: nil) 124 | snapshotView.layer.add(shadowAnimation, forKey: nil) 125 | snapshotView.layer.add(transformAnimation, forKey: nil) 126 | 127 | snapshotView.layer.opacity = 1 128 | snapshotView.layer.shadowOpacity = 0 129 | snapshotView.layer.transform = CATransform3DIdentity 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /Source/ReorderController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | /** 26 | The style of the reorder spacer cell. Determines whether the cell separator line is visible. 27 | 28 | - Automatic: The style is determined based on the table view's style (plain or grouped). 29 | - Hidden: The spacer cell is hidden, and the separator line is not visible. 30 | - Transparent: The spacer cell is given a transparent background color, and the separator line is visible. 31 | */ 32 | public enum ReorderSpacerCellStyle { 33 | case automatic 34 | case hidden 35 | case transparent 36 | } 37 | 38 | // MARK: - TableViewReorderDelegate 39 | 40 | /** 41 | The delegate of a `ReorderController` must adopt the `TableViewReorderDelegate` protocol. This protocol defines methods for handling the reordering of rows. 42 | */ 43 | public protocol TableViewReorderDelegate: class { 44 | 45 | /** 46 | Tells the delegate that the user has moved a row from one location to another. Use this method to update your data source. 47 | - Parameter tableView: The table view requesting this action. 48 | - Parameter sourceIndexPath: The index path of the row to be moved. 49 | - Parameter destinationIndexPath: The index path of the row's new location. 50 | */ 51 | func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) 52 | 53 | /** 54 | Asks the reorder delegate whether a given row can be moved. 55 | - Parameter tableView: The table view requesting this information. 56 | - Parameter indexPath: The index path of a row. 57 | */ 58 | func tableView(_ tableView: UITableView, canReorderRowAt indexPath: IndexPath) -> Bool 59 | 60 | /** 61 | When attempting to move a row from a sourceIndexPath to a proposedDestinationIndexPath, asks the reorder delegate what the actual targetIndexPath should be. This allows the reorder delegate to selectively allow or modify reordering between sections or groups of rows, for example. 62 | - Parameter tableView: The table view requesting this information. 63 | - Parameter sourceIndexPath: The original index path of the row to be moved. 64 | - Parameter proposedDestinationIndexPath: The potential index path of the row's new location. 65 | */ 66 | func tableView(_ tableView: UITableView, targetIndexPathForReorderFromRowAt sourceIndexPath: IndexPath, to proposedDestinationIndexPath: IndexPath) -> IndexPath 67 | 68 | /** 69 | Tells the delegate that the user has begun reordering a row. 70 | - Parameter tableView: The table view providing this information. 71 | - Parameter indexPath: The index path of the selected row. 72 | */ 73 | func tableViewDidBeginReordering(_ tableView: UITableView, at indexPath: IndexPath) 74 | 75 | /** 76 | Tells the delegate that the user has finished reordering. 77 | - Parameter tableView: The table view providing this information. 78 | - Parameter initialSourceIndexPath: The initial index path of the selected row, before reordering began. 79 | - Parameter finalDestinationIndexPath: The final index path of the selected row. 80 | */ 81 | func tableViewDidFinishReordering(_ tableView: UITableView, from initialSourceIndexPath: IndexPath, to finalDestinationIndexPath: IndexPath) 82 | 83 | } 84 | 85 | public extension TableViewReorderDelegate { 86 | 87 | func tableView(_ tableView: UITableView, canReorderRowAt indexPath: IndexPath) -> Bool { 88 | return true 89 | } 90 | 91 | func tableView(_ tableView: UITableView, targetIndexPathForReorderFromRowAt sourceIndexPath: IndexPath, to proposedDestinationIndexPath: IndexPath) -> IndexPath { 92 | return proposedDestinationIndexPath 93 | } 94 | 95 | func tableViewDidBeginReordering(_ tableView: UITableView, at indexPath: IndexPath) { 96 | } 97 | 98 | func tableViewDidFinishReordering(_ tableView: UITableView, from initialSourceIndexPath: IndexPath, to finalDestinationIndexPath:IndexPath) { 99 | } 100 | 101 | } 102 | 103 | // MARK: - ReorderController 104 | 105 | /** 106 | An object that manages drag-and-drop reordering of table view cells. 107 | */ 108 | public class ReorderController: NSObject { 109 | 110 | // MARK: - Public interface 111 | 112 | /// The delegate of the reorder controller. 113 | public weak var delegate: TableViewReorderDelegate? 114 | 115 | /// Whether reordering is enabled. 116 | public var isEnabled: Bool = true { 117 | didSet { reorderGestureRecognizer.isEnabled = isEnabled } 118 | } 119 | 120 | public var longPressDuration: TimeInterval = 0.3 { 121 | didSet { 122 | reorderGestureRecognizer.minimumPressDuration = longPressDuration 123 | } 124 | } 125 | 126 | public var cancelsTouchesInView: Bool = false { 127 | didSet { 128 | reorderGestureRecognizer.cancelsTouchesInView = cancelsTouchesInView 129 | } 130 | } 131 | 132 | /// The duration of the cell selection animation. 133 | public var animationDuration: TimeInterval = 0.2 134 | 135 | /// The opacity of the selected cell. 136 | public var cellOpacity: CGFloat = 1 137 | 138 | /// The scale factor for the selected cell. 139 | public var cellScale: CGFloat = 1 140 | 141 | /// The shadow color for the selected cell. 142 | public var shadowColor: UIColor = .black 143 | 144 | /// The shadow opacity for the selected cell. 145 | public var shadowOpacity: CGFloat = 0.3 146 | 147 | /// The shadow radius for the selected cell. 148 | public var shadowRadius: CGFloat = 10 149 | 150 | /// The shadow offset for the selected cell. 151 | public var shadowOffset = CGSize(width: 0, height: 3) 152 | 153 | /// The spacer cell style. 154 | public var spacerCellStyle: ReorderSpacerCellStyle = .automatic 155 | 156 | /// Whether or not autoscrolling is enabled 157 | public var autoScrollEnabled = true 158 | 159 | /** 160 | Returns a `UITableViewCell` if the table view should display a spacer cell at the given index path. 161 | 162 | Call this method at the beginning of your `tableView(_:cellForRowAt:)`, like so: 163 | ``` 164 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 165 | if let spacer = tableView.reorder.spacerCell(for: indexPath) { 166 | return spacer 167 | } 168 | 169 | // ... 170 | } 171 | ``` 172 | - Parameter indexPath: The index path 173 | - Returns: An optional `UITableViewCell`. 174 | */ 175 | public func spacerCell(for indexPath: IndexPath) -> UITableViewCell? { 176 | if case let .reordering(context) = reorderState, indexPath == context.destinationRow { 177 | return createSpacerCell() 178 | } else if case let .ready(snapshotRow) = reorderState, indexPath == snapshotRow { 179 | return createSpacerCell() 180 | } 181 | return nil 182 | } 183 | 184 | // MARK: - Internal state 185 | 186 | struct ReorderContext { 187 | var sourceRow: IndexPath 188 | var destinationRow: IndexPath 189 | var snapshotOffset: CGFloat 190 | var touchPosition: CGPoint 191 | } 192 | 193 | enum ReorderState { 194 | case ready(snapshotRow: IndexPath?) 195 | case reordering(context: ReorderContext) 196 | } 197 | 198 | weak var tableView: UITableView? 199 | 200 | var reorderState: ReorderState = .ready(snapshotRow: nil) 201 | var snapshotView: UIView? = nil 202 | 203 | var autoScrollDisplayLink: CADisplayLink? 204 | var lastAutoScrollTimeStamp: CFTimeInterval? 205 | 206 | lazy var reorderGestureRecognizer: UILongPressGestureRecognizer = { 207 | let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleReorderGesture)) 208 | gestureRecognizer.delegate = self 209 | gestureRecognizer.minimumPressDuration = self.longPressDuration 210 | return gestureRecognizer 211 | }() 212 | 213 | // MARK: - Lifecycle 214 | 215 | init(tableView: UITableView) { 216 | super.init() 217 | 218 | self.tableView = tableView 219 | tableView.addGestureRecognizer(reorderGestureRecognizer) 220 | 221 | reorderState = .ready(snapshotRow: nil) 222 | } 223 | 224 | // MARK: - Reordering 225 | 226 | func beginReorder(touchPosition: CGPoint) { 227 | guard case .ready = reorderState, 228 | let delegate = delegate, 229 | let tableView = tableView, 230 | let superview = tableView.superview 231 | else { return } 232 | 233 | let tableTouchPosition = superview.convert(touchPosition, to: tableView) 234 | 235 | guard let sourceRow = tableView.indexPathForRow(at: tableTouchPosition), 236 | delegate.tableView(tableView, canReorderRowAt: sourceRow) 237 | else { return } 238 | 239 | createSnapshotViewForCell(at: sourceRow) 240 | animateSnapshotViewIn() 241 | activateAutoScrollDisplayLink() 242 | 243 | tableView.reloadData() 244 | 245 | let snapshotOffset = (snapshotView?.center.y ?? 0) - touchPosition.y 246 | 247 | let context = ReorderContext( 248 | sourceRow: sourceRow, 249 | destinationRow: sourceRow, 250 | snapshotOffset: snapshotOffset, 251 | touchPosition: touchPosition 252 | ) 253 | reorderState = .reordering(context: context) 254 | 255 | delegate.tableViewDidBeginReordering(tableView, at: sourceRow) 256 | } 257 | 258 | func updateReorder(touchPosition: CGPoint) { 259 | guard case .reordering(let context) = reorderState else { return } 260 | 261 | var newContext = context 262 | newContext.touchPosition = touchPosition 263 | reorderState = .reordering(context: newContext) 264 | 265 | updateSnapshotViewPosition() 266 | updateDestinationRow() 267 | } 268 | 269 | func endReorder() { 270 | guard case .reordering(let context) = reorderState, 271 | let tableView = tableView, 272 | let superview = tableView.superview 273 | else { return } 274 | 275 | reorderState = .ready(snapshotRow: context.destinationRow) 276 | 277 | let cellRectInTableView = tableView.rectForRow(at: context.destinationRow) 278 | let cellRect = tableView.convert(cellRectInTableView, to: superview) 279 | let cellRectCenter = CGPoint(x: cellRect.midX, y: cellRect.midY) 280 | 281 | // If no values change inside a UIView animation block, the completion handler is called immediately. 282 | // This is a workaround for that case. 283 | if snapshotView?.center == cellRectCenter { 284 | snapshotView?.center.y += 0.1 285 | } 286 | 287 | UIView.animate(withDuration: animationDuration, 288 | animations: { 289 | self.snapshotView?.center = CGPoint(x: cellRect.midX, y: cellRect.midY) 290 | }, 291 | completion: { _ in 292 | if case let .ready(snapshotRow) = self.reorderState, let row = snapshotRow { 293 | self.reorderState = .ready(snapshotRow: nil) 294 | UIView.performWithoutAnimation { 295 | tableView.reloadRows(at: [row], with: .none) 296 | } 297 | self.removeSnapshotView() 298 | } 299 | } 300 | ) 301 | animateSnapshotViewOut() 302 | clearAutoScrollDisplayLink() 303 | 304 | delegate?.tableViewDidFinishReordering(tableView, from: context.sourceRow, to: context.destinationRow) 305 | } 306 | 307 | // MARK: - Spacer cell 308 | 309 | private func createSpacerCell() -> UITableViewCell? { 310 | guard let snapshotView = snapshotView else { return nil } 311 | 312 | let cell = UITableViewCell() 313 | let height = snapshotView.bounds.height 314 | 315 | NSLayoutConstraint( 316 | item: cell, 317 | attribute: .height, 318 | relatedBy: .equal, 319 | toItem: nil, 320 | attribute: .notAnAttribute, 321 | multiplier: 0, 322 | constant: height 323 | ).isActive = true 324 | 325 | let hideCell: Bool 326 | switch spacerCellStyle { 327 | case .automatic: hideCell = tableView?.style == .grouped 328 | case .hidden: hideCell = true 329 | case .transparent: hideCell = false 330 | } 331 | 332 | if hideCell { 333 | cell.isHidden = true 334 | } else { 335 | cell.backgroundColor = .clear 336 | } 337 | 338 | return cell 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /Source/UITableView+Reorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Adam Shin 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | import UIKit 24 | 25 | extension UITableView { 26 | 27 | private struct AssociatedKeys { 28 | static var reorderController: UInt8 = 0 29 | } 30 | 31 | /// An object that manages drag-and-drop reordering of table view cells. 32 | public var reorder: ReorderController { 33 | if let controller = objc_getAssociatedObject(self, &AssociatedKeys.reorderController) as? ReorderController { 34 | return controller 35 | } else { 36 | let controller = ReorderController(tableView: self) 37 | objc_setAssociatedObject(self, &AssociatedKeys.reorderController, controller, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 38 | return controller 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /SwiftReorder.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftReorder' 3 | s.version = '7.2.0' 4 | s.license = 'MIT' 5 | s.summary = 'Easy drag-and-drop reordering for UITableViews' 6 | s.homepage = 'https://github.com/adamshin/SwiftReorder' 7 | s.author = 'Adam Shin' 8 | 9 | s.swift_version = '5.0' 10 | s.platform = :ios, '8.0' 11 | 12 | s.source = { :git => 'https://github.com/adamshin/SwiftReorder.git', :tag => s.version } 13 | s.source_files = 'Source/*' 14 | end 15 | -------------------------------------------------------------------------------- /SwiftReorder.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 66FC50F11D5EE49D00CFCCCE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50E81D5EE49D00CFCCCE /* AppDelegate.swift */; }; 11 | 66FC50F21D5EE49D00CFCCCE /* BasicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50E91D5EE49D00CFCCCE /* BasicViewController.swift */; }; 12 | 66FC50F41D5EE49D00CFCCCE /* EffectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50EB1D5EE49D00CFCCCE /* EffectsViewController.swift */; }; 13 | 66FC50F51D5EE49D00CFCCCE /* GroupedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50EC1D5EE49D00CFCCCE /* GroupedViewController.swift */; }; 14 | 66FC50F71D5EE49D00CFCCCE /* LongListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50EE1D5EE49D00CFCCCE /* LongListViewController.swift */; }; 15 | 66FC50F81D5EE49D00CFCCCE /* NonMovableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50EF1D5EE49D00CFCCCE /* NonMovableViewController.swift */; }; 16 | 66FC50F91D5EE49D00CFCCCE /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50F01D5EE49D00CFCCCE /* RootViewController.swift */; }; 17 | AB6E455C21A82AAD00D47CFC /* CustomCellsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6E455B21A82AAD00D47CFC /* CustomCellsViewController.swift */; }; 18 | D9DAD5E12134663E00484E71 /* SwiftReorder.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAD5DF2134663E00484E71 /* SwiftReorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | D9DAD5E42134663E00484E71 /* SwiftReorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAD5DD2134663E00484E71 /* SwiftReorder.framework */; }; 20 | D9DAD5E52134663E00484E71 /* SwiftReorder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAD5DD2134663E00484E71 /* SwiftReorder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 21 | D9DAD5EA2134668200484E71 /* UITableView+Reorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50FA1D5EE4CA00CFCCCE /* UITableView+Reorder.swift */; }; 22 | D9DAD5EB2134668200484E71 /* ReorderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FC50FC1D5EE4D600CFCCCE /* ReorderController.swift */; }; 23 | D9DAD5EC2134668200484E71 /* ReorderController+SnapshotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DF25131D8080A000C19289 /* ReorderController+SnapshotView.swift */; }; 24 | D9DAD5ED2134668200484E71 /* ReorderController+DestinationRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DF25111D8080A000C19289 /* ReorderController+DestinationRow.swift */; }; 25 | D9DAD5EE2134668200484E71 /* ReorderController+AutoScroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DF25101D8080A000C19289 /* ReorderController+AutoScroll.swift */; }; 26 | D9DAD5EF2134668200484E71 /* ReorderController+GestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DF25121D8080A000C19289 /* ReorderController+GestureRecognizer.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | D9DAD5E22134663E00484E71 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 6602568B1CE6A5170029CB5F /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = D9DAD5DC2134663E00484E71; 35 | remoteInfo = SwiftReorder; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXCopyFilesBuildPhase section */ 40 | D9DAD5E92134663E00484E71 /* Embed Frameworks */ = { 41 | isa = PBXCopyFilesBuildPhase; 42 | buildActionMask = 2147483647; 43 | dstPath = ""; 44 | dstSubfolderSpec = 10; 45 | files = ( 46 | D9DAD5E52134663E00484E71 /* SwiftReorder.framework in Embed Frameworks */, 47 | ); 48 | name = "Embed Frameworks"; 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXCopyFilesBuildPhase section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 660256931CE6A5170029CB5F /* SwiftReorderExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftReorderExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 66DF25101D8080A000C19289 /* ReorderController+AutoScroll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ReorderController+AutoScroll.swift"; path = "Source/ReorderController+AutoScroll.swift"; sourceTree = ""; }; 56 | 66DF25111D8080A000C19289 /* ReorderController+DestinationRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ReorderController+DestinationRow.swift"; path = "Source/ReorderController+DestinationRow.swift"; sourceTree = ""; }; 57 | 66DF25121D8080A000C19289 /* ReorderController+GestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ReorderController+GestureRecognizer.swift"; path = "Source/ReorderController+GestureRecognizer.swift"; sourceTree = ""; }; 58 | 66DF25131D8080A000C19289 /* ReorderController+SnapshotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ReorderController+SnapshotView.swift"; path = "Source/ReorderController+SnapshotView.swift"; sourceTree = ""; }; 59 | 66FC50E81D5EE49D00CFCCCE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Example/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 60 | 66FC50E91D5EE49D00CFCCCE /* BasicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BasicViewController.swift; path = Example/BasicViewController.swift; sourceTree = SOURCE_ROOT; }; 61 | 66FC50EB1D5EE49D00CFCCCE /* EffectsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EffectsViewController.swift; path = Example/EffectsViewController.swift; sourceTree = SOURCE_ROOT; }; 62 | 66FC50EC1D5EE49D00CFCCCE /* GroupedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GroupedViewController.swift; path = Example/GroupedViewController.swift; sourceTree = SOURCE_ROOT; }; 63 | 66FC50ED1D5EE49D00CFCCCE /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = SOURCE_ROOT; }; 64 | 66FC50EE1D5EE49D00CFCCCE /* LongListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LongListViewController.swift; path = Example/LongListViewController.swift; sourceTree = SOURCE_ROOT; }; 65 | 66FC50EF1D5EE49D00CFCCCE /* NonMovableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NonMovableViewController.swift; path = Example/NonMovableViewController.swift; sourceTree = SOURCE_ROOT; }; 66 | 66FC50F01D5EE49D00CFCCCE /* RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RootViewController.swift; path = Example/RootViewController.swift; sourceTree = SOURCE_ROOT; }; 67 | 66FC50FA1D5EE4CA00CFCCCE /* UITableView+Reorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UITableView+Reorder.swift"; path = "Source/UITableView+Reorder.swift"; sourceTree = ""; }; 68 | 66FC50FC1D5EE4D600CFCCCE /* ReorderController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReorderController.swift; path = Source/ReorderController.swift; sourceTree = ""; }; 69 | AB6E455B21A82AAD00D47CFC /* CustomCellsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCellsViewController.swift; sourceTree = ""; }; 70 | D9DAD5DD2134663E00484E71 /* SwiftReorder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftReorder.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | D9DAD5DF2134663E00484E71 /* SwiftReorder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftReorder.h; sourceTree = ""; }; 72 | D9DAD5E02134663E00484E71 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | 660256901CE6A5170029CB5F /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | D9DAD5E42134663E00484E71 /* SwiftReorder.framework in Frameworks */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | D9DAD5D92134663E00484E71 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 6602568A1CE6A5170029CB5F = { 95 | isa = PBXGroup; 96 | children = ( 97 | 660256A81CE6A72F0029CB5F /* Source */, 98 | 660256951CE6A5170029CB5F /* Example */, 99 | D9DAD5DE2134663E00484E71 /* SwiftReorder */, 100 | 660256941CE6A5170029CB5F /* Products */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 660256941CE6A5170029CB5F /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 660256931CE6A5170029CB5F /* SwiftReorderExample.app */, 108 | D9DAD5DD2134663E00484E71 /* SwiftReorder.framework */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | 660256951CE6A5170029CB5F /* Example */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 66FC50E81D5EE49D00CFCCCE /* AppDelegate.swift */, 117 | 66FC50F01D5EE49D00CFCCCE /* RootViewController.swift */, 118 | 66FC50E91D5EE49D00CFCCCE /* BasicViewController.swift */, 119 | 66FC50EC1D5EE49D00CFCCCE /* GroupedViewController.swift */, 120 | 66FC50EE1D5EE49D00CFCCCE /* LongListViewController.swift */, 121 | 66FC50EF1D5EE49D00CFCCCE /* NonMovableViewController.swift */, 122 | 66FC50EB1D5EE49D00CFCCCE /* EffectsViewController.swift */, 123 | AB6E455B21A82AAD00D47CFC /* CustomCellsViewController.swift */, 124 | 66FC50ED1D5EE49D00CFCCCE /* Info.plist */, 125 | ); 126 | path = Example; 127 | sourceTree = ""; 128 | }; 129 | 660256A81CE6A72F0029CB5F /* Source */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 66FC50FA1D5EE4CA00CFCCCE /* UITableView+Reorder.swift */, 133 | 66FC50FC1D5EE4D600CFCCCE /* ReorderController.swift */, 134 | 66DF25131D8080A000C19289 /* ReorderController+SnapshotView.swift */, 135 | 66DF25111D8080A000C19289 /* ReorderController+DestinationRow.swift */, 136 | 66DF25101D8080A000C19289 /* ReorderController+AutoScroll.swift */, 137 | 66DF25121D8080A000C19289 /* ReorderController+GestureRecognizer.swift */, 138 | ); 139 | name = Source; 140 | sourceTree = ""; 141 | }; 142 | D9DAD5DE2134663E00484E71 /* SwiftReorder */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | D9DAD5DF2134663E00484E71 /* SwiftReorder.h */, 146 | D9DAD5E02134663E00484E71 /* Info.plist */, 147 | ); 148 | path = SwiftReorder; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXHeadersBuildPhase section */ 154 | D9DAD5DA2134663E00484E71 /* Headers */ = { 155 | isa = PBXHeadersBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | D9DAD5E12134663E00484E71 /* SwiftReorder.h in Headers */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXHeadersBuildPhase section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | 660256921CE6A5170029CB5F /* SwiftReorderExample */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 660256A51CE6A5170029CB5F /* Build configuration list for PBXNativeTarget "SwiftReorderExample" */; 168 | buildPhases = ( 169 | 6602568F1CE6A5170029CB5F /* Sources */, 170 | 660256901CE6A5170029CB5F /* Frameworks */, 171 | 660256911CE6A5170029CB5F /* Resources */, 172 | D9DAD5E92134663E00484E71 /* Embed Frameworks */, 173 | ); 174 | buildRules = ( 175 | ); 176 | dependencies = ( 177 | D9DAD5E32134663E00484E71 /* PBXTargetDependency */, 178 | ); 179 | name = SwiftReorderExample; 180 | productName = SwiftReorder; 181 | productReference = 660256931CE6A5170029CB5F /* SwiftReorderExample.app */; 182 | productType = "com.apple.product-type.application"; 183 | }; 184 | D9DAD5DC2134663E00484E71 /* SwiftReorder */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = D9DAD5E82134663E00484E71 /* Build configuration list for PBXNativeTarget "SwiftReorder" */; 187 | buildPhases = ( 188 | D9DAD5D82134663E00484E71 /* Sources */, 189 | D9DAD5D92134663E00484E71 /* Frameworks */, 190 | D9DAD5DA2134663E00484E71 /* Headers */, 191 | D9DAD5DB2134663E00484E71 /* Resources */, 192 | ); 193 | buildRules = ( 194 | ); 195 | dependencies = ( 196 | ); 197 | name = SwiftReorder; 198 | productName = SwiftReorder; 199 | productReference = D9DAD5DD2134663E00484E71 /* SwiftReorder.framework */; 200 | productType = "com.apple.product-type.framework"; 201 | }; 202 | /* End PBXNativeTarget section */ 203 | 204 | /* Begin PBXProject section */ 205 | 6602568B1CE6A5170029CB5F /* Project object */ = { 206 | isa = PBXProject; 207 | attributes = { 208 | LastSwiftUpdateCheck = 0730; 209 | LastUpgradeCheck = 1020; 210 | ORGANIZATIONNAME = "Adam Shin"; 211 | TargetAttributes = { 212 | 660256921CE6A5170029CB5F = { 213 | CreatedOnToolsVersion = 7.3; 214 | LastSwiftMigration = 1020; 215 | }; 216 | D9DAD5DC2134663E00484E71 = { 217 | CreatedOnToolsVersion = 9.4.1; 218 | LastSwiftMigration = 1020; 219 | ProvisioningStyle = Automatic; 220 | }; 221 | }; 222 | }; 223 | buildConfigurationList = 6602568E1CE6A5170029CB5F /* Build configuration list for PBXProject "SwiftReorder" */; 224 | compatibilityVersion = "Xcode 3.2"; 225 | developmentRegion = English; 226 | hasScannedForEncodings = 0; 227 | knownRegions = ( 228 | English, 229 | en, 230 | Base, 231 | ); 232 | mainGroup = 6602568A1CE6A5170029CB5F; 233 | productRefGroup = 660256941CE6A5170029CB5F /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | 660256921CE6A5170029CB5F /* SwiftReorderExample */, 238 | D9DAD5DC2134663E00484E71 /* SwiftReorder */, 239 | ); 240 | }; 241 | /* End PBXProject section */ 242 | 243 | /* Begin PBXResourcesBuildPhase section */ 244 | 660256911CE6A5170029CB5F /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | D9DAD5DB2134663E00484E71 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXResourcesBuildPhase section */ 259 | 260 | /* Begin PBXSourcesBuildPhase section */ 261 | 6602568F1CE6A5170029CB5F /* Sources */ = { 262 | isa = PBXSourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 66FC50F91D5EE49D00CFCCCE /* RootViewController.swift in Sources */, 266 | AB6E455C21A82AAD00D47CFC /* CustomCellsViewController.swift in Sources */, 267 | 66FC50F81D5EE49D00CFCCCE /* NonMovableViewController.swift in Sources */, 268 | 66FC50F11D5EE49D00CFCCCE /* AppDelegate.swift in Sources */, 269 | 66FC50F71D5EE49D00CFCCCE /* LongListViewController.swift in Sources */, 270 | 66FC50F51D5EE49D00CFCCCE /* GroupedViewController.swift in Sources */, 271 | 66FC50F21D5EE49D00CFCCCE /* BasicViewController.swift in Sources */, 272 | 66FC50F41D5EE49D00CFCCCE /* EffectsViewController.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | D9DAD5D82134663E00484E71 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | D9DAD5EA2134668200484E71 /* UITableView+Reorder.swift in Sources */, 281 | D9DAD5EF2134668200484E71 /* ReorderController+GestureRecognizer.swift in Sources */, 282 | D9DAD5EE2134668200484E71 /* ReorderController+AutoScroll.swift in Sources */, 283 | D9DAD5ED2134668200484E71 /* ReorderController+DestinationRow.swift in Sources */, 284 | D9DAD5EC2134668200484E71 /* ReorderController+SnapshotView.swift in Sources */, 285 | D9DAD5EB2134668200484E71 /* ReorderController.swift in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXSourcesBuildPhase section */ 290 | 291 | /* Begin PBXTargetDependency section */ 292 | D9DAD5E32134663E00484E71 /* PBXTargetDependency */ = { 293 | isa = PBXTargetDependency; 294 | target = D9DAD5DC2134663E00484E71 /* SwiftReorder */; 295 | targetProxy = D9DAD5E22134663E00484E71 /* PBXContainerItemProxy */; 296 | }; 297 | /* End PBXTargetDependency section */ 298 | 299 | /* Begin XCBuildConfiguration section */ 300 | 660256A31CE6A5170029CB5F /* Debug */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ALWAYS_SEARCH_USER_PATHS = NO; 304 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 311 | CLANG_WARN_BOOL_CONVERSION = YES; 312 | CLANG_WARN_COMMA = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = dwarf; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | ENABLE_TESTABILITY = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu99; 335 | GCC_DYNAMIC_NO_PIC = NO; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_OPTIMIZATION_LEVEL = 0; 338 | GCC_PREPROCESSOR_DEFINITIONS = ( 339 | "DEBUG=1", 340 | "$(inherited)", 341 | ); 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 349 | MTL_ENABLE_DEBUG_INFO = YES; 350 | ONLY_ACTIVE_ARCH = YES; 351 | SDKROOT = iphoneos; 352 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 660256A41CE6A5170029CB5F /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 362 | CLANG_ANALYZER_NONNULL = YES; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 368 | CLANG_WARN_BOOL_CONVERSION = YES; 369 | CLANG_WARN_COMMA = YES; 370 | CLANG_WARN_CONSTANT_CONVERSION = YES; 371 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 372 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INFINITE_RECURSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 379 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 382 | CLANG_WARN_STRICT_PROTOTYPES = YES; 383 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 384 | CLANG_WARN_UNREACHABLE_CODE = YES; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 387 | COPY_PHASE_STRIP = NO; 388 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 389 | ENABLE_NS_ASSERTIONS = NO; 390 | ENABLE_STRICT_OBJC_MSGSEND = YES; 391 | GCC_C_LANGUAGE_STANDARD = gnu99; 392 | GCC_NO_COMMON_BLOCKS = YES; 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 400 | MTL_ENABLE_DEBUG_INFO = NO; 401 | SDKROOT = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 660256A61CE6A5170029CB5F /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 412 | CODE_SIGN_IDENTITY = "iPhone Developer"; 413 | INFOPLIST_FILE = Example/Info.plist; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 415 | PRODUCT_BUNDLE_IDENTIFIER = adamshin.SwiftReorder; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SWIFT_VERSION = 5.0; 418 | }; 419 | name = Debug; 420 | }; 421 | 660256A71CE6A5170029CB5F /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 425 | CODE_SIGN_IDENTITY = "iPhone Developer"; 426 | DEVELOPMENT_TEAM = ""; 427 | INFOPLIST_FILE = Example/Info.plist; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = adamshin.SwiftReorder; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | SWIFT_VERSION = 5.0; 432 | }; 433 | name = Release; 434 | }; 435 | D9DAD5E62134663E00484E71 /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 439 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 440 | CLANG_ENABLE_OBJC_WEAK = YES; 441 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 442 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 443 | CODE_SIGN_IDENTITY = ""; 444 | CODE_SIGN_STYLE = Automatic; 445 | CURRENT_PROJECT_VERSION = 1; 446 | DEFINES_MODULE = YES; 447 | DYLIB_COMPATIBILITY_VERSION = 1; 448 | DYLIB_CURRENT_VERSION = 1; 449 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 450 | GCC_C_LANGUAGE_STANDARD = gnu11; 451 | INFOPLIST_FILE = SwiftReorder/Info.plist; 452 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 454 | PRODUCT_BUNDLE_IDENTIFIER = adamshin.SwiftReorder.SwiftReorder; 455 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 456 | SKIP_INSTALL = YES; 457 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 458 | SWIFT_VERSION = 5.0; 459 | TARGETED_DEVICE_FAMILY = "1,2"; 460 | VERSIONING_SYSTEM = "apple-generic"; 461 | VERSION_INFO_PREFIX = ""; 462 | }; 463 | name = Debug; 464 | }; 465 | D9DAD5E72134663E00484E71 /* Release */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 469 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 470 | CLANG_ENABLE_OBJC_WEAK = YES; 471 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 472 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 473 | CODE_SIGN_IDENTITY = ""; 474 | CODE_SIGN_STYLE = Automatic; 475 | CURRENT_PROJECT_VERSION = 1; 476 | DEFINES_MODULE = YES; 477 | DYLIB_COMPATIBILITY_VERSION = 1; 478 | DYLIB_CURRENT_VERSION = 1; 479 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 480 | GCC_C_LANGUAGE_STANDARD = gnu11; 481 | INFOPLIST_FILE = SwiftReorder/Info.plist; 482 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 483 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 484 | PRODUCT_BUNDLE_IDENTIFIER = adamshin.SwiftReorder.SwiftReorder; 485 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 486 | SKIP_INSTALL = YES; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | VERSIONING_SYSTEM = "apple-generic"; 490 | VERSION_INFO_PREFIX = ""; 491 | }; 492 | name = Release; 493 | }; 494 | /* End XCBuildConfiguration section */ 495 | 496 | /* Begin XCConfigurationList section */ 497 | 6602568E1CE6A5170029CB5F /* Build configuration list for PBXProject "SwiftReorder" */ = { 498 | isa = XCConfigurationList; 499 | buildConfigurations = ( 500 | 660256A31CE6A5170029CB5F /* Debug */, 501 | 660256A41CE6A5170029CB5F /* Release */, 502 | ); 503 | defaultConfigurationIsVisible = 0; 504 | defaultConfigurationName = Release; 505 | }; 506 | 660256A51CE6A5170029CB5F /* Build configuration list for PBXNativeTarget "SwiftReorderExample" */ = { 507 | isa = XCConfigurationList; 508 | buildConfigurations = ( 509 | 660256A61CE6A5170029CB5F /* Debug */, 510 | 660256A71CE6A5170029CB5F /* Release */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | D9DAD5E82134663E00484E71 /* Build configuration list for PBXNativeTarget "SwiftReorder" */ = { 516 | isa = XCConfigurationList; 517 | buildConfigurations = ( 518 | D9DAD5E62134663E00484E71 /* Debug */, 519 | D9DAD5E72134663E00484E71 /* Release */, 520 | ); 521 | defaultConfigurationIsVisible = 0; 522 | defaultConfigurationName = Release; 523 | }; 524 | /* End XCConfigurationList section */ 525 | }; 526 | rootObject = 6602568B1CE6A5170029CB5F /* Project object */; 527 | } 528 | -------------------------------------------------------------------------------- /SwiftReorder.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftReorder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftReorder.xcodeproj/xcshareddata/xcschemes/SwiftReorder.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwiftReorder/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftReorder/SwiftReorder.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftReorder.h 3 | // SwiftReorder 4 | // 5 | // Created by Joseph Duffy on 27/08/2018. 6 | // Copyright © 2018 Adam Shin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftReorder. 12 | FOUNDATION_EXPORT double SwiftReorderVersionNumber; 13 | 14 | //! Project version string for SwiftReorder. 15 | FOUNDATION_EXPORT const unsigned char SwiftReorderVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | --------------------------------------------------------------------------------