Skip to content

Instantly share code, notes, and snippets.

@Codelaby
Created December 14, 2023 20:03
Show Gist options
  • Save Codelaby/e9572df2fdc305173c4c7862ab3ced6b to your computer and use it in GitHub Desktop.
Save Codelaby/e9572df2fdc305173c4c7862ab3ced6b to your computer and use it in GitHub Desktop.
Demo_LicenseAgree
import SwiftUI
struct ViewOffsetKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
ZStack {
content().background(
GeometryReader { proxy in
Color.clear.preference(
key: SizePreferenceKey.self,
value: proxy.size
)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.size = preferences
}
}
}
struct CheckboxStyle: ToggleStyle {
@Environment(\.isEnabled) var isEnabled
let controlColor = Color(UIColor.secondaryLabel)
let activeColor = Color.accentColor
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : isEnabled ? "circle" : "circle.dashed")
.resizable()
.frame(width: 24, height: 24)
.foregroundStyle(configuration.isOn ? activeColor : controlColor)
.saturation(isEnabled ? 1: 0)
.opacity(isEnabled ? 1 : 0.3)
configuration.label
.font(.callout)
.saturation(isEnabled ? 1: 0)
.opacity(isEnabled ? 1 : 0.3)
}
.onTapGesture { configuration.isOn.toggle() }
}
}
struct Demo_LicenseAgree: View {
@Environment(\.dismiss) var dismiss
@State var hasScrolledToEnd: Bool = false
@State var checked: Bool = false
let spaceName = "scroll"
@State var wholeSize: CGSize = .zero
@State var scrollViewSize: CGSize = .zero
var body: some View {
NavigationStack {
ChildSizeReader(size: $wholeSize) {
ScrollView {
ChildSizeReader(size: $scrollViewSize) {
VStack(alignment: .leading, spacing: 0) {
Text(generateParagraphs(10))
.font(.caption)
}
.padding()
.background(
GeometryReader { proxy in
Color.clear.preference(
key: ViewOffsetKey.self,
value: -1 * proxy.frame(in: .named(spaceName)).origin.y
)
}
)
.onPreferenceChange(
ViewOffsetKey.self,
perform: { value in
//print("ViewOffsetKey offset: \(value)")
//print("ViewOffsetKey height: \(scrollViewSize.height)")
//print("wholeSize.height: \(wholeSize.height)")
if value >= scrollViewSize.height - wholeSize.height {
//print("User has reached the bottom of the ScrollView.")
hasScrolledToEnd = true
} else {
//print("not reached.")
}
}
)
}
}
.coordinateSpace(name: spaceName)
}
.safeAreaInset(edge: .bottom, content: {
licenseControls
})
.navigationTitle("License Agreement")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
closeButton
}
}
.onChange(of: scrollViewSize, { oldValue, newValue in
//print("scrollViewSize: \(newValue)")
if newValue.height - wholeSize.height <= 0 {
hasScrolledToEnd = true
}
})
}
}
@ViewBuilder
var closeButton: some View {
Button(action: {
dismiss()
}) {
Image(systemName: "xmark.circle.fill")
.font(.body)
.symbolRenderingMode(.palette)
.foregroundStyle(Color(uiColor: .secondaryLabel), .regularMaterial)
}
}
@ViewBuilder
private var licenseControls: some View {
VStack {
HStack {
Toggle("I have read and agree to T&Cs", isOn: $checked)
.padding(10)
.disabled(!hasScrolledToEnd)
.toggleStyle(CheckboxStyle())
}
Button("Continue", action: {
print("continue")
})
.controlSize(.extraLarge)
.buttonStyle(.borderedProminent)
.disabled(!checked)
}
.frame(maxWidth: .infinity)
.background(.regularMaterial)
}
}
#Preview {
Demo_LicenseAgree()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment