Skip to content

Commit af3b8fb

Browse files
author
Trent Guillory
committed
Basics are working
1 parent bf2283c commit af3b8fb

File tree

7 files changed

+190
-13
lines changed

7 files changed

+190
-13
lines changed

SnapToScrollDemo/SnapToScrollDemo.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
84C99CD826D99BA100C1D5C4 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C99CD726D99BA100C1D5C4 /* Model.swift */; };
1011
84D9FA3626D9753600F87EF5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D9FA3526D9753600F87EF5 /* AppDelegate.swift */; };
1112
84D9FA3826D9753600F87EF5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D9FA3726D9753600F87EF5 /* SceneDelegate.swift */; };
1213
84D9FA3A26D9753600F87EF5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D9FA3926D9753600F87EF5 /* ContentView.swift */; };
@@ -17,6 +18,7 @@
1718
/* End PBXBuildFile section */
1819

1920
/* Begin PBXFileReference section */
21+
84C99CD726D99BA100C1D5C4 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
2022
84D9FA3226D9753600F87EF5 /* SnapToScrollDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SnapToScrollDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
2123
84D9FA3526D9753600F87EF5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
2224
84D9FA3726D9753600F87EF5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -65,6 +67,7 @@
6567
84D9FA3526D9753600F87EF5 /* AppDelegate.swift */,
6668
84D9FA3726D9753600F87EF5 /* SceneDelegate.swift */,
6769
84D9FA3926D9753600F87EF5 /* ContentView.swift */,
70+
84C99CD726D99BA100C1D5C4 /* Model.swift */,
6871
);
6972
path = Sources;
7073
sourceTree = "<group>";
@@ -171,6 +174,7 @@
171174
84D9FA3626D9753600F87EF5 /* AppDelegate.swift in Sources */,
172175
84D9FA3826D9753600F87EF5 /* SceneDelegate.swift in Sources */,
173176
84D9FA3A26D9753600F87EF5 /* ContentView.swift in Sources */,
177+
84C99CD826D99BA100C1D5C4 /* Model.swift in Sources */,
174178
);
175179
runOnlyForDeploymentPostprocessing = 0;
176180
};

SnapToScrollDemo/Sources/ContentView.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,29 @@ import SwiftUI
44
// MARK: - ContentView
55

66
struct ContentView: View {
7+
8+
@State var items = [("one", UUID()), ("two", UUID()), ("three", UUID()), ("four", UUID()), ("five", UUID()), ("six", UUID())]
9+
710
var body: some View {
811
VStack {
9-
SnapHStack()
12+
13+
HStackSnap {
14+
15+
ForEach(items, id: \.1) { item in
16+
17+
Text(item.0)
18+
.font(.largeTitle)
19+
.padding()
20+
.overlay(GeometryReaderOverlay(id: item.1))
21+
}
22+
}
1023
}
24+
25+
/// SnapHStack {
26+
/// customForEach { item in
27+
///
28+
/// }
29+
/// }
1130
}
1231
}
1332

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
//
3+
//import Foundation
4+
//
5+
//struct Model: Identifiable {
6+
//
7+
// var systemImage: String
8+
// var
9+
//}

Sources/HStackSnap.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
public struct HStackSnap<Content: View>: View {
5+
6+
// MARK: Lifecycle
7+
8+
public init(@ViewBuilder content: @escaping () -> Content) {
9+
10+
self.content = content
11+
}
12+
13+
// MARK: Public
14+
15+
public var body: some View {
16+
17+
GeometryReader { geometry in
18+
ScrollView(.horizontal) {
19+
20+
HStack(content: content)
21+
.frame(maxWidth: .infinity)
22+
.offset(x: scrollOffset, y: .zero)
23+
}
24+
.onPreferenceChange(ContentPreferenceKey.self, perform: { preferences in
25+
26+
for pref in preferences {
27+
28+
itemFrames[pref.id] = pref
29+
// print(pref.rect.minX)
30+
}
31+
})
32+
.coordinateSpace(name: ContentPreferenceKey.coordinateSpace)
33+
.disabled(true)
34+
.gesture(
35+
36+
DragGesture()
37+
.onChanged { gesture in
38+
39+
self.scrollOffset = gesture.translation.width + prevScrollOffset
40+
41+
}.onEnded { event in
42+
43+
// scrollOffset += event.translation.width
44+
// dragOffset = 0
45+
46+
guard var closestFrame: ContentPreferenceData = itemFrames.first?.value else { return }
47+
48+
func distanceToFrame(x: CGFloat, absolute: Bool) -> CGFloat {
49+
50+
if absolute {
51+
return abs(targetOffset - x)
52+
} else {
53+
return targetOffset - x
54+
}
55+
}
56+
57+
for (key, value) in itemFrames {
58+
59+
let currDistance = distanceToFrame(
60+
x: closestFrame.rect.minX,
61+
absolute: true)
62+
let newDistance = distanceToFrame(x: value.rect.minX, absolute: true)
63+
64+
print("~~ \(value.rect.maxX)")
65+
66+
if newDistance < currDistance {
67+
68+
closestFrame = value
69+
}
70+
}
71+
72+
withAnimation {
73+
74+
print(distanceToFrame(x: closestFrame.rect.minX, absolute: false))
75+
76+
scrollOffset += distanceToFrame(
77+
x: closestFrame.rect.minX,
78+
absolute: false)
79+
}
80+
81+
})
82+
.onTapGesture {
83+
84+
scrollOffset = 0
85+
prevScrollOffset = 0
86+
}
87+
}
88+
}
89+
90+
// MARK: Internal
91+
92+
var content: () -> Content
93+
94+
// MARK: Private
95+
96+
@State private var scrollOffset: CGFloat = 0
97+
@State private var prevScrollOffset: CGFloat = 0
98+
99+
@State private var targetOffset: CGFloat = 0
100+
101+
@State private var itemFrames: [UUID: ContentPreferenceData] = [:]
102+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
// MARK: - ExerciseEditorCellPreferenceData
5+
6+
struct ContentPreferenceData: Equatable {
7+
8+
let id: UUID
9+
let rect: CGRect
10+
}
11+
12+
// MARK: - ExerciseEditorCellPreferenceKey
13+
14+
struct ContentPreferenceKey: PreferenceKey {
15+
16+
typealias Value = [ContentPreferenceData]
17+
18+
// MARK: Internal
19+
20+
static var defaultValue: [ContentPreferenceData] = []
21+
22+
static var coordinateSpace: String = "ContentPreferenceKey"
23+
24+
static func reduce(
25+
value: inout Value,
26+
nextValue: () -> Value) {
27+
28+
value.append(contentsOf: nextValue())
29+
}
30+
}

Sources/SnapHStack.swift

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
public struct GeometryReaderOverlay: View {
5+
6+
let id: UUID
7+
8+
public init(id: UUID) {
9+
10+
self.id = id
11+
}
12+
13+
public var body: some View {
14+
15+
GeometryReader { geometry in
16+
17+
Rectangle().fill(Color.blue.opacity(0.5))
18+
.preference(
19+
key: ContentPreferenceKey.self,
20+
value: [ContentPreferenceData(
21+
id: id,
22+
rect: geometry.frame(in: .named(ContentPreferenceKey.coordinateSpace)))])
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)