diff --git a/BottomSheetSwiftUI.podspec b/BottomSheetSwiftUI.podspec
index ec190d366..2607fbf03 100644
--- a/BottomSheetSwiftUI.podspec
+++ b/BottomSheetSwiftUI.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'BottomSheetSwiftUI'
- s.version = '2.4.0'
+ s.version = '2.5.0'
s.summary = 'A sliding Sheet from the bottom of the Screen with 3 States build with SwiftUI.'
s.homepage = 'https://github.com/LucasMucGH/BottomSheet'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00d1a23dd..3ef52d0dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,17 @@
BottomSheet Changelog
==================
+#### v2.5.0
+- Update Copyright
+- Update swift-tools-version
+- Update deprecated code (real fix for #19, replaces #20)
+- Add `.absolutePositionValue` option #37
+- Add `BottomSheetPositionAbsolute`
+- Use explicit animations #31
+- Hide examples in ReadMe
+- Update and fix `.appleScrollBehavior` #37
+- Code clean up
+
#### v2.4.0
- Add option to enable shadow
- Add pod install
diff --git a/Package.swift b/Package.swift
index 98350ef48..79254eef8 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
diff --git a/README.md b/README.md
index a3f5a253b..21bb31b6a 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,8 @@ struct ContentView: View {
### Options
+`.absolutePositionValue` Allows absolute values in pixels to be used as BottomSheetPosition values.
+
`.allowContentDrag` Allows the BottomSheet to move when dragging the mainContent.
- Do not use if the mainContent is packed into a ScrollView.
@@ -173,15 +175,15 @@ struct ContentView: View {
## Custom States
-
You can create your own custom BottomSheetPosition enum:
- The enum must be conforming to `CGFloat` and `CaseIterable`
- The case and enum name doesnt matter
- The case/state with `rawValue == 0` is hiding the BottomSheet
- - The value can be anythig between `0` and `1` (`x <= 1`, `x >= 0`)
- - The value is the height of the BottomSheet propotional to the screen height (`1 == 100% == full screen`)
+ - The value can be anythig between `0` and `1` (`x <= 1`, `x >= 0`) or anything above `0` (`x >= 0`) when using the`.absolutePositionValue` option
+ - The value is the height of the BottomSheet propotional to the screen height (`1 == 100% == full screen`) or the height of the BottomSheet in pixel (`1 == 1px`) when using the`.absolutePositionValue` option
- The lowest value (greater than 0) automaticly gets the `.bottom` behavior. To prevent this please use the option `.noBottomPosition`
+This BottomSheetPosition uses relative values.
```swift
import SwiftUI
@@ -190,6 +192,13 @@ enum CustomBottomSheetPosition: CGFloat, CaseIterable {
}
```
+This BottomSheetPositionAbsolute uses absolute values and requires the the`.absolutePositionValue` option.
+```swift
+public enum BottomSheetPositionAbsolute: CGFloat, CaseIterable {
+ case top = 750, middle = 300, bottom = 100, hidden = 0
+}
+```
+
## Examples
**PLEASE NOTE:** When installed via Cocoapods, please keep in mind that the pod is called `BottomSheetSwiftUI` and not `BottomSheet`; so please use `import BottomSheetSwiftUI` instead.
@@ -198,22 +207,26 @@ enum CustomBottomSheetPosition: CGFloat, CaseIterable {
This BottomSheet shows additional information about a book.
You can close it by swiping it away, by tapping on the background or the close button.
-It also uses a custom `enum` for the states, since only the states `.middle`, `.bottom` and `.hidden` should exist.
+The drag indicator is hidden.
+It uses a custom `enum` for the states with absolute values, since only the states `.middle`, `.bottom` and `.hidden` should exist with a predefined absolute height.
+
+Source Code
+
```swift
import SwiftUI
import BottomSheet
-//The custom BottomSheetPosition enum.
+//The custom BottomSheetPosition enum with absolute values.
enum BookBottomSheetPosition: CGFloat, CaseIterable {
- case middle = 0.4, bottom = 0.125, hidden = 0
+ case middle = 300, bottom = 100, hidden = 0
}
struct BookDetailView: View {
- @State private var bottomSheetPosition: BookBottomSheetPosition = .middle
+ @State var bottomSheetPosition: BookBottomSheetPosition = .middle
let backgroundColors: [Color] = [Color(red: 0.2, green: 0.85, blue: 0.7), Color(red: 0.13, green: 0.55, blue: 0.45)]
let readMoreColors: [Color] = [Color(red: 0.70, green: 0.22, blue: 0.22), Color(red: 1, green: 0.32, blue: 0.32)]
@@ -224,7 +237,7 @@ struct BookDetailView: View {
LinearGradient(gradient: Gradient(colors: self.backgroundColors), startPoint: .topLeading, endPoint: .bottomTrailing)
.edgesIgnoringSafeArea(.all)
- .bottomSheet(bottomSheetPosition: self.$bottomSheetPosition, options: [.allowContentDrag, .showCloseButton(), .swipeToDismiss, .tapToDissmiss], headerContent: {
+ .bottomSheet(bottomSheetPosition: self.$bottomSheetPosition, options: [.noDragIndicator, .allowContentDrag, .showCloseButton(), .swipeToDismiss, .tapToDissmiss, .absolutePositionValue], headerContent: {
//The name of the book as the heading and the author as the subtitle with a divider.
VStack(alignment: .leading) {
Text("Wuthering Heights")
@@ -279,27 +292,36 @@ struct BookButton: ButtonStyle {
}
}
```
+
### Word Search View
This BottomSheet shows nouns which can be filtered by searching.
-It adopts the scrolling behavior of apple, so that you can only scroll the `ScrollView` in the `.top` position.
+It adopts the scrolling behavior of apple, so that you can only scroll the `ScrollView` in the `.top` position.
The higher the BottomSheet is dragged, the more blurry the background becomes (with the BlurEffect .dark) to move the focus to the BottomSheet.
+
+Source Code
+
```swift
import SwiftUI
import BottomSheet
struct WordSearchView: View {
- @State private var bottomSheetPosition: BottomSheetPosition = .middle
+ @State var bottomSheetPosition: BottomSheetPosition = .middle
+ @State var searchText: String = ""
- @State private var searchText: String = ""
let backgroundColors: [Color] = [Color(red: 0.28, green: 0.28, blue: 0.53), Color(red: 1, green: 0.69, blue: 0.26)]
let words: [String] = ["birthday", "pancake", "expansion", "brick", "bushes", "coal", "calendar", "home", "pig", "bath", "reading", "cellar", "knot", "year", "ink"]
+ var filteredWords: [String] {
+ self.words.filter({ $0.contains(self.searchText.lowercased()) || self.searchText.isEmpty })
+ }
+
+
var body: some View {
//A green gradient as a background that ignores the safe area.
LinearGradient(gradient: Gradient(colors: self.backgroundColors), startPoint: .topLeading, endPoint: .bottomTrailing)
@@ -322,7 +344,7 @@ struct WordSearchView: View {
}
}) {
//The list of nouns that will be filtered by the searchText.
- ForEach(self.words.filter({ $0.contains(self.searchText.lowercased()) || self.searchText.isEmpty}), id: \.self) { word in
+ ForEach(self.filteredWords, id: \.self) { word in
Text(word)
.font(.title)
.padding([.leading, .bottom])
@@ -330,12 +352,13 @@ struct WordSearchView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.transition(.opacity)
- .animation(.easeInOut)
+ .animation(.easeInOut, value: self.filteredWords)
.padding(.top)
}
}
}
```
+
### Artist Songs View
@@ -344,13 +367,16 @@ It has a custom animation and color for the drag indicator and the background, a
+
+Source Code
+
```swift
import SwiftUI
import BottomSheet
struct ArtistSongsView: View {
- @State private var bottomSheetPosition: BottomSheetPosition = .middle
+ @State var bottomSheetPosition: BottomSheetPosition = .middle
let backgroundColors: [Color] = [Color(red: 0.17, green: 0.17, blue: 0.33), Color(red: 0.80, green: 0.38, blue: 0.2)]
let songs: [String] = ["One Dance (feat. Wizkid & Kyla)", "God's Plan", "SICKO MODE", "In My Feelings", "Work (feat. Drake)", "Nice For What", "Hotline Bling", "Too Good (feat. Rihanna)", "Life Is Good (feat. Drake)"]
@@ -374,6 +400,7 @@ struct ArtistSongsView: View {
}
}
```
+
## Contributing
diff --git a/Sources/BottomSheet/BottomSheet.swift b/Sources/BottomSheet/BottomSheet.swift
index ea670e809..38b8e2112 100644
--- a/Sources/BottomSheet/BottomSheet.swift
+++ b/Sources/BottomSheet/BottomSheet.swift
@@ -2,7 +2,7 @@
// BottomSheet.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
@@ -14,6 +14,8 @@ public struct BottomSheet {
return lhs.rawValue == rhs.rawValue
}
+ ///Allows absolute values in pixels to be used as BottomSheetPosition values.
+ case absolutePositionValue
///Allows the BottomSheet to move when dragging the mainContent. Do not use if the mainContent is packed into a ScrollView.
case allowContentDrag
///Sets the animation for opening and closing the BottomSheet.
@@ -62,6 +64,8 @@ public struct BottomSheet {
*/
public var rawValue: String {
switch self {
+ case .absolutePositionValue:
+ return "absolutePositionValue"
case .allowContentDrag:
return "allowContentDrag"
case .animation:
diff --git a/Sources/BottomSheet/BottomSheetArray.swift b/Sources/BottomSheet/BottomSheetArray.swift
index 8dc713ab5..138e4d031 100644
--- a/Sources/BottomSheet/BottomSheetArray.swift
+++ b/Sources/BottomSheet/BottomSheetArray.swift
@@ -2,12 +2,16 @@
// BottomSheetArray.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
internal extension Array where Element == BottomSheet.Options {
+ var absolutePositionValue: Bool {
+ return self.contains(BottomSheet.Options.absolutePositionValue)
+ }
+
var allowContentDrag: Bool {
return self.contains(BottomSheet.Options.allowContentDrag)
}
diff --git a/Sources/BottomSheet/BottomSheetPosition.swift b/Sources/BottomSheet/BottomSheetPosition.swift
index 0cc42cc1e..3fd9cb81f 100644
--- a/Sources/BottomSheet/BottomSheetPosition.swift
+++ b/Sources/BottomSheet/BottomSheetPosition.swift
@@ -2,29 +2,55 @@
// BottomSheetPosition.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
/**
- The default BottomSheetPosition; it has the following cases and values: `top = 0.975`, `middle = 0.4`, `bottom = 0.125`, `hidden = 0`
+ The default BottomSheetPosition; it has the following cases and values: `top = 0.975`, `middle = 0.4`, `bottom = 0.125`, `hidden = 0`.
+
+ This BottomSheetPosition uses relative values. For absolute values in pixels, see BottomSheetPositionAbsolute.
You can create your own custom BottomSheetPosition enum:
- The enum must be conforming to `CGFloat` and `CaseIterable`
- The case and enum name doesnt matter
- The case/state with `rawValue == 0` is hiding the BottomSheet
- - The value can be anythig between `0` and `1` (`x <= 1`, `x >= 0`)
- - The value is the height of the BottomSheet propotional to the screen height (`1 == 100% == full screen`)
+ - The value can be anythig between `0` and `1` (`x <= 1`, `x >= 0`) or anything above `0` (`x >= 0`) when using the`.absolutePositionValue` option
+ - The value is the height of the BottomSheet propotional to the screen height (`1 == 100% == full screen`) or the height of the BottomSheet in pixel (`1 == 1px`) when using the`.absolutePositionValue` option
- The lowest value (greater than 0) automaticly gets the `.bottom` behavior. To prevent this please use the option `.noBottomPosition`
*/
public enum BottomSheetPosition: CGFloat, CaseIterable {
- //The state where the height of the BottomSheet is 97.5%
+ ///The state where the height of the BottomSheet is 97.5%
case top = 0.975
- //The state where the height of the BottomSheet is 40%
+ ///The state where the height of the BottomSheet is 40%
case middle = 0.4
- //The state where the height of the BottomSheet is 12.5% and the `mainContent` is hidden
+ ///The state where the height of the BottomSheet is 12.5% and the `mainContent` is hidden
case bottom = 0.125
- //The state where the BottomSheet is hidden
+ ///The state where the BottomSheet is hidden
+ case hidden = 0
+}
+
+/**
+ The default BottomSheetPositionAbsolute; it has the following cases and values: `top = 750`, `middle = 300`, `bottom = 100`, `hidden = 0`.
+
+ This BottomSheetPositionAbsolute uses absolute values and requires the the`.absolutePositionValue` option. For relative values in pixels, see BottomSheetPosition.
+
+ You can create your own custom BottomSheetPosition enum:
+ - The enum must be conforming to `CGFloat` and `CaseIterable`
+ - The case and enum name doesnt matter
+ - The case/state with `rawValue == 0` is hiding the BottomSheet
+ - The value can be anythig between `0` and `1` (`x <= 1`, `x >= 0`) or anything above `0` (`x >= 0`) when using the`.absolutePositionValue` option
+ - The value is the height of the BottomSheet propotional to the screen height (`1 == 100% == full screen`) or the height of the BottomSheet in pixel (`1 == 1px`) when using the`.absolutePositionValue` option
+ - The lowest value (greater than 0) automaticly gets the `.bottom` behavior. To prevent this please use the option `.noBottomPosition`
+ */
+public enum BottomSheetPositionAbsolute: CGFloat, CaseIterable {
+ ///The state where the height of the BottomSheet is 750px
+ case top = 750
+ ///The state where the height of the BottomSheet is 300px
+ case middle = 300
+ ///The state where the height of the BottomSheet is 100px and the `mainContent` is hidden
+ case bottom = 100
+ ///The state where the BottomSheet is hidden
case hidden = 0
}
diff --git a/Sources/BottomSheet/BottomSheetView.swift b/Sources/BottomSheet/BottomSheetView.swift
index ba5f97da1..0739a2c9b 100644
--- a/Sources/BottomSheet/BottomSheetView.swift
+++ b/Sources/BottomSheet/BottomSheetView.swift
@@ -2,15 +2,16 @@
// BottomSheetView.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
+import Combine
-@available(iOSApplicationExtension, unavailable)
internal struct BottomSheetView: View where bottomSheetPositionEnum.RawValue == CGFloat, bottomSheetPositionEnum: CaseIterable {
@State private var translation: CGFloat = 0
+ @State private var isScrollEnabled: Bool = true
@Binding private var bottomSheetPosition: bottomSheetPositionEnum
private let options: [BottomSheet.Options]
@@ -45,13 +46,12 @@ internal struct BottomSheetView 0 {
+ self.translation = offset.y
+ self.endEditing()
+
+ let height: CGFloat = (self.translation * 2) / geometry.size.height
+ self.switchPosition(with: height)
+ }
+ }) {
self.mainContent
}
- .disabled(!self.isTopPosition)
} else {
self.mainContent
}
@@ -118,14 +127,16 @@ internal struct BottomSheetView Double {
+ withAnimation(.linear) {
+ if self.options.backgroundBlur {
+ if self.options.absolutePositionValue {
+ return Double((self.bottomSheetPosition.rawValue - self.translation) / geometry.size.height)
+ } else {
+ return Double((self.bottomSheetPosition.rawValue * geometry.size.height - self.translation) / geometry.size.height)
+ }
+ } else {
+ return 0
+ }
+ }
+ }
+
+ private func headerContentPadding(geometry: GeometryProxy) -> CGFloat {
+ withAnimation(self.options.animation) {
+ if self.isBottomPosition {
+ return geometry.safeAreaInsets.bottom + 25
+ } else if self.headerContent == nil {
+ return 20
+ } else {
+ return 0
+ }
+ }
+ }
+
+ private func frameHeightValue(geometry: GeometryProxy) -> Double {
+ withAnimation(self.options.animation) {
+ if self.options.absolutePositionValue {
+ return min(max(self.bottomSheetPosition.rawValue - self.translation, 0), geometry.size.height * 1.05)
+ } else {
+ return min(max((geometry.size.height * self.bottomSheetPosition.rawValue) - self.translation, 0), geometry.size.height * 1.05)
+ }
+ }
+ }
+
+ private func offsetYValue(geometry: GeometryProxy) -> Double {
+ withAnimation(self.options.animation) {
+ if self.isHiddenPosition {
+ return max(geometry.size.height + geometry.safeAreaInsets.bottom, geometry.size.height * -0.05)
+ } else if self.isBottomPosition {
+ if self.options.absolutePositionValue {
+ return max(geometry.size.height - self.bottomSheetPosition.rawValue + self.translation + geometry.safeAreaInsets.bottom, geometry.size.height * -0.05)
+ } else {
+ return max(geometry.size.height - (geometry.size.height * self.bottomSheetPosition.rawValue) + self.translation + geometry.safeAreaInsets.bottom, geometry.size.height * -0.05)
+ }
+ } else {
+ if self.options.absolutePositionValue {
+ return max(geometry.size.height - self.bottomSheetPosition.rawValue + self.translation, geometry.size.height * -0.05)
+ } else {
+ return max(geometry.size.height - (geometry.size.height * self.bottomSheetPosition.rawValue) + self.translation, geometry.size.height * -0.05)
+ }
+ }
}
}
private func endEditing() -> Void {
- UIApplication.shared.windows.filter{$0.isKeyWindow}.first?.endEditing(true)
+ UIApplication.shared.endEditing()
}
private func tapToDismiss() -> Void {
@@ -191,58 +268,64 @@ internal struct BottomSheetView Void {
- if let hiddenPosition = bottomSheetPositionEnum(rawValue: 0) {
- self.bottomSheetPosition = hiddenPosition
+ withAnimation(self.options.animation) {
+ if let hiddenPosition = bottomSheetPositionEnum(rawValue: 0) {
+ self.bottomSheetPosition = hiddenPosition
+ }
+
+ self.endEditing()
}
-
- self.endEditing()
}
private func switchPositionIndicator() -> Void {
- if !self.isHiddenPosition {
-
- if let currentIndex = self.allCases.firstIndex(where: { $0 == self.bottomSheetPosition }), self.allCases.count > 1 {
- if currentIndex == self.allCases.endIndex - 1 {
- if self.allCases[currentIndex - 1].rawValue != 0 {
- self.bottomSheetPosition = self.allCases[currentIndex - 1]
+ withAnimation(self.options.animation) {
+ if !self.isHiddenPosition {
+
+ if let currentIndex = self.allCases.firstIndex(where: { $0 == self.bottomSheetPosition }), self.allCases.count > 1 {
+ if currentIndex == self.allCases.endIndex - 1 {
+ if self.allCases[currentIndex - 1].rawValue != 0 {
+ self.bottomSheetPosition = self.allCases[currentIndex - 1]
+ }
+ } else {
+ self.bottomSheetPosition = self.allCases[currentIndex + 1]
}
- } else {
- self.bottomSheetPosition = self.allCases[currentIndex + 1]
}
+
}
+ self.endEditing()
}
-
- self.endEditing()
}
private func switchPosition(with height: CGFloat) -> Void {
- if !self.isHiddenPosition {
-
- if let currentIndex = self.allCases.firstIndex(where: { $0 == self.bottomSheetPosition }), self.allCases.count > 1 {
- if height <= -0.1 && height > -0.3 {
- if currentIndex < self.allCases.endIndex - 1 {
- self.bottomSheetPosition = self.allCases[currentIndex + 1]
- }
- } else if height <= -0.3 {
- self.bottomSheetPosition = self.allCases[self.allCases.endIndex - 1]
- } else if height >= 0.1 && height < 0.3 {
- if currentIndex > self.allCases.startIndex && (self.allCases[currentIndex - 1].rawValue != 0 || (self.allCases[currentIndex - 1].rawValue == 0 && self.options.swipeToDismiss)) {
- self.bottomSheetPosition = self.allCases[currentIndex - 1]
- }
- } else if height >= 0.3 {
- if (self.allCases[self.allCases.startIndex].rawValue == 0 && self.options.swipeToDismiss) || self.allCases[self.allCases.startIndex].rawValue != 0 {
- self.bottomSheetPosition = self.allCases[self.allCases.startIndex]
- } else {
- self.bottomSheetPosition = self.allCases[self.allCases.startIndex + 1]
+ withAnimation(self.options.animation) {
+ if !self.isHiddenPosition {
+
+ if let currentIndex = self.allCases.firstIndex(where: { $0 == self.bottomSheetPosition }), self.allCases.count > 1 {
+ if height <= -0.1 && height > -0.3 {
+ if currentIndex < self.allCases.endIndex - 1 {
+ self.bottomSheetPosition = self.allCases[currentIndex + 1]
+ }
+ } else if height <= -0.3 {
+ self.bottomSheetPosition = self.allCases[self.allCases.endIndex - 1]
+ } else if height >= 0.1 && height < 0.3 {
+ if currentIndex > self.allCases.startIndex && (self.allCases[currentIndex - 1].rawValue != 0 || (self.allCases[currentIndex - 1].rawValue == 0 && self.options.swipeToDismiss)) {
+ self.bottomSheetPosition = self.allCases[currentIndex - 1]
+ }
+ } else if height >= 0.3 {
+ if (self.allCases[self.allCases.startIndex].rawValue == 0 && self.options.swipeToDismiss) || self.allCases[self.allCases.startIndex].rawValue != 0 {
+ self.bottomSheetPosition = self.allCases[self.allCases.startIndex]
+ } else {
+ self.bottomSheetPosition = self.allCases[self.allCases.startIndex + 1]
+ }
}
}
+
}
+ self.translation = 0
+ self.endEditing()
}
-
- self.translation = 0
- self.endEditing()
}
@@ -254,14 +337,13 @@ internal struct BottomSheetView>>, _PaddingLayout> {
init(bottomSheetPosition: Binding, options: [BottomSheet.Options], title: String?, @ViewBuilder content: () -> mContent) {
if title == nil {
self.init(bottomSheetPosition: bottomSheetPosition, options: options, headerContent: { return nil }, mainContent: content)
} else {
self.init(bottomSheetPosition: bottomSheetPosition, options: options, headerContent: { return Text(title!)
- .font(.title).bold().lineLimit(1).padding(.bottom) as? hContent }, mainContent: content)
+ .font(.title).bold().lineLimit(1).padding(.bottom) as? hContent }, mainContent: content)
}
}
}
diff --git a/Sources/BottomSheet/Helper/BindingExtension.swift b/Sources/BottomSheet/Helper/BindingExtension.swift
new file mode 100644
index 000000000..42397b56c
--- /dev/null
+++ b/Sources/BottomSheet/Helper/BindingExtension.swift
@@ -0,0 +1,19 @@
+//
+// BindingExtension.swift
+//
+// Created by Lucas Zischka.
+// Copyright © 2021 Lucas Zischka. All rights reserved.
+//
+
+import SwiftUI
+
+internal extension Binding {
+ /// Wrapper to listen to didSet of Binding
+ func didSet(_ didSet: @escaping ((newValue: Value, oldValue: Value)) -> Void) -> Binding {
+ return .init(get: { self.wrappedValue }, set: { newValue in
+ let oldValue = self.wrappedValue
+ self.wrappedValue = newValue
+ didSet((newValue, oldValue))
+ })
+ }
+}
diff --git a/Sources/BottomSheet/Helper/UIApplicationExtension.swift b/Sources/BottomSheet/Helper/UIApplicationExtension.swift
new file mode 100644
index 000000000..f262e57f3
--- /dev/null
+++ b/Sources/BottomSheet/Helper/UIApplicationExtension.swift
@@ -0,0 +1,14 @@
+//
+// UIApplicationExtension.swift
+//
+// Created by Lucas Zischka.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
+//
+
+import UIKit
+
+internal extension UIApplication {
+ func endEditing() {
+ sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
+ }
+}
diff --git a/Sources/BottomSheet/ViewExtension.swift b/Sources/BottomSheet/Helper/ViewExtension.swift
similarity index 96%
rename from Sources/BottomSheet/ViewExtension.swift
rename to Sources/BottomSheet/Helper/ViewExtension.swift
index 770884394..19edee89d 100644
--- a/Sources/BottomSheet/ViewExtension.swift
+++ b/Sources/BottomSheet/Helper/ViewExtension.swift
@@ -2,12 +2,11 @@
// ViewExtension.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
-@available(iOSApplicationExtension, unavailable)
public extension View {
/**
A modifer to add a BottomSheet to your view.
diff --git a/Sources/BottomSheet/HelperViews/BSScrollView.swift b/Sources/BottomSheet/HelperViews/BSScrollView.swift
new file mode 100644
index 000000000..368d86226
--- /dev/null
+++ b/Sources/BottomSheet/HelperViews/BSScrollView.swift
@@ -0,0 +1,65 @@
+//
+// BSScrollView.swift
+//
+// Created by Lucas Zischka.
+// Copyright © 2021 Lucas Zischka. All rights reserved.
+//
+
+import SwiftUI
+
+internal struct BSScrollView: View {
+
+ @Binding private var isScrollEnabled: Bool
+
+ private let defaultAxes: Axis.Set
+ private let showsIndicators: Bool
+ private let onOffsetChange: (CGPoint) -> Void
+ private let content: Content
+
+ private var axes: Axis.Set {
+ return self.isScrollEnabled ? self.defaultAxes : []
+ }
+
+
+ internal var body: some View {
+ ScrollView(self.axes, showsIndicators: self.showsIndicators) {
+ GeometryReader { geometry in
+ Color.clear
+ .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scrollView")).origin
+ )
+ }
+ .frame(width: 0, height: 0)
+ self.content
+ //.padding(.top, -8)
+ .frame(maxWidth: .infinity, alignment: .top)
+ }
+ .coordinateSpace(name: "scrollView")
+ .onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: self.onOffsetChange)
+ }
+
+
+ internal init(axes: Axis.Set = .vertical, showsIndicators: Bool = true, isScrollEnabled: Binding = .constant(true), onOffsetChange: @escaping (CGPoint) -> Void = { _ in }, @ViewBuilder content: () -> Content) {
+ self.defaultAxes = axes
+ self.showsIndicators = showsIndicators
+ self._isScrollEnabled = isScrollEnabled
+ self.onOffsetChange = onOffsetChange
+ self.content = content()
+ }
+}
+
+private struct ScrollOffsetPreferenceKey: PreferenceKey {
+ static var defaultValue: CGPoint = .zero
+
+ static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {}
+}
+
+struct BSScrollView_Previews: PreviewProvider {
+ static var previews: some View {
+ BSScrollView {
+ ForEach(0...100, id: \.self) { i in
+ Text("\(i)")
+ }
+ }
+
+ }
+}
diff --git a/Sources/BottomSheet/HelperViews/EffectView.swift b/Sources/BottomSheet/HelperViews/EffectView.swift
index bf7accb09..ce3e80db9 100644
--- a/Sources/BottomSheet/HelperViews/EffectView.swift
+++ b/Sources/BottomSheet/HelperViews/EffectView.swift
@@ -2,7 +2,7 @@
// EffectView.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI
diff --git a/Sources/BottomSheet/HelperViews/RoundedCorner.swift b/Sources/BottomSheet/HelperViews/RoundedCorner.swift
index 5abe52a0f..46fc252b2 100644
--- a/Sources/BottomSheet/HelperViews/RoundedCorner.swift
+++ b/Sources/BottomSheet/HelperViews/RoundedCorner.swift
@@ -2,7 +2,7 @@
// RoundedCorner.swift
//
// Created by Lucas Zischka.
-// Copyright © 2021 Lucas Zischka. All rights reserved.
+// Copyright © 2021-2022 Lucas Zischka. All rights reserved.
//
import SwiftUI