Full Code of Wouter125/BottomSheet for AI

main 83f016b6ad8e cached
37 files
61.0 KB
16.4k tokens
1 requests
Download .txt
Repository: Wouter125/BottomSheet
Branch: main
Commit: 83f016b6ad8e
Files: 37
Total size: 61.0 KB

Directory structure:
gitextract_vhrt5p88/

├── .gitignore
├── Example/
│   ├── BottomSheetExample/
│   │   ├── Apple Applications/
│   │   │   └── StocksExample.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── BottomSheetExampleApp.swift
│   │   ├── ExampleOverview.swift
│   │   ├── Examples/
│   │   │   └── StaticScrollViewExample.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   └── View Modifiers/
│   │       └── CornerRadius.swift
│   └── BottomSheetExample.xcodeproj/
│       ├── project.pbxproj
│       ├── project.xcworkspace/
│       │   ├── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       ├── IDEWorkspaceChecks.plist
│       │       └── WorkspaceSettings.xcsettings
│       └── xcshareddata/
│           └── xcschemes/
│               └── BottomSheetExample.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   └── BottomSheet/
│       ├── Animation/
│       │   ├── Animation.swift
│       │   └── AnimationDefaults.swift
│       ├── BottomSheet.swift
│       ├── Detents/
│       │   ├── DetentDefaults.swift
│       │   ├── DetentHelpers.swift
│       │   └── Detents.swift
│       ├── Helpers/
│       │   ├── KeyboardReader.swift
│       │   └── Snapping.swift
│       ├── Preference Keys/
│       │   ├── BackgroundInteractionKey.swift
│       │   ├── ConfigKey.swift
│       │   └── IndicatorKey.swift
│       ├── UIKit Views/
│       │   └── UIScrollViewWrapper.swift
│       ├── View Modifiers/
│       │   ├── View+AnimationChange.swift
│       │   ├── View+BackgroundInteraction.swift
│       │   ├── View+Detents.swift
│       │   ├── View+DragIndicator.swift
│       │   └── View+SheetPlus.swift
│       └── Views/
│           └── DragIndicator.swift
└── Tests/
    └── BottomSheetTests/
        └── BottomSheetTests.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata


================================================
FILE: Example/BottomSheetExample/Apple Applications/StocksExample.swift
================================================
//
//  Stocks.swift
//  BottomSheetExample
//
//  Created by Wouter van de Kamp on 04/12/2022.
//

import SwiftUI
import BottomSheet

struct StocksExample: View {
    @EnvironmentObject var settings: SheetSettings

    var body: some View {
        VStack {
            Button("Close") {
                settings.isPresented.toggle()
            }

            Button("Change") {
                settings.selectedDetent = .large
            }

            Color.clear
                .navigationBarTitleDisplayMode(.inline)
                .navigationTitle("\(settings.translation.rounded())")
                .onAppear {
                    settings.isPresented = true
                    settings.activeSheetType = .stocks
                }
        }
    }
}

struct StocksHeader: View {
    var body: some View {
        VStack {
            HStack {
                VStack(alignment: .leading, spacing: 2) {
                    Text("Business News")
                        .font(.title)
                        .fontWeight(.heavy)
                    Text("From Yahoo Finance")
                        .foregroundColor(Color(UIColor.secondaryLabel))
                }
                .padding(.top, 10)
                .padding(.bottom, 16)

                Spacer()
            }

            Divider()
             .frame(height: 1)
             .background(Color(UIColor.systemGray6))
        }
        .padding(.top, 8)
        .padding(.horizontal, 16)
    }
}

struct StocksMainContent: View {
    var body: some View {
        VStack(spacing: 0) {
            ScrollView {
                ForEach(0..<5, id: \.self) { _ in
                    newsRow
                }
            }
            Spacer(minLength: 72)
        }
    }

    var newsRow: some View {
        VStack {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Text("FX Empire")
                        .font(.caption)
                        .foregroundColor(Color(UIColor.secondaryLabel))

                    Text("Bitcoin (BTC) treads water after brief visit to sub-$39,000")
                        .font(.headline)
                        .foregroundColor(Color(UIColor.label))

                    Text("While Bitcoin (BTC) struggled on Saturday, XRP")
                        .foregroundColor(Color(UIColor.secondaryLabel))
                        .lineLimit(1)
                }
                .padding(.vertical, 16)

                Spacer()
            }

            HStack {
                Text("13h ago")
                    .font(.caption)
                    .fontWeight(.medium)
                    .foregroundColor(Color(UIColor.secondaryLabel))

                Spacer()
            }
        }
        .padding(.horizontal, 16)
    }
}

struct StocksExample_Previews: PreviewProvider {
    static var previews: some View {
        StocksExample()
    }
}


================================================
FILE: Example/BottomSheetExample/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Example/BottomSheetExample/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "20x20"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "29x29"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "40x40"
    },
    {
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "60x60"
    },
    {
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "60x60"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "20x20"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "29x29"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "40x40"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "76x76"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "76x76"
    },
    {
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "83.5x83.5"
    },
    {
      "idiom" : "ios-marketing",
      "scale" : "1x",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Example/BottomSheetExample/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Example/BottomSheetExample/BottomSheetExampleApp.swift
================================================
//
//  BottomSheetExampleApp.swift
//  BottomSheetExample
//
//  Created by Wouter van de Kamp on 10/03/2022.
//

import SwiftUI

@main
struct BottomSheetExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ExampleOverview()
        }
    }
}


================================================
FILE: Example/BottomSheetExample/ExampleOverview.swift
================================================
//
//  ExampleOverview.swift
//  BottomSheetExample
//
//  Created by Wouter van de Kamp on 24/05/2022.
//

import SwiftUI
import BottomSheet

enum SheetExampleTypes {
    case home
    case stocks
    case staticScrollView
}

class SheetSettings: ObservableObject {
    @Published var isPresented = false
    @Published var activeSheetType: SheetExampleTypes = .home
    @Published var selectedDetent: BottomSheet.PresentationDetent = .medium
    @Published var translation: CGFloat = BottomSheet.PresentationDetent.large.size
}

struct ExampleOverview: View {
    @StateObject var settings = SheetSettings()

    var views: [(label: String, view: AnyView)] = [
        (label: "Stocks example", view: AnyView(StocksExample())),
        (label: "Static scrollview example", view: AnyView(StaticScrollViewExample()))
    ]

    @ViewBuilder
    var headerContent: some View {
        switch settings.activeSheetType {
        case .stocks:
            StocksHeader()
        case .staticScrollView:
            StaticScrollViewHeader()
        default:
            EmptyView()
        }
    }

    @ViewBuilder
    var mainContent: some View {
        switch settings.activeSheetType {
        case .stocks:
            StocksMainContent()
                .presentationDetentsPlus(
                    [.height(244), .medium, .large],
                    selection: $settings.selectedDetent
                )
        case .staticScrollView:
            StaticScrollViewContent()
                .presentationDetentsPlus(
                    [.height(244), .height(380), .height(480), .large],
                    selection: $settings.selectedDetent
                )
                .presentationDragIndicatorPlus(.visible)
                .presentationBackgroundInteractionPlus(.enabled(upThrough: .height(380)))
        default:
            EmptyView()
        }
    }

    var body: some View {
        ZStack {
            NavigationView {
                List(views.indices, id: \.self) { index in
                    NavigationLink(destination: views[index].view) {
                        Text(views[index].label)
                    }
                }
                .background(Color(UIColor.systemGroupedBackground))
                .listStyle(.grouped)
                .navigationTitle("Examples")
                .onAppear {
                    settings.isPresented = false
                    settings.activeSheetType = .home
                    settings.selectedDetent = .medium
                }
            }
            .navigationViewStyle(.stack)
        }
        .environmentObject(settings)
        .sheetPlus(
            isPresented: $settings.isPresented,
            background: (
                Color(UIColor.secondarySystemBackground)
                    .cornerRadius(12, corners: [.topLeft, .topRight])
            ),
            onDrag: { translation in
                settings.translation = translation
            },
            header: { headerContent },
            main: {
                mainContent
            }
        )
        .overlay(
            VStack(spacing: 0) {
                Divider()
                    .frame(height: 1)
                    .background(Color(UIColor.systemGray6))

                HStack {
                    Text("Yahoo Finance")
                    Spacer()
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 16)
            }
            .background(
                Color(UIColor.secondarySystemBackground)
                    .edgesIgnoringSafeArea([.bottom])
            )
            .opacity(
                settings.activeSheetType == .stocks ? 1 : 0
            )
            ,
            alignment: .bottom
        )
    }
}

struct ExampleOverview_Previews: PreviewProvider {
    static var previews: some View {
        ExampleOverview()
    }
}


================================================
FILE: Example/BottomSheetExample/Examples/StaticScrollViewExample.swift
================================================
//
//  StaticScrollViewExample.swift
//  BottomSheetExample
//
//  Created by Wouter van de Kamp on 28/10/2023.
//

import SwiftUI

struct StaticScrollViewExample: View {
    @EnvironmentObject var settings: SheetSettings

    var body: some View {
        VStack {
            Button("Close") {
                settings.isPresented.toggle()
            }

            Button("Change") {
                settings.selectedDetent = .large
            }

            Color.clear
                .navigationBarTitleDisplayMode(.inline)
                .navigationTitle("\(settings.translation.rounded())")
                .onAppear {
                    settings.isPresented = true
                    settings.activeSheetType = .staticScrollView
                }
        }
    }
}

struct StaticScrollViewHeader: View {
    @State private var searchterm = ""

    var body: some View {
        VStack {
            TextField("Search item", text: $searchterm)
        }
    }
}

struct StaticScrollViewContent: View {
    var body: some View {
        ScrollView {
            ForEach(0..<5, id: \.self) { idx in
                Text("Item \(idx)")
            }
        }
    }
}

struct StaticScrollViewExample_Previews: PreviewProvider {
    static var previews: some View {
        StaticScrollViewExample()
    }
}


================================================
FILE: Example/BottomSheetExample/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Example/BottomSheetExample/View Modifiers/CornerRadius.swift
================================================
//
//  CornerRadius.swift
//  BottomSheetExample
//
//  Created by Wouter van de Kamp on 25/03/2022.
//

import SwiftUI

struct CornerRadiusStyle: ViewModifier {
    var radius: CGFloat
    var corners: UIRectCorner

    struct CornerRadiusShape: Shape {
        var radius = CGFloat.infinity
        var corners = UIRectCorner.allCorners

        func path(in rect: CGRect) -> Path {
            let path = UIBezierPath(
                roundedRect: rect,
                byRoundingCorners: corners,
                cornerRadii: CGSize(width: radius, height: radius)
            )
            return Path(path.cgPath)
        }
    }

    func body(content: Content) -> some View {
        content
            .clipShape(CornerRadiusShape(radius: radius, corners: corners))
    }
}

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
    }
}


================================================
FILE: Example/BottomSheetExample.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 55;
	objects = {

/* Begin PBXBuildFile section */
		6C0BD46D27DA862D000AF3CD /* BottomSheetExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C0BD46C27DA862D000AF3CD /* BottomSheetExampleApp.swift */; };
		6C0BD47127DA862D000AF3CD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6C0BD47027DA862D000AF3CD /* Assets.xcassets */; };
		6C0BD47427DA862D000AF3CD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6C0BD47327DA862D000AF3CD /* Preview Assets.xcassets */; };
		6C6EB0B6292AEADC00106A1D /* BottomSheet in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6EB0B5292AEADC00106A1D /* BottomSheet */; };
		6C763147283D774500463709 /* ExampleOverview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C763146283D774500463709 /* ExampleOverview.swift */; };
		6C8CBF1D2AED12E00007E10E /* StaticScrollViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8CBF1C2AED12E00007E10E /* StaticScrollViewExample.swift */; };
		6CDF5A0D27ED33C7004609F4 /* CornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDF5A0C27ED33C7004609F4 /* CornerRadius.swift */; };
		6CF78515293D36FB000E6581 /* StocksExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF78514293D36FB000E6581 /* StocksExample.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		6C0BD46927DA862D000AF3CD /* BottomSheetExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BottomSheetExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
		6C0BD46C27DA862D000AF3CD /* BottomSheetExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetExampleApp.swift; sourceTree = "<group>"; };
		6C0BD47027DA862D000AF3CD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		6C0BD47327DA862D000AF3CD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
		6C1DE0102889CF10003C6EE9 /* BottomSheet */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BottomSheet; path = ..; sourceTree = "<group>"; };
		6C763146283D774500463709 /* ExampleOverview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleOverview.swift; sourceTree = "<group>"; };
		6C8CBF1C2AED12E00007E10E /* StaticScrollViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticScrollViewExample.swift; sourceTree = "<group>"; };
		6CDF5A0C27ED33C7004609F4 /* CornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadius.swift; sourceTree = "<group>"; };
		6CF78514293D36FB000E6581 /* StocksExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StocksExample.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		6C0BD46627DA862D000AF3CD /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				6C6EB0B6292AEADC00106A1D /* BottomSheet in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		6C0BD46027DA862C000AF3CD = {
			isa = PBXGroup;
			children = (
				6C1DE0102889CF10003C6EE9 /* BottomSheet */,
				6C0BD46B27DA862D000AF3CD /* BottomSheetExample */,
				6C0BD46A27DA862D000AF3CD /* Products */,
				6C0BD47A27DA87A1000AF3CD /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		6C0BD46A27DA862D000AF3CD /* Products */ = {
			isa = PBXGroup;
			children = (
				6C0BD46927DA862D000AF3CD /* BottomSheetExample.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		6C0BD46B27DA862D000AF3CD /* BottomSheetExample */ = {
			isa = PBXGroup;
			children = (
				6C0BD46C27DA862D000AF3CD /* BottomSheetExampleApp.swift */,
				6C763146283D774500463709 /* ExampleOverview.swift */,
				6CF78516293D3703000E6581 /* Apple Applications */,
				6C8CBF1B2AED12C60007E10E /* Examples */,
				6CDF5A0E27ED33D3004609F4 /* View Modifiers */,
				6C0BD47027DA862D000AF3CD /* Assets.xcassets */,
				6C0BD47227DA862D000AF3CD /* Preview Content */,
			);
			path = BottomSheetExample;
			sourceTree = "<group>";
		};
		6C0BD47227DA862D000AF3CD /* Preview Content */ = {
			isa = PBXGroup;
			children = (
				6C0BD47327DA862D000AF3CD /* Preview Assets.xcassets */,
			);
			path = "Preview Content";
			sourceTree = "<group>";
		};
		6C0BD47A27DA87A1000AF3CD /* Frameworks */ = {
			isa = PBXGroup;
			children = (
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		6C8CBF1B2AED12C60007E10E /* Examples */ = {
			isa = PBXGroup;
			children = (
				6C8CBF1C2AED12E00007E10E /* StaticScrollViewExample.swift */,
			);
			path = Examples;
			sourceTree = "<group>";
		};
		6CDF5A0E27ED33D3004609F4 /* View Modifiers */ = {
			isa = PBXGroup;
			children = (
				6CDF5A0C27ED33C7004609F4 /* CornerRadius.swift */,
			);
			path = "View Modifiers";
			sourceTree = "<group>";
		};
		6CF78516293D3703000E6581 /* Apple Applications */ = {
			isa = PBXGroup;
			children = (
				6CF78514293D36FB000E6581 /* StocksExample.swift */,
			);
			path = "Apple Applications";
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		6C0BD46827DA862D000AF3CD /* BottomSheetExample */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 6C0BD47727DA862D000AF3CD /* Build configuration list for PBXNativeTarget "BottomSheetExample" */;
			buildPhases = (
				6C0BD46527DA862D000AF3CD /* Sources */,
				6C0BD46627DA862D000AF3CD /* Frameworks */,
				6C939DBE294DFF9200F6EF50 /* Swiftlint */,
				6C0BD46727DA862D000AF3CD /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = BottomSheetExample;
			packageProductDependencies = (
				6C6EB0B5292AEADC00106A1D /* BottomSheet */,
			);
			productName = BottomSheetExample;
			productReference = 6C0BD46927DA862D000AF3CD /* BottomSheetExample.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		6C0BD46127DA862C000AF3CD /* Project object */ = {
			isa = PBXProject;
			attributes = {
				BuildIndependentTargetsInParallel = 1;
				LastSwiftUpdateCheck = 1320;
				LastUpgradeCheck = 1320;
				TargetAttributes = {
					6C0BD46827DA862D000AF3CD = {
						CreatedOnToolsVersion = 13.2.1;
					};
				};
			};
			buildConfigurationList = 6C0BD46427DA862C000AF3CD /* Build configuration list for PBXProject "BottomSheetExample" */;
			compatibilityVersion = "Xcode 13.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 6C0BD46027DA862C000AF3CD;
			packageReferences = (
			);
			productRefGroup = 6C0BD46A27DA862D000AF3CD /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				6C0BD46827DA862D000AF3CD /* BottomSheetExample */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		6C0BD46727DA862D000AF3CD /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				6C0BD47427DA862D000AF3CD /* Preview Assets.xcassets in Resources */,
				6C0BD47127DA862D000AF3CD /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
		6C939DBE294DFF9200F6EF50 /* Swiftlint */ = {
			isa = PBXShellScriptBuildPhase;
			alwaysOutOfDate = 1;
			buildActionMask = 2147483647;
			files = (
			);
			inputFileListPaths = (
			);
			inputPaths = (
			);
			name = Swiftlint;
			outputFileListPaths = (
			);
			outputPaths = (
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n  swiftlint\nelse\n  echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
		};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		6C0BD46527DA862D000AF3CD /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				6C0BD46D27DA862D000AF3CD /* BottomSheetExampleApp.swift in Sources */,
				6C8CBF1D2AED12E00007E10E /* StaticScrollViewExample.swift in Sources */,
				6CDF5A0D27ED33C7004609F4 /* CornerRadius.swift in Sources */,
				6C763147283D774500463709 /* ExampleOverview.swift in Sources */,
				6CF78515293D36FB000E6581 /* StocksExample.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		6C0BD47527DA862D000AF3CD /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 15.2;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		6C0BD47627DA862D000AF3CD /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 15.2;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		6C0BD47827DA862D000AF3CD /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_ASSET_PATHS = "BottomSheetExample/Preview\\ Content";
				DEVELOPMENT_TEAM = KZAMEFAGHT;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
				INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = com.chargetrip.BottomSheetExample;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		6C0BD47927DA862D000AF3CD /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_ASSET_PATHS = "BottomSheetExample/Preview\\ Content";
				DEVELOPMENT_TEAM = KZAMEFAGHT;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
				INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = com.chargetrip.BottomSheetExample;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		6C0BD46427DA862C000AF3CD /* Build configuration list for PBXProject "BottomSheetExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				6C0BD47527DA862D000AF3CD /* Debug */,
				6C0BD47627DA862D000AF3CD /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		6C0BD47727DA862D000AF3CD /* Build configuration list for PBXNativeTarget "BottomSheetExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				6C0BD47827DA862D000AF3CD /* Debug */,
				6C0BD47927DA862D000AF3CD /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCSwiftPackageProductDependency section */
		6C6EB0B5292AEADC00106A1D /* BottomSheet */ = {
			isa = XCSwiftPackageProductDependency;
			productName = BottomSheet;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = 6C0BD46127DA862C000AF3CD /* Project object */;
}


================================================
FILE: Example/BottomSheetExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>PreviewsEnabled</key>
	<false/>
</dict>
</plist>


================================================
FILE: Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1410"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "6C0BD46827DA862D000AF3CD"
               BuildableName = "BottomSheetExample.app"
               BlueprintName = "BottomSheetExample"
               ReferencedContainer = "container:BottomSheetExample.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "6C0BD46827DA862D000AF3CD"
            BuildableName = "BottomSheetExample.app"
            BlueprintName = "BottomSheetExample"
            ReferencedContainer = "container:BottomSheetExample.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "6C0BD46827DA862D000AF3CD"
            BuildableName = "BottomSheetExample.app"
            BlueprintName = "BottomSheetExample"
            ReferencedContainer = "container:BottomSheetExample.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Wouter van de Kamp.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: Package.swift
================================================
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "BottomSheet",
    platforms: [
        .iOS(.v14)
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "BottomSheet",
            targets: ["BottomSheet"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "BottomSheet",
            dependencies: []),
        .testTarget(
            name: "BottomSheetTests",
            dependencies: ["BottomSheet"]),
    ]
)


================================================
FILE: README.md
================================================
# BottomSheet

An iOS library for SwiftUI to create draggable sheet experiences similar to iOS applications like Maps and Stocks. 

## Feature overview 

The library currently supports;

- [x] Unlimited snap positions
- [x] Realtime position callback
- [x] Absolute and relative positioning
- [x] Customizable animation parameters
- [x] An optional sticky header
- [x] Views with and without a scrollview

## How to install

Currently BottomSheet is only available through the [Swift Package Manager](https://swift.org/package-manager/) or manual install. 

1. Installation through Swift Package Manager can be done by going to `File > Add Packages`. Then enter the following URL in the searchbar; `https://github.com/Wouter125/BottomSheet`.

2. Manual installation can be done by cloning this repository and dragging all assets into your Xcode Project.

## How to use

1. Import BottomSheet

2. Create a state property that contains the presentation state of the bottom sheet and one for the current selection;

```
@Published var isPresented = false
@Published var selectedDetent: BottomSheet.PresentationDetent = .medium
```

4. Add the `BottomSheetView` to your SwiftUI view hierachy by using a view modifier;

```
.sheetPlus(
    isPresented: $isPresented,
    header: { },
    main: { 
        EmptyView()
            .presentationDetentsPlus(
                [.height(244), .fraction(0.4), .medium, .large],
                selection: $selectedDetent
            )
    }
)
```

5. Optionally receive the current panel position with a callback, change the background color, show a drag indicator or limit the background interaction based on the height;
```
.sheetPlus(
    isPresented: $isPresented,
    background: (
        Color(UIColor.secondarySystemBackground)
    ),
    onDrag: { translation in
        print(translation)
    },
    header: { EmptyView() },
    main: {
        EmptyView()
            .presentationDetentsPlus(
                [.height(244), .fraction(0.4), .medium, .large],
                selection: $selectedDetent
            )
            .presentationDragIndicatorPlus(.visible)
            .presentationBackgroundInteractionPlus(.enabled(upThrough: .height(380)))
    }
)
```

## Interface

| Modifier                 | Type                | Default | Description                                                                       |
|--------------------------|---------------------|---------|-----------------------------------------------------------------------------------|
| animationCurve.mass      | Double              | 1.2     | The mass of the object attached to the spring.                                    |
| animationCurve.stiffness | Double              | 200     | The stiffness of the spring.                                                      |
| animationCurve.damping   | Double              | 25      | The spring damping value.                                                         |

## Example

To give you an idea of how to use this library you can use the example that is attached to this repo. Simply clone it and open the `BottomSheetExample` folder in Xcode.

## Roadmap

1. Add landscape support
2. Add iPad support


================================================
FILE: Sources/BottomSheet/Animation/Animation.swift
================================================
//
//  Animation.swift
//  
//
//  Created by Wouter van de Kamp on 26/11/2022.
//

import Foundation

public struct SheetAnimation {
    var mass: Double
    var stiffness: Double
    var damping: Double
    
    public init(mass: Double, stiffness: Double, damping: Double) {
        self.mass = mass
        self.stiffness = stiffness
        self.damping = damping
    }
}


================================================
FILE: Sources/BottomSheet/Animation/AnimationDefaults.swift
================================================
//
//  AnimationDefaults.swift
//  
//
//  Created by Wouter van de Kamp on 26/11/2022.
//

import Foundation

public struct SheetAnimationDefaults {
    public static let mass: Double =  1.2
    public static let stiffness: Double = 200
    public static let damping: Double = 25
}


================================================
FILE: Sources/BottomSheet/BottomSheet.swift
================================================
//
//  BottomSheet.swift
//
//
//  Created by Wouter van de Kamp on 26/11/2022.
//

import SwiftUI

struct SheetPlus<HContent: View, MContent: View, Background: View>: ViewModifier, KeyboardReader {
    @Binding private var isPresented: Bool
    
    @State private var translation: CGFloat = 0
    @State private var sheetConfig: SheetPlusConfig?
    @State private var showDragIndicator: VisibilityPlus?
    @State private var allowBackgroundInteraction: PresentationBackgroundInteractionPlus?
    
    @State private var newValue = 0.0
    @State private var startTime: DragGesture.Value?
    
    @State private var detents: Set<PresentationDetent> = []
    @State private var limits: (min: CGFloat, max: CGFloat) = (min: 0, max: 0)
    
    let mainContent: MContent
    let headerContent: HContent
    let animationCurve: SheetAnimation
    let onDismiss: () -> Void
    let onDrag: (CGFloat) -> Void
    let background: Background
    
    init(
        isPresented: Binding<Bool>,
        animationCurve: SheetAnimation,
        background: Background,
        onDismiss: @escaping () -> Void,
        onDrag: @escaping (CGFloat) -> Void,
        @ViewBuilder hcontent: () -> HContent,
        @ViewBuilder mcontent: () -> MContent
    ) {
        self._isPresented = isPresented
        
        self.animationCurve = animationCurve
        self.background = background
        self.onDismiss = onDismiss
        self.onDrag = onDrag
        
        self.headerContent = hcontent()
        self.mainContent = mcontent()
    }
    
    func body(content: Content) -> some View {
        ZStack() {
            content
                .allowsHitTesting(allowBackgroundInteraction == .disabled ? false : true)
                
            if isPresented {
                GeometryReader { geometry in
                    VStack(spacing: 0) {
                        // If / else statement here breaks the animation from the bottom level
                        // Might want to see if we can refactor the top level animation a bit
                        DragIndicator(
                            translation: $translation,
                            detents: detents
                        )
                            .frame(height: showDragIndicator == .visible ? 22 : 0)
                            .opacity(showDragIndicator == .visible ? 1 : 0)

                        headerContent
                            .contentShape(Rectangle())
                            .gesture(
                                DragGesture(coordinateSpace: .global)
                                    .onChanged { value in
                                        translation -= value.location.y - value.startLocation.y - newValue
                                        newValue = value.location.y - value.startLocation.y
                                        
                                        if startTime == nil {
                                            startTime = value
                                        }
                                    }
                                    .onEnded { value in
                                        // Reset the distance on release so we start with a
                                        // clean translation next time
                                        newValue = 0
                                        
                                        // Calculate velocity based on pt/s so it matches the UIPanGesture
                                        let distance: CGFloat = value.translation.height
                                        let time: CGFloat = value.time.timeIntervalSince(startTime!.time)
                                        
                                        let yVelocity: CGFloat = -1 * ((distance / time) / 1000)
                                        startTime = nil
                                        
                                        if let result = snapBottomSheet(translation, detents, yVelocity) {
                                            translation = result.size
                                            sheetConfig?.selectedDetent = result
                                        }
                                    }
                            )
                        
                        UIScrollViewWrapper(
                            translation: $translation,
                            preferenceKey: $sheetConfig,
                            limits: limits,
                            detents: detents
                        ) {
                            mainContent
                                .frame(width: geometry.size.width)
                        }
                    }
                    .background(background)
                    .frame(height:
                            (limits.max - geometry.safeAreaInsets.top) > 0
                                ? limits.max - geometry.safeAreaInsets.top
                                : limits.max
                    )
                    .onChange(of: translation) { newValue in
                        // Small little hack to make the iOS scroll behaviour work smoothly
                        if limits.max == 0 { return }
                        translation = min(limits.max, max(newValue, limits.min))

                        currentGlobalTranslation = translation
                    }
                    .onAnimationChange(of: translation) { value in
                        onDrag(value)
                    }
                    .offset(y: UIScreen.main.bounds.height - translation)
                    .onDisappear {
                        translation = 0
                        detents = []
                        
                        onDismiss()
                    }
                    .animation(
                        .interpolatingSpring(
                            mass: animationCurve.mass,
                            stiffness: animationCurve.stiffness,
                            damping: animationCurve.damping
                        )
                    )
                }
                .edgesIgnoringSafeArea([.bottom])
                .transition(.move(edge: .bottom))
            }
        }
        .onPreferenceChange(SheetPlusKey.self) { value in
            /// Quick hack to prevent the scrollview from resetting the height when keyboard shows up.
            /// Replace if the root cause has been located.
            if value.detents.count == 0 { return }
                                                
            sheetConfig = value
            translation = value.translation

            detents = value.detents
            limits = detentLimits(detents: detents)
        }
        .onPreferenceChange(SheetPlusIndicatorKey.self) { value in
            showDragIndicator = value
        }
        .onPreferenceChange(SheetPlusBackgroundInteractionKey.self) { value in
            allowBackgroundInteraction = value
        }
    }
}


================================================
FILE: Sources/BottomSheet/Detents/DetentDefaults.swift
================================================
//
//  DetentsDefaults.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import SwiftUI

internal struct PresentationDetentDefaults {
    static let small: CGFloat = UIScreen.main.bounds.height * 0.2
    static let medium: CGFloat = UIScreen.main.bounds.height * 0.5
    static let large: CGFloat = UIScreen.main.bounds.height * 0.9
}


================================================
FILE: Sources/BottomSheet/Detents/DetentHelpers.swift
================================================
//
//  DetentsHelper.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import SwiftUI

/// Computes the limits of how far the sheet can move.
/// - Parameter detents: The list of detents provided when initializing the bottomsheet
/// - Returns: Tuple with top and bottom. Top reflects the offset from the top of the screen when the sheet is in it's largest form. Bottom reflects the offset from the top of the screen when the sheet is in it's smallest form.
internal func detentLimits(detents: Set<PresentationDetent>) -> (min: CGFloat, max: CGFloat) {
    let detentLimits: [CGFloat] = detents
        .map { detent in
            switch detent {
            case .small:
                return PresentationDetentDefaults.small
            case .medium:
                return PresentationDetentDefaults.medium
            case .large:
                return PresentationDetentDefaults.large
            case .fraction(let fraction):
                return UIScreen.main.bounds.height * fraction
            case .height(let height):
                return height
            }
        }
        .sorted(by: <)
    
    return (min: detentLimits.first ?? 0, max: detentLimits.last ?? 0)
}


================================================
FILE: Sources/BottomSheet/Detents/Detents.swift
================================================
//
//  Detents.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import SwiftUI

public enum PresentationDetent: Hashable {
    case small
    case medium
    case large
    case fraction(CGFloat)
    case height(CGFloat)

    public var size: CGFloat {
        switch self {
        case .small:
            return PresentationDetentDefaults.small
        case .medium:
            return PresentationDetentDefaults.medium
        case .large:
            return PresentationDetentDefaults.large
        case .fraction(let fraction):
            return min(
                UIScreen.main.bounds.height * fraction,
                UIScreen.main.bounds.height
            )
        case .height(let height):
            return min(
                height,
                UIScreen.main.bounds.height
            )
        }
    }
}


================================================
FILE: Sources/BottomSheet/Helpers/KeyboardReader.swift
================================================
//
//  KeyboardReader.swift
//  
//
//  Created by Wouter van de Kamp on 28/10/2023.
//

import Combine
import UIKit

protocol KeyboardReader {
    var keyboardPublisher: AnyPublisher<Bool, Never> { get }
}

extension KeyboardReader {
    var keyboardPublisher: AnyPublisher<Bool, Never> {
        Publishers.Merge(
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .map { _ in true },

            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .map { _ in false }
        )
        .eraseToAnyPublisher()
    }
}


================================================
FILE: Sources/BottomSheet/Helpers/Snapping.swift
================================================
//
//  Snapping.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import Foundation

/// Helper function that computes where the bottomsheet should snap to
/// - Parameters:
///   - translation: the current translated distance
///   - detents: the detents the translation can snap to
///   - yVelocity: the speed at which the drag gesture ended. Used to compute a snapping behaviour
/// - Returns: The snapping position distance
internal func snapBottomSheet(_ translation: CGFloat, _ detents: Set<PresentationDetent>, _ yVelocity: CGFloat) -> PresentationDetent? {
    let detents = detents.sorted(by: { $0.size < $1.size })
    
    let position: [PresentationDetent] = detents.enumerated().compactMap { idx, detent in
        if idx < detents.index(before: detents.count) {
            let detentBracket = (
                lower: detents[idx],
                middle: detents[idx].size + ((detents[idx + 1].size - detents[idx].size) / 2),
                upper: detents[idx + 1]
            )            
            
            if detentBracket.lower.size...detentBracket.upper.size ~= translation {
                if abs(yVelocity) > 1.8 {
                    return yVelocity > 0 ? detentBracket.upper : detentBracket.lower
                } else {
                    return translation > detentBracket.middle
                    ? detentBracket.upper
                    : detentBracket.lower
                }
            }
        }
        
        return nil
    }
    
    return position.first
}


================================================
FILE: Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift
================================================
//
//  BackgroundInteractionKey.swift
//  
//
//  Created by Wouter van de Kamp on 29/10/2023.
//

import SwiftUI

// Currently using a global var.
// Might want to rework this by setting up the view modifiers a bit different.
// Probably something that we can hold translation in 1 var. Now both need to be in sync.
var currentGlobalTranslation: CGFloat = 0

public enum PresentationBackgroundInteractionPlus {
    case automatic
    case disabled
    case enabled

    public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteractionPlus {
        currentGlobalTranslation > detent.size ? .disabled : .enabled
    }
}

struct SheetPlusBackgroundInteractionKey: PreferenceKey {
    static var defaultValue: PresentationBackgroundInteractionPlus = .automatic

    static func reduce(value: inout PresentationBackgroundInteractionPlus, nextValue: () -> PresentationBackgroundInteractionPlus) {
        value = nextValue()
    }
}


================================================
FILE: Sources/BottomSheet/Preference Keys/ConfigKey.swift
================================================
//
//  ConfigKey.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import SwiftUI

struct SheetPlusConfig: Equatable {
    let detents: Set<PresentationDetent>
    @Binding var selectedDetent: PresentationDetent
    let translation: CGFloat
    
    
    static func == (lhs: SheetPlusConfig, rhs: SheetPlusConfig) -> Bool {
        return lhs.selectedDetent == rhs.selectedDetent && lhs.translation == rhs.translation && lhs.detents == rhs.detents
    }
}

struct SheetPlusKey: PreferenceKey {
    static var defaultValue: SheetPlusConfig = SheetPlusConfig(detents: [], selectedDetent: .constant(.height(.zero)), translation: 0)
    
    static func reduce(value: inout SheetPlusConfig, nextValue: () -> SheetPlusConfig) {
        /// This prevents the translation changes to be called whenever the keyboard is triggered.
        /// If the keyboard gets triggered it will also reset the whole configkey and losing the binding.
        /// https://stackoverflow.com/questions/67644164/preferencekey-issue-swiftui-sometimes-seems-to-generate-additional-views-that
        value = nextValue() != defaultValue ? nextValue() : value
    }
}


================================================
FILE: Sources/BottomSheet/Preference Keys/IndicatorKey.swift
================================================
//
//  IndicatorKey.swift
//  
//
//  Created by Wouter van de Kamp on 29/10/2023.
//

import Foundation
import SwiftUI

struct SheetPlusIndicatorKey: PreferenceKey {
    static var defaultValue: VisibilityPlus = .automatic

    static func reduce(value: inout VisibilityPlus, nextValue: () -> VisibilityPlus) {
        value = nextValue()
    }
}


================================================
FILE: Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift
================================================
//
//  UIScrollViewWrapper.swift
//  
//
//  Created by Wouter van de Kamp on 20/11/2022.
//

import Foundation
import SwiftUI
import UIKit

internal struct UIScrollViewWrapper<Content: View>: UIViewRepresentable {
    @Binding var translation: CGFloat
    @Binding var preferenceKey: SheetPlusConfig?
    
    let limits: (min: CGFloat, max: CGFloat)
    let detents: Set<PresentationDetent>
    
    let content: () -> Content
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        let hostingController = context.coordinator.hostingController
        
        scrollView.addSubview(hostingController.view)
        
        scrollView.contentInsetAdjustmentBehavior = .automatic
        scrollView.alwaysBounceVertical = true
        scrollView.delegate = context.coordinator
        
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        
        scrollView.addConstraints([
            hostingController.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
        ])
        
        hostingController.view.backgroundColor = .clear
        scrollView.backgroundColor = .clear

        scrollView.layoutIfNeeded()
        
        return scrollView
    }
    
    func updateUIView(_ scrollView: UIScrollView, context: Context) {
        context.coordinator.limits = limits
        context.coordinator.detents = detents
        
        context.coordinator.hostingController.rootView = self.content()
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(
            representable: self,
            hostingController: UIHostingController(rootView: content()),
            limits: limits,
            detents: detents
        )
    }
    
    class Coordinator: NSObject, UIScrollViewDelegate {
        private var scrollOffset: CGFloat = 0
        private var newValue: CGFloat = 0
        
        var representable: UIScrollViewWrapper
        var hostingController: UIHostingController<Content>
        
        var limits: (min: CGFloat, max: CGFloat)
        var detents: Set<PresentationDetent>
        
        init(
            representable: UIScrollViewWrapper,
            hostingController: UIHostingController<Content>,
            limits: (min: CGFloat, max: CGFloat),
            detents: Set<PresentationDetent>
        ) {
            self.hostingController = hostingController
            self.limits = limits
            self.detents = detents
            self.representable = representable
        }
        
        private func shouldDragSheet(_ scrollViewPosition: CGFloat, isFixedHeight: Bool) -> Bool {
            // Translation on a scrollview without an overflow get's set to 0 somehow.
            // Implemented this check to prevent it from snapping back to the original position.
            // Need to dive deeper to figure out why it gets set to 0.
            if isFixedHeight && representable.translation == 0 {
                if scrollViewPosition > scrollOffset {
                    scrollOffset = scrollViewPosition
                }

                return scrollViewPosition < 0
            }

            if representable.translation >= limits.max {
                if scrollViewPosition > scrollOffset {
                    scrollOffset = scrollViewPosition
                }

                return scrollViewPosition < 0
            }

            return true
        }

        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let isFixedHeight = scrollView.contentSize.height < scrollView.frame.size.height

            guard scrollView.isTracking else { return }
            guard shouldDragSheet(scrollView.contentOffset.y, isFixedHeight: isFixedHeight) else {
                scrollView.showsVerticalScrollIndicator = true
                return
            }
            
            let localTranslation = scrollView.panGestureRecognizer.translation(in: scrollView.superview).y - scrollOffset
            let translationDelta = localTranslation - newValue

            representable.translation -= translationDelta

            newValue = localTranslation

            scrollView.showsVerticalScrollIndicator = false
            scrollView.contentOffset.y = .zero
        }

        func scrollViewWillEndDragging(
            _ scrollView: UIScrollView,
            withVelocity velocity: CGPoint,
            targetContentOffset: UnsafeMutablePointer<CGPoint>
        ) {
            if representable.translation != limits.max {
                targetContentOffset.pointee = .zero
            }
            
            if let result = snapBottomSheet(
                representable.translation,
                detents,
                scrollView.contentOffset.y > 0 ? 0 : velocity.y
            ) {
                representable.translation = result.size
                representable.preferenceKey?.selectedDetent = result
            }

            scrollOffset = 0
            newValue = 0
        }
    }
}


================================================
FILE: Sources/BottomSheet/View Modifiers/View+AnimationChange.swift
================================================
//
//  OnAnimationChange.swift
//  
//
//  Created by Wouter van de Kamp on 10/03/2022.
//

import SwiftUI

internal struct AnimationObserverModifier<Value>: AnimatableModifier where Value: VectorArithmetic {
    var animatableData: Value {
        didSet {
            updateAnimationData()
        }
    }

    private var update: (CGFloat) -> Void

    init(observedValue: Value, update: @escaping (CGFloat) -> Void) {
        self.animatableData = observedValue
        self.update = update
    }

    func body(content: Content) -> some View {
        return content
    }

    private func updateAnimationData() {
        DispatchQueue.main.async {
            // swiftlint:disable force_cast
            update(animatableData as! CGFloat)
        }
   }
}

extension View {
    func onAnimationChange<Value: VectorArithmetic>(
        of value: Value,
        perform: @escaping (CGFloat) -> Void
    ) -> ModifiedContent<Self, AnimationObserverModifier<Value>> {
        return modifier(AnimationObserverModifier(observedValue: value, update: perform))
    }
}


================================================
FILE: Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift
================================================
//
//  View+BackgroundInteraction.swift
//  
//
//  Created by Wouter van de Kamp on 29/10/2023.
//

import SwiftUI

extension View {
    public func presentationBackgroundInteractionPlus(
        _ interaction: PresentationBackgroundInteractionPlus
    ) -> some View {
        return self.preference(
            key: SheetPlusBackgroundInteractionKey.self,
            value: interaction
        )
    }
}


================================================
FILE: Sources/BottomSheet/View Modifiers/View+Detents.swift
================================================
//
//  View+Detents.swift
//  
//
//  Created by Wouter van de Kamp on 02/07/2023.
//

import SwiftUI

extension View {    
    public func presentationDetentsPlus(
        _ detents: Set<PresentationDetent>
    ) -> some View {
        let sortedDetents = Array(detents).sorted(by: { $0.size < $1.size })
        
        return self.preference(
            key: SheetPlusKey.self,
            value: SheetPlusConfig(
                detents: detents,
                selectedDetent: Binding(get: { sortedDetents.first! }, set: { _ in }),
                translation: sortedDetents.first!.size
            )
        )
    }
    
    public func presentationDetentsPlus(
        _ detents: Set<PresentationDetent>,
        selection: Binding<PresentationDetent>
    ) -> some View {
        return self.preference(
            key: SheetPlusKey.self,
            value: SheetPlusConfig(
                detents: detents,
                selectedDetent: selection,
                translation: selection.wrappedValue.size
            )
        )
    }
}



================================================
FILE: Sources/BottomSheet/View Modifiers/View+DragIndicator.swift
================================================
//
//  View+DragIndicator.swift
//  
//
//  Created by Wouter van de Kamp on 29/10/2023.
//

import SwiftUI

public enum VisibilityPlus {
    case hidden
    case visible
    case automatic
}

extension View {
    public func presentationDragIndicatorPlus(
        _ visibility: VisibilityPlus
    ) -> some View {
        return self.preference(
            key: SheetPlusIndicatorKey.self,
            value: visibility
        )
    }
}


================================================
FILE: Sources/BottomSheet/View Modifiers/View+SheetPlus.swift
================================================
//
//  View+SheetPlus.swift
//  
//
//  Created by Wouter van de Kamp on 21/11/2022.
//

import SwiftUI

extension View {
    public func sheetPlus<HContent: View, MContent: View, Background: View>(
        isPresented: Binding<Bool>,
        animationCurve: SheetAnimation = SheetAnimation(
            mass: SheetAnimationDefaults.mass,
            stiffness: SheetAnimationDefaults.stiffness,
            damping: SheetAnimationDefaults.damping
        ),
        background: Background = Color(UIColor.systemBackground),
        onDismiss: @escaping () -> Void = {},
        onDrag: @escaping (CGFloat) -> Void = { _ in },
        header: () -> HContent = { EmptyView() },
        main: () -> MContent
    ) -> some View {
        modifier(
            SheetPlus(
                isPresented: isPresented,
                animationCurve: animationCurve,
                background: background,
                onDismiss: onDismiss,
                onDrag: onDrag,
                hcontent: header,
                mcontent: main
            )
        )
    }
}


================================================
FILE: Sources/BottomSheet/Views/DragIndicator.swift
================================================
//
//  DragIndicator.swift
//  
//
//  Created by Wouter van de Kamp on 29/10/2023.
//

import SwiftUI

struct DragIndicator: View {
    @Binding var translation: CGFloat
    var detents: Set<PresentationDetent>

    var body: some View {
        RoundedRectangle(cornerRadius: 3)
            .contentShape(Rectangle())
            .frame(width: 42, height: 6)
            .foregroundColor(Color(UIColor.systemGray3))
            .padding(.vertical, 8)
            .onTapGesture {
                let sortedDetents = detents.sorted { $0.size < $1.size }
                let nextDetent = sortedDetents.first(where: { $0.size > translation })

                if let nextDetent = nextDetent {
                    translation = nextDetent.size
                } else {
                    translation = sortedDetents.first!.size
                }
            }
    }
}

struct DragIndicator_Previews: PreviewProvider {
    static var previews: some View {
        DragIndicator(
            translation: .constant(0),
            detents: []
        )
    }
}


================================================
FILE: Tests/BottomSheetTests/BottomSheetTests.swift
================================================
import XCTest
@testable import BottomSheet

final class BottomSheetTests: XCTestCase {
    func testExample() throws {
        
    }
}
Download .txt
gitextract_vhrt5p88/

├── .gitignore
├── Example/
│   ├── BottomSheetExample/
│   │   ├── Apple Applications/
│   │   │   └── StocksExample.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── BottomSheetExampleApp.swift
│   │   ├── ExampleOverview.swift
│   │   ├── Examples/
│   │   │   └── StaticScrollViewExample.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   └── View Modifiers/
│   │       └── CornerRadius.swift
│   └── BottomSheetExample.xcodeproj/
│       ├── project.pbxproj
│       ├── project.xcworkspace/
│       │   ├── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       ├── IDEWorkspaceChecks.plist
│       │       └── WorkspaceSettings.xcsettings
│       └── xcshareddata/
│           └── xcschemes/
│               └── BottomSheetExample.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   └── BottomSheet/
│       ├── Animation/
│       │   ├── Animation.swift
│       │   └── AnimationDefaults.swift
│       ├── BottomSheet.swift
│       ├── Detents/
│       │   ├── DetentDefaults.swift
│       │   ├── DetentHelpers.swift
│       │   └── Detents.swift
│       ├── Helpers/
│       │   ├── KeyboardReader.swift
│       │   └── Snapping.swift
│       ├── Preference Keys/
│       │   ├── BackgroundInteractionKey.swift
│       │   ├── ConfigKey.swift
│       │   └── IndicatorKey.swift
│       ├── UIKit Views/
│       │   └── UIScrollViewWrapper.swift
│       ├── View Modifiers/
│       │   ├── View+AnimationChange.swift
│       │   ├── View+BackgroundInteraction.swift
│       │   ├── View+Detents.swift
│       │   ├── View+DragIndicator.swift
│       │   └── View+SheetPlus.swift
│       └── Views/
│           └── DragIndicator.swift
└── Tests/
    └── BottomSheetTests/
        └── BottomSheetTests.swift
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (69K chars).
[
  {
    "path": ".gitignore",
    "chars": 126,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
  },
  {
    "path": "Example/BottomSheetExample/Apple Applications/StocksExample.swift",
    "chars": 2908,
    "preview": "//\n//  Stocks.swift\n//  BottomSheetExample\n//\n//  Created by Wouter van de Kamp on 04/12/2022.\n//\n\nimport SwiftUI\nimport"
  },
  {
    "path": "Example/BottomSheetExample/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "Example/BottomSheetExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1591,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Example/BottomSheetExample/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Example/BottomSheetExample/BottomSheetExampleApp.swift",
    "chars": 269,
    "preview": "//\n//  BottomSheetExampleApp.swift\n//  BottomSheetExample\n//\n//  Created by Wouter van de Kamp on 10/03/2022.\n//\n\nimport"
  },
  {
    "path": "Example/BottomSheetExample/ExampleOverview.swift",
    "chars": 3883,
    "preview": "//\n//  ExampleOverview.swift\n//  BottomSheetExample\n//\n//  Created by Wouter van de Kamp on 24/05/2022.\n//\n\nimport Swift"
  },
  {
    "path": "Example/BottomSheetExample/Examples/StaticScrollViewExample.swift",
    "chars": 1317,
    "preview": "//\n//  StaticScrollViewExample.swift\n//  BottomSheetExample\n//\n//  Created by Wouter van de Kamp on 28/10/2023.\n//\n\nimpo"
  },
  {
    "path": "Example/BottomSheetExample/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Example/BottomSheetExample/View Modifiers/CornerRadius.swift",
    "chars": 990,
    "preview": "//\n//  CornerRadius.swift\n//  BottomSheetExample\n//\n//  Created by Wouter van de Kamp on 25/03/2022.\n//\n\nimport SwiftUI\n"
  },
  {
    "path": "Example/BottomSheetExample.xcodeproj/project.pbxproj",
    "chars": 16987,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Example/BottomSheetExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "chars": 226,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme",
    "chars": 2939,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1410\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "LICENSE",
    "chars": 1075,
    "preview": "MIT License\n\nCopyright (c) 2024 Wouter van de Kamp.\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "Package.swift",
    "chars": 1071,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "README.md",
    "chars": 3195,
    "preview": "# BottomSheet\n\nAn iOS library for SwiftUI to create draggable sheet experiences similar to iOS applications like Maps an"
  },
  {
    "path": "Sources/BottomSheet/Animation/Animation.swift",
    "chars": 377,
    "preview": "//\n//  Animation.swift\n//  \n//\n//  Created by Wouter van de Kamp on 26/11/2022.\n//\n\nimport Foundation\n\npublic struct She"
  },
  {
    "path": "Sources/BottomSheet/Animation/AnimationDefaults.swift",
    "chars": 283,
    "preview": "//\n//  AnimationDefaults.swift\n//  \n//\n//  Created by Wouter van de Kamp on 26/11/2022.\n//\n\nimport Foundation\n\npublic st"
  },
  {
    "path": "Sources/BottomSheet/BottomSheet.swift",
    "chars": 7007,
    "preview": "//\n//  BottomSheet.swift\n//\n//\n//  Created by Wouter van de Kamp on 26/11/2022.\n//\n\nimport SwiftUI\n\nstruct SheetPlus<HCo"
  },
  {
    "path": "Sources/BottomSheet/Detents/DetentDefaults.swift",
    "chars": 352,
    "preview": "//\n//  DetentsDefaults.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport SwiftUI\n\ninternal struc"
  },
  {
    "path": "Sources/BottomSheet/Detents/DetentHelpers.swift",
    "chars": 1207,
    "preview": "//\n//  DetentsHelper.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport SwiftUI\n\n/// Computes the"
  },
  {
    "path": "Sources/BottomSheet/Detents/Detents.swift",
    "chars": 848,
    "preview": "//\n//  Detents.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport SwiftUI\n\npublic enum Presentati"
  },
  {
    "path": "Sources/BottomSheet/Helpers/KeyboardReader.swift",
    "chars": 664,
    "preview": "//\n//  KeyboardReader.swift\n//  \n//\n//  Created by Wouter van de Kamp on 28/10/2023.\n//\n\nimport Combine\nimport UIKit\n\npr"
  },
  {
    "path": "Sources/BottomSheet/Helpers/Snapping.swift",
    "chars": 1530,
    "preview": "//\n//  Snapping.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport Foundation\n\n/// Helper functio"
  },
  {
    "path": "Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift",
    "chars": 966,
    "preview": "//\n//  BackgroundInteractionKey.swift\n//  \n//\n//  Created by Wouter van de Kamp on 29/10/2023.\n//\n\nimport SwiftUI\n\n// Cu"
  },
  {
    "path": "Sources/BottomSheet/Preference Keys/ConfigKey.swift",
    "chars": 1155,
    "preview": "//\n//  ConfigKey.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport SwiftUI\n\nstruct SheetPlusConf"
  },
  {
    "path": "Sources/BottomSheet/Preference Keys/IndicatorKey.swift",
    "chars": 348,
    "preview": "//\n//  IndicatorKey.swift\n//  \n//\n//  Created by Wouter van de Kamp on 29/10/2023.\n//\n\nimport Foundation\nimport SwiftUI\n"
  },
  {
    "path": "Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift",
    "chars": 5302,
    "preview": "//\n//  UIScrollViewWrapper.swift\n//  \n//\n//  Created by Wouter van de Kamp on 20/11/2022.\n//\n\nimport Foundation\nimport S"
  },
  {
    "path": "Sources/BottomSheet/View Modifiers/View+AnimationChange.swift",
    "chars": 1069,
    "preview": "//\n//  OnAnimationChange.swift\n//  \n//\n//  Created by Wouter van de Kamp on 10/03/2022.\n//\n\nimport SwiftUI\n\ninternal str"
  },
  {
    "path": "Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift",
    "chars": 409,
    "preview": "//\n//  View+BackgroundInteraction.swift\n//  \n//\n//  Created by Wouter van de Kamp on 29/10/2023.\n//\n\nimport SwiftUI\n\next"
  },
  {
    "path": "Sources/BottomSheet/View Modifiers/View+Detents.swift",
    "chars": 1054,
    "preview": "//\n//  View+Detents.swift\n//  \n//\n//  Created by Wouter van de Kamp on 02/07/2023.\n//\n\nimport SwiftUI\n\nextension View { "
  },
  {
    "path": "Sources/BottomSheet/View Modifiers/View+DragIndicator.swift",
    "chars": 440,
    "preview": "//\n//  View+DragIndicator.swift\n//  \n//\n//  Created by Wouter van de Kamp on 29/10/2023.\n//\n\nimport SwiftUI\n\npublic enum"
  },
  {
    "path": "Sources/BottomSheet/View Modifiers/View+SheetPlus.swift",
    "chars": 1065,
    "preview": "//\n//  View+SheetPlus.swift\n//  \n//\n//  Created by Wouter van de Kamp on 21/11/2022.\n//\n\nimport SwiftUI\n\nextension View "
  },
  {
    "path": "Sources/BottomSheet/Views/DragIndicator.swift",
    "chars": 1057,
    "preview": "//\n//  DragIndicator.swift\n//  \n//\n//  Created by Wouter van de Kamp on 29/10/2023.\n//\n\nimport SwiftUI\n\nstruct DragIndic"
  },
  {
    "path": "Tests/BottomSheetTests/BottomSheetTests.swift",
    "chars": 136,
    "preview": "import XCTest\n@testable import BottomSheet\n\nfinal class BottomSheetTests: XCTestCase {\n    func testExample() throws {\n "
  }
]

About this extraction

This page contains the full source code of the Wouter125/BottomSheet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (61.0 KB), approximately 16.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!