diff --git a/Example/BottomSheetExample.xcodeproj/project.pbxproj b/Example/BottomSheetExample.xcodeproj/project.pbxproj index 4f4d4b6..10b085a 100644 --- a/Example/BottomSheetExample.xcodeproj/project.pbxproj +++ b/Example/BottomSheetExample.xcodeproj/project.pbxproj @@ -144,7 +144,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; + LastUpgradeCheck = 1510; TargetAttributes = { 6C0BD46827DA862D000AF3CD = { CreatedOnToolsVersion = 13.2.1; @@ -201,7 +201,7 @@ ); 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"; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n swiftlint --fix && swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -258,6 +258,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -272,7 +273,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -319,6 +320,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -327,7 +329,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -347,6 +349,7 @@ DEVELOPMENT_ASSET_PATHS = "BottomSheetExample/Preview\\ Content"; DEVELOPMENT_TEAM = KZAMEFAGHT; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -359,7 +362,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chargetrip.BottomSheetExample; + PRODUCT_BUNDLE_IDENTIFIER = com.test.BottomSheetExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -377,6 +380,7 @@ DEVELOPMENT_ASSET_PATHS = "BottomSheetExample/Preview\\ Content"; DEVELOPMENT_TEAM = KZAMEFAGHT; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -389,7 +393,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chargetrip.BottomSheetExample; + PRODUCT_BUNDLE_IDENTIFIER = com.test.BottomSheetExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme b/Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme index 2071993..be8edd6 100644 --- a/Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme +++ b/Example/BottomSheetExample.xcodeproj/xcshareddata/xcschemes/BottomSheetExample.xcscheme @@ -1,6 +1,6 @@ : ViewModifier, KeyboardReader { +struct SheetPlus: ViewModifier { @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 = [] + + @State private var sheetConfig: SheetPlusConfig? @State private var limits: (min: CGFloat, max: CGFloat) = (min: 0, max: 0) - + + @State private var detents: Set = [] + + @State private var showDragIndicator: VisibilityPlus? + @State private var cornerRadius: CGFloat? + @State private var background: EquatableBackground? + @State private var isInteractiveDismissDisabled = true + + @State private var backgroundInteractionDetentLimit: CGFloat? + @State private var isBackgroundInteractionEnabled = true + + @State private var viewWillPresent = false + + let animationCurve: SheetAnimation + let onDrag: (CGFloat) -> Void let mainContent: MContent let headerContent: HContent - let animationCurve: SheetAnimation let onDismiss: () -> Void - let onDrag: (CGFloat) -> Void - let background: Background - + init( isPresented: Binding, animationCurve: SheetAnimation, - background: Background, onDismiss: @escaping () -> Void, - onDrag: @escaping (CGFloat) -> Void, + onDrag: @escaping (CGFloat) -> Void = { _ in }, @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 isInteractionEnabled() -> Bool { + return true + } + func body(content: Content) -> some View { - ZStack() { + ZStack { content - .allowsHitTesting(allowBackgroundInteraction == .disabled ? false : true) - - if isPresented { - GeometryReader { geometry in + .allowsHitTesting(!isPresented || isBackgroundInteractionEnabled) + .zIndex(1) + .onChange(of: isPresented) { value in + if (value == true) { + viewWillPresent = value + } else { + translation = .zero + } + } + + if viewWillPresent { + // VStack to stick the sheet to the bottom + VStack(spacing: 0) { + Spacer() + + // VStack to keep the drag indicator and header at the top of the card 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) + // Geometry reader keeps the content outside the header vertically centered +// GeometryReader { geometry in + // Holds the header customization + VStack(spacing: 0) { + DragIndicator( + translation: $translation, + detents: detents + ) + .frame(height: showDragIndicator == .hidden ? 0 : 22) + .opacity(showDragIndicator == .hidden ? 0 : 1) - headerContent + headerContent + } +// .frame(width: geometry.size.width) .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 - } + onDragEnded(with: value) } ) - - UIScrollViewWrapper( - translation: $translation, - preferenceKey: $sheetConfig, - limits: limits, - detents: detents - ) { - mainContent - .frame(width: geometry.size.width) +// } + +// Spacer() + + GeometryReader { geometry in + UIScrollViewWrapper( + translation: $translation, + preferenceKey: $sheetConfig, + isInteractiveDismissDisabled: $isInteractiveDismissDisabled, + limits: limits, + detents: detents + ) { + mainContent + .frame(width: geometry.size.width) + } } + +// Spacer() } - .background(background) - .frame(height: - (limits.max - geometry.safeAreaInsets.top) > 0 - ? limits.max - geometry.safeAreaInsets.top - : limits.max + .background( + ZStack() { + background?.view + .cornerRadius(cornerRadius ?? 0) + } ) - .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)) + .frame( + width: UIScreen.main.bounds.width, + height: translation + ) + .clipped() + .onChange(of: translation) { value in + let minLimit = isInteractiveDismissDisabled && isPresented ? limits.min : .zero + translation = min(limits.max, max(value, minLimit)) - currentGlobalTranslation = translation + if let interactionDetent = backgroundInteractionDetentLimit { + isBackgroundInteractionEnabled = translation < interactionDetent + } } .onAnimationChange(of: translation) { value in - onDrag(value) + onDrag(value > .zero ? value : .zero) + + if (translation == 0 && value == .zero) { + isPresented = false + viewWillPresent = false + } } - .offset(y: UIScreen.main.bounds.height - translation) .onDisappear { - translation = 0 - detents = [] - onDismiss() } .animation( @@ -137,15 +163,11 @@ struct SheetPlus: ViewModifier ) ) } - .edgesIgnoringSafeArea([.bottom]) - .transition(.move(edge: .bottom)) + .edgesIgnoringSafeArea(.bottom) + .zIndex(2) } } .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 @@ -156,7 +178,42 @@ struct SheetPlus: ViewModifier showDragIndicator = value } .onPreferenceChange(SheetPlusBackgroundInteractionKey.self) { value in - allowBackgroundInteraction = value + backgroundInteractionDetentLimit = value + } + .onPreferenceChange(SheetPlusInteractiveDismissDisabledKey.self) { value in + isInteractiveDismissDisabled = value + } + .onPreferenceChange(SheetPlusPresentationCornerRadiusKey.self) { value in + cornerRadius = value + } + .onPreferenceChange(SheetPlusPresentationBackgroundKey.self) { value in + background = value + } + } + + func onDragEnded(with value: DragGesture.Value) { + // 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 = startTime != nil ? value.time.timeIntervalSince(startTime!.time) : 0 + + let yVelocity: CGFloat = -1 * ((distance / time) / 1000) + startTime = nil + + if let result = snapBottomSheet( + translation, + detents, + yVelocity, + isInteractiveDismissDisabled + ) { + translation = result.size + + if result.size != .zero { + sheetConfig?.selectedDetent = result + } } } } diff --git a/Sources/BottomSheet/Detents/Detents.swift b/Sources/BottomSheet/Detents/Detents.swift index 2e2aa16..f488949 100644 --- a/Sources/BottomSheet/Detents/Detents.swift +++ b/Sources/BottomSheet/Detents/Detents.swift @@ -7,11 +7,40 @@ import SwiftUI -public enum PresentationDetent: Hashable { +/** + An enumeration to represent various form of PresentationDetent + + - `small`: A small sized bottom sheet. `.fraction(0.2)` + - `medium`: A medium sized bottom sheet, `.fraction(0.5)` + - `large`: A large sized bottom sheet. `.fraction(0.9)` + - `fraction`: Relative to screen height. + - `height`: A constant height. + */ + +public enum PresentationDetent: Hashable, Sendable { + /** + The system detent for a sheet that’s approximately a quarter height of the screen, and is inactive in compact height. + */ case small + + /** + The system detent for a sheet that’s approximately half the height of the screen, and is inactive in compact height. + */ case medium + + /** + The system detent for a sheet at full height. + */ case large + + /** + A custom detent with the specified fractional height. + */ case fraction(CGFloat) + + /** + A custom detent with the specified height. + */ case height(CGFloat) public var size: CGFloat { diff --git a/Sources/BottomSheet/Helpers/Snapping.swift b/Sources/BottomSheet/Helpers/Snapping.swift index 976bc3c..c11e8b2 100644 --- a/Sources/BottomSheet/Helpers/Snapping.swift +++ b/Sources/BottomSheet/Helpers/Snapping.swift @@ -13,18 +13,34 @@ import Foundation /// - 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, _ yVelocity: CGFloat) -> PresentationDetent? { - let detents = detents.sorted(by: { $0.size < $1.size }) - +internal func snapBottomSheet( + _ translation: CGFloat, + _ detents: Set, + _ yVelocity: CGFloat, + _ isInteractiveDismissDisabled: Bool +) -> PresentationDetent? { + var detents = detents.sorted(by: { $0.size < $1.size }) + + // Insert a custom detent at 0. So we can hide the sheet all the way at the bottom. + if (!isInteractiveDismissDisabled) { + detents.insert(PresentationDetent.height(.zero), at: 0) + } + 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 { + // Exception for snapping to dismiss position more easily on smaller sheets. + // Last swipe down is just to dismiss it always. + if (detentBracket.lower.size == 0 && translation < (detentBracket.upper.size - 50)) { + return detentBracket.lower + } + if abs(yVelocity) > 1.8 { return yVelocity > 0 ? detentBracket.upper : detentBracket.lower } else { diff --git a/Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift b/Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift index 66d1e07..6abadb8 100644 --- a/Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift +++ b/Sources/BottomSheet/Preference Keys/BackgroundInteractionKey.swift @@ -7,25 +7,32 @@ 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 +public struct PresentationBackgroundInteractionPlus: Hashable, Sendable { + internal enum Kind: Hashable, Sendable { + case automatic + case disabled + case enabled(upThrough: PresentationDetent?) + } + + internal let kind: Kind + + internal init(kind: Kind) { + self.kind = kind + } + + public static let automatic = Self.init(kind: .automatic) + public static let enabled = Self.init(kind: .enabled(upThrough: nil)) + public static let disabled = Self.init(kind: .disabled) + + public static func enabled(upThrough detent: PresentationDetent) -> Self { + .init(kind: .enabled(upThrough: detent)) } } struct SheetPlusBackgroundInteractionKey: PreferenceKey { - static var defaultValue: PresentationBackgroundInteractionPlus = .automatic + static var defaultValue: CGFloat? = nil - static func reduce(value: inout PresentationBackgroundInteractionPlus, nextValue: () -> PresentationBackgroundInteractionPlus) { + static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { value = nextValue() } } diff --git a/Sources/BottomSheet/Preference Keys/ConfigKey.swift b/Sources/BottomSheet/Preference Keys/ConfigKey.swift index 6837078..feb3718 100644 --- a/Sources/BottomSheet/Preference Keys/ConfigKey.swift +++ b/Sources/BottomSheet/Preference Keys/ConfigKey.swift @@ -12,7 +12,6 @@ struct SheetPlusConfig: Equatable { @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 } @@ -20,11 +19,8 @@ struct SheetPlusConfig: Equatable { 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 + value = nextValue() } } diff --git a/Sources/BottomSheet/Preference Keys/InteractiveDismissKey.swift b/Sources/BottomSheet/Preference Keys/InteractiveDismissKey.swift new file mode 100644 index 0000000..e9d8d69 --- /dev/null +++ b/Sources/BottomSheet/Preference Keys/InteractiveDismissKey.swift @@ -0,0 +1,17 @@ +// +// File.swift +// +// +// Created by Wouter van de Kamp on 25/12/2023. +// + +import Foundation +import SwiftUI + +struct SheetPlusInteractiveDismissDisabledKey: PreferenceKey { + static var defaultValue: Bool = true + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} diff --git a/Sources/BottomSheet/Preference Keys/PresentationBackgroundKey.swift b/Sources/BottomSheet/Preference Keys/PresentationBackgroundKey.swift new file mode 100644 index 0000000..16e611e --- /dev/null +++ b/Sources/BottomSheet/Preference Keys/PresentationBackgroundKey.swift @@ -0,0 +1,29 @@ +// +// PresentationBackgroundKey.swift +// +// +// Created by Wouter van de Kamp on 03/02/2024. +// + +import SwiftUI + +struct EquatableBackground: Equatable { + let id = UUID().uuidString + let alignment: Alignment + let view: AnyView + + static func == (lhs: EquatableBackground, rhs: EquatableBackground) -> Bool { + return lhs.id == rhs.id + } +} + +struct SheetPlusPresentationBackgroundKey: PreferenceKey { + static var defaultValue: EquatableBackground = EquatableBackground(alignment: .center, view: AnyView(EmptyView())) + + static func reduce( + value: inout EquatableBackground, + nextValue: () -> EquatableBackground + ) { + value = nextValue() + } +} diff --git a/Sources/BottomSheet/Preference Keys/PresentationCornerRadiusKey.swift b/Sources/BottomSheet/Preference Keys/PresentationCornerRadiusKey.swift new file mode 100644 index 0000000..f0b16a3 --- /dev/null +++ b/Sources/BottomSheet/Preference Keys/PresentationCornerRadiusKey.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Wouter van de Kamp on 30/01/2024. +// + +import SwiftUI + +struct SheetPlusPresentationCornerRadiusKey: PreferenceKey { + static var defaultValue: CGFloat? = nil + + static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { + value = nextValue() + } +} diff --git a/Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift b/Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift index ff040fc..85c6c1c 100644 --- a/Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift +++ b/Sources/BottomSheet/UIKit Views/UIScrollViewWrapper.swift @@ -12,10 +12,11 @@ import UIKit internal struct UIScrollViewWrapper: UIViewRepresentable { @Binding var translation: CGFloat @Binding var preferenceKey: SheetPlusConfig? - + @Binding var isInteractiveDismissDisabled: Bool + let limits: (min: CGFloat, max: CGFloat) let detents: Set - + let content: () -> Content func makeUIView(context: Context) -> UIScrollView { @@ -48,7 +49,7 @@ internal struct UIScrollViewWrapper: UIViewRepresentable { func updateUIView(_ scrollView: UIScrollView, context: Context) { context.coordinator.limits = limits context.coordinator.detents = detents - + context.coordinator.hostingController.rootView = self.content() } @@ -138,10 +139,14 @@ internal struct UIScrollViewWrapper: UIViewRepresentable { if let result = snapBottomSheet( representable.translation, detents, - scrollView.contentOffset.y > 0 ? 0 : velocity.y + scrollView.contentOffset.y > 0 ? 0 : velocity.y, + representable.isInteractiveDismissDisabled ) { representable.translation = result.size - representable.preferenceKey?.selectedDetent = result + + if result.size != .zero { + representable.preferenceKey?.selectedDetent = result + } } scrollOffset = 0 diff --git a/Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift b/Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift index 2ba48cb..36dd00b 100644 --- a/Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift +++ b/Sources/BottomSheet/View Modifiers/View+BackgroundInteraction.swift @@ -11,9 +11,15 @@ extension View { public func presentationBackgroundInteractionPlus( _ interaction: PresentationBackgroundInteractionPlus ) -> some View { - return self.preference( - key: SheetPlusBackgroundInteractionKey.self, - value: interaction - ) + return transformPreference(SheetPlusBackgroundInteractionKey.self) { value in + switch interaction.kind { + case .automatic: + value = nil + case .disabled: + value = 0 + case .enabled(let detent): + value = detent?.size ?? 0 + } + } } } diff --git a/Sources/BottomSheet/View Modifiers/View+DragIndicator.swift b/Sources/BottomSheet/View Modifiers/View+DragIndicator.swift index 2cc62d0..33e3842 100644 --- a/Sources/BottomSheet/View Modifiers/View+DragIndicator.swift +++ b/Sources/BottomSheet/View Modifiers/View+DragIndicator.swift @@ -7,6 +7,7 @@ import SwiftUI +@frozen public enum VisibilityPlus { case hidden case visible diff --git a/Sources/BottomSheet/View Modifiers/View+InteractiveDismiss.swift b/Sources/BottomSheet/View Modifiers/View+InteractiveDismiss.swift new file mode 100644 index 0000000..aaf33cd --- /dev/null +++ b/Sources/BottomSheet/View Modifiers/View+InteractiveDismiss.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Wouter van de Kamp on 25/12/2023. +// + +import Foundation +import SwiftUI + +extension View { + public func interactiveDismissDisabledPlus( + _ isDisabled: Bool + ) -> some View { + return self.preference( + key: SheetPlusInteractiveDismissDisabledKey.self, + value: isDisabled + ) + } +} diff --git a/Sources/BottomSheet/View Modifiers/View+PresentationBackground.swift b/Sources/BottomSheet/View Modifiers/View+PresentationBackground.swift new file mode 100644 index 0000000..1ba3ada --- /dev/null +++ b/Sources/BottomSheet/View Modifiers/View+PresentationBackground.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Wouter van de Kamp on 27/01/2024. +// + +import SwiftUI + +extension View { + public func presentationBackgroundPlus( + alignment: Alignment = .center, + @ViewBuilder content: () -> V + ) -> some View { + return self.preference( + key: SheetPlusPresentationBackgroundKey.self, + value: EquatableBackground(alignment: alignment, view: AnyView(content())) + ) + } +} diff --git a/Sources/BottomSheet/View Modifiers/View+PresentationCornerRadius.swift b/Sources/BottomSheet/View Modifiers/View+PresentationCornerRadius.swift new file mode 100644 index 0000000..61c763b --- /dev/null +++ b/Sources/BottomSheet/View Modifiers/View+PresentationCornerRadius.swift @@ -0,0 +1,19 @@ +// +// View+PresentationCornerRadius.swift +// +// +// Created by Wouter van de Kamp on 30/01/2024. +// + +import SwiftUI + +extension View { + public func presentationCornerRadiusPlus( + cornerRadius: CGFloat? + ) -> some View { + return self.preference( + key: SheetPlusPresentationCornerRadiusKey.self, + value: cornerRadius + ) + } +} diff --git a/Sources/BottomSheet/View Modifiers/View+SheetPlus.swift b/Sources/BottomSheet/View Modifiers/View+SheetPlus.swift index dea298c..6f80406 100644 --- a/Sources/BottomSheet/View Modifiers/View+SheetPlus.swift +++ b/Sources/BottomSheet/View Modifiers/View+SheetPlus.swift @@ -8,14 +8,13 @@ import SwiftUI extension View { - public func sheetPlus( + public func sheetPlus( isPresented: Binding, 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() }, @@ -25,7 +24,6 @@ extension View { SheetPlus( isPresented: isPresented, animationCurve: animationCurve, - background: background, onDismiss: onDismiss, onDrag: onDrag, hcontent: header, diff --git a/Sources/BottomSheet/Views/DragIndicator.swift b/Sources/BottomSheet/Views/DragIndicator.swift index edeb7f9..f72f629 100644 --- a/Sources/BottomSheet/Views/DragIndicator.swift +++ b/Sources/BottomSheet/Views/DragIndicator.swift @@ -24,7 +24,7 @@ struct DragIndicator: View { if let nextDetent = nextDetent { translation = nextDetent.size } else { - translation = sortedDetents.first!.size + translation = sortedDetents.first?.size ?? 0 } } }