├── .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 | 
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 |
--------------------------------------------------------------------------------