Skip to content

Commit 1219082

Browse files
author
Trent Guillory
committed
Finalized calculation for opacity.
1 parent ddaf9ae commit 1219082

File tree

7 files changed

+189
-41
lines changed

7 files changed

+189
-41
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1300"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "ScrollViewReactiveHeader"
18+
BuildableName = "ScrollViewReactiveHeader"
19+
BlueprintName = "ScrollViewReactiveHeader"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "ScrollViewReactiveHeader"
54+
BuildableName = "ScrollViewReactiveHeader"
55+
BlueprintName = "ScrollViewReactiveHeader"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55

66
let package = Package(
77
name: "ScrollViewReactiveHeader",
8-
platforms: [.macOS(.v11), .iOS(.v14),],
8+
platforms: [.macOS(.v11), .iOS(.v13),],
99
products: [
1010
// Products define the executables and libraries a package produces, and make them visible to other packages.
1111
.library(

ScrollViewReactiveHeaderDemoApp/Sources/ContentView.swift

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,41 @@ import SwiftUI
44
// MARK: - ContentView
55

66
struct ContentView: View {
7-
7+
88
var body: some View {
99

10-
ScrollViewReactiveHeader(header: {
11-
12-
HeaderView()
13-
}, headerOverlay: {
14-
15-
HeaderOverlay()
16-
}, body: {
17-
18-
ScrollViewContent()
19-
})
10+
ZStack(alignment: .topLeading) {
11+
12+
ScrollViewReactiveHeader(header: {
13+
14+
HeaderView()
15+
}, headerOverlay: {
16+
17+
HeaderOverlay()
18+
}, body: {
19+
20+
ScrollViewContent()
21+
})
22+
23+
HStack {
24+
25+
Spacer()
26+
27+
Image(systemName: "magnifyingglass")
28+
.imageScale(.medium)
29+
30+
Text("Where are you going?")
31+
.font(.callout)
32+
.fontWeight(.medium)
33+
34+
Spacer()
35+
}
36+
.opacity(0.8)
37+
.padding(6)
38+
.background(Color.white)
39+
.cornerRadius(20)
40+
.padding()
41+
}
2042
}
2143
}
2244

@@ -29,7 +51,7 @@ struct HeaderView: View {
2951
Image("night-sky")
3052
.resizable()
3153
.aspectRatio(contentMode: .fill)
32-
.frame(height: 350)
54+
.frame(height: 450)
3355
}
3456
}
3557

@@ -39,8 +61,23 @@ struct HeaderOverlay: View {
3961

4062
var body: some View {
4163

42-
Text("Not sure where to go? \n Perfect.")
43-
.foregroundColor(.white)
64+
VStack {
65+
66+
Spacer()
67+
68+
Text("Not sure where to go?")
69+
.font(.title)
70+
.frame(maxWidth: .infinity, alignment: .center)
71+
.foregroundColor(.white)
72+
73+
Text("Perfect")
74+
.font(.title)
75+
.frame(maxWidth: .infinity, alignment: .center)
76+
.foregroundColor(.white)
77+
78+
Spacer()
79+
}
80+
.frame(height: 450)
4481
}
4582
}
4683

@@ -61,7 +98,7 @@ struct ScrollViewContent: View {
6198

6299
Text("content")
63100
.frame(maxWidth: .infinity, alignment: .leading)
64-
101+
65102
Spacer()
66103
}
67104
.frame(height: 600)
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import Foundation
22
import SwiftUI
33

4-
public struct GeometryReaderOverlay: View {
4+
struct ContentPreferenceData: Equatable {
5+
6+
let rect: CGRect
7+
}
8+
9+
struct GeometryReaderOverlay<Key: PreferenceKey>: View where Key.Value == ContentPreferenceData {
510

611
// MARK: Lifecycle
712

8-
public init(coordinateSpace: String = "ReactiveHeader") {
13+
public init(key: Key.Type, coordinateSpace: String = "ReactiveHeader") {
914

1015
self.coordinateSpace = coordinateSpace
16+
self.preferenceKey = key.self
1117
}
1218

1319
// MARK: Public
@@ -18,7 +24,7 @@ public struct GeometryReaderOverlay: View {
1824

1925
Rectangle().fill(Color.clear)
2026
.preference(
21-
key: ScrollViewHeaderKey.self,
27+
key: preferenceKey.self,
2228
value: ContentPreferenceData(
2329
rect: geometry.frame(in: .named(coordinateSpace))))
2430
}
@@ -27,4 +33,5 @@ public struct GeometryReaderOverlay: View {
2733
// MARK: Internal
2834

2935
let coordinateSpace: String
36+
let preferenceKey: Key.Type
3037
}

Sources/Model/HeaderPreferenceKey.swift renamed to Sources/HeaderGeometry/HeaderPreferenceKey.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import Foundation
22
import SwiftUI
33

4-
// MARK: - ContentPreferenceData
5-
6-
struct ContentPreferenceData: Equatable {
7-
8-
let rect: CGRect
9-
}
10-
114
// MARK: - ScrollViewHeaderKey
125

136
struct ScrollViewHeaderKey: PreferenceKey {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
// MARK: - ScrollViewHeaderKey
5+
6+
struct ScrollViewBodyKey: PreferenceKey {
7+
8+
typealias Value = ContentPreferenceData
9+
10+
// MARK: Internal
11+
12+
static var defaultValue: ContentPreferenceData = .init(rect: .zero)
13+
14+
static func reduce(
15+
value: inout Value,
16+
nextValue: () -> Value) {
17+
18+
value = nextValue()
19+
}
20+
}

Sources/ScrollViewReactiveHeader.swift

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,70 @@ public struct ScrollViewReactiveHeader<A, B, C>: View where A: View, B: View, C:
2424
GeometryReader { geometry in
2525

2626
VStack(content: header)
27-
.overlay(GeometryReaderOverlay())
27+
.overlay(GeometryReaderOverlay(key: ScrollViewHeaderKey.self))
28+
.offset(x: .zero, y: headerOffset)
29+
.scaleEffect(headerScale)
30+
.opacity(headerOpacity)
2831

2932
VStack(content: headerOverlay)
3033

31-
ScrollViewReader { proxy in
34+
ScrollView {
3235

33-
ScrollView {
34-
35-
VStack {}
36-
.frame(height: headerHeight)
36+
VStack {}
37+
.frame(height: headerHeight)
38+
.overlay(GeometryReaderOverlay(key: ScrollViewBodyKey.self))
3739

38-
VStack(content: bodyContent)
39-
.background(backgroundColor)
40-
}
40+
VStack(content: bodyContent)
41+
.background(backgroundColor)
4142
}
4243
}
4344
.onPreferenceChange(ScrollViewHeaderKey.self, perform: { preference in
44-
45+
4546
guard preference.rect != .zero,
4647
headerHeight == .none else { return }
47-
48+
4849
headerHeight = preference.rect.height
4950
})
5051
}
51-
}
52-
53-
var backgroundColor: Color {
52+
.background(backgroundColor)
53+
.coordinateSpace(name: "ReactiveHeader")
54+
.onPreferenceChange(ScrollViewBodyKey.self, perform: { preference in
5455

55-
colorScheme == .dark ? .black : .white
56+
headerOffset = min(0, preference.rect.minY / 10)
57+
58+
headerScale = max(1, 1 + preference.rect.minY / 500)
59+
60+
guard let headerHeight = headerHeight else { return }
61+
62+
let startingY = headerHeight / 2
63+
64+
if abs(preference.rect.minY) > startingY {
65+
66+
headerOpacity = (1 - (abs(preference.rect.minY) - startingY) / startingY)
67+
} else {
68+
69+
headerOpacity = 1
70+
}
71+
})
5672
}
5773

5874
// MARK: Internal
5975

6076
@Environment(\.colorScheme) var colorScheme
6177

78+
var backgroundColor: Color {
79+
80+
colorScheme == .dark ? .black : .white
81+
}
82+
6283
// MARK: Private
6384

6485
private var header: () -> A
6586
private var headerOverlay: () -> B
6687
private var bodyContent: () -> C
6788

6889
@State private var headerHeight: CGFloat?
90+
@State private var headerOffset: CGFloat = .zero
91+
@State private var headerScale: CGFloat = 1
92+
@State private var headerOpacity: CGFloat = 1
6993
}

0 commit comments

Comments
 (0)