├── .gitignore
├── .travis.yml
├── Example
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Local Podspecs
│ │ └── SwiftyComments.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── SwiftScanner
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Sources
│ │ │ └── StringScanner.swift
│ ├── SwipeCellKit
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Source
│ │ │ ├── Extensions.swift
│ │ │ ├── SwipeAccessibilityCustomAction.swift
│ │ │ ├── SwipeAction.swift
│ │ │ ├── SwipeActionButton.swift
│ │ │ ├── SwipeActionTransitioning.swift
│ │ │ ├── SwipeActionsView.swift
│ │ │ ├── SwipeAnimator.swift
│ │ │ ├── SwipeCollectionViewCell+Accessibility.swift
│ │ │ ├── SwipeCollectionViewCell+Display.swift
│ │ │ ├── SwipeCollectionViewCell.swift
│ │ │ ├── SwipeCollectionViewCellDelegate.swift
│ │ │ ├── SwipeController.swift
│ │ │ ├── SwipeExpanding.swift
│ │ │ ├── SwipeExpansionStyle.swift
│ │ │ ├── SwipeFeedback.swift
│ │ │ ├── SwipeOptions.swift
│ │ │ ├── SwipeTableViewCell+Accessibility.swift
│ │ │ ├── SwipeTableViewCell+Display.swift
│ │ │ ├── SwipeTableViewCell.swift
│ │ │ ├── SwipeTableViewCellDelegate.swift
│ │ │ ├── SwipeTransitionLayout.swift
│ │ │ └── Swipeable.swift
│ └── Target Support Files
│ │ ├── Pods-SwiftyComments_Example
│ │ ├── Info.plist
│ │ ├── Pods-SwiftyComments_Example-acknowledgements.markdown
│ │ ├── Pods-SwiftyComments_Example-acknowledgements.plist
│ │ ├── Pods-SwiftyComments_Example-dummy.m
│ │ ├── Pods-SwiftyComments_Example-frameworks.sh
│ │ ├── Pods-SwiftyComments_Example-resources.sh
│ │ ├── Pods-SwiftyComments_Example-umbrella.h
│ │ ├── Pods-SwiftyComments_Example.debug.xcconfig
│ │ ├── Pods-SwiftyComments_Example.modulemap
│ │ └── Pods-SwiftyComments_Example.release.xcconfig
│ │ ├── SwiftScanner
│ │ ├── Info.plist
│ │ ├── SwiftScanner-dummy.m
│ │ ├── SwiftScanner-prefix.pch
│ │ ├── SwiftScanner-umbrella.h
│ │ ├── SwiftScanner.modulemap
│ │ └── SwiftScanner.xcconfig
│ │ ├── SwiftyComments
│ │ ├── Info.plist
│ │ ├── SwiftyComments-dummy.m
│ │ ├── SwiftyComments-prefix.pch
│ │ ├── SwiftyComments-umbrella.h
│ │ ├── SwiftyComments.modulemap
│ │ └── SwiftyComments.xcconfig
│ │ └── SwipeCellKit
│ │ ├── Info.plist
│ │ ├── SwipeCellKit-dummy.m
│ │ ├── SwipeCellKit-prefix.pch
│ │ ├── SwipeCellKit-umbrella.h
│ │ ├── SwipeCellKit.modulemap
│ │ └── SwipeCellKit.xcconfig
├── SwiftyComments.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── SwiftyComments-Example.xcscheme
├── SwiftyComments.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── SwiftyComments
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ └── LaunchScreen.xib
│ ├── DemosTableViewController.swift
│ ├── HNCommentCell.swift
│ ├── HNCommentContentParser.swift
│ ├── HNCommentsViewController.swift
│ ├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── HNRespond.imageset
│ │ ├── Contents.json
│ │ └── HNRespond.pdf
│ ├── downvte.imageset
│ │ ├── Contents.json
│ │ └── downvte.pdf
│ ├── exprt.imageset
│ │ ├── Contents.json
│ │ └── exprt.pdf
│ ├── hadDownvte.imageset
│ │ ├── Contents.json
│ │ └── hadDownvte.pdf
│ ├── hadUpvte.imageset
│ │ ├── Contents.json
│ │ └── hadUpvte.pdf
│ ├── imgurDownvte.imageset
│ │ ├── Contents.json
│ │ └── imgurDownvte.pdf
│ ├── imgurUpvte.imageset
│ │ ├── Contents.json
│ │ └── imgurUpvte.pdf
│ ├── mre.imageset
│ │ ├── Contents.json
│ │ └── mre.pdf
│ └── upvte.imageset
│ │ ├── Contents.json
│ │ └── upvte.pdf
│ ├── ImgurCommentCell.swift
│ ├── ImgurCommentsViewController.swift
│ ├── Info.plist
│ ├── LoremSwiftum.swift
│ ├── MockupData.swift
│ ├── RedditCommentCell.swift
│ ├── RedditCommentsViewController.swift
│ ├── SimpleCommentCell.swift
│ ├── SimpleCommentsViewController.swift
│ └── ViewController.swift
├── LICENSE
├── README.md
├── Screenshots
├── HN.mov
├── HNExample.gif
├── HNExample.png
├── HNSwipe.gif
├── HNSwipe.mov
├── Imgur.mov
├── ImgurExample.gif
├── ImgurExample.png
├── Reddit.mov
├── RedditExample.gif
├── RedditExample.png
├── RedditSwipe.gif
├── RedditSwipe.mov
├── schema.png
└── schema_small.png
├── SwiftyComments.podspec
├── SwiftyComments
├── Assets
│ └── Media.xcassets
│ │ ├── Contents.json
│ │ └── collapse.imageset
│ │ ├── Collapse.pdf
│ │ └── Contents.json
└── Classes
│ ├── AbstractComment.swift
│ ├── CommentCell.swift
│ ├── CommentsViewController.swift
│ └── CommentsViewDelegate.swift
└── _Pods.xcodeproj
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode11.1
3 | #xcode_workspace: Example/SwiftyComments.xcworkspace
4 | #xcode_scheme: SwiftyComments-Example
5 | #xcode_sdk: iphonesimulator9.3
6 | podfile: Example/Podfile
7 | before_install:
8 | - pod install --project-directory=Example
9 | script:
10 | - xcodebuild -workspace Example/SwiftyComments.xcworkspace -scheme 'SwiftyComments-Example' -sdk iphonesimulator build
11 | #cache:
12 | # directories:
13 | # - Pods
14 | # - $HOME/.cocoapods
15 | # - $HOME/Library/Caches/CocoaPods
16 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | target 'SwiftyComments_Example' do
4 | pod 'SwiftyComments', :path => '../'
5 | pod 'SwiftScanner', '~> 1.0.2'
6 | end
7 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SwiftScanner (1.0.3)
3 | - SwiftyComments (0.2.0):
4 | - SwipeCellKit (= 2.6.0)
5 | - SwipeCellKit (2.6.0)
6 |
7 | DEPENDENCIES:
8 | - SwiftScanner (~> 1.0.2)
9 | - SwiftyComments (from `../`)
10 |
11 | EXTERNAL SOURCES:
12 | SwiftyComments:
13 | :path: ../
14 |
15 | SPEC CHECKSUMS:
16 | SwiftScanner: 70897e82bece6e6a0803b105efed6118c46fa20e
17 | SwiftyComments: 38006364a5aaa93914b98d5af1b2959e35c999b2
18 | SwipeCellKit: 935ca28c187ec6e1ffb2b578cf8ddca842bfdcbb
19 |
20 | PODFILE CHECKSUM: 7711333d67fdc56a7062f213fae2e44bac4a7fa6
21 |
22 | COCOAPODS: 1.4.0
23 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/SwiftyComments.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SwiftyComments",
3 | "version": "0.2.0",
4 | "summary": "UITableView based component designed to display a hierarchy of expandable/foldable comments.",
5 | "description": "SwiftyComments is a UITableView based component designed to display a hierarchy of expandable/foldable comments.",
6 | "homepage": "https://github.com/tsucres/SwiftyComments",
7 | "screenshots": [
8 | "https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/ImgurExample.png",
9 | "https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/HNExample.png",
10 | "https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/RedditExample.png"
11 | ],
12 | "license": {
13 | "type": "MIT",
14 | "file": "LICENSE"
15 | },
16 | "authors": {
17 | "Stéphane Sercu": "stefsercu@gmail.com"
18 | },
19 | "source": {
20 | "git": "https://github.com/tsucres/SwiftyComments.git",
21 | "tag": "0.2.0"
22 | },
23 | "platforms": {
24 | "ios": "9.0"
25 | },
26 | "source_files": "SwiftyComments/Classes/**/*",
27 | "resources": "SwiftyComments/Assets/*.xcassets",
28 | "dependencies": {
29 | "SwipeCellKit": [
30 | "2.6.0"
31 | ]
32 | },
33 | "frameworks": "UIKit",
34 | "swift_version": "4.1"
35 | }
36 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SwiftScanner (1.0.3)
3 | - SwiftyComments (0.2.0):
4 | - SwipeCellKit (= 2.6.0)
5 | - SwipeCellKit (2.6.0)
6 |
7 | DEPENDENCIES:
8 | - SwiftScanner (~> 1.0.2)
9 | - SwiftyComments (from `../`)
10 |
11 | EXTERNAL SOURCES:
12 | SwiftyComments:
13 | :path: ../
14 |
15 | SPEC CHECKSUMS:
16 | SwiftScanner: 70897e82bece6e6a0803b105efed6118c46fa20e
17 | SwiftyComments: 38006364a5aaa93914b98d5af1b2959e35c999b2
18 | SwipeCellKit: 935ca28c187ec6e1ffb2b578cf8ddca842bfdcbb
19 |
20 | PODFILE CHECKSUM: 7711333d67fdc56a7062f213fae2e44bac4a7fa6
21 |
22 | COCOAPODS: 1.4.0
23 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Pods/SwiftScanner/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 daniele margutti
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.
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 | Copyright (c) 2017 Jeremy Koch
3 |
4 | http://jerkoch.com
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/README.md:
--------------------------------------------------------------------------------
1 | # SwipeCellKit
2 |
3 | [](https://travis-ci.org/jerkoch/SwipeCellKit)
4 | [][podLink]
5 | [](https://developer.apple.com/swift/)
6 | [][mitLink]
7 | [][docsLink]
8 | [](https://github.com/Carthage/Carthage)
9 | [](https://twitter.com/mkurabi)
10 |
11 | *Swipeable UITableViewCell/UICollectionViewCell based on the stock Mail.app, implemented in Swift.*
12 |
13 |
14 |
15 | ## About
16 |
17 | A swipeable `UITableViewCell` or `UICollectionViewCell` with support for:
18 |
19 | * Left and right swipe actions
20 | * Action buttons with: *text only, text + image, image only*
21 | * Haptic Feedback
22 | * Customizable transitions: *Border, Drag, and Reveal*
23 | * Customizable action button behavior during swipe
24 | * Animated expansion when dragging past threshold
25 | * Customizable expansion animations
26 | * Support for both `UITableView` and `UICollectionView`
27 | * Accessibility
28 |
29 | ## Background
30 |
31 | Check out my [blog post](https://jerkoch.com/2017/02/07/swiper-no-swiping.html) on how *SwipeCellKit* came to be.
32 |
33 | ## Demo
34 |
35 | ### Transition Styles
36 |
37 | The transition style describes how the action buttons are exposed during the swipe.
38 |
39 | #### Border
40 |
41 |
42 |
43 | #### Drag
44 |
45 |
46 |
47 | #### Reveal
48 |
49 |
50 |
51 | #### Customized
52 |
53 |
54 |
55 | ### Expansion Styles
56 |
57 | The expansion style describes the behavior when the cell is swiped past a defined threshold.
58 |
59 | #### None
60 |
61 |
62 |
63 | #### Selection
64 |
65 |
66 |
67 | #### Destructive
68 |
69 |
70 |
71 | #### Customized
72 |
73 |
74 |
75 | ## Requirements
76 |
77 | * Swift 5.0
78 | * Xcode 10.2+
79 | * iOS 9.0+
80 |
81 | ## Installation
82 |
83 | #### [CocoaPods](http://cocoapods.org) (recommended)
84 |
85 | ````ruby
86 | use_frameworks!
87 |
88 | # Latest release in CocoaPods
89 | pod 'SwipeCellKit'
90 |
91 | # Get the latest on develop
92 | pod 'SwipeCellKit', :git => 'https://github.com/SwipeCellKit/SwipeCellKit.git', :branch => 'develop'
93 |
94 | # If you have NOT upgraded to Swift 5.0, use the last Swift 4.2/Xcode 10.2 compatible release
95 | pod 'SwipeCellKit', '2.5.4'
96 |
97 | # If you have NOT upgraded to Swift 4.2, use the last non-swift 4.2 compatible release
98 | pod 'SwipeCellKit', '2.4.3'
99 | ````
100 |
101 | #### [Carthage](https://github.com/Carthage/Carthage)
102 |
103 | ````bash
104 | github "SwipeCellKit/SwipeCellKit"
105 | ````
106 |
107 | ## Documentation
108 |
109 | Read the [docs][docsLink]. Generated with [jazzy](https://github.com/realm/jazzy). Hosted by [GitHub Pages](https://pages.github.com).
110 |
111 | ## Usage for UITableView
112 |
113 | Set the `delegate` property on `SwipeTableViewCell`:
114 |
115 | ````swift
116 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
117 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! SwipeTableViewCell
118 | cell.delegate = self
119 | return cell
120 | }
121 | ````
122 |
123 | Adopt the `SwipeTableViewCellDelegate` protocol:
124 |
125 | ````swift
126 | func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
127 | guard orientation == .right else { return nil }
128 |
129 | let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
130 | // handle action by updating model with deletion
131 | }
132 |
133 | // customize the action appearance
134 | deleteAction.image = UIImage(named: "delete")
135 |
136 | return [deleteAction]
137 | }
138 | ````
139 |
140 | Optionally, you can implement the `editActionsOptionsForRowAt` method to customize the behavior of the swipe actions:
141 |
142 | ````swift
143 | func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
144 | var options = SwipeOptions()
145 | options.expansionStyle = .destructive
146 | options.transitionStyle = .border
147 | return options
148 | }
149 | ````
150 | ## Usage for UICollectionView
151 |
152 | Set the `delegate` property on `SwipeCollectionViewCell`:
153 |
154 | ````swift
155 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
156 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! SwipeCollectionViewCell
157 | cell.delegate = self
158 | return cell
159 | }
160 | ````
161 |
162 | Adopt the `SwipeCollectionViewCellDelegate` protocol:
163 |
164 | ````swift
165 | func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
166 | guard orientation == .right else { return nil }
167 |
168 | let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
169 | // handle action by updating model with deletion
170 | }
171 |
172 | // customize the action appearance
173 | deleteAction.image = UIImage(named: "delete")
174 |
175 | return [deleteAction]
176 | }
177 | ````
178 |
179 | Optionally, you can implement the `editActionsOptionsForItemAt` method to customize the behavior of the swipe actions:
180 |
181 | ````swift
182 | func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
183 | var options = SwipeOptions()
184 | options.expansionStyle = .destructive
185 | options.transitionStyle = .border
186 | return options
187 | }
188 | ````
189 | ### Transitions
190 |
191 | Three built-in transition styles are provided by `SwipeTransitionStyle`:
192 |
193 | * .border: The visible action area is equally divide between all action buttons.
194 | * .drag: The visible action area is dragged, pinned to the cell, with each action button fully sized as it is exposed.
195 | * .reveal: The visible action area sits behind the cell, pinned to the edge of the table view, and is revealed as the cell is dragged aside.
196 |
197 | See [Customizing Transitions](https://github.com/SwipeCellKit/SwipeCellKit/blob/develop/Guides/Advanced.md) for more details on customizing button appearance as the swipe is performed.
198 |
199 | #### Transition Delegate
200 |
201 | Transition for a `SwipeAction` can be observered by setting a `SwipeActionTransitioning` on the `transitionDelegate` property. This allows you to observe what percentage is visible and access to the underlying `UIButton` for that `SwipeAction`.
202 |
203 | ### Expansion
204 |
205 | Four built-in expansion styles are provided by `SwipeExpansionStyle`:
206 |
207 | * .selection
208 | * .destructive (like Mail.app)
209 | * .destructiveAfterFill (like Mailbox/Tweetbot)
210 | * .fill
211 |
212 | Much effort has gone into making `SwipeExpansionStyle` extremely customizable. If these built-in styles do not meet your needs, see [Customizing Expansion](https://github.com/SwipeCellKit/SwipeCellKit/blob/develop/Guides/Advanced.md) for more details on creating custom styles.
213 |
214 | The built-in `.fill` expansion style requires manual action fulfillment. This means your action handler must call `SwipeAction.fulfill(style:)` at some point during or after invocation to resolve the fill expansion. The supplied `ExpansionFulfillmentStyle` allows you to delete or reset the cell at some later point (possibly after further user interaction).
215 |
216 | The built-in `.destructive`, and `.destructiveAfterFill` expansion styles are configured to automatically perform row deletion when the action handler is invoked (automatic fulfillment). Your deletion behavior may require coordination with other row animations (eg. inside `beginUpdates` and `endUpdates`). In this case, you can easily create a custom `SwipeExpansionStyle` which requires manual fulfillment to trigger deletion:
217 |
218 | ````swift
219 | var options = SwipeTableOptions()
220 | options.expansionStyle = .destructive(automaticallyDelete: false)
221 | ````
222 |
223 | > **NOTE**: You must call `SwipeAction.fulfill(with style:)` at some point while/after your action handler is invoked to trigger deletion. Do not call `deleteRows` directly.
224 |
225 | ````swift
226 | let delete = SwipeAction(style: .destructive, title: nil) { action, indexPath in
227 | // Update model
228 | self.emails.remove(at: indexPath.row)
229 | action.fulfill(with: .delete)
230 | }
231 | ````
232 |
233 | ## Advanced
234 |
235 | See the [Advanced Guide](https://github.com/SwipeCellKit/SwipeCellKit/blob/develop/Guides/Advanced.md) for more details on customization.
236 |
237 | ## Credits
238 |
239 | Maintained by [**@mkurabi**](https://twitter.com/mkurabi).
240 |
241 | ## Showcase
242 |
243 | We're interested in knowing [who's using *SwipeCellKit*](https://github.com/SwipeCellKit/SwipeCellKit/blob/develop/SHOWCASE.md) in their app. Please submit a pull request to add your app!
244 |
245 | ## License
246 |
247 | `SwipeCellKit` is released under an [MIT License][mitLink]. See `LICENSE` for details.
248 |
249 | *Please provide attribution, it is greatly appreciated.*
250 |
251 | [podLink]:https://cocoapods.org/pods/SwipeCellKit
252 | [docsLink]:https://swipecellkit.github.io/SwipeCellKit/
253 | [mitLink]:http://opensource.org/licenses/MIT
254 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UITableView {
11 | var swipeCells: [SwipeTableViewCell] {
12 | return visibleCells.compactMap({ $0 as? SwipeTableViewCell })
13 | }
14 |
15 | func hideSwipeCell() {
16 | swipeCells.forEach { $0.hideSwipe(animated: true) }
17 | }
18 | }
19 |
20 | extension UICollectionView {
21 | var swipeCells: [SwipeCollectionViewCell] {
22 | return visibleCells.compactMap({ $0 as? SwipeCollectionViewCell })
23 | }
24 |
25 | func hideSwipeCell() {
26 | swipeCells.forEach { $0.hideSwipe(animated: true) }
27 | }
28 |
29 | func setGestureEnabled(_ enabled: Bool) {
30 | gestureRecognizers?.forEach {
31 | guard $0 != panGestureRecognizer else { return }
32 |
33 | $0.isEnabled = enabled
34 | }
35 | }
36 | }
37 |
38 | extension UIScrollView {
39 | var swipeables: [Swipeable] {
40 | switch self {
41 | case let tableView as UITableView:
42 | return tableView.swipeCells
43 | case let collectionView as UICollectionView:
44 | return collectionView.swipeCells
45 | default:
46 | return []
47 | }
48 | }
49 |
50 | func hideSwipeables() {
51 | switch self {
52 | case let tableView as UITableView:
53 | tableView.hideSwipeCell()
54 | case let collectionView as UICollectionView:
55 | collectionView.hideSwipeCell()
56 | default:
57 | return
58 | }
59 | }
60 | }
61 |
62 | extension UIPanGestureRecognizer {
63 | func elasticTranslation(in view: UIView?, withLimit limit: CGSize, fromOriginalCenter center: CGPoint, applyingRatio ratio: CGFloat = 0.20) -> CGPoint {
64 | let translation = self.translation(in: view)
65 |
66 | guard let sourceView = self.view else {
67 | return translation
68 | }
69 |
70 | let updatedCenter = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
71 | let distanceFromCenter = CGSize(width: abs(updatedCenter.x - sourceView.bounds.midX),
72 | height: abs(updatedCenter.y - sourceView.bounds.midY))
73 |
74 | let inverseRatio = 1.0 - ratio
75 | let scale: (x: CGFloat, y: CGFloat) = (updatedCenter.x < sourceView.bounds.midX ? -1 : 1, updatedCenter.y < sourceView.bounds.midY ? -1 : 1)
76 | let x = updatedCenter.x - (distanceFromCenter.width > limit.width ? inverseRatio * (distanceFromCenter.width - limit.width) * scale.x : 0)
77 | let y = updatedCenter.y - (distanceFromCenter.height > limit.height ? inverseRatio * (distanceFromCenter.height - limit.height) * scale.y : 0)
78 |
79 | return CGPoint(x: x, y: y)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeAccessibilityCustomAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeAccessibilityCustomAction.swift
3 | // SwipeCellKit
4 | //
5 | // Created by Jeremy Koch
6 | // Copyright © 2017 Jeremy Koch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SwipeAccessibilityCustomAction: UIAccessibilityCustomAction {
12 | let action: SwipeAction
13 | let indexPath: IndexPath
14 |
15 | init?(action: SwipeAction, indexPath: IndexPath, target: Any, selector: Selector) {
16 |
17 | self.action = action
18 | self.indexPath = indexPath
19 |
20 | let name = action.accessibilityLabel ?? action.title ?? action.image?.accessibilityIdentifier ?? nil
21 |
22 | if let name = name {
23 | super.init(name: name, target: target, selector: selector)
24 | } else {
25 | return nil
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeAction.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Constants that help define the appearance of action buttons.
11 | public enum SwipeActionStyle: Int {
12 | /// Apply a style that reflects standard non-destructive actions.
13 | case `default`
14 |
15 | /// Apply a style that reflects destructive actions.
16 | case destructive
17 | }
18 |
19 | /**
20 | The `SwipeAction` object defines a single action to present when the user swipes horizontally in a table/collection item.
21 |
22 | This class lets you define one or more custom actions to display for a given item in your table/collection. Each instance of this class represents a single action to perform and includes the text, formatting information, and behavior for the corresponding button.
23 | */
24 | public class SwipeAction: NSObject {
25 | /// An optional unique action identifier.
26 | public var identifier: String?
27 |
28 | /// The title of the action button.
29 | ///
30 | /// - note: You must specify a title or an image.
31 | public var title: String?
32 |
33 | /// The style applied to the action button.
34 | public var style: SwipeActionStyle
35 |
36 | /// The object that is notified as transitioning occurs.
37 | public var transitionDelegate: SwipeActionTransitioning?
38 |
39 | /// The font to use for the title of the action button.
40 | ///
41 | /// - note: If you do not specify a font, a 15pt system font is used.
42 | public var font: UIFont?
43 |
44 | /// The text color of the action button.
45 | ///
46 | /// - note: If you do not specify a color, white is used.
47 | public var textColor: UIColor?
48 |
49 | /// The highlighted text color of the action button.
50 | ///
51 | /// - note: If you do not specify a color, `textColor` is used.
52 | public var highlightedTextColor: UIColor?
53 |
54 | /// The image used for the action button.
55 | ///
56 | /// - note: You must specify a title or an image.
57 | public var image: UIImage?
58 |
59 | /// The highlighted image used for the action button.
60 | ///
61 | /// - note: If you do not specify a highlight image, the default `image` is used for the highlighted state.
62 | public var highlightedImage: UIImage?
63 |
64 | /// The closure to execute when the user taps the button associated with this action.
65 | public var handler: ((SwipeAction, IndexPath) -> Void)?
66 |
67 | /// The background color of the action button.
68 | ///
69 | /// - note: Use this property to specify the background color for your button. If you do not specify a value for this property, the framework assigns a default color based on the value in the style property.
70 | public var backgroundColor: UIColor?
71 |
72 | /// The highlighted background color of the action button.
73 | ///
74 | /// - note: Use this property to specify the highlighted background color for your button.
75 | public var highlightedBackgroundColor: UIColor?
76 |
77 | /// The visual effect to apply to the action button.
78 | ///
79 | /// - note: Assigning a visual effect object to this property adds that effect to the background of the action button.
80 | public var backgroundEffect: UIVisualEffect?
81 |
82 | /// A Boolean value that determines whether the actions menu is automatically hidden upon selection.
83 | ///
84 | /// - note: When set to `true`, the actions menu is automatically hidden when the action is selected. The default value is `false`.
85 | public var hidesWhenSelected = false
86 |
87 | /**
88 | Constructs a new `SwipeAction` instance.
89 |
90 | - parameter style: The style of the action button.
91 | - parameter title: The title of the action button.
92 | - parameter handler: The closure to execute when the user taps the button associated with this action.
93 | */
94 | public init(style: SwipeActionStyle, title: String?, handler: ((SwipeAction, IndexPath) -> Void)?) {
95 | self.title = title
96 | self.style = style
97 | self.handler = handler
98 | }
99 |
100 | /**
101 | Calling this method performs the configured expansion completion animation including deletion, if necessary. Calling this method more than once has no effect.
102 |
103 | You should only call this method from the implementation of your action `handler` method.
104 |
105 | - parameter style: The desired style for completing the expansion action.
106 | */
107 | public func fulfill(with style: ExpansionFulfillmentStyle) {
108 | completionHandler?(style)
109 | }
110 |
111 | // MARK: - Internal
112 |
113 | internal var completionHandler: ((ExpansionFulfillmentStyle) -> Void)?
114 | }
115 |
116 | /// Describes how expansion should be resolved once the action has been fulfilled.
117 | public enum ExpansionFulfillmentStyle {
118 | /// Implies the item will be deleted upon action fulfillment.
119 | case delete
120 |
121 | /// Implies the item will be reset and the actions view hidden upon action fulfillment.
122 | case reset
123 | }
124 |
125 | // MARK: - Internal
126 |
127 | internal extension SwipeAction {
128 | var hasBackgroundColor: Bool {
129 | return backgroundColor != .clear && backgroundEffect == nil
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeActionButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeActionButton.swift
3 | //
4 | // Created by Jeremy Koch.
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class SwipeActionButton: UIButton {
11 | var spacing: CGFloat = 8
12 | var shouldHighlight = true
13 | var highlightedBackgroundColor: UIColor?
14 |
15 | var maximumImageHeight: CGFloat = 0
16 | var verticalAlignment: SwipeVerticalAlignment = .centerFirstBaseline
17 |
18 |
19 | var currentSpacing: CGFloat {
20 | return (currentTitle?.isEmpty == false && imageHeight > 0) ? spacing : 0
21 | }
22 |
23 | var alignmentRect: CGRect {
24 | let contentRect = self.contentRect(forBounds: bounds)
25 | let titleHeight = titleBoundingRect(with: verticalAlignment == .centerFirstBaseline ? CGRect.infinite.size : contentRect.size).integral.height
26 | let totalHeight = imageHeight + titleHeight + currentSpacing
27 |
28 | return contentRect.center(size: CGSize(width: contentRect.width, height: totalHeight))
29 | }
30 |
31 | private var imageHeight: CGFloat {
32 | get {
33 | return currentImage == nil ? 0 : maximumImageHeight
34 | }
35 | }
36 |
37 | override var intrinsicContentSize: CGSize {
38 | return CGSize(width: UIView.noIntrinsicMetric, height: contentEdgeInsets.top + alignmentRect.height + contentEdgeInsets.bottom)
39 | }
40 |
41 | convenience init(action: SwipeAction) {
42 | self.init(frame: .zero)
43 |
44 | contentHorizontalAlignment = .center
45 |
46 | tintColor = action.textColor ?? .white
47 | let highlightedTextColor = action.highlightedTextColor ?? tintColor
48 | highlightedBackgroundColor = action.highlightedBackgroundColor ?? UIColor.black.withAlphaComponent(0.1)
49 |
50 | titleLabel?.font = action.font ?? UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
51 | titleLabel?.textAlignment = .center
52 | titleLabel?.lineBreakMode = .byWordWrapping
53 | titleLabel?.numberOfLines = 0
54 |
55 | accessibilityLabel = action.accessibilityLabel
56 |
57 | setTitle(action.title, for: .normal)
58 | setTitleColor(tintColor, for: .normal)
59 | setTitleColor(highlightedTextColor, for: .highlighted)
60 | setImage(action.image, for: .normal)
61 | setImage(action.highlightedImage ?? action.image, for: .highlighted)
62 | }
63 |
64 | override var isHighlighted: Bool {
65 | didSet {
66 | guard shouldHighlight else { return }
67 |
68 | backgroundColor = isHighlighted ? highlightedBackgroundColor : .clear
69 | }
70 | }
71 |
72 | func preferredWidth(maximum: CGFloat) -> CGFloat {
73 | let width = maximum > 0 ? maximum : CGFloat.greatestFiniteMagnitude
74 | let textWidth = titleBoundingRect(with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)).width
75 | let imageWidth = currentImage?.size.width ?? 0
76 |
77 | return min(width, max(textWidth, imageWidth) + contentEdgeInsets.left + contentEdgeInsets.right)
78 | }
79 |
80 | func titleBoundingRect(with size: CGSize) -> CGRect {
81 | guard let title = currentTitle, let font = titleLabel?.font else { return .zero }
82 |
83 | return title.boundingRect(with: size,
84 | options: [.usesLineFragmentOrigin],
85 | attributes: [NSAttributedString.Key.font: font],
86 | context: nil).integral
87 | }
88 |
89 | override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
90 | var rect = contentRect.center(size: titleBoundingRect(with: contentRect.size).size)
91 | rect.origin.y = alignmentRect.minY + imageHeight + currentSpacing
92 | return rect.integral
93 | }
94 |
95 | override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
96 | var rect = contentRect.center(size: currentImage?.size ?? .zero)
97 | rect.origin.y = alignmentRect.minY + (imageHeight - rect.height) / 2
98 | return rect
99 | }
100 | }
101 |
102 | extension CGRect {
103 | func center(size: CGSize) -> CGRect {
104 | let dx = width - size.width
105 | let dy = height - size.height
106 | return CGRect(x: origin.x + dx * 0.5, y: origin.y + dy * 0.5, width: size.width, height: size.height)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeActionTransitioning.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeActionTransitioning.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | Adopt the `SwipeActionTransitioning` protocol in objects that implement custom appearance of actions during transition.
12 | */
13 | public protocol SwipeActionTransitioning {
14 | /**
15 | Tells the delegate that transition change has occured.
16 | */
17 | func didTransition(with context: SwipeActionTransitioningContext) -> Void
18 | }
19 |
20 | /**
21 | The `SwipeActionTransitioningContext` type provides information relevant to a specific action as transitioning occurs.
22 | */
23 | public struct SwipeActionTransitioningContext {
24 | /// The unique action identifier.
25 | public let actionIdentifier: String?
26 |
27 | /// The button that is changing.
28 | public let button: UIButton
29 |
30 | /// The old visibility percentage between 0.0 and 1.0.
31 | public let newPercentVisible: CGFloat
32 |
33 | /// The new visibility percentage between 0.0 and 1.0.
34 | public let oldPercentVisible: CGFloat
35 |
36 | internal let wrapperView: UIView
37 |
38 | internal init(actionIdentifier: String?, button: UIButton, newPercentVisible: CGFloat, oldPercentVisible: CGFloat, wrapperView: UIView) {
39 | self.actionIdentifier = actionIdentifier
40 | self.button = button
41 | self.newPercentVisible = newPercentVisible
42 | self.oldPercentVisible = oldPercentVisible
43 | self.wrapperView = wrapperView
44 | }
45 |
46 | /// Sets the background color behind the action button.
47 | ///
48 | /// - parameter color: The background color.
49 | public func setBackgroundColor(_ color: UIColor?) {
50 | wrapperView.backgroundColor = color
51 | }
52 | }
53 |
54 | /**
55 | A scale transition object drives the custom appearance of actions during transition.
56 |
57 | As button's percentage visibility crosses the `threshold`, the `ScaleTransition` object will animate from `initialScale` to `identity`. The default settings provide a "pop-like" effect as the buttons are exposed more than 50%.
58 | */
59 | public struct ScaleTransition: SwipeActionTransitioning {
60 |
61 | /// Returns a `ScaleTransition` instance with default transition options.
62 | public static var `default`: ScaleTransition { return ScaleTransition() }
63 |
64 | /// The duration of the animation.
65 | public let duration: Double
66 |
67 | /// The initial scale factor used before the action button percent visible is greater than the threshold.
68 | public let initialScale: CGFloat
69 |
70 | /// The percent visible threshold that triggers the scaling animation.
71 | public let threshold: CGFloat
72 |
73 | /**
74 | Contructs a new `ScaleTransition` instance.
75 |
76 | - parameter duration: The duration of the animation.
77 |
78 | - parameter initialScale: The initial scale factor used before the action button percent visible is greater than the threshold.
79 |
80 | - parameter threshold: The percent visible threshold that triggers the scaling animation.
81 |
82 | - returns: The new `ScaleTransition` instance.
83 | */
84 | public init(duration: Double = 0.15, initialScale: CGFloat = 0.8, threshold: CGFloat = 0.5) {
85 | self.duration = duration
86 | self.initialScale = initialScale
87 | self.threshold = threshold
88 | }
89 |
90 | /// :nodoc:
91 | public func didTransition(with context: SwipeActionTransitioningContext) -> Void {
92 | if context.oldPercentVisible == 0 {
93 | context.button.transform = .init(scaleX: initialScale, y: initialScale)
94 | }
95 |
96 | if context.oldPercentVisible < threshold && context.newPercentVisible >= threshold {
97 | UIView.animate(withDuration: duration) {
98 | context.button.transform = .identity
99 | }
100 | } else if context.oldPercentVisible >= threshold && context.newPercentVisible < threshold {
101 | UIView.animate(withDuration: duration) {
102 | context.button.transform = .init(scaleX: self.initialScale, y: self.initialScale)
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeAnimator.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | protocol SwipeAnimator {
11 | /// A Boolean value indicating whether the animation is currently running.
12 | var isRunning: Bool { get }
13 |
14 | /**
15 | The animation to be run by the SwipeAnimator
16 |
17 | - parameter animation: The closure to be executed by the animator
18 | */
19 | func addAnimations(_ animation: @escaping () -> Void)
20 |
21 | /**
22 | Completion handler for the animation that is going to be started
23 |
24 | - parameter completion: The closure to be execute on completion of the animator
25 | */
26 | func addCompletion(completion: @escaping (Bool) -> Void)
27 |
28 | /**
29 | Starts the defined animation
30 | */
31 | func startAnimation()
32 |
33 | /**
34 | Starts the defined animation after the given delay
35 |
36 | - parameter delay: Delay of the animation
37 | */
38 | func startAnimation(afterDelay delay: TimeInterval)
39 |
40 | /**
41 | Stops the animations at their current positions.
42 |
43 | - parameter withoutFinishing: A Boolean indicating whether any final actions should be performed.
44 | */
45 | func stopAnimation(_ withoutFinishing: Bool)
46 | }
47 |
48 | @available(iOS 10.0, *)
49 | extension UIViewPropertyAnimator: SwipeAnimator {
50 | func addCompletion(completion: @escaping (Bool) -> Void) {
51 | addCompletion { position in
52 | completion(position == .end)
53 | }
54 | }
55 | }
56 |
57 | class UIViewSpringAnimator: SwipeAnimator {
58 | var isRunning: Bool = false
59 |
60 | let duration:TimeInterval
61 | let damping:CGFloat
62 | let velocity:CGFloat
63 |
64 | var animations:(() -> Void)?
65 | var completion:((Bool) -> Void)?
66 |
67 | required init(duration: TimeInterval,
68 | damping: CGFloat,
69 | initialVelocity velocity: CGFloat = 0) {
70 | self.duration = duration
71 | self.damping = damping
72 | self.velocity = velocity
73 | }
74 |
75 | func addAnimations(_ animations: @escaping () -> Void) {
76 | self.animations = animations
77 | }
78 |
79 | func addCompletion(completion: @escaping (Bool) -> Void) {
80 | self.completion = { [weak self] finished in
81 | guard self?.isRunning == true else { return }
82 |
83 | self?.isRunning = false
84 | self?.animations = nil
85 | self?.completion = nil
86 |
87 | completion(finished)
88 | }
89 | }
90 |
91 | func startAnimation() {
92 | self.startAnimation(afterDelay: 0)
93 | }
94 |
95 | func startAnimation(afterDelay delay:TimeInterval) {
96 | guard let animations = animations else { return }
97 |
98 | isRunning = true
99 |
100 | UIView.animate(withDuration: duration,
101 | delay: delay,
102 | usingSpringWithDamping: damping,
103 | initialSpringVelocity: velocity,
104 | options: [.curveEaseInOut, .allowUserInteraction],
105 | animations: animations,
106 | completion: completion)
107 | }
108 |
109 | func stopAnimation(_ withoutFinishing: Bool) {
110 | isRunning = false
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeCollectionViewCell+Accessibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeCollectionViewCell+Accessibility.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension SwipeCollectionViewCell {
11 | /// :nodoc:
12 | open override func accessibilityElementCount() -> Int {
13 | guard state != .center else {
14 | return super.accessibilityElementCount()
15 | }
16 |
17 | return 1
18 | }
19 |
20 | /// :nodoc:
21 | open override func accessibilityElement(at index: Int) -> Any? {
22 | guard state != .center else {
23 | return super.accessibilityElement(at: index)
24 | }
25 |
26 | return actionsView
27 | }
28 |
29 | /// :nodoc:
30 | open override func index(ofAccessibilityElement element: Any) -> Int {
31 | guard state != .center else {
32 | return super.index(ofAccessibilityElement: element)
33 | }
34 |
35 | return element is SwipeActionsView ? 0 : NSNotFound
36 | }
37 | }
38 |
39 | extension SwipeCollectionViewCell {
40 | /// :nodoc:
41 | open override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
42 | get {
43 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) else {
44 | return super.accessibilityCustomActions
45 | }
46 |
47 | let leftActions = delegate?.collectionView(collectionView, editActionsForItemAt: indexPath, for: .left) ?? []
48 | let rightActions = delegate?.collectionView(collectionView, editActionsForItemAt: indexPath, for: .right) ?? []
49 |
50 | let actions = [rightActions.first, leftActions.first].compactMap({ $0 }) + rightActions.dropFirst() + leftActions.dropFirst()
51 |
52 | if actions.count > 0 {
53 | return actions.compactMap({ SwipeAccessibilityCustomAction(action: $0,
54 | indexPath: indexPath,
55 | target: self,
56 | selector: #selector(performAccessibilityCustomAction(accessibilityCustomAction:))) })
57 | } else {
58 | return super.accessibilityCustomActions
59 | }
60 | }
61 |
62 | set {
63 | super.accessibilityCustomActions = newValue
64 | }
65 | }
66 |
67 | @objc func performAccessibilityCustomAction(accessibilityCustomAction: SwipeAccessibilityCustomAction) -> Bool {
68 | guard let collectionView = collectionView else { return false }
69 |
70 | let swipeAction = accessibilityCustomAction.action
71 |
72 | swipeAction.handler?(swipeAction, accessibilityCustomAction.indexPath)
73 |
74 | if swipeAction.style == .destructive {
75 | collectionView.deleteItems(at: [accessibilityCustomAction.indexPath])
76 | }
77 |
78 | return true
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeCollectionViewCell+Display.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeCollectionViewCell+Display.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension SwipeCollectionViewCell {
11 | /// The point at which the origin of the cell is offset from the non-swiped origin.
12 | public var swipeOffset: CGFloat {
13 | set { setSwipeOffset(newValue, animated: false) }
14 | get { return contentView.frame.midX - bounds.midX }
15 | }
16 |
17 | /**
18 | Hides the swipe actions and returns the cell to center.
19 |
20 | - parameter animated: Specify `true` to animate the hiding of the swipe actions or `false` to hide it immediately.
21 |
22 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
23 | */
24 | public func hideSwipe(animated: Bool, completion: ((Bool) -> Void)? = nil) {
25 | swipeController.hideSwipe(animated: animated, completion: completion)
26 | }
27 |
28 | /**
29 | Shows the swipe actions for the specified orientation.
30 |
31 | - parameter orientation: The side of the cell on which to show the swipe actions.
32 |
33 | - parameter animated: Specify `true` to animate the showing of the swipe actions or `false` to show them immediately.
34 |
35 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
36 | */
37 | public func showSwipe(orientation: SwipeActionsOrientation, animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
38 | setSwipeOffset(.greatestFiniteMagnitude * orientation.scale * -1,
39 | animated: animated,
40 | completion: completion)
41 | }
42 |
43 | /**
44 | The point at which the origin of the cell is offset from the non-swiped origin.
45 |
46 | - parameter offset: A point (expressed in points) that is offset from the non-swiped origin.
47 |
48 | - parameter animated: Specify `true` to animate the transition to the new offset, `false` to make the transition immediate.
49 |
50 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
51 | */
52 | public func setSwipeOffset(_ offset: CGFloat, animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
53 | swipeController.setSwipeOffset(offset, animated: animated, completion: completion)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeCollectionViewCell.swift
3 | // SwipeCellKit
4 | //
5 | // Created by Jeremy Koch
6 | // Copyright © 2017 Jeremy Koch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | The `SwipeCollectionViewCell` class extends `UICollectionViewCell` and provides more flexible options for cell swiping behavior.
13 |
14 |
15 | The default behavior closely matches the stock Mail.app. If you want to customize the transition style (ie. how the action buttons are exposed), or the expansion style (the behavior when the row is swiped passes a defined threshold), you can return the appropriately configured `SwipeOptions` via the `SwipeCollectionViewCellDelegate` delegate.
16 | */
17 | open class SwipeCollectionViewCell: UICollectionViewCell {
18 | /// The object that acts as the delegate of the `SwipeCollectionViewCell`.
19 | public weak var delegate: SwipeCollectionViewCellDelegate?
20 |
21 | var state = SwipeState.center
22 | var actionsView: SwipeActionsView?
23 | var scrollView: UIScrollView? {
24 | return collectionView
25 | }
26 | var indexPath: IndexPath? {
27 | return collectionView?.indexPath(for: self)
28 | }
29 | var panGestureRecognizer: UIGestureRecognizer
30 | {
31 | return swipeController.panGestureRecognizer;
32 | }
33 |
34 | var swipeController: SwipeController!
35 | var isPreviouslySelected = false
36 |
37 | weak var collectionView: UICollectionView?
38 |
39 | /// :nodoc:
40 | open override var frame: CGRect {
41 | set { super.frame = state.isActive ? CGRect(origin: CGPoint(x: frame.minX, y: newValue.minY), size: newValue.size) : newValue }
42 | get { return super.frame }
43 | }
44 |
45 | /// :nodoc:
46 | open override var isHighlighted: Bool {
47 | set {
48 | guard state == .center else { return }
49 | super.isHighlighted = newValue
50 | }
51 | get { return super.isHighlighted }
52 | }
53 |
54 | /// :nodoc:
55 | open override var layoutMargins: UIEdgeInsets {
56 | get {
57 | return frame.origin.x != 0 ? swipeController.originalLayoutMargins : super.layoutMargins
58 | }
59 | set {
60 | super.layoutMargins = newValue
61 | }
62 | }
63 |
64 | /// :nodoc:
65 | override public init(frame: CGRect) {
66 | super.init(frame: frame)
67 |
68 | configure()
69 | }
70 |
71 | /// :nodoc:
72 | required public init?(coder aDecoder: NSCoder) {
73 | super.init(coder: aDecoder)
74 |
75 | configure()
76 | }
77 |
78 | deinit {
79 | collectionView?.panGestureRecognizer.removeTarget(self, action: nil)
80 | }
81 |
82 | func configure() {
83 | contentView.clipsToBounds = false
84 |
85 | swipeController = SwipeController(swipeable: self, actionsContainerView: contentView)
86 | swipeController.delegate = self
87 | }
88 |
89 | /// :nodoc:
90 | override open func prepareForReuse() {
91 | super.prepareForReuse()
92 |
93 | reset()
94 | resetSelectedState()
95 | }
96 |
97 | /// :nodoc:
98 | override open func didMoveToSuperview() {
99 | super.didMoveToSuperview()
100 |
101 | var view: UIView = self
102 | while let superview = view.superview {
103 | view = superview
104 |
105 | if let collectionView = view as? UICollectionView {
106 | self.collectionView = collectionView
107 |
108 | swipeController.scrollView = scrollView
109 |
110 | collectionView.panGestureRecognizer.removeTarget(self, action: nil)
111 | collectionView.panGestureRecognizer.addTarget(self, action: #selector(handleCollectionPan(gesture:)))
112 | return
113 | }
114 | }
115 | }
116 |
117 | /// :nodoc:
118 | open override func willMove(toWindow newWindow: UIWindow?) {
119 | super.willMove(toWindow: newWindow)
120 |
121 | if newWindow == nil {
122 | reset()
123 | }
124 | }
125 |
126 | // Override so we can accept touches anywhere within the cell's original frame.
127 | // This is required to detect touches on the `SwipeActionsView` sitting alongside the
128 | // `SwipeCollectionViewCell`.
129 | /// :nodoc:
130 | override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
131 | guard let superview = superview else { return false }
132 |
133 | let point = convert(point, to: superview)
134 |
135 | if !UIAccessibility.isVoiceOverRunning {
136 | for cell in collectionView?.swipeCells ?? [] {
137 | if (cell.state == .left || cell.state == .right) && !cell.contains(point: point) {
138 | collectionView?.hideSwipeCell()
139 | return false
140 | }
141 | }
142 | }
143 |
144 | return contains(point: point)
145 | }
146 |
147 | func contains(point: CGPoint) -> Bool {
148 | return frame.contains(point)
149 | }
150 |
151 | // Override hitTest(_:with:) here so that we can make sure our `actionsView` gets the touch event
152 | // if it's supposed to, since otherwise, our `contentView` will swallow it and pass it up to
153 | // the collection view.
154 | /// :nodoc:
155 | open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
156 | guard let actionsView = actionsView else { return super.hitTest(point, with: event) }
157 | let modifiedPoint = actionsView.convert(point, from: self)
158 | return actionsView.hitTest(modifiedPoint, with: event) ?? super.hitTest(point, with: event)
159 | }
160 |
161 | /// :nodoc:
162 | override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
163 | return swipeController.gestureRecognizerShouldBegin(gestureRecognizer)
164 | }
165 |
166 | /// :nodoc:
167 | open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
168 | super.traitCollectionDidChange(previousTraitCollection)
169 |
170 | swipeController.traitCollectionDidChange(from: previousTraitCollection, to: self.traitCollection)
171 | }
172 |
173 | @objc func handleCollectionPan(gesture: UIPanGestureRecognizer) {
174 | if gesture.state == .began {
175 | hideSwipe(animated: true)
176 | }
177 | }
178 |
179 | func reset() {
180 | contentView.clipsToBounds = false
181 | swipeController.reset()
182 | collectionView?.setGestureEnabled(true)
183 | }
184 |
185 | func resetSelectedState() {
186 | if isPreviouslySelected {
187 | if let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) {
188 | collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
189 | }
190 | }
191 | isPreviouslySelected = false
192 | }
193 | }
194 |
195 | extension SwipeCollectionViewCell: SwipeControllerDelegate {
196 | func swipeController(_ controller: SwipeController, canBeginEditingSwipeableFor orientation: SwipeActionsOrientation) -> Bool {
197 | return true
198 | }
199 |
200 | func swipeController(_ controller: SwipeController, editActionsForSwipeableFor orientation: SwipeActionsOrientation) -> [SwipeAction]? {
201 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) else { return nil }
202 |
203 | return delegate?.collectionView(collectionView, editActionsForItemAt: indexPath, for: orientation)
204 | }
205 |
206 | func swipeController(_ controller: SwipeController, editActionsOptionsForSwipeableFor orientation: SwipeActionsOrientation) -> SwipeOptions {
207 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) else { return SwipeOptions() }
208 |
209 | return delegate?.collectionView(collectionView, editActionsOptionsForItemAt: indexPath, for: orientation) ?? SwipeOptions()
210 | }
211 |
212 | func swipeController(_ controller: SwipeController, visibleRectFor scrollView: UIScrollView) -> CGRect? {
213 | guard let collectionView = collectionView else { return nil }
214 |
215 | return delegate?.visibleRect(for: collectionView)
216 | }
217 |
218 | func swipeController(_ controller: SwipeController, willBeginEditingSwipeableFor orientation: SwipeActionsOrientation) {
219 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) else { return }
220 |
221 | // Remove highlight and deselect any selected cells
222 | super.isHighlighted = false
223 | isPreviouslySelected = isSelected
224 | collectionView.deselectItem(at: indexPath, animated: false)
225 |
226 | delegate?.collectionView(collectionView, willBeginEditingItemAt: indexPath, for: orientation)
227 | }
228 |
229 | func swipeController(_ controller: SwipeController, didEndEditingSwipeableFor orientation: SwipeActionsOrientation) {
230 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self), let actionsView = self.actionsView else { return }
231 |
232 | resetSelectedState()
233 |
234 | delegate?.collectionView(collectionView, didEndEditingItemAt: indexPath, for: actionsView.orientation)
235 | }
236 |
237 | func swipeController(_ controller: SwipeController, didDeleteSwipeableAt indexPath: IndexPath) {
238 | collectionView?.deleteItems(at: [indexPath])
239 | }
240 | }
241 |
242 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeCollectionViewCellDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeCollectionViewCellDelegate.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | The `SwipeCollectionViewCellDelegate` protocol is adopted by an object that manages the display of action buttons when the item is swiped.
12 | */
13 | public protocol SwipeCollectionViewCellDelegate: class {
14 | /**
15 | Asks the delegate for the actions to display in response to a swipe in the specified item.
16 |
17 | - parameter collectionView: The collection view object which owns the item requesting this information.
18 |
19 | - parameter indexPath: The index path of the item.
20 |
21 | - parameter orientation: The side of the item requesting this information.
22 |
23 | - returns: An array of `SwipeAction` objects representing the actions for the item. Each action you provide is used to create a button that the user can tap. Returning `nil` will prevent swiping for the supplied orientation.
24 | */
25 | func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]?
26 |
27 | /**
28 | Asks the delegate for the display options to be used while presenting the action buttons.
29 |
30 | - parameter collectionView: The collection view object which owns the item requesting this information.
31 |
32 | - parameter indexPath: The index path of the item.
33 |
34 | - parameter orientation: The side of the item requesting this information.
35 |
36 | - returns: A `SwipeOptions` instance which configures the behavior of the action buttons.
37 |
38 | - note: If not implemented, a default `SwipeOptions` instance is used.
39 | */
40 | func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions
41 |
42 | /**
43 | Tells the delegate that the collection view is about to go into editing mode.
44 |
45 | - parameter collectionView: The collection view object providing this information.
46 |
47 | - parameter indexPath: The index path of the item.
48 |
49 | - parameter orientation: The side of the item.
50 | */
51 | func collectionView(_ collectionView: UICollectionView, willBeginEditingItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation)
52 |
53 | /**
54 | Tells the delegate that the collection view has left editing mode.
55 |
56 | - parameter collectionView: The collection view object providing this information.
57 |
58 | - parameter indexPath: The index path of the item.
59 |
60 | - parameter orientation: The side of the item.
61 | */
62 | func collectionView(_ collectionView: UICollectionView, didEndEditingItemAt indexPath: IndexPath?, for orientation: SwipeActionsOrientation)
63 |
64 | /**
65 | Asks the delegate for visibile rectangle of the collection view, which is used to ensure swipe actions are vertically centered within the visible portion of the item.
66 |
67 | - parameter collectionView: The collection view object providing this information.
68 |
69 | - returns: The visible rectangle of the collection view.
70 |
71 | - note: The returned rectange should be in the collection view's own coordinate system. Returning `nil` will result in no vertical offset to be be calculated.
72 | */
73 | func visibleRect(for collectionView: UICollectionView) -> CGRect?
74 | }
75 |
76 | /**
77 | Default implementation of `SwipeCollectionViewCellDelegate` methods
78 | */
79 | public extension SwipeCollectionViewCellDelegate {
80 | func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
81 | return SwipeOptions()
82 | }
83 |
84 | func collectionView(_ collectionView: UICollectionView, willBeginEditingItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) {}
85 |
86 | func collectionView(_ collectionView: UICollectionView, didEndEditingItemAt indexPath: IndexPath?, for orientation: SwipeActionsOrientation) {}
87 |
88 | func visibleRect(for collectionView: UICollectionView) -> CGRect? {
89 | return nil
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeExpanding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeExpanding.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | Adopt the `SwipeExpanding` protocol in objects that implement custom appearance of actions during expansion.
12 | */
13 | public protocol SwipeExpanding {
14 |
15 | /**
16 | Asks your object for the animation timing parameters.
17 |
18 | - parameter buttons: The expansion action button, which includes expanding action plus the remaining actions in the view.
19 |
20 | - parameter expanding: The new expansion state.
21 |
22 | - parameter otherActionButtons: The other action buttons in the view, not including the action button being expanded.
23 | */
24 |
25 | func animationTimingParameters(buttons: [UIButton], expanding: Bool) -> SwipeExpansionAnimationTimingParameters
26 |
27 | /**
28 | Tells your object when the expansion state is changing.
29 |
30 | - parameter button: The expansion action button.
31 |
32 | - parameter expanding: The new expansion state.
33 |
34 | - parameter otherActionButtons: The other action buttons in the view, not including the action button being expanded.
35 | */
36 | func actionButton(_ button: UIButton, didChange expanding: Bool, otherActionButtons: [UIButton])
37 | }
38 |
39 | /**
40 | Specifies timing information for the overall expansion animation.
41 | */
42 | public struct SwipeExpansionAnimationTimingParameters {
43 |
44 | /// Returns a `SwipeExpansionAnimationTimingParameters` instance with default animation parameters.
45 | public static var `default`: SwipeExpansionAnimationTimingParameters { return SwipeExpansionAnimationTimingParameters() }
46 |
47 | /// The duration of the expansion animation.
48 | public var duration: Double
49 |
50 | /// The delay before starting the expansion animation.
51 | public var delay: Double
52 |
53 | /**
54 | Contructs a new `SwipeExpansionAnimationTimingParameters` instance.
55 |
56 | - parameter duration: The duration of the animation.
57 |
58 | - parameter delay: The delay before starting the expansion animation.
59 |
60 | - returns: The new `SwipeExpansionAnimationTimingParameters` instance.
61 | */
62 | public init(duration: Double = 0.6, delay: Double = 0) {
63 | self.duration = duration
64 | self.delay = delay
65 | }
66 | }
67 |
68 | /**
69 | A scale and alpha expansion object drives the custom appearance of the effected actions during expansion.
70 | */
71 | public struct ScaleAndAlphaExpansion: SwipeExpanding {
72 |
73 | /// Returns a `ScaleAndAlphaExpansion` instance with default expansion options.
74 | public static var `default`: ScaleAndAlphaExpansion { return ScaleAndAlphaExpansion() }
75 |
76 | /// The duration of the animation.
77 | public let duration: Double
78 |
79 | /// The scale factor used during animation.
80 | public let scale: CGFloat
81 |
82 | /// The inter-button delay between animations.
83 | public let interButtonDelay: Double
84 |
85 | /**
86 | Contructs a new `ScaleAndAlphaExpansion` instance.
87 |
88 | - parameter duration: The duration of the animation.
89 |
90 | - parameter scale: The scale factor used during animation.
91 |
92 | - parameter interButtonDelay: The inter-button delay between animations.
93 |
94 | - returns: The new `ScaleAndAlphaExpansion` instance.
95 | */
96 | public init(duration: Double = 0.15, scale: CGFloat = 0.8, interButtonDelay: Double = 0.1) {
97 | self.duration = duration
98 | self.scale = scale
99 | self.interButtonDelay = interButtonDelay
100 | }
101 |
102 | /// :nodoc:
103 | public func animationTimingParameters(buttons: [UIButton], expanding: Bool) -> SwipeExpansionAnimationTimingParameters {
104 | var timingParameters = SwipeExpansionAnimationTimingParameters.default
105 | timingParameters.delay = expanding ? interButtonDelay : 0
106 | return timingParameters
107 | }
108 |
109 | /// :nodoc:
110 | public func actionButton(_ button: UIButton, didChange expanding: Bool, otherActionButtons: [UIButton]) {
111 | let buttons = expanding ? otherActionButtons : otherActionButtons.reversed()
112 |
113 | buttons.enumerated().forEach { index, button in
114 | UIView.animate(withDuration: duration, delay: interButtonDelay * Double(expanding ? index : index + 1), options: [], animations: {
115 | button.transform = expanding ? .init(scaleX: self.scale, y: self.scale) : .identity
116 | button.alpha = expanding ? 0.0 : 1.0
117 | }, completion: nil)
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeFeedback.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeFeedback.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SwipeFeedback {
11 | enum Style {
12 | case light
13 | case medium
14 | case heavy
15 | }
16 |
17 | @available(iOS 10.0.1, *)
18 | private var feedbackGenerator: UIImpactFeedbackGenerator? {
19 | get {
20 | return _feedbackGenerator as? UIImpactFeedbackGenerator
21 | }
22 | set {
23 | _feedbackGenerator = newValue
24 | }
25 | }
26 |
27 | private var _feedbackGenerator: Any?
28 |
29 | init(style: Style) {
30 | if #available(iOS 10.0.1, *) {
31 | switch style {
32 | case .light:
33 | feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
34 | case .medium:
35 | feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
36 | case .heavy:
37 | feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
38 | }
39 | } else {
40 | _feedbackGenerator = nil
41 | }
42 | }
43 |
44 | func prepare() {
45 | if #available(iOS 10.0.1, *) {
46 | feedbackGenerator?.prepare()
47 | }
48 | }
49 |
50 | func impactOccurred() {
51 | if #available(iOS 10.0.1, *) {
52 | feedbackGenerator?.impactOccurred()
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Options.swift
3 | //
4 | // Created by Jeremy Koch.
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /// :nodoc:
11 | public typealias SwipeTableOptions = SwipeOptions
12 |
13 | /// The `SwipeOptions` class provides options for transistion and expansion behavior for swiped cell.
14 | public struct SwipeOptions {
15 | /// The transition style. Transition is the style of how the action buttons are exposed during the swipe.
16 | public var transitionStyle: SwipeTransitionStyle = .border
17 |
18 | /// The expansion style. Expansion is the behavior when the cell is swiped past a defined threshold.
19 | public var expansionStyle: SwipeExpansionStyle?
20 |
21 | /// The object that is notified when expansion changes.
22 | ///
23 | /// - note: If an `expansionDelegate` is not provided, and the expanding action is configured with a clear background, the system automatically uses the default `ScaleAndAlphaExpansion` to show/hide underlying actions.
24 | public var expansionDelegate: SwipeExpanding?
25 |
26 | /// The background color behind the action buttons.
27 | public var backgroundColor: UIColor?
28 |
29 | /// The largest allowable button width.
30 | ///
31 | /// - note: By default, the value is set to the table/collection view divided by the number of action buttons minus some additional padding. If the value is set to 0, then word wrapping will not occur and the buttons will grow as large as needed to fit the entire title/image.
32 | public var maximumButtonWidth: CGFloat?
33 |
34 | /// The smallest allowable button width.
35 | ///
36 | /// - note: By default, the system chooses an appropriate size.
37 | public var minimumButtonWidth: CGFloat?
38 |
39 | /// The vertical alignment mode used for when a button image and title are present.
40 | public var buttonVerticalAlignment: SwipeVerticalAlignment = .centerFirstBaseline
41 |
42 | /// The amount of space, in points, between the border and the button image or title.
43 | public var buttonPadding: CGFloat?
44 |
45 | /// The amount of space, in points, between the button image and the button title.
46 | public var buttonSpacing: CGFloat?
47 |
48 | /// Constructs a new `SwipeOptions` instance with default options.
49 | public init() {}
50 | }
51 |
52 | /// Describes the transition style. Transition is the style of how the action buttons are exposed during the swipe.
53 | public enum SwipeTransitionStyle {
54 | /// The visible action area is equally divide between all action buttons.
55 | case border
56 |
57 | /// The visible action area is dragged, pinned to the cell, with each action button fully sized as it is exposed.
58 | case drag
59 |
60 | /// The visible action area sits behind the cell, pinned to the edge of the table/collection view, and is revealed as the cell is dragged aside.
61 | case reveal
62 | }
63 |
64 | /// Describes which side of the cell that the action buttons will be displayed.
65 | public enum SwipeActionsOrientation: CGFloat {
66 | /// The left side of the cell.
67 | case left = -1
68 |
69 | /// The right side of the cell.
70 | case right = 1
71 |
72 | var scale: CGFloat {
73 | return rawValue
74 | }
75 | }
76 |
77 | /// Describes the alignment mode used when action button images and titles are provided.
78 | public enum SwipeVerticalAlignment {
79 | /// All actions will be inspected and the tallest image and first baseline offset of title text will be used to create the alignment rectangle.
80 | ///
81 | /// - note: This mode will ensure the image and first line of each button title and consistently aligned across the swipe view.
82 | case centerFirstBaseline
83 |
84 | /// The action button image height and full title height are used to create the aligment rectange.
85 | ///
86 | /// - note: Buttons with varying number of lines will not be consistently aligned across the swipe view.
87 | case center
88 | }
89 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeTableViewCell+Accessibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeTableViewCell+Accessibility.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension SwipeTableViewCell {
11 | /// :nodoc:
12 | open override func accessibilityElementCount() -> Int {
13 | guard state != .center else {
14 | return super.accessibilityElementCount()
15 | }
16 |
17 | return 1
18 | }
19 |
20 | /// :nodoc:
21 | open override func accessibilityElement(at index: Int) -> Any? {
22 | guard state != .center else {
23 | return super.accessibilityElement(at: index)
24 | }
25 |
26 | return actionsView
27 | }
28 |
29 | /// :nodoc:
30 | open override func index(ofAccessibilityElement element: Any) -> Int {
31 | guard state != .center else {
32 | return super.index(ofAccessibilityElement: element)
33 | }
34 |
35 | return element is SwipeActionsView ? 0 : NSNotFound
36 | }
37 | }
38 |
39 | extension SwipeTableViewCell {
40 | /// :nodoc:
41 | open override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
42 | get {
43 | guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else {
44 | return super.accessibilityCustomActions
45 | }
46 |
47 | let leftActions = delegate?.tableView(tableView, editActionsForRowAt: indexPath, for: .left) ?? []
48 | let rightActions = delegate?.tableView(tableView, editActionsForRowAt: indexPath, for: .right) ?? []
49 |
50 | let actions = [rightActions.first, leftActions.first].compactMap({ $0 }) + rightActions.dropFirst() + leftActions.dropFirst()
51 |
52 | if actions.count > 0 {
53 | return actions.compactMap({ SwipeAccessibilityCustomAction(action: $0,
54 | indexPath: indexPath,
55 | target: self,
56 | selector: #selector(performAccessibilityCustomAction(accessibilityCustomAction:))) })
57 | } else {
58 | return super.accessibilityCustomActions
59 | }
60 | }
61 |
62 | set {
63 | super.accessibilityCustomActions = newValue
64 | }
65 | }
66 |
67 | @objc func performAccessibilityCustomAction(accessibilityCustomAction: SwipeAccessibilityCustomAction) -> Bool {
68 | guard let tableView = tableView else { return false }
69 |
70 | let swipeAction = accessibilityCustomAction.action
71 |
72 | swipeAction.handler?(swipeAction, accessibilityCustomAction.indexPath)
73 |
74 | if swipeAction.style == .destructive {
75 | tableView.deleteRows(at: [accessibilityCustomAction.indexPath], with: .fade)
76 | }
77 |
78 | return true
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeTableViewCell+Display.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeTableViewCell+Display.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension SwipeTableViewCell {
11 | /// The point at which the origin of the cell is offset from the non-swiped origin.
12 | public var swipeOffset: CGFloat {
13 | set { setSwipeOffset(newValue, animated: false) }
14 | get { return frame.midX - bounds.midX }
15 | }
16 |
17 | /**
18 | Hides the swipe actions and returns the cell to center.
19 |
20 | - parameter animated: Specify `true` to animate the hiding of the swipe actions or `false` to hide it immediately.
21 |
22 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
23 | */
24 | public func hideSwipe(animated: Bool, completion: ((Bool) -> Void)? = nil) {
25 | swipeController.hideSwipe(animated: animated, completion: completion)
26 | }
27 |
28 | /**
29 | Shows the swipe actions for the specified orientation.
30 |
31 | - parameter orientation: The side of the cell on which to show the swipe actions.
32 |
33 | - parameter animated: Specify `true` to animate the showing of the swipe actions or `false` to show them immediately.
34 |
35 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
36 | */
37 | public func showSwipe(orientation: SwipeActionsOrientation, animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
38 | setSwipeOffset(.greatestFiniteMagnitude * orientation.scale * -1,
39 | animated: animated,
40 | completion: completion)
41 | }
42 |
43 | /**
44 | The point at which the origin of the cell is offset from the non-swiped origin.
45 |
46 | - parameter offset: A point (expressed in points) that is offset from the non-swiped origin.
47 |
48 | - parameter animated: Specify `true` to animate the transition to the new offset, `false` to make the transition immediate.
49 |
50 | - parameter completion: The closure to be executed once the animation has finished. A `Boolean` argument indicates whether or not the animations actually finished before the completion handler was called.
51 | */
52 | public func setSwipeOffset(_ offset: CGFloat, animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
53 | swipeController.setSwipeOffset(offset, animated: animated, completion: completion)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeTableViewCell.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | The `SwipeTableViewCell` class extends `UITableViewCell` and provides more flexible options for cell swiping behavior.
12 |
13 |
14 | The default behavior closely matches the stock Mail.app. If you want to customize the transition style (ie. how the action buttons are exposed), or the expansion style (the behavior when the row is swiped passes a defined threshold), you can return the appropriately configured `SwipeOptions` via the `SwipeTableViewCellDelegate` delegate.
15 | */
16 | open class SwipeTableViewCell: UITableViewCell {
17 |
18 | /// The object that acts as the delegate of the `SwipeTableViewCell`.
19 | public weak var delegate: SwipeTableViewCellDelegate?
20 |
21 | var state = SwipeState.center
22 | var actionsView: SwipeActionsView?
23 | var scrollView: UIScrollView? {
24 | return tableView
25 | }
26 | var indexPath: IndexPath? {
27 | return tableView?.indexPath(for: self)
28 | }
29 | var panGestureRecognizer: UIGestureRecognizer
30 | {
31 | return swipeController.panGestureRecognizer;
32 | }
33 |
34 | var swipeController: SwipeController!
35 | var isPreviouslySelected = false
36 |
37 | weak var tableView: UITableView?
38 |
39 | /// :nodoc:
40 | open override var frame: CGRect {
41 | set { super.frame = state.isActive ? CGRect(origin: CGPoint(x: frame.minX, y: newValue.minY), size: newValue.size) : newValue }
42 | get { return super.frame }
43 | }
44 |
45 | /// :nodoc:
46 | open override var layoutMargins: UIEdgeInsets {
47 | get {
48 | return frame.origin.x != 0 ? swipeController.originalLayoutMargins : super.layoutMargins
49 | }
50 | set {
51 | super.layoutMargins = newValue
52 | }
53 | }
54 |
55 | /// :nodoc:
56 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
57 | super.init(style: style, reuseIdentifier: reuseIdentifier)
58 |
59 | configure()
60 | }
61 |
62 | /// :nodoc:
63 | required public init?(coder aDecoder: NSCoder) {
64 | super.init(coder: aDecoder)
65 |
66 | configure()
67 | }
68 |
69 | deinit {
70 | tableView?.panGestureRecognizer.removeTarget(self, action: nil)
71 | }
72 |
73 | func configure() {
74 | clipsToBounds = false
75 |
76 | swipeController = SwipeController(swipeable: self, actionsContainerView: self)
77 | swipeController.delegate = self
78 | }
79 |
80 | /// :nodoc:
81 | override open func prepareForReuse() {
82 | super.prepareForReuse()
83 |
84 | reset()
85 | resetSelectedState()
86 | }
87 |
88 | /// :nodoc:
89 | override open func didMoveToSuperview() {
90 | super.didMoveToSuperview()
91 |
92 | var view: UIView = self
93 | while let superview = view.superview {
94 | view = superview
95 |
96 | if let tableView = view as? UITableView {
97 | self.tableView = tableView
98 |
99 | swipeController.scrollView = tableView;
100 |
101 | tableView.panGestureRecognizer.removeTarget(self, action: nil)
102 | tableView.panGestureRecognizer.addTarget(self, action: #selector(handleTablePan(gesture:)))
103 | return
104 | }
105 | }
106 | }
107 |
108 | /// :nodoc:
109 | override open func setEditing(_ editing: Bool, animated: Bool) {
110 | super.setEditing(editing, animated: animated)
111 |
112 | if editing {
113 | hideSwipe(animated: false)
114 | }
115 | }
116 |
117 | // Override so we can accept touches anywhere within the cell's minY/maxY.
118 | // This is required to detect touches on the `SwipeActionsView` sitting alongside the
119 | // `SwipeTableCell`.
120 | /// :nodoc:
121 | override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
122 | guard let superview = superview else { return false }
123 |
124 | let point = convert(point, to: superview)
125 |
126 | if !UIAccessibility.isVoiceOverRunning {
127 | for cell in tableView?.swipeCells ?? [] {
128 | if (cell.state == .left || cell.state == .right) && !cell.contains(point: point) {
129 | tableView?.hideSwipeCell()
130 | return false
131 | }
132 | }
133 | }
134 |
135 | return contains(point: point)
136 | }
137 |
138 | func contains(point: CGPoint) -> Bool {
139 | return point.y > frame.minY && point.y < frame.maxY
140 | }
141 |
142 | /// :nodoc:
143 | override open func setHighlighted(_ highlighted: Bool, animated: Bool) {
144 | if state == .center {
145 | super.setHighlighted(highlighted, animated: animated)
146 | }
147 | }
148 |
149 | /// :nodoc:
150 | override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
151 | return swipeController.gestureRecognizerShouldBegin(gestureRecognizer)
152 | }
153 |
154 | /// :nodoc:
155 | open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
156 | super.traitCollectionDidChange(previousTraitCollection)
157 |
158 | swipeController.traitCollectionDidChange(from: previousTraitCollection, to: self.traitCollection)
159 | }
160 |
161 | @objc func handleTablePan(gesture: UIPanGestureRecognizer) {
162 | if gesture.state == .began {
163 | hideSwipe(animated: true)
164 | }
165 | }
166 |
167 | func reset() {
168 | swipeController.reset()
169 | clipsToBounds = false
170 | }
171 |
172 | func resetSelectedState() {
173 | if isPreviouslySelected {
174 | if let tableView = tableView, let indexPath = tableView.indexPath(for: self) {
175 | tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
176 | }
177 | }
178 | isPreviouslySelected = false
179 | }
180 | }
181 |
182 | extension SwipeTableViewCell: SwipeControllerDelegate {
183 | func swipeController(_ controller: SwipeController, canBeginEditingSwipeableFor orientation: SwipeActionsOrientation) -> Bool {
184 | return self.isEditing == false
185 | }
186 |
187 | func swipeController(_ controller: SwipeController, editActionsForSwipeableFor orientation: SwipeActionsOrientation) -> [SwipeAction]? {
188 | guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return nil }
189 |
190 | return delegate?.tableView(tableView, editActionsForRowAt: indexPath, for: orientation)
191 | }
192 |
193 | func swipeController(_ controller: SwipeController, editActionsOptionsForSwipeableFor orientation: SwipeActionsOrientation) -> SwipeOptions {
194 | guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return SwipeOptions() }
195 |
196 | return delegate?.tableView(tableView, editActionsOptionsForRowAt: indexPath, for: orientation) ?? SwipeOptions()
197 | }
198 |
199 | func swipeController(_ controller: SwipeController, visibleRectFor scrollView: UIScrollView) -> CGRect? {
200 | guard let tableView = tableView else { return nil }
201 |
202 | return delegate?.visibleRect(for: tableView)
203 | }
204 |
205 | func swipeController(_ controller: SwipeController, willBeginEditingSwipeableFor orientation: SwipeActionsOrientation) {
206 | guard let tableView = tableView, let indexPath = tableView.indexPath(for: self) else { return }
207 |
208 | // Remove highlight and deselect any selected cells
209 | super.setHighlighted(false, animated: false)
210 | isPreviouslySelected = isSelected
211 | tableView.deselectRow(at: indexPath, animated: false)
212 |
213 | delegate?.tableView(tableView, willBeginEditingRowAt: indexPath, for: orientation)
214 | }
215 |
216 | func swipeController(_ controller: SwipeController, didEndEditingSwipeableFor orientation: SwipeActionsOrientation) {
217 | guard let tableView = tableView, let indexPath = tableView.indexPath(for: self), let actionsView = self.actionsView else { return }
218 |
219 | resetSelectedState()
220 |
221 | delegate?.tableView(tableView, didEndEditingRowAt: indexPath, for: actionsView.orientation)
222 | }
223 |
224 | func swipeController(_ controller: SwipeController, didDeleteSwipeableAt indexPath: IndexPath) {
225 | tableView?.deleteRows(at: [indexPath], with: .none)
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeTableViewCellDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeTableViewCellDelegate.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | The `SwipeTableViewCellDelegate` protocol is adopted by an object that manages the display of action buttons when the cell is swiped.
12 | */
13 | public protocol SwipeTableViewCellDelegate: class {
14 |
15 | /**
16 | Asks the delegate for the actions to display in response to a swipe in the specified row.
17 |
18 | - parameter tableView: The table view object which owns the cell requesting this information.
19 |
20 | - parameter indexPath: The index path of the row.
21 |
22 | - parameter orientation: The side of the cell requesting this information.
23 |
24 | - returns: An array of `SwipeAction` objects representing the actions for the row. Each action you provide is used to create a button that the user can tap. Returning `nil` will prevent swiping for the supplied orientation.
25 | */
26 | func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]?
27 |
28 | /**
29 | Asks the delegate for the display options to be used while presenting the action buttons.
30 |
31 | - parameter tableView: The table view object which owns the cell requesting this information.
32 |
33 | - parameter indexPath: The index path of the row.
34 |
35 | - parameter orientation: The side of the cell requesting this information.
36 |
37 | - returns: A `SwipeOptions` instance which configures the behavior of the action buttons.
38 |
39 | - note: If not implemented, a default `SwipeOptions` instance is used.
40 | */
41 | func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions
42 |
43 | /**
44 | Tells the delegate that the table view is about to go into editing mode.
45 |
46 | - parameter tableView: The table view object providing this information.
47 |
48 | - parameter indexPath: The index path of the row.
49 |
50 | - parameter orientation: The side of the cell.
51 | */
52 | func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation)
53 |
54 | /**
55 | Tells the delegate that the table view has left editing mode.
56 |
57 | - parameter tableView: The table view object providing this information.
58 |
59 | - parameter indexPath: The index path of the row.
60 |
61 | - parameter orientation: The side of the cell.
62 | */
63 | func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?, for orientation: SwipeActionsOrientation)
64 |
65 | /**
66 | Asks the delegate for visibile rectangle of the table view, which is used to ensure swipe actions are vertically centered within the visible portion of the cell.
67 |
68 | - parameter tableView: The table view object providing this information.
69 |
70 | - returns: The visible rectangle of the table view.
71 |
72 | - note: The returned rectange should be in the table view's own coordinate system. Returning `nil` will result in no vertical offset to be be calculated.
73 | */
74 | func visibleRect(for tableView: UITableView) -> CGRect?
75 | }
76 |
77 | /**
78 | Default implementation of `SwipeTableViewCellDelegate` methods
79 | */
80 | public extension SwipeTableViewCellDelegate {
81 | func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
82 | return SwipeOptions()
83 | }
84 |
85 | func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) {}
86 |
87 | func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?, for orientation: SwipeActionsOrientation) {}
88 |
89 | func visibleRect(for tableView: UITableView) -> CGRect? {
90 | return nil
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/SwipeTransitionLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwipeTransitionLayout.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | // MARK: - Layout Protocol
11 |
12 | protocol SwipeTransitionLayout {
13 | func container(view: UIView, didChangeVisibleWidthWithContext context: ActionsViewLayoutContext)
14 | func layout(view: UIView, atIndex index: Int, with context: ActionsViewLayoutContext)
15 | func visibleWidthsForViews(with context: ActionsViewLayoutContext) -> [CGFloat]
16 | }
17 |
18 | // MARK: - Layout Context
19 |
20 | struct ActionsViewLayoutContext {
21 | let numberOfActions: Int
22 | let orientation: SwipeActionsOrientation
23 | let contentSize: CGSize
24 | let visibleWidth: CGFloat
25 | let minimumButtonWidth: CGFloat
26 |
27 | init(numberOfActions: Int, orientation: SwipeActionsOrientation, contentSize: CGSize = .zero, visibleWidth: CGFloat = 0, minimumButtonWidth: CGFloat = 0) {
28 | self.numberOfActions = numberOfActions
29 | self.orientation = orientation
30 | self.contentSize = contentSize
31 | self.visibleWidth = visibleWidth
32 | self.minimumButtonWidth = minimumButtonWidth
33 | }
34 |
35 | static func newContext(for actionsView: SwipeActionsView) -> ActionsViewLayoutContext {
36 | return ActionsViewLayoutContext(numberOfActions: actionsView.actions.count,
37 | orientation: actionsView.orientation,
38 | contentSize: actionsView.contentSize,
39 | visibleWidth: actionsView.visibleWidth,
40 | minimumButtonWidth: actionsView.minimumButtonWidth)
41 | }
42 | }
43 |
44 | // MARK: - Supported Layout Implementations
45 |
46 | class BorderTransitionLayout: SwipeTransitionLayout {
47 | func container(view: UIView, didChangeVisibleWidthWithContext context: ActionsViewLayoutContext) {
48 | }
49 |
50 | func layout(view: UIView, atIndex index: Int, with context: ActionsViewLayoutContext) {
51 | let diff = context.visibleWidth - context.contentSize.width
52 | view.frame.origin.x = (CGFloat(index) * context.contentSize.width / CGFloat(context.numberOfActions) + diff) * context.orientation.scale
53 | }
54 |
55 | func visibleWidthsForViews(with context: ActionsViewLayoutContext) -> [CGFloat] {
56 | let diff = context.visibleWidth - context.contentSize.width
57 | let visibleWidth = context.contentSize.width / CGFloat(context.numberOfActions) + diff
58 |
59 | // visible widths are all the same regardless of the action view position
60 | return (0.. [CGFloat] {
74 | return (0.. [CGFloat] {
86 | return super.visibleWidthsForViews(with: context).reversed()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Example/Pods/SwipeCellKit/Source/Swipeable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Swipeable.swift
3 | //
4 | // Created by Jeremy Koch
5 | // Copyright © 2017 Jeremy Koch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | // MARK: - Internal
11 |
12 | protocol Swipeable {
13 | var state: SwipeState { get set }
14 |
15 | var actionsView: SwipeActionsView? { get set }
16 |
17 | var frame: CGRect { get }
18 |
19 | var scrollView: UIScrollView? { get }
20 |
21 | var indexPath: IndexPath? { get }
22 |
23 | var panGestureRecognizer: UIGestureRecognizer { get }
24 | }
25 |
26 | extension SwipeTableViewCell: Swipeable {}
27 | extension SwipeCollectionViewCell: Swipeable {}
28 |
29 | enum SwipeState: Int {
30 | case center = 0
31 | case left
32 | case right
33 | case dragging
34 | case animatingToCenter
35 |
36 | init(orientation: SwipeActionsOrientation) {
37 | self = orientation == .left ? .left : .right
38 | }
39 |
40 | var isActive: Bool { return self != .center }
41 | }
42 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## SwiftScanner
5 |
6 | Copyright (c) 2015 daniele margutti
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | ## SwiftyComments
27 |
28 | Copyright (c) 2017 Stéphane Sercu
29 |
30 | Permission is hereby granted, free of charge, to any person obtaining a copy
31 | of this software and associated documentation files (the "Software"), to deal
32 | in the Software without restriction, including without limitation the rights
33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34 | copies of the Software, and to permit persons to whom the Software is
35 | furnished to do so, subject to the following conditions:
36 |
37 | The above copyright notice and this permission notice shall be included in
38 | all copies or substantial portions of the Software.
39 |
40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46 | THE SOFTWARE.
47 |
48 |
49 | ## SwipeCellKit
50 |
51 | MIT License
52 | Copyright (c) 2017 Jeremy Koch
53 |
54 | http://jerkoch.com
55 |
56 | Permission is hereby granted, free of charge, to any person obtaining a copy
57 | of this software and associated documentation files (the "Software"), to deal
58 | in the Software without restriction, including without limitation the rights
59 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
60 | copies of the Software, and to permit persons to whom the Software is
61 | furnished to do so, subject to the following conditions:
62 |
63 | The above copyright notice and this permission notice shall be included in all
64 | copies or substantial portions of the Software.
65 |
66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
68 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
69 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
70 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
71 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
72 | SOFTWARE.
73 | Generated by CocoaPods - https://cocoapods.org
74 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Copyright (c) 2015 daniele margutti <me@danielemargutti.com>
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 | License
37 | MIT
38 | Title
39 | SwiftScanner
40 | Type
41 | PSGroupSpecifier
42 |
43 |
44 | FooterText
45 | Copyright (c) 2017 Stéphane Sercu <stefsercu@gmail.com>
46 |
47 | Permission is hereby granted, free of charge, to any person obtaining a copy
48 | of this software and associated documentation files (the "Software"), to deal
49 | in the Software without restriction, including without limitation the rights
50 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
51 | copies of the Software, and to permit persons to whom the Software is
52 | furnished to do so, subject to the following conditions:
53 |
54 | The above copyright notice and this permission notice shall be included in
55 | all copies or substantial portions of the Software.
56 |
57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
63 | THE SOFTWARE.
64 |
65 | License
66 | MIT
67 | Title
68 | SwiftyComments
69 | Type
70 | PSGroupSpecifier
71 |
72 |
73 | FooterText
74 | MIT License
75 | Copyright (c) 2017 Jeremy Koch
76 |
77 | http://jerkoch.com
78 |
79 | Permission is hereby granted, free of charge, to any person obtaining a copy
80 | of this software and associated documentation files (the "Software"), to deal
81 | in the Software without restriction, including without limitation the rights
82 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83 | copies of the Software, and to permit persons to whom the Software is
84 | furnished to do so, subject to the following conditions:
85 |
86 | The above copyright notice and this permission notice shall be included in all
87 | copies or substantial portions of the Software.
88 |
89 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95 | SOFTWARE.
96 | License
97 | MIT
98 | Title
99 | SwipeCellKit
100 | Type
101 | PSGroupSpecifier
102 |
103 |
104 | FooterText
105 | Generated by CocoaPods - https://cocoapods.org
106 | Title
107 |
108 | Type
109 | PSGroupSpecifier
110 |
111 |
112 | StringsTable
113 | Acknowledgements
114 | Title
115 | Acknowledgements
116 |
117 |
118 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_SwiftyComments_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_SwiftyComments_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | # Used as a return value for each invocation of `strip_invalid_archs` function.
10 | STRIP_BINARY_RETVAL=0
11 |
12 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
13 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
14 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
15 |
16 | # Copies and strips a vendored framework
17 | install_framework()
18 | {
19 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
20 | local source="${BUILT_PRODUCTS_DIR}/$1"
21 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
22 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
23 | elif [ -r "$1" ]; then
24 | local source="$1"
25 | fi
26 |
27 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
28 |
29 | if [ -L "${source}" ]; then
30 | echo "Symlinked..."
31 | source="$(readlink "${source}")"
32 | fi
33 |
34 | # Use filter instead of exclude so missing patterns don't throw errors.
35 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
36 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
37 |
38 | local basename
39 | basename="$(basename -s .framework "$1")"
40 | binary="${destination}/${basename}.framework/${basename}"
41 | if ! [ -r "$binary" ]; then
42 | binary="${destination}/${basename}"
43 | fi
44 |
45 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
46 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
47 | strip_invalid_archs "$binary"
48 | fi
49 |
50 | # Resign the code if required by the build settings to avoid unstable apps
51 | code_sign_if_enabled "${destination}/$(basename "$1")"
52 |
53 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
54 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
55 | local swift_runtime_libs
56 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
57 | for lib in $swift_runtime_libs; do
58 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
59 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
60 | code_sign_if_enabled "${destination}/${lib}"
61 | done
62 | fi
63 | }
64 |
65 | # Copies and strips a vendored dSYM
66 | install_dsym() {
67 | local source="$1"
68 | if [ -r "$source" ]; then
69 | # Copy the dSYM into a the targets temp dir.
70 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
71 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
72 |
73 | local basename
74 | basename="$(basename -s .framework.dSYM "$source")"
75 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
76 |
77 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
78 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
79 | strip_invalid_archs "$binary"
80 | fi
81 |
82 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
83 | # Move the stripped file into its final destination.
84 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
85 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
86 | else
87 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
88 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
89 | fi
90 | fi
91 | }
92 |
93 | # Signs a framework with the provided identity
94 | code_sign_if_enabled() {
95 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
96 | # Use the current code_sign_identitiy
97 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
98 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
99 |
100 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
101 | code_sign_cmd="$code_sign_cmd &"
102 | fi
103 | echo "$code_sign_cmd"
104 | eval "$code_sign_cmd"
105 | fi
106 | }
107 |
108 | # Strip invalid architectures
109 | strip_invalid_archs() {
110 | binary="$1"
111 | # Get architectures for current target binary
112 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
113 | # Intersect them with the architectures we are building for
114 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
115 | # If there are no archs supported by this binary then warn the user
116 | if [[ -z "$intersected_archs" ]]; then
117 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
118 | STRIP_BINARY_RETVAL=0
119 | return
120 | fi
121 | stripped=""
122 | for arch in $binary_archs; do
123 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
124 | # Strip non-valid architectures in-place
125 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
126 | stripped="$stripped $arch"
127 | fi
128 | done
129 | if [[ "$stripped" ]]; then
130 | echo "Stripped $binary of architectures:$stripped"
131 | fi
132 | STRIP_BINARY_RETVAL=1
133 | }
134 |
135 |
136 | if [[ "$CONFIGURATION" == "Debug" ]]; then
137 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftScanner/SwiftScanner.framework"
138 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftyComments/SwiftyComments.framework"
139 | install_framework "${BUILT_PRODUCTS_DIR}/SwipeCellKit/SwipeCellKit.framework"
140 | fi
141 | if [[ "$CONFIGURATION" == "Release" ]]; then
142 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftScanner/SwiftScanner.framework"
143 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftyComments/SwiftyComments.framework"
144 | install_framework "${BUILT_PRODUCTS_DIR}/SwipeCellKit/SwipeCellKit.framework"
145 | fi
146 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
147 | wait
148 | fi
149 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_SwiftyComments_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_SwiftyComments_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SwiftScanner" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyComments" "${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftScanner/SwiftScanner.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyComments/SwiftyComments.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit/SwipeCellKit.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "SwiftScanner" -framework "SwiftyComments" -framework "SwipeCellKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_SwiftyComments_Example {
2 | umbrella header "Pods-SwiftyComments_Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-SwiftyComments_Example/Pods-SwiftyComments_Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SwiftScanner" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyComments" "${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftScanner/SwiftScanner.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyComments/SwiftyComments.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit/SwipeCellKit.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "SwiftScanner" -framework "SwiftyComments" -framework "SwipeCellKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.3
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/SwiftScanner-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SwiftScanner : NSObject
3 | @end
4 | @implementation PodsDummy_SwiftScanner
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/SwiftScanner-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/SwiftScanner-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double SwiftScannerVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char SwiftScannerVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/SwiftScanner.modulemap:
--------------------------------------------------------------------------------
1 | framework module SwiftScanner {
2 | umbrella header "SwiftScanner-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftScanner/SwiftScanner.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftScanner
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftScanner
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.2.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/SwiftyComments-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SwiftyComments : NSObject
3 | @end
4 | @implementation PodsDummy_SwiftyComments
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/SwiftyComments-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/SwiftyComments-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double SwiftyCommentsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char SwiftyCommentsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/SwiftyComments.modulemap:
--------------------------------------------------------------------------------
1 | framework module SwiftyComments {
2 | umbrella header "SwiftyComments-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftyComments/SwiftyComments.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyComments
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
5 | OTHER_LDFLAGS = -framework "UIKit"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 2.6.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/SwipeCellKit-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SwipeCellKit : NSObject
3 | @end
4 | @implementation PodsDummy_SwipeCellKit
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/SwipeCellKit-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/SwipeCellKit-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double SwipeCellKitVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char SwipeCellKitVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/SwipeCellKit.modulemap:
--------------------------------------------------------------------------------
1 | framework module SwipeCellKit {
2 | umbrella header "SwipeCellKit-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwipeCellKit/SwipeCellKit.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwipeCellKit
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwipeCellKit
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Example/SwiftyComments.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/SwiftyComments.xcodeproj/xcshareddata/xcschemes/SwiftyComments-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
76 |
78 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/Example/SwiftyComments.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/SwiftyComments.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SwiftyComments
4 | //
5 | // Created by tsucres on 10/09/2017.
6 | // Copyright (c) 2017 tsucres. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 | window?.makeKeyAndVisible()
20 | let navController = UINavigationController(rootViewController: DemosTableViewController(style: .plain))
21 | window?.rootViewController = navController
22 |
23 | return true
24 | }
25 |
26 | func applicationWillResignActive(_ application: UIApplication) {
27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
29 | }
30 |
31 | func applicationDidEnterBackground(_ application: UIApplication) {
32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
34 | }
35 |
36 | func applicationWillEnterForeground(_ application: UIApplication) {
37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 | func applicationDidBecomeActive(_ application: UIApplication) {
41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
42 | }
43 |
44 | func applicationWillTerminate(_ application: UIApplication) {
45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
46 | }
47 |
48 |
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/DemosTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemosTableViewController.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 1/10/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class DemosTableViewController: UITableViewController {
12 | private let demoNames = ["Basic", "Imgur", "Reddit", "Hackernews", "Hackernews - foldable", "Imgur - Full expanded", "Reddit - foldable"]
13 |
14 | private let controllerClasses = [SimpleCommentsViewController.self,
15 | ImgurCommentsViewController.self,
16 | RedditCommentsViewController.self,
17 | HNCommentsViewController.self,
18 | FoldableHNCommentsViewController.self,
19 | FullyExpandedImgurVC.self,
20 | FoldableRedditCommentsViewController.self]
21 |
22 | private var defaultNavBarTintColor: UIColor?
23 | private var defaultNavTintColor: UIColor?
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | self.defaultNavBarTintColor = self.navigationController?.navigationBar.barTintColor
27 | self.defaultNavTintColor = self.navigationController?.navigationBar.tintColor
28 |
29 | }
30 |
31 | override func viewWillAppear(_ animated: Bool) {
32 | super.viewWillAppear(animated)
33 | if #available(iOS 13.0, *) {
34 | self.navigationController?.navigationBar.barTintColor = .systemBackground
35 | self.navigationController?.navigationBar.tintColor = .label
36 | } else {
37 | self.navigationController?.navigationBar.barTintColor = self.defaultNavBarTintColor ?? .white
38 | self.navigationController?.navigationBar.tintColor = self.defaultNavTintColor ?? .black
39 | }
40 |
41 | }
42 |
43 | override var preferredStatusBarStyle: UIStatusBarStyle {
44 | return .default
45 | }
46 |
47 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
48 | self.navigationController!.pushViewController(controllerClasses[indexPath.row].init(), animated: true)
49 | }
50 |
51 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
52 | return controllerClasses.count
53 | }
54 |
55 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
56 | let cellId = "cellId"
57 | var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
58 | if cell == nil {
59 | cell = UITableViewCell(style: .default, reuseIdentifier: cellId)
60 | }
61 | cell!.textLabel?.text = demoNames[indexPath.row]
62 | return cell!
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/HNCommentContentParser.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 | import SwiftScanner
4 |
5 | // ==========================
6 | // Adaptation of https://github.com/malcommac/SwiftRichString/blob/master/Sources/SwiftRichString/MarkupString.swift
7 | // ==========================
8 |
9 | class HNCommentContentParser {
10 | static let htmlEntities = ["quot":"\"","amp":"&","apos":"'","lt":"<","gt":">", "#x2F":"/", "#38":"&", "#62":">", "#x27":"'", "#60":"<"]
11 |
12 | /// Turns the html of a comment from Hackernews to an AttributedString
13 | /// valid html = https://news.ycombinator.com/formatdoc
14 | public static func buildAttributedText(From markup: String, textColor: UIColor = .white, fontSize: CGFloat = 14, linkColor: UIColor = .orange) -> NSAttributedString? {
15 | guard let (text, tags) = try? parse(markup) else {
16 | return nil
17 | }
18 |
19 |
20 |
21 | let attributedText = NSMutableAttributedString(string: text)
22 | let font = UIFont.systemFont(ofSize: fontSize, weight: .regular)
23 | let paragraphStyle = NSMutableParagraphStyle()
24 | paragraphStyle.paragraphSpacing = 0.4*font.lineHeight
25 | paragraphStyle.lineSpacing = 0.1*font.lineHeight
26 | attributedText.addAttributes([.font: font,
27 | .foregroundColor: textColor,
28 | NSAttributedString.Key.paragraphStyle: paragraphStyle
29 | ], range: NSRange.init(location: 0, length: text.count))
30 | for tag in tags {
31 | if tag.name == "a" {
32 | let initialLen = text.count
33 | var aContent = String(text.dropFirst(tag.range!.lowerBound))
34 | aContent = String(aContent.dropLast(initialLen - tag.range!.upperBound))
35 | let attributes: [NSAttributedString.Key: Any] = [.link: aContent, .underlineStyle: NSUnderlineStyle.single.rawValue, .foregroundColor: linkColor]
36 | attributedText.addAttributes(attributes, range: NSRange(tag.range!))
37 | } else if tag.name == "code" {
38 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Courier", size: 14)! ]
39 | attributedText.addAttributes(attributes, range: NSRange(tag.range!))
40 | } else if tag.name == "i" {
41 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.italicSystemFont(ofSize: 14)]
42 | attributedText.addAttributes(attributes, range: NSRange(tag.range!))
43 | } else if tag.name == "h1" { // for AskHN only
44 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 18)]
45 | attributedText.addAttributes(attributes, range: NSRange(tag.range!))
46 | }
47 |
48 | }
49 | return NSAttributedString(attributedString: attributedText)
50 | }
51 |
52 | private static func parse(_ content: String) throws -> (text: String, tags: [Tag]) {
53 | let scanner = StringScanner(content)
54 | var tagStacks: [Tag] = [] // temporary stack
55 | var tagsList: [Tag] = [] // final stack with all found tags
56 |
57 | var plainText = String()
58 | while !scanner.isAtEnd {
59 | // scan text and accumulate it until we found a special entity (starting with &) or an open tag character (<)
60 | if let textString = try scanner.scan(upTo: CharacterSet(charactersIn: "<&")) {
61 | plainText += textString
62 | } else {
63 | // We have encountered a special entity or an open/close tag
64 | if scanner.match("&") == true {
65 | // It's a special entity so get it until the end (; character) and replace it with encoded char
66 | if let entityValue = try scanner.scan(upTo: ";") {
67 | if let spec = htmlEntities[entityValue] {
68 | plainText += spec
69 | }
70 | try scanner.skip()
71 | }
72 | continue
73 | } else if scanner.match("<") == true {
74 | let rawTag = try scanner.scan(upTo: ">")
75 |
76 | if var tag = Tag(raw: rawTag) {
77 | if tag.name == "p" { // it's a return carriage, we want to translate it directly
78 | plainText += "\n"
79 | continue
80 | }
81 | let endIndex = plainText.count
82 | if tag.isOpenTag == true {
83 | // it's an open tag, store the start index
84 | // (the upperbund is temporary the same of the lower bound, we will update it
85 | // at the end, before adding it to the list of the tags)
86 | tag.range = endIndex..?
114 | /// true if tag represent an open tag
115 | fileprivate(set) var isOpenTag: Bool
116 | /// the content of the href attribute (only if the tag name is 'a'
117 | public var href: String?
118 |
119 | public init?(raw content: String?) {
120 | guard let content = content else {
121 | return nil
122 | }
123 | // Read tag name
124 | let tagScanner = StringScanner(content)
125 | do {
126 | self.isOpenTag = (tagScanner.match("/") == false)
127 | guard let name = try tagScanner.scan(untilIn: CharacterSet.alphanumerics) else {
128 | return nil
129 | }
130 | self.name = name
131 | if self.name == "a" {
132 | let linkPattern = "href=\\\"(.*)\\\"" // #bestURLRegexEver
133 | let linkRegex = try! NSRegularExpression(pattern: linkPattern, options: [])
134 | let matches = linkRegex.matches(in: content, options: [], range: NSRange(location: 0, length: content.count))
135 | if matches.count > 0 {
136 | let linkMatch = matches[0]
137 | if linkMatch.numberOfRanges == 2 {
138 | let startIndex = String.Index(encodedOffset: linkMatch.range(at: 1).lowerBound)
139 | let endIndex = String.Index(encodedOffset: linkMatch.range(at: 1).upperBound)
140 | self.href = String(content[startIndex.. CommentCell {
88 | let commentCell = tableView.dequeueReusableCell(withIdentifier: commentCellId) as! HNCommentCell
89 | let comment = commentModel as! AttributedTextComment
90 | commentCell.level = comment.level
91 | commentCell.commentContentAttributed = comment.attributedContent
92 | commentCell.posterName = comment.posterName
93 | commentCell.date = comment.soMuchTimeAgo()
94 | commentCell.nReplies = comment.replies.count
95 | commentCell.upvoted = comment.upvoted
96 |
97 | commentCell.content.upvoteBtn.addTarget(self, action: #selector(upvoted), for: .touchUpInside)
98 | commentCell.content.upvoteBtn.tag = indexPath.row
99 | commentCell.content.replyBtn.addTarget(self, action: #selector(replied), for: .touchUpInside)
100 | commentCell.content.replyBtn.tag = indexPath.row
101 |
102 | commentCell.delegate = nil
103 | commentCell.isFolded = comment.isFolded && !isCellExpanded(indexPath: indexPath)
104 | // ===========================================
105 | // If you want to experiment loading the attributedText with DocumentType.html
106 | // uncoment the following
107 | /*
108 | if let data = comment.body!.data(using: String.Encoding.utf8, allowLossyConversion: true) {
109 | commentCell.commentContentAttributed = try? NSAttributedString(data: data,
110 | options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
111 | documentAttributes: nil)
112 | }
113 | */
114 | // ===========================================
115 |
116 |
117 | // Enable the shouldInterractWithURL
118 | //commentCell.content.bodyView.delegate = self
119 |
120 | return commentCell
121 | }
122 |
123 | @objc func upvoted(sender: UIButton) {
124 | print("Upvote comment at index \(sender.tag) of currentlyDisplayed")
125 | }
126 | @objc func replied(sender: UIButton) {
127 | print("Reply to comment at index \(sender.tag) of currentlyDisplayed")
128 | }
129 |
130 | // What to do when the user taps on a link in the comment
131 | /*func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
132 | let startPos = textView.position(from: textView.beginningOfDocument, in: .right, offset: characterRange.location)
133 | let endPos = textView.position(from: textView.beginningOfDocument, in: .right, offset: characterRange.location + characterRange.length)
134 |
135 | return true
136 | }*/
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/HNRespond.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "HNRespond.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/HNRespond.imageset/HNRespond.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/HNRespond.imageset/HNRespond.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/downvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "downvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/downvte.imageset/downvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/downvte.imageset/downvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/exprt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "exprt.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/exprt.imageset/exprt.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/exprt.imageset/exprt.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/hadDownvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "hadDownvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/hadDownvte.imageset/hadDownvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/hadDownvte.imageset/hadDownvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/hadUpvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "hadUpvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/hadUpvte.imageset/hadUpvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/hadUpvte.imageset/hadUpvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/imgurDownvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "imgurDownvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/imgurDownvte.imageset/imgurDownvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/imgurDownvte.imageset/imgurDownvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/imgurUpvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "imgurUpvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/imgurUpvte.imageset/imgurUpvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/imgurUpvte.imageset/imgurUpvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/mre.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "mre.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/mre.imageset/mre.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/mre.imageset/mre.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/upvte.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "upvte.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/SwiftyComments/Images.xcassets/upvte.imageset/upvte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Example/SwiftyComments/Images.xcassets/upvte.imageset/upvte.pdf
--------------------------------------------------------------------------------
/Example/SwiftyComments/ImgurCommentsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImgurCommentsViewController.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 27/06/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyComments
11 |
12 | class FullyExpandedImgurVC: ImgurCommentsViewController {
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | self.fullyExpanded = true
16 | }
17 | }
18 | class ImgurCommentsViewController: CommentsViewController {
19 | private let commentCellId = "imgurComentCellId"
20 | var allComments: [RichComment] = [] // All the comments (nested, not in a linear format)
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | tableView.register(ImgurCommentCell.self, forCellReuseIdentifier: commentCellId)
25 |
26 | tableView.backgroundColor = ImgurConstants.backgroundColor
27 |
28 | allComments = RandomDiscussion.generate().comments
29 |
30 | //linearizeComments(comments: allComments, linearizedComment: ¤tlyDisplayed)
31 |
32 | currentlyDisplayed = allComments
33 |
34 | }
35 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> CommentCell {
36 | let commentCell = tableView.dequeueReusableCell(withIdentifier: commentCellId, for: indexPath) as! ImgurCommentCell
37 | let comment = currentlyDisplayed[indexPath.row] as! RichComment
38 | commentCell.level = comment.level
39 | commentCell.commentContent = comment.body
40 | commentCell.posterName = comment.posterName
41 | commentCell.date = comment.soMuchTimeAgo()
42 | commentCell.upvotes = comment.upvotes ?? 0
43 | commentCell.downvotes = comment.downvotes ?? 0
44 | commentCell.nReplies = comment.replies.count
45 |
46 | commentCell.content.upvoteButton.tag = indexPath.row
47 | commentCell.content.upvoteButton.addTarget(self, action: #selector(upvoted(sender:)), for: .touchUpInside)
48 |
49 | commentCell.content.downvoteButton.tag = indexPath.row
50 | commentCell.content.downvoteButton.addTarget(self, action: #selector(downvoted(sender:)), for: .touchUpInside)
51 |
52 |
53 | commentCell.upvoted = comment.upvoted
54 | commentCell.downvoted = comment.downvoted
55 | return commentCell
56 | }
57 |
58 | @objc func upvoted(sender: UIButton) {
59 | let indexPath = IndexPath(row: sender.tag, section: 0)
60 | if let cell = tableView.cellForRow(at: indexPath) as? ImgurCommentCell {
61 | let intendToUpvote = !cell.upvoted
62 | cell.upvoted = intendToUpvote
63 | if cell.upvotes != nil {
64 | cell.upvotes! += Int((intendToUpvote ? 1 : -1))
65 | }
66 | if cell.downvoted {
67 | cell.downvoted = false
68 | cell.downvotes! -= 1
69 | }
70 |
71 | if let comment = currentlyDisplayed[sender.tag] as? RichComment {
72 | comment.upvoted = intendToUpvote
73 | if comment.upvotes != nil {
74 | comment.upvotes! += Int((intendToUpvote ? 1 : -1))
75 | }
76 | if comment.downvoted {
77 | comment.downvoted = false
78 | comment.downvotes! -= 1
79 | }
80 | }
81 | }
82 | }
83 | @objc func downvoted(sender: UIButton) {
84 | let indexPath = IndexPath(row: sender.tag, section: 0)
85 | if let cell = tableView.cellForRow(at: indexPath) as? ImgurCommentCell {
86 | let intendToDownvote = !cell.downvoted
87 | cell.downvoted = intendToDownvote
88 | if cell.downvotes != nil {
89 | cell.downvotes! += Int((intendToDownvote ? 1 : -1))
90 | }
91 | if cell.upvoted {
92 | cell.upvoted = false
93 | cell.upvotes! -= 1
94 | }
95 | if let comment = currentlyDisplayed[sender.tag] as? RichComment {
96 | comment.downvoted = intendToDownvote
97 | if comment.downvotes != nil {
98 | comment.downvotes! += Int((intendToDownvote ? 1 : -1))
99 | }
100 | if comment.upvoted {
101 | comment.upvoted = false
102 | comment.upvotes! -= 1
103 | }
104 | }
105 | }
106 | }
107 |
108 |
109 |
110 |
111 | override func viewWillAppear(_ animated: Bool) {
112 | super.viewWillAppear(animated)
113 | self.navigationController?.navigationBar.barTintColor = ImgurConstants.backgroundColor
114 | self.navigationController?.navigationBar.tintColor = ImgurConstants.posterColor
115 | }
116 |
117 | override var preferredStatusBarStyle: UIStatusBarStyle {
118 | return .lightContent
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIViewControllerBasedStatusBarAppearance
6 |
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/MockupData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockupData.swift
3 | // SwiftyComments_Example
4 | //
5 | // Created by Stéphane Sercu on 9/10/17.
6 | // Copyright © 2017 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyComments
11 |
12 | class RandomDiscussion {
13 | var comments: [AttributedTextComment]! = []
14 |
15 |
16 | /**
17 | Generate a random list of comments
18 | - note: the postedDate of the replies may be anterior to the comment they are a reply to, and you know what? No one cares.
19 | - parameters:
20 | - size: number of root comments
21 | - maximumChildren: maximum number of replies to a comment. This number is randomly chosen.
22 | */
23 | static func generate(size: Int = 10, maximumChildren: Int = 4) -> RandomDiscussion {
24 | let discussion = RandomDiscussion()
25 | for _ in 0 ..< size {
26 | var rootComment = randomComent()
27 | addReplyRecurs(&rootComment, maximumChildren: maximumChildren)
28 | discussion.comments.append(rootComment)
29 | }
30 | return discussion
31 | }
32 |
33 | /**
34 | Generate a random, lipsum-filled, comment.
35 | */
36 | static func randomComent() -> AttributedTextComment {
37 | let com = AttributedTextComment()
38 | com.id = Int(arc4random_uniform(100000)) + 1
39 | com.body = Lorem.sentences(Int(arc4random_uniform(4)) + 1)
40 | com.posterName = Lorem.word
41 | com.postedDate = Double(arc4random_uniform(UInt32(1483228800 - 1262304000)-1)) + 1262304000
42 | com.upvotes = Int(arc4random_uniform(500)) + 1
43 | com.downvotes = Int(arc4random_uniform(100)) + 1
44 | com.title = Lorem.sentence
45 | return com
46 | }
47 |
48 | /**
49 | Recursively add a random number of replies to parent.
50 | At each recursion, the maximum number of children is
51 | decreased by 1 until it reaches 0.
52 | */
53 | private static func addReplyRecurs( _ parent: inout AttributedTextComment, maximumChildren: Int) {
54 | if maximumChildren == 0 { return }
55 | for _ in 0..<(Int(arc4random_uniform(UInt32(maximumChildren-1))) + 1) {
56 | var com = randomComent()
57 | parent.addReply(com)
58 | com.replyTo = parent
59 | com.level = parent.level+1
60 | addReplyRecurs(&com, maximumChildren: maximumChildren-1)
61 | }
62 |
63 | }
64 |
65 |
66 | }
67 |
68 | /// Model of a comment with attributedText content.
69 | class AttributedTextComment: RichComment {
70 | var attributedContent: NSAttributedString?
71 | }
72 |
73 |
74 | /**
75 | This class models a comment with all the most
76 | common attributes in the commenting systems.
77 | It's used as an exemple through the implemented
78 | commenting systems.
79 | **/
80 | class RichComment: BaseComment {
81 | var id: Int?
82 | var upvotes: Int?
83 | var downvotes: Int?
84 | var body: String?
85 | var title: String?
86 | var posterName: String?
87 | var postedDate: Double? // epochtime (since 1970)
88 | var upvoted: Bool = false
89 | var downvoted: Bool = false
90 | var isFolded: Bool = false
91 |
92 | /**
93 | Express the postedDate in following format: "[x] [time period] ago"
94 | */
95 | func soMuchTimeAgo() -> String? {
96 | if self.postedDate == nil {
97 | return nil
98 | }
99 | let diff = Date().timeIntervalSince1970 - self.postedDate!
100 | var str: String = ""
101 | if diff < 60 {
102 | str = "now"
103 | } else if diff < 3600 {
104 | let out = Int(round(diff/60))
105 | str = (out == 1 ? "1 minute ago" : "\(out) minutes ago")
106 | } else if diff < 3600 * 24 {
107 | let out = Int(round(diff/3600))
108 | str = (out == 1 ? "1 hour ago" : "\(out) hours ago")
109 | } else if diff < 3600 * 24 * 2 {
110 | str = "yesterday"
111 | } else if diff < 3600 * 24 * 7 {
112 | let out = Int(round(diff/(3600*24)))
113 | str = (out == 1 ? "1 day ago" : "\(out) days ago")
114 | } else if diff < 3600 * 24 * 7 * 4{
115 | let out = Int(round(diff/(3600*24*7)))
116 | str = (out == 1 ? "1 week ago" : "\(out) weeks ago")
117 | } else if diff < 3600 * 24 * 7 * 4 * 12{
118 | let out = Int(round(diff/(3600*24*7*4)))
119 | str = (out == 1 ? "1 month ago" : "\(out) months ago")
120 | } else {//if diff < 3600 * 24 * 7 * 4 * 12{
121 | let out = Int(round(diff/(3600*24*7*4*12)))
122 | str = (out == 1 ? "1 year ago" : "\(out) years ago")
123 | }
124 | return str
125 | }
126 | }
127 |
128 | class BaseComment: AbstractComment {
129 | var replies: [AbstractComment]! = []
130 | var level: Int!
131 | weak var replyTo: AbstractComment?
132 |
133 | convenience init() {
134 | self.init(level: 0, replyTo: nil)
135 | }
136 | init(level: Int, replyTo: BaseComment?) {
137 | self.level = level
138 | self.replyTo = replyTo
139 | }
140 | func addReply(_ reply: BaseComment) {
141 | self.replies.append(reply)
142 | }
143 |
144 | }
145 |
146 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/RedditCommentsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedditCommentsViewController.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 27/06/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyComments
11 |
12 | class FoldableRedditCommentsViewController: RedditCommentsViewController, CommentsViewDelegate {
13 |
14 | func commentCellExpanded(atIndex index: Int) {
15 | updateCellFoldState(false, atIndex: index)
16 | }
17 |
18 | func commentCellFolded(atIndex index: Int) {
19 | updateCellFoldState(true, atIndex: index)
20 | }
21 |
22 | private func updateCellFoldState(_ folded: Bool, atIndex index: Int) {
23 | let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) as! RedditCommentCell
24 | cell.animateIsFolded(fold: folded)
25 | (self.currentlyDisplayed[index] as! RichComment).isFolded = folded
26 | self.tableView.beginUpdates()
27 | self.tableView.endUpdates()
28 | }
29 |
30 | override func viewDidLoad() {
31 | self.fullyExpanded = true
32 | super.viewDidLoad()
33 | self.delegate = self
34 |
35 | }
36 |
37 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
38 | let selectedCom: AbstractComment = currentlyDisplayed[indexPath.row]
39 | let selectedIndex = indexPath.row
40 |
41 | // Enable cell folding for comments without replies
42 | if selectedCom.replies.count == 0 {
43 | if (selectedCom as! RichComment).isFolded {
44 | commentCellExpanded(atIndex: selectedIndex)
45 | } else {
46 | commentCellFolded(atIndex: selectedIndex)
47 | }
48 | } else {
49 | super.tableView(tableView, didSelectRowAt: indexPath)
50 | }
51 | }
52 |
53 |
54 | }
55 | class RedditCommentsViewController: CommentsViewController {
56 | private let commentCellId = "redditComentCellId"
57 | var allComments: [RichComment] = [] // All the comments (nested, not in a linear format)
58 |
59 | override func viewDidLoad() {
60 | super.viewDidLoad()
61 | tableView.register(RedditCommentCell.self, forCellReuseIdentifier: commentCellId)
62 |
63 | tableView.backgroundColor = RedditConstants.backgroundColor
64 |
65 | allComments = RandomDiscussion.generate().comments
66 |
67 | currentlyDisplayed = allComments
68 |
69 | self.swipeToHide = true
70 | self.swipeActionAppearance.swipeActionColor = RedditConstants.flashyColor
71 | }
72 |
73 | override open func commentsView(_ tableView: UITableView, commentCellForModel commentModel: AbstractComment, atIndexPath indexPath: IndexPath) -> CommentCell {
74 | let commentCell = tableView.dequeueReusableCell(withIdentifier: commentCellId, for: indexPath) as! RedditCommentCell
75 | let comment = currentlyDisplayed[indexPath.row] as! RichComment
76 | commentCell.level = comment.level
77 | commentCell.commentContent = comment.body
78 | commentCell.posterName = comment.posterName
79 | commentCell.date = comment.soMuchTimeAgo()
80 | commentCell.upvotes = comment.upvotes
81 | commentCell.isFolded = comment.isFolded && !isCellExpanded(indexPath: indexPath)
82 | return commentCell
83 | }
84 |
85 | override func viewWillAppear(_ animated: Bool) {
86 | super.viewWillAppear(animated)
87 | self.navigationController?.navigationBar.barTintColor = RedditConstants.flashyColor
88 | self.navigationController?.navigationBar.tintColor = .white
89 | }
90 |
91 | override var preferredStatusBarStyle: UIStatusBarStyle {
92 | return .lightContent
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/SimpleCommentCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleCommentView.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 24/04/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyComments
11 |
12 | class SimpleCommentCell: CommentCell {
13 | var content:SimpleCommentView {
14 | get {
15 | return self.commentViewContent as! SimpleCommentView
16 | }
17 | }
18 | open var commentContent: String! = "content" {
19 | didSet {
20 | (self.commentViewContent as! SimpleCommentView).commentContent = commentContent
21 | }
22 | }
23 | open var posterName: String! {
24 | get {
25 | return self.content.posterName
26 | } set(value) {
27 | self.content.posterName = value
28 | }
29 | }
30 |
31 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
32 | super.init(style: style, reuseIdentifier: reuseIdentifier)
33 | self.commentViewContent = SimpleCommentView()
34 |
35 |
36 | self.rootCommentMarginColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
37 | self.rootCommentMargin = 20
38 | self.commentMarginColor = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)
39 | self.commentMargin = 10
40 | self.indentationColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
41 | self.indentationIndicatorColor = #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
42 | self.indentationIndicatorThickness = 5
43 | self.indentationUnit = 40
44 |
45 | self.commentViewContent?.backgroundColor = .gray
46 |
47 | }
48 | required init?(coder aDecoder: NSCoder) {
49 | fatalError("init(coder:) has not been implemented")
50 | }
51 | }
52 |
53 | class SimpleCommentView: UIView {
54 | open var commentContent: String! = "content" {
55 | didSet {
56 | contentLabel.text = commentContent
57 | }
58 | }
59 | open var posterName: String! = "username" {
60 | didSet {
61 | posterLabel.text = posterName
62 | }
63 | }
64 |
65 |
66 | convenience init() {
67 | self.init(frame: CGRect.zero)
68 | }
69 | override init(frame: CGRect) {
70 | super.init(frame: frame)
71 | setLayout()
72 | }
73 |
74 | required init?(coder aDecoder: NSCoder) {
75 | fatalError("init(coder:) has not been implemented")
76 | }
77 |
78 | private func setLayout() {
79 | let margin: CGFloat = 10
80 |
81 | addSubview(posterLabel)
82 | posterLabel.translatesAutoresizingMaskIntoConstraints = false
83 | posterLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: margin).isActive = true
84 | posterLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: margin).isActive = true
85 |
86 | addSubview(contentLabel)
87 | contentLabel.translatesAutoresizingMaskIntoConstraints = false
88 | contentLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: margin).isActive = true
89 | contentLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -margin).isActive = true
90 | contentLabel.topAnchor.constraint(equalTo: posterLabel.bottomAnchor).isActive = true
91 |
92 |
93 | addSubview(controlView)
94 | controlView.translatesAutoresizingMaskIntoConstraints = false
95 | controlView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -margin).isActive = true
96 | controlView.topAnchor.constraint(equalTo: contentLabel.bottomAnchor).isActive = true
97 | controlView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
98 |
99 | }
100 | var contentLabel: UILabel = {
101 | let lbl = UILabel()
102 | lbl.text = "No content"
103 | lbl.textColor = UIColor.black
104 | lbl.lineBreakMode = .byWordWrapping
105 | lbl.font = UIFont.systemFont(ofSize: 13, weight: UIFont.Weight.regular)
106 | lbl.numberOfLines = 0
107 | lbl.textAlignment = .left
108 | return lbl
109 | }()
110 | var posterLabel: UILabel = {
111 | let lbl = UILabel()
112 | lbl.text = "annonymous"
113 | lbl.textColor = #colorLiteral(red: 0.6470588235, green: 0.6470588235, blue: 0.6470588235, alpha: 1)
114 | lbl.font = UIFont.systemFont(ofSize: 11, weight: UIFont.Weight.regular)
115 | lbl.textAlignment = .left
116 | return lbl
117 | }()
118 | var controlView: UIView = {
119 | let v = UIView()
120 | let actionBtn = UIButton(type: UIButton.ButtonType.infoDark)
121 | actionBtn.setTitle("Like", for: .normal)
122 |
123 | v.addSubview(actionBtn)
124 |
125 | actionBtn.translatesAutoresizingMaskIntoConstraints = false
126 |
127 | actionBtn.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -10).isActive = true
128 | actionBtn.bottomAnchor.constraint(equalTo: v.bottomAnchor).isActive = true
129 | actionBtn.topAnchor.constraint(equalTo: v.topAnchor).isActive = true
130 | actionBtn.leadingAnchor.constraint(equalTo: v.leadingAnchor).isActive = true
131 |
132 | return v
133 | }()
134 | }
135 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/SimpleCommentsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 15/04/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyComments
11 |
12 | /// Simplest implementation, colored to show the different zones in a commentCell.
13 | class SimpleCommentsViewController: CommentsViewController, UITextViewDelegate {
14 | private let commentCellId = "simpleCommentCellId"
15 | var allComments: [RichComment] = [] // All the comments (nested, not in a linear format)
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | tableView.register(SimpleCommentCell.self, forCellReuseIdentifier: commentCellId)
20 |
21 | allComments = RandomDiscussion.generate().comments
22 | currentlyDisplayed = allComments
23 |
24 | }
25 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> CommentCell {
26 | let commentCell = tableView.dequeueReusableCell(withIdentifier: commentCellId, for: indexPath) as! SimpleCommentCell
27 | let comment = currentlyDisplayed[indexPath.row] as! RichComment
28 | commentCell.level = comment.level
29 | commentCell.commentContent = comment.body
30 | commentCell.posterName = comment.posterName
31 | return commentCell
32 | }
33 |
34 | }
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Example/SwiftyComments/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SwiftyComments
4 | //
5 | // Created by tsucres on 10/09/2017.
6 | // Copyright (c) 2017 tsucres. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Stéphane Sercu
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 |
--------------------------------------------------------------------------------
/Screenshots/HN.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/HN.mov
--------------------------------------------------------------------------------
/Screenshots/HNExample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/HNExample.gif
--------------------------------------------------------------------------------
/Screenshots/HNExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/HNExample.png
--------------------------------------------------------------------------------
/Screenshots/HNSwipe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/HNSwipe.gif
--------------------------------------------------------------------------------
/Screenshots/HNSwipe.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/HNSwipe.mov
--------------------------------------------------------------------------------
/Screenshots/Imgur.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/Imgur.mov
--------------------------------------------------------------------------------
/Screenshots/ImgurExample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/ImgurExample.gif
--------------------------------------------------------------------------------
/Screenshots/ImgurExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/ImgurExample.png
--------------------------------------------------------------------------------
/Screenshots/Reddit.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/Reddit.mov
--------------------------------------------------------------------------------
/Screenshots/RedditExample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/RedditExample.gif
--------------------------------------------------------------------------------
/Screenshots/RedditExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/RedditExample.png
--------------------------------------------------------------------------------
/Screenshots/RedditSwipe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/RedditSwipe.gif
--------------------------------------------------------------------------------
/Screenshots/RedditSwipe.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/RedditSwipe.mov
--------------------------------------------------------------------------------
/Screenshots/schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/schema.png
--------------------------------------------------------------------------------
/Screenshots/schema_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/Screenshots/schema_small.png
--------------------------------------------------------------------------------
/SwiftyComments.podspec:
--------------------------------------------------------------------------------
1 |
2 | Pod::Spec.new do |s|
3 | s.name = 'SwiftyComments'
4 | s.version = '0.2.1'
5 | s.summary = 'UITableView based component designed to display a hierarchy of expandable/foldable comments.'
6 |
7 | s.description = <<-DESC
8 | SwiftyComments is a UITableView based component designed to display a hierarchy of expandable/foldable comments.
9 | DESC
10 |
11 | s.homepage = 'https://github.com/tsucres/SwiftyComments'
12 | s.screenshots = 'https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/ImgurExample.png', 'https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/HNExample.png', "https://github.com/tsucres/SwiftyComments/raw/master/Screenshots/RedditExample.png"
13 | s.license = { :type => 'MIT', :file => 'LICENSE' }
14 | s.author = { 'Stéphane Sercu' => 'stefsercu@gmail.com' }
15 | s.source = { :git => 'https://github.com/tsucres/SwiftyComments.git', :tag => s.version.to_s }
16 |
17 | s.ios.deployment_target = '9.0'
18 |
19 | s.source_files = 'SwiftyComments/Classes/**/*'
20 | s.resources = "SwiftyComments/Assets/*.xcassets"
21 |
22 | s.dependency 'SwipeCellKit', '2.6.0'
23 |
24 | s.frameworks = 'UIKit'
25 | s.swift_version = '4.1'
26 | end
27 |
--------------------------------------------------------------------------------
/SwiftyComments/Assets/Media.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SwiftyComments/Assets/Media.xcassets/collapse.imageset/Collapse.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tsucres/SwiftyComments/e79768d6376a5494af59eedb207b4ac1dd4ad5e4/SwiftyComments/Assets/Media.xcassets/collapse.imageset/Collapse.pdf
--------------------------------------------------------------------------------
/SwiftyComments/Assets/Media.xcassets/collapse.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Collapse.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/SwiftyComments/Classes/AbstractComment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbstractComment.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 9/10/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Defines the minimal properties a comment model must have (requiered by the CommentsViewController)
12 | public protocol AbstractComment: class {
13 | var replies: [AbstractComment]! { get set }
14 | var level: Int! { get set }
15 | /*weak*/ var replyTo: AbstractComment? { get set }
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftyComments/Classes/CommentCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentCell.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 24/04/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwipeCellKit
11 |
12 | struct DefaultValues {
13 | static let rootCommentMarginColor = UIColor(red: 247/255, green: 247/255, blue: 245/255, alpha: 1)
14 | static let rootCommentMargin: CGFloat = 10
15 | static let commentMarginColor = UIColor.black
16 | static let commentMargin: CGFloat = 1
17 | static let identationColor = UIColor.black
18 | static let commentBackgroundColor = UIColor.red
19 | static let indentationIndicatorColor = UIColor.gray
20 | static let indentationIndicatorThickness: CGFloat = 1
21 | }
22 |
23 |
24 | /**
25 | This class manages everything relating to the level of the
26 | comment: identations, background colors, spacings, etc...
27 | */
28 | open class CommentCell: SwipeTableViewCell {
29 | /// Color of the separation between 2 root comments
30 | open var rootCommentMarginColor: UIColor! = DefaultValues.rootCommentMarginColor {
31 | didSet {
32 | updateCommentMargin()
33 | }
34 | }
35 | /// Space between 2 root comments
36 | open var rootCommentMargin: CGFloat! = DefaultValues.rootCommentMargin {
37 | didSet {
38 | updateCommentMargin()
39 | }
40 | }
41 | /// Color of the separation between 2 indented comments
42 | open var commentMarginColor: UIColor! = DefaultValues.commentMarginColor {
43 | didSet {
44 | updateCommentMargin()
45 | }
46 | }
47 | /// Space between 2 indented comments
48 | open var commentMargin: CGFloat! = DefaultValues.commentMargin {
49 | didSet {
50 | updateCommentMargin()
51 | }
52 | }
53 | /// Color of the space above an indented comment
54 | open var indentationColor: UIColor! {//= DefaultValues.identationColor {
55 | get {
56 | return backgroundColor
57 | } set(value) {
58 | backgroundColor = value
59 | }
60 | }
61 |
62 | /// Color of the vertical indentation indicators
63 | open var indentationIndicatorColor: UIColor! = DefaultValues.indentationIndicatorColor {
64 | didSet {
65 | updateIndentationIndicators()
66 | }
67 | }
68 |
69 | /// Thickness of the vertical indentation indicators
70 | open var indentationIndicatorThickness: CGFloat! = CGFloat(DefaultValues.indentationIndicatorThickness) {
71 | didSet {
72 | updateIndentationIndicators()
73 | }
74 | }
75 |
76 | /// Indicates weither the vertical indentation indicators extends to the replies
77 | open var isIndentationIndicatorsExtended: Bool! = false {
78 | didSet {
79 | updateIndentationIndicators()
80 | }
81 | }
82 |
83 | /// Defines the identation per level
84 | open var indentationUnit = 10 {
85 | didSet {
86 | indentationConstraint?.constant = CGFloat(self.level*indentationUnit)
87 | updateIndentationIndicators()
88 | updateCommentMargin()
89 | }
90 | }
91 |
92 | open var level = 0 {
93 | didSet {
94 | contentView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight // solves a warning (http://stackoverflow.com/questions/26100053/uitableviewcells-contentview-gets-unwanted-height-44-constraint)
95 | indentationConstraint?.constant = CGFloat(self.level*indentationUnit)
96 | updateIndentationIndicators()
97 | updateCommentMargin()
98 | }
99 | }
100 |
101 |
102 |
103 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
104 | super.init(style: style, reuseIdentifier: reuseIdentifier)
105 | selectionStyle = .none
106 | indentationColor = DefaultValues.identationColor
107 | setupView()
108 | }
109 |
110 | required public init?(coder aDecoder: NSCoder) {
111 | fatalError("init(coder:) has not been implemented")
112 | }
113 |
114 |
115 |
116 | private func setupView() {
117 | updateIndentationIndicators()
118 | setupCommentMargin()
119 |
120 | contentView.addSubview(commentView)
121 | commentView.translatesAutoresizingMaskIntoConstraints = false
122 | commentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
123 | indentationConstraint = commentView.leadingAnchor.constraint(
124 | equalTo: contentView.leadingAnchor,
125 | constant: CGFloat(self.level*indentationUnit))
126 | indentationConstraint?.isActive = true
127 | commentView.topAnchor.constraint(equalTo: hSeparator.bottomAnchor).isActive = true
128 | commentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
129 | }
130 |
131 |
132 | private let commentView:UIView! = UIView()
133 |
134 |
135 | /// This is the key element of the class. It's the actual view of a comment.
136 | open var commentViewContent: UIView? {
137 | get {
138 | if self.commentView.subviews.count > 0 {
139 | return self.commentView.subviews[0]
140 | }
141 | return nil
142 | } set(v) {
143 | commentView.subviews.forEach({ $0.removeFromSuperview() })
144 | if v != nil {
145 | commentView.addSubview(v!)
146 | v!.translatesAutoresizingMaskIntoConstraints = false
147 | v!.trailingAnchor.constraint(equalTo: commentView.trailingAnchor).isActive = true
148 | v!.leadingAnchor.constraint(equalTo: commentView.leadingAnchor).isActive = true
149 | v!.topAnchor.constraint(equalTo: commentView.topAnchor).isActive = true
150 | v!.bottomAnchor.constraint(equalTo: commentView.bottomAnchor).isActive = true
151 | }
152 | }
153 | }
154 |
155 | // Indentation
156 | private var indentationConstraint: NSLayoutConstraint?
157 |
158 | // Vertical indentation indicators
159 | private var vSeparators: [UIView] = []
160 | private func updateIndentationIndicators() {
161 | // Remove the eventual existing ones
162 | vSeparators.forEach({(body) in
163 | body.removeFromSuperview()
164 | })
165 | if self.level > 0 { // No indicators for root comments
166 | let n = self.isIndentationIndicatorsExtended! ? self.level : 1 // number of indicators to draw
167 | for i in 1...n {
168 | let sep = UIView()
169 | vSeparators.append(sep)
170 | contentView.addSubview(sep)
171 | sep.translatesAutoresizingMaskIntoConstraints = false
172 | sep.topAnchor.constraint(equalTo: contentView.topAnchor, constant:commentMargin).isActive = true
173 | sep.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
174 | sep.widthAnchor.constraint(equalToConstant: indentationIndicatorThickness).isActive = true
175 | sep.leadingAnchor.constraint(
176 | equalTo: contentView.leadingAnchor,
177 | constant: CGFloat((self.level-i+1)*indentationUnit)).isActive = true
178 | sep.backgroundColor = indentationIndicatorColor
179 | }
180 | }
181 | }
182 |
183 | // Horizontal separator
184 | private var hSeparator = UIView()
185 | private var hSepHeightConstraint: NSLayoutConstraint?
186 | private var hSepLeadingConstraint: NSLayoutConstraint?
187 | private func setupCommentMargin() {
188 | contentView.addSubview(hSeparator)
189 | hSeparator.translatesAutoresizingMaskIntoConstraints = false
190 | hSeparator.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
191 | hSepHeightConstraint = hSeparator.leadingAnchor.constraint(
192 | equalTo: contentView.leadingAnchor,
193 | constant: CGFloat(self.level*indentationUnit))
194 | hSepHeightConstraint?.isActive = true
195 | hSeparator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
196 |
197 | if self.level == 0 {
198 | hSeparator.backgroundColor = self.rootCommentMarginColor
199 | hSepHeightConstraint = hSeparator.heightAnchor.constraint(equalToConstant: self.rootCommentMargin)
200 |
201 | } else {
202 | hSeparator.backgroundColor = self.commentMarginColor
203 | hSepHeightConstraint = hSeparator.heightAnchor.constraint(equalToConstant: self.commentMargin)
204 | }
205 | hSepHeightConstraint?.isActive = true
206 | }
207 | private func updateCommentMargin() {
208 | hSepHeightConstraint?.constant = CGFloat(self.level*indentationUnit)
209 | if self.level == 0 {
210 | hSeparator.backgroundColor = self.rootCommentMarginColor
211 | hSepHeightConstraint?.constant = self.rootCommentMargin
212 |
213 | } else {
214 | hSeparator.backgroundColor = self.commentMarginColor
215 | hSepHeightConstraint?.constant = self.commentMargin
216 | }
217 | }
218 |
219 | }
220 |
221 |
--------------------------------------------------------------------------------
/SwiftyComments/Classes/CommentsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentsViewController.swift
3 | // Commenting
4 | //
5 | // Created by Stéphane Sercu on 26/06/17.
6 | // Copyright © 2017 Stéphane Sercu. All rights reserved.
7 | //
8 | import UIKit
9 | import SwipeCellKit
10 |
11 | /// Encapsulates all the visual properties of a SwipeCellKit's SwipeAction
12 | open class SwipeActionAppearance {
13 | open var swipeActionColor: UIColor = #colorLiteral(red: 0.4147516489, green: 0.8618777394, blue: 0.8051461577, alpha: 1)
14 | open var swipeActionHighlightedColor: UIColor = #colorLiteral(red: 0.7551190257, green: 0.8365258574, blue: 0.8273909688, alpha: 1)
15 |
16 | open var swipeActionText: String = "Collapse"
17 | open var swipeActionTextColor: UIColor = .white
18 | open var swipeActionHighlightedTextColor: UIColor = .white
19 | open var swipeActionTextFont: UIFont = UIFont.systemFont(ofSize: 17.0, weight: .bold)
20 |
21 | open var swipeActionImage: UIImage = SwipeActionAppearance.getCollapseImage()
22 | open var swipeActionHighlightedImage: UIImage = SwipeActionAppearance.getCollapseImage()
23 | static func getCollapseImage() -> UIImage {
24 | let bundle = Bundle(for: CommentsViewController.self)
25 | return UIImage(named: "collapse", in: bundle, compatibleWith: nil)!
26 | }
27 |
28 | }
29 | /// ViewController displaying expandable comments.
30 | open class CommentsViewController: UITableViewController, SwipeTableViewCellDelegate {
31 |
32 | /// The list of comments correctly displayed in the tableView (in linearized form)
33 | var _currentlyDisplayed: [AbstractComment] = []
34 | open var currentlyDisplayed: [AbstractComment] {
35 | get {
36 | return _currentlyDisplayed
37 | } set(value) {
38 | if fullyExpanded {
39 | linearizeComments(comments: value, linearizedComments: &_currentlyDisplayed)
40 | } else {
41 | _currentlyDisplayed = value
42 | }
43 | }
44 | }
45 |
46 | /// If true, when a cell is expanded, the tableView will scroll to make the new cells visible
47 | open var makeExpandedCellsVisible: Bool = true
48 |
49 | /// Enable/Disable the "swipe to hide" gesture
50 | open var swipeToHide: Bool = true
51 |
52 | open var swipeActionAppearance = SwipeActionAppearance()
53 |
54 | open var fullyExpanded: Bool = false {
55 | didSet {
56 | if fullyExpanded {
57 | self.linearizeCurrentlyDisplayedComs()
58 | }
59 | }
60 | }
61 |
62 | open weak var delegate: CommentsViewDelegate? = nil
63 |
64 | deinit {
65 | //print("CommentsVC deinited!")
66 | }
67 | override open func viewDidLoad() {
68 | super.viewDidLoad()
69 |
70 | // Tableview style
71 | tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
72 |
73 | if #available(iOS 11.0, *) {
74 | tableView.estimatedRowHeight = 0
75 | tableView.estimatedSectionFooterHeight = 0
76 | tableView.estimatedSectionHeaderHeight = 0
77 | } else {
78 | tableView.rowHeight = UITableView.automaticDimension
79 | tableView.estimatedRowHeight = 400.0
80 | }
81 | }
82 |
83 | /**
84 | Helper function that takes a list of root comments and turn it in
85 | a linearized list of all the root + children comments.
86 | - Parameters:
87 | - comments: The input, nested, list of comments
88 | - linearizedComments: a reference to the list that will contain the comments
89 | - sort: a function that is applied recursively on each sub-list of comments
90 | */
91 | public func linearizeComments(comments: [AbstractComment], linearizedComments: inout [AbstractComment], sort: ((inout [AbstractComment])->Void)? = nil) {
92 | var coms = comments
93 | if sort != nil {
94 | sort!(&coms)
95 | }
96 | for c in coms {
97 | linearizedComments.append(c)
98 | linearizeComments(comments: c.replies, linearizedComments: &linearizedComments, sort: sort)
99 | }
100 | }
101 | /// Linearize the comments in _currentlyDisplayed.
102 | public func linearizeCurrentlyDisplayedComs() {
103 | var linearizedComs: [AbstractComment] = []
104 | linearizeComments(comments: _currentlyDisplayed, linearizedComments: &linearizedComs)
105 | _currentlyDisplayed = linearizedComs
106 | }
107 |
108 | override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
109 | return currentlyDisplayed.count
110 | }
111 |
112 | override open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
113 | return UITableView.automaticDimension
114 | }
115 | override open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
116 | return UITableView.automaticDimension
117 | }
118 |
119 | override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
120 | let selectedCom: AbstractComment = _currentlyDisplayed[indexPath.row]
121 | let selectedIndex = indexPath.row
122 |
123 | if selectedCom.replies.count > 0 { // if expandable
124 | if isCellExpanded(indexPath: indexPath) {
125 | // collapse
126 | var nCellsToDelete = 0
127 | repeat {
128 | nCellsToDelete += 1
129 | } while (_currentlyDisplayed.count > selectedIndex+nCellsToDelete+1 && _currentlyDisplayed[selectedIndex+nCellsToDelete+1].level > selectedCom.level)
130 |
131 | _currentlyDisplayed.removeSubrange(Range(uncheckedBounds: (lower: selectedIndex+1 , upper: selectedIndex+nCellsToDelete+1)))
132 | var indexPaths: [IndexPath] = []
133 | for i in 0.. UITableViewCell {
162 | let comment = currentlyDisplayed[indexPath.row]
163 | let commentCell = commentsView(tableView, commentCellForModel: comment, atIndexPath: indexPath)
164 | if swipeToHide {
165 | commentCell.delegate = self
166 | }
167 | return commentCell
168 | }
169 |
170 | open func commentsView(_ tableView: UITableView, commentCellForModel commentModel: AbstractComment, atIndexPath indexPath: IndexPath) -> CommentCell {
171 | return CommentCell()
172 | }
173 |
174 | open func isCellExpanded(indexPath: IndexPath) -> Bool {
175 | let com: AbstractComment = _currentlyDisplayed[indexPath.row]
176 | return _currentlyDisplayed.count > indexPath.row+1 && // if not last cell
177 | _currentlyDisplayed[indexPath.row+1].level > com.level // if replies are displayed
178 | }
179 |
180 | open func commentsView(_ tableView: UITableView, isCommentExpandable commentModel: AbstractComment, atIndexPath indexPath: IndexPath) -> Bool {
181 | return swipeToHide && isCellExpanded(indexPath: indexPath)
182 | }
183 |
184 | public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
185 | guard orientation == .right else { return nil }
186 | guard commentsView(tableView, isCommentExpandable: _currentlyDisplayed[indexPath.row], atIndexPath: indexPath) else { return nil }
187 |
188 | let collapseAction = SwipeAction(style: .destructive,
189 | title: swipeActionAppearance.swipeActionText)
190 | { [weak self](action, indexPath) in
191 | if self != nil {
192 | self!.tableView(self!.tableView, didSelectRowAt: indexPath)
193 | action.fulfill(with: .reset)
194 | }
195 | }
196 |
197 | // customize the action appearance
198 | collapseAction.backgroundColor = swipeActionAppearance.swipeActionColor
199 | collapseAction.highlightedBackgroundColor = swipeActionAppearance.swipeActionHighlightedColor
200 | collapseAction.textColor = swipeActionAppearance.swipeActionTextColor
201 | collapseAction.highlightedTextColor = swipeActionAppearance.swipeActionHighlightedTextColor
202 | collapseAction.font = swipeActionAppearance.swipeActionTextFont
203 | collapseAction.image = swipeActionAppearance.swipeActionImage
204 | collapseAction.highlightedImage = swipeActionAppearance.swipeActionHighlightedImage
205 | return [collapseAction]
206 | }
207 |
208 | public func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
209 | var options = SwipeTableOptions()
210 | options.expansionStyle = .destructive(automaticallyDelete: false)
211 | return options
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/SwiftyComments/Classes/CommentsViewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentsViewDelegate.swift
3 | // SwiftyComments
4 | //
5 | // Created by Stéphane Sercu on 12/02/18.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol CommentsViewDelegate: class {
11 | func commentCellExpanded(atIndex index: Int)
12 | func commentCellFolded(atIndex index: Int)
13 | }
14 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------