Skip to content

Commit d448fb2

Browse files
committed
Add EightDirections
1 parent 55408ca commit d448fb2

File tree

9 files changed

+233
-27
lines changed

9 files changed

+233
-27
lines changed

Example/Example.xcodeproj/project.pbxproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
FBB08B742445186500385E5A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FBB08B732445186500385E5A /* Assets.xcassets */; };
1414
FBB08B772445186500385E5A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FBB08B762445186500385E5A /* Preview Assets.xcassets */; };
1515
FBB08B7A2445186500385E5A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBB08B782445186500385E5A /* LaunchScreen.storyboard */; };
16-
FBB08B9D2445DBBA00385E5A /* CardStack in Frameworks */ = {isa = PBXBuildFile; productRef = FBB08B9C2445DBBA00385E5A /* CardStack */; };
16+
FBF3C4E52449BD4B000AC410 /* CardStack in Frameworks */ = {isa = PBXBuildFile; productRef = FBF3C4E42449BD4B000AC410 /* CardStack */; };
1717
/* End PBXBuildFile section */
1818

1919
/* Begin PBXFileReference section */
@@ -32,7 +32,7 @@
3232
isa = PBXFrameworksBuildPhase;
3333
buildActionMask = 2147483647;
3434
files = (
35-
FBB08B9D2445DBBA00385E5A /* CardStack in Frameworks */,
35+
FBF3C4E52449BD4B000AC410 /* CardStack in Frameworks */,
3636
);
3737
runOnlyForDeploymentPostprocessing = 0;
3838
};
@@ -94,7 +94,7 @@
9494
);
9595
name = iOSExample;
9696
packageProductDependencies = (
97-
FBB08B9C2445DBBA00385E5A /* CardStack */,
97+
FBF3C4E42449BD4B000AC410 /* CardStack */,
9898
);
9999
productName = Example;
100100
productReference = FBB08B6A2445186200385E5A /* CardStackExample.app */;
@@ -125,7 +125,7 @@
125125
);
126126
mainGroup = FBB08B612445186200385E5A;
127127
packageReferences = (
128-
FBB08B9B2445DBBA00385E5A /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */,
128+
FBF3C4E32449BD4B000AC410 /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */,
129129
);
130130
productRefGroup = FBB08B6B2445186200385E5A /* Products */;
131131
projectDirPath = "";
@@ -352,20 +352,20 @@
352352
/* End XCConfigurationList section */
353353

354354
/* Begin XCRemoteSwiftPackageReference section */
355-
FBB08B9B2445DBBA00385E5A /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */ = {
355+
FBF3C4E32449BD4B000AC410 /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */ = {
356356
isa = XCRemoteSwiftPackageReference;
357357
repositoryURL = "https://github.com/dadalar/SwiftUI-CardStackView.git";
358358
requirement = {
359-
branch = master;
360-
kind = branch;
359+
kind = exactVersion;
360+
version = 0.0.2;
361361
};
362362
};
363363
/* End XCRemoteSwiftPackageReference section */
364364

365365
/* Begin XCSwiftPackageProductDependency section */
366-
FBB08B9C2445DBBA00385E5A /* CardStack */ = {
366+
FBF3C4E42449BD4B000AC410 /* CardStack */ = {
367367
isa = XCSwiftPackageProductDependency;
368-
package = FBB08B9B2445DBBA00385E5A /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */;
368+
package = FBF3C4E32449BD4B000AC410 /* XCRemoteSwiftPackageReference "SwiftUI-CardStackView" */;
369369
productName = CardStack;
370370
};
371371
/* End XCSwiftPackageProductDependency section */

Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/example.gif

3.29 MB
Loading

Example/iOSExample/ContentView.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ struct Basic: View {
8585
CardStack(
8686
direction: LeftRight.direction,
8787
data: data,
88-
onSwipe: { direction in
89-
print("Swiped to \(direction)")
88+
onSwipe: { card, direction in
89+
print("Swiped \(card.name) to \(direction)")
9090
},
9191
content: { person, _, _ in
9292
CardView(person: person)
@@ -106,8 +106,8 @@ struct Thumbs: View {
106106
CardStack(
107107
direction: LeftRight.direction,
108108
data: data,
109-
onSwipe: { direction in
110-
print("Swiped to \(direction)")
109+
onSwipe: { card, direction in
110+
print("Swiped \(card.name) to \(direction)")
111111
},
112112
content: { person, direction, _ in
113113
CardViewWithThumbs(person: person, direction: direction)
@@ -127,7 +127,7 @@ struct AddingCards: View {
127127
CardStack(
128128
direction: LeftRight.direction,
129129
data: data,
130-
onSwipe: { _ in
130+
onSwipe: { _, _ in
131131
self.data.append(Person.mock.randomElement()!)
132132
},
133133
content: { person, _, _ in
@@ -149,8 +149,8 @@ struct ReloadCards: View {
149149
CardStack(
150150
direction: LeftRight.direction,
151151
data: data,
152-
onSwipe: { direction in
153-
print("Swiped to \(direction)")
152+
onSwipe: { card, direction in
153+
print("Swiped \(card.name) to \(direction)")
154154
},
155155
content: { person, _, _ in
156156
CardView(person: person)
@@ -191,8 +191,8 @@ struct CustomDirection: View {
191191
CardStack(
192192
direction: MyDirection.direction,
193193
data: data,
194-
onSwipe: { direction in
195-
print("Swiped to \(direction)")
194+
onSwipe: { card, direction in
195+
print("Swiped \(card.name) to \(direction)")
196196
},
197197
content: { person, _, _ in
198198
CardView(person: person)
@@ -212,8 +212,8 @@ struct CustomConfiguration: View {
212212
CardStack(
213213
direction: LeftRight.direction,
214214
data: data,
215-
onSwipe: { direction in
216-
print("Swiped to \(direction)")
215+
onSwipe: { card, direction in
216+
print("Swiped \(card.name) to \(direction)")
217217
},
218218
content: { person, _, _ in
219219
CardView(person: person)

README.md

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,133 @@
1-
# CardStack
1+
# 🃏 CardStack
22

3-
A description of this package.
3+
[![Swift](https://img.shields.io/badge/swift-5.1-brightgreen.svg?style=flat)](https://swift.org)
4+
[![Version](https://img.shields.io/cocoapods/v/SwiftUICardStack.svg?style=flat)](http://cocoapods.org/pods/SwiftUICardStack)
5+
[![License](https://img.shields.io/cocoapods/l/SwiftUICardStack.svg?style=flat)](http://cocoapods.org/pods/SwiftUICardStack)
6+
[![Platform](https://img.shields.io/cocoapods/p/SwiftUICardStack.svg?style=flat)](http://cocoapods.org/pods/SwiftUICardStack)
7+
8+
A easy-to-use SwiftUI view for Tinder like cards on iOS, macOS & watchOS.
9+
10+
![Alt text](/Example/example.gif?raw=true "CardStack example gif")
11+
12+
## Installation
13+
14+
### Xcode 11 & Swift Package Manager
15+
16+
Use the package repository URL in Xcode or SPM package.swift file: `https://github.com/dadalar/SwiftUI-CardStackView.git`
17+
18+
### CocoaPods
19+
20+
CardStack is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:
21+
22+
```ruby
23+
pod "SwiftUICardStack"
24+
```
25+
26+
## Usage
27+
28+
The usage of this component is similar to SwiftUI's List. A basic implementation would be like this:
29+
30+
```swift
31+
@State var cards: [Card] // This is the data to be shown in CardStack
32+
33+
CardStack(
34+
direction: LeftRight.direction, // See below for directions
35+
data: cards,
36+
onSwipe: { card, direction in // Closure to be called when a card is swiped.
37+
print("Swiped \(card) to \(direction)")
38+
},
39+
content: { card, direction, isOnTop in // View builder function
40+
CardView(card)
41+
}
42+
)
43+
```
44+
45+
### Direction
46+
47+
CardStack needs to know which directions are available and how a swipe angle can be transformed into that direction. This is a conscious decision to make the component easily extendable while keeping type safety. The argument that needs to be passed to CardStack Initializer is a simple `(Double) -> Direction?` function. The Double input here is the angle in degrees where 0 points to up and 180 points to down. Direction is a generic type, that means users of this library can use their own types. Return nil from this function to indicate that that angle is not a valid direction (users won't be able to swipe to that direction).
48+
49+
There are the following predefined directions (`LeftRight`, `FourDirections`, `EightDirections`) and each of them define a direction(double:) function which can used in the CardStack Initializer. You can check the example project for a custom direction implementation.
50+
51+
### Configuration
52+
53+
CardStack can be configured with SwiftUI's standard environment values. It can be directly set on the CardStack or an encapsulating view of it.
54+
55+
```swift
56+
CardStack(
57+
// Initialize
58+
)
59+
.environment(\.cardStackConfiguration, CardStackConfiguration(
60+
maxVisibleCards: 3,
61+
swipeThreshold: 0.1,
62+
cardOffset: 40,
63+
cardScale: 0.2,
64+
animation: .linear
65+
))
66+
```
67+
68+
### Use case: Appending items
69+
70+
It's really easy to load new data and append to the stack. Just make sure the data property is marked as `@State` and then you can append to the array. Please check the example project for a real case scenario.
71+
72+
```swift
73+
struct AddingCards: View {
74+
@State var data: [Person] // Some initial data
75+
76+
var body: some View {
77+
CardStack(
78+
direction: LeftRight.direction,
79+
data: data,
80+
onSwipe: { _, _ in },
81+
content: { person, _, _ in
82+
CardView(person: person)
83+
}
84+
)
85+
.navigationBarItems(trailing:
86+
Button(action: {
87+
self.data.append(contentsOf: [ /* some new data */ ])
88+
}) {
89+
Text("Append")
90+
}
91+
)
92+
}
93+
}
94+
```
95+
96+
### Use case: Reload items
97+
98+
Since the component keeps an internal index of the current card, changing the order of the data or appending/removing items before the current item will break the component. If you want to replace the whole data, you need to force SwiftUI to reconstruct the component by changing the id of the component. Please check the example project for a real case scenario.
99+
100+
```swift
101+
struct ReloadCards: View {
102+
@State var reloadToken = UUID()
103+
@State var data: [Person] = Person.mock.shuffled()
104+
105+
var body: some View {
106+
CardStack(
107+
direction: LeftRight.direction,
108+
data: data,
109+
onSwipe: { _, _ in },
110+
content: { person, _, _ in
111+
CardView(person: person)
112+
}
113+
)
114+
.id(reloadToken)
115+
.navigationBarItems(trailing:
116+
Button(action: {
117+
self.reloadToken = UUID()
118+
self.data = Person.mock.shuffled()
119+
}) {
120+
Text("Reload")
121+
}
122+
)
123+
}
124+
}
125+
```
126+
127+
## Author
128+
129+
Deniz Adalar, [email protected]
130+
131+
## License
132+
133+
CardStack is available under the MIT license. See the LICENSE file for more info.

Sources/CardStack/CardStack.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public extension CardStack where Data.Element: Identifiable, ID == Data.Element.
8585

8686
public extension CardStack where Data.Element: Hashable, ID == Data.Element {
8787

88-
init(
88+
init(
8989
direction: @escaping (Double) -> Direction?,
9090
data: Data,
9191
onSwipe: @escaping (Data.Element, Direction) -> (),

Sources/CardStack/CardSwipeDirection.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
import Foundation
22

3+
/*
4+
5+
Degrees will be passed as Double between 0 and 360 where 0 points to top and 180 points to bottom.
6+
7+
0
8+
|
9+
|
10+
|
11+
270--------------90
12+
|
13+
|
14+
|
15+
180
16+
17+
*/
18+
319
public enum LeftRight {
420
case left, right
521

@@ -24,3 +40,20 @@ public enum FourDirections {
2440
}
2541
}
2642
}
43+
44+
public enum EightDirections {
45+
case top, right, bottom, left, topLeft, topRight, bottomLeft, bottomRight
46+
47+
public static func direction(degrees: Double) -> Self? {
48+
switch degrees {
49+
case 022.5..<067.5: return .topRight
50+
case 067.5..<112.5: return .right
51+
case 112.5..<157.5: return .bottomRight
52+
case 157.5..<202.5: return .bottom
53+
case 202.5..<247.5: return .bottomLeft
54+
case 247.5..<292.5: return .left
55+
case 292.5..<337.5: return .topLeft
56+
default: return .top
57+
}
58+
}
59+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import XCTest
2+
import CardStack
3+
4+
final class DirectionTests: XCTestCase {
5+
6+
func testLeftRight() {
7+
func assertDirection(_ degrees: Double, _ direction: LeftRight?) {
8+
XCTAssertEqual(LeftRight.direction(degrees: degrees), direction)
9+
}
10+
11+
assertDirection(0, nil)
12+
assertDirection(90, .right)
13+
assertDirection(180, nil)
14+
assertDirection(270, .left)
15+
}
16+
17+
func testFourDirections() {
18+
func assertDirection(_ degrees: Double, _ direction: FourDirections?) {
19+
XCTAssertEqual(FourDirections.direction(degrees: degrees), direction)
20+
}
21+
22+
assertDirection(0, .top)
23+
assertDirection(90, .right)
24+
assertDirection(180, .bottom)
25+
assertDirection(270, .left)
26+
}
27+
28+
func testEightDirections() {
29+
func assertDirection(_ degrees: Double, _ direction: EightDirections?) {
30+
XCTAssertEqual(EightDirections.direction(degrees: degrees), direction)
31+
}
32+
33+
assertDirection(0, .top)
34+
assertDirection(45, .topRight)
35+
assertDirection(90, .right)
36+
assertDirection(135, .bottomRight)
37+
assertDirection(180, .bottom)
38+
assertDirection(225, .bottomLeft)
39+
assertDirection(270, .left)
40+
assertDirection(315, .topLeft)
41+
}
42+
43+
}

0 commit comments

Comments
 (0)