Skip to content

Commit 38273cd

Browse files
authored
Merge pull request #1 from fermoya/rotation-effect
Rotation effect
2 parents 1d22a58 + 106565d commit 38273cd

File tree

6 files changed

+68
-7
lines changed

6 files changed

+68
-7
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ whereas a value greater than one will make it look like a box:
6363

6464
<img src="resources/page_aspect_ratio_greater_than_1.png" alt="PageAspectRatio greater than 1" height="640"/>
6565

66-
You can also use `interactive` to pass a shrink ratio that will be applied to those components that are not focused, that is, those elements whose index is different from `pageIndex` binding:
66+
### Animations
67+
68+
Use `interactive` to pass a shrink ratio that will be applied to those components that are not focused, that is, those elements whose index is different from `pageIndex` binding:
6769

6870
```swift
6971
Pager(...)
@@ -72,6 +74,16 @@ Pager(...)
7274

7375
<img src="resources/interactive-pager.gif" alt="Interactive pager"/>
7476

77+
You can also use `rotation3D` to add a rotation effect to your pages:
78+
79+
```swift
80+
Pager(...)
81+
.itemSpacing(10)
82+
.rotation3D()
83+
```
84+
85+
<img src="resources/rotation3D.gif" alt="PageAspectRatio lower than 1" height="640"/>
86+
7587
### Gestures
7688

7789
`Pager` comes with the following built-in gestures:

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

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

Sources/SwiftUIPager/Pager+Buildable.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,22 @@ extension Pager: Buildable {
1212

1313
/// Call this method to provide a shrink ratio that will apply to the items that are not focused.
1414
///
15-
/// - Parameter scale: shrink ratio
16-
/// - Note: `scale` must be lower than _1_, otherwise it defaults to _1_
15+
/// - Parameter scale: shrink ratio
16+
/// - Note: `scale` must be lower than _1_, otherwise it defaults to _1_
1717
public func interactive(_ scale: CGFloat) -> Self {
18+
guard !shouldRotate else { return self }
1819
let scale = min(1, abs(scale))
1920
return mutating(keyPath: \.interactiveScale, value: scale)
2021
}
22+
23+
/// Call this method to add a 3D rotation effect.
24+
///
25+
/// - Parameter value: `true` if the pages should have a 3D rotation effect
26+
/// - Note: If you call this method, any previous or later call to `interactive` will have no effect.
27+
public func rotation3D(_ value: Bool = true) -> Self {
28+
mutating(keyPath: \.interactiveScale, value: rotationInteractiveScale)
29+
.mutating(keyPath: \.shouldRotate, value: value)
30+
}
2131

2232
/// Provides an offset to modify the
2333
public func contentOffset(_ pageOffset: Double) -> Self {

Sources/SwiftUIPager/Pager+Helper.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,31 @@ extension Pager {
104104
return max(offsetUpperbound, min(offsetLowerbound, offset))
105105
}
106106

107+
/// Angle for the 3D rotation effect
108+
func angle(for item: Data) -> Angle {
109+
guard shouldRotate else { return .zero }
110+
guard let index = data.firstIndex(of: item) else { return .zero }
111+
112+
let totalIncrement = abs(totalOffset / pageDistance)
113+
114+
let currentAngle = index == page ? .zero : index < page ? Angle(degrees: rotationDegrees) : Angle(degrees: -rotationDegrees)
115+
guard isDragging else {
116+
return currentAngle
117+
}
118+
119+
let newAngle = direction == .forward ? Angle(degrees: currentAngle.degrees + rotationDegrees * Double(totalIncrement)) : Angle(degrees: currentAngle.degrees - rotationDegrees * Double(totalIncrement) )
120+
return newAngle
121+
}
122+
123+
/// Axis for the rotations effect
124+
func axis(for item: Data) -> (CGFloat, CGFloat, CGFloat) {
125+
guard shouldRotate else { return (0, 0, 0) }
126+
guard let index = data.firstIndex(of: item) else { return (0, 0, 0) }
127+
128+
let currentXAxis: CGFloat = index == page ? 0 : index < page ? rotationAxis.x : -rotationAxis.x
129+
return (currentXAxis, rotationAxis.y, rotationAxis.z)
130+
}
131+
107132
/// Scale that applies to a particular item
108133
func scale(for item: Data) -> CGFloat {
109134
guard isDragging else { return isFocused(item) ? 1 : interactiveScale }

Sources/SwiftUIPager/Pager.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ public struct Pager<Data, Content>: View where Content: View, Data: Identifiabl
4545
/// A ratio of 3, for instance, would mean the items held in memory are enough
4646
/// to cover 3 times the size of the pager
4747
let recyclingRatio = 4
48+
49+
/// Angle of rotation when should rotate
50+
let rotationDegrees: Double = 20
51+
52+
/// Angle of rotation when should rotate
53+
let rotationInteractiveScale: CGFloat = 0.7
54+
55+
/// Axis of rotation when should rotate
56+
let rotationAxis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0.2, 1, 0)
4857

4958
/*** Dependencies ***/
5059

@@ -58,6 +67,9 @@ public struct Pager<Data, Content>: View where Content: View, Data: Identifiabl
5867

5968
/// Shrink ratio that affects the items that aren't focused
6069
var interactiveScale: CGFloat = 1
70+
71+
/// `true` if pages should have a 3D rotation effect
72+
var shouldRotate: Bool = false
6173

6274
/// Used to modify `Pager` offset outside this view
6375
var contentOffset: CGFloat = 0
@@ -94,9 +106,9 @@ public struct Pager<Data, Content>: View where Content: View, Data: Identifiabl
94106

95107
/// Initializes a new Pager.
96108
///
97-
/// - Parameter page: Binding to the index of the focused page
98-
/// - Parameter data: Array of items to populate the content
99-
/// - Parameter content: Factory method to build new pages
109+
/// - Parameter page: Binding to the index of the focused page
110+
/// - Parameter data: Array of items to populate the content
111+
/// - Parameter content: Factory method to build new pages
100112
public init(page: Binding<Int>, data: [Data], @ViewBuilder content: @escaping (Data) -> Content) {
101113
self._page = page
102114
self.data = data
@@ -109,6 +121,8 @@ public struct Pager<Data, Content>: View where Content: View, Data: Identifiabl
109121
self.content(item)
110122
.frame(size: self.pageSize)
111123
.scaleEffect(self.scale(for: item))
124+
.rotation3DEffect(self.angle(for: item),
125+
axis: self.axis(for: item))
112126
.onTapGesture (perform: {
113127
withAnimation(.spring()) {
114128
self.scrollToItem(item)

resources/rotation3D.gif

4.33 MB
Loading

0 commit comments

Comments
 (0)