Skip to content

Commit

Permalink
fix: working dentent setting and improved performance (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Wouter125 authored Jul 2, 2023
1 parent 90f322e commit 715f6c1
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ struct StocksExample: View {
settings.isPresented.toggle()
}

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

Color.clear
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("\(settings.translation.rounded())")
Expand Down
7 changes: 5 additions & 2 deletions Example/BottomSheetExample/ExampleOverview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ struct ExampleOverview: View {
return AnyView(
StocksMainContent()
.presentationDetentsPlus(
[.height(244), .medium, .large]
[.height(244), .medium, .large],
selection: $settings.selectedDetent
)
)
default:
Expand Down Expand Up @@ -76,10 +77,12 @@ struct ExampleOverview: View {
Color(UIColor.secondarySystemBackground)
.cornerRadius(12, corners: [.topLeft, .topRight])
),
onDrag: { translation in
settings.translation = translation
},
header: { headerContent() },
main: {
mainContent()
.onSheetDrag(translation: $settings.translation)
}
)
.overlay(
Expand Down
201 changes: 88 additions & 113 deletions Sources/BottomSheet/BottomSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,28 @@ import SwiftUI
struct SheetPlus<HContent: View, MContent: View, Background: View>: ViewModifier {
@Binding private var isPresented: Bool

let onDismiss: () -> Void
let hcontent: HContent
let mcontent: MContent
let animationCurve: SheetAnimation
let background: Background
@State private var translation: CGFloat = 0
@State private var sheetConfig: SheetPlusConfig?

@State private var offset = 0.0
@State private var newValue = 0.0
@State private var translation: CGFloat = 0
@State private var startTime: DragGesture.Value?

@State private var detents: Set<PresentationDetent> = []
@State private var preferenceKey: SheetPlusConfigKey?
@State private var limits: (min: CGFloat, max: CGFloat) = (min: 0, max: 0)

@State private var translationKey: SheetPlusTranslationKey?

var onDrag: ((_ position: CGFloat) -> Void)?
let mainContent: MContent
let headerContent: HContent
let animationCurve: SheetAnimation
let onDismiss: () -> Void
let onDrag: (CGFloat) -> Void
let background: Background

public init(
init(
isPresented: Binding<Bool>,
animationCurve: SheetAnimation,
background: Background,
onDismiss: @escaping () -> Void,
onDrag: @escaping (CGFloat) -> Void,
@ViewBuilder hcontent: () -> HContent,
@ViewBuilder mcontent: () -> MContent
) {
Expand All @@ -35,122 +33,99 @@ struct SheetPlus<HContent: View, MContent: View, Background: View>: ViewModifier
self.animationCurve = animationCurve
self.background = background
self.onDismiss = onDismiss
self.onDrag = onDrag

self.hcontent = hcontent()
self.mcontent = mcontent()
self.headerContent = hcontent()
self.mainContent = mcontent()
}

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

VStack {
if isPresented {
GeometryReader { geometry in
VStack(spacing: 0) {
hcontent
.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
}

if isPresented {
GeometryReader { geometry in
VStack(spacing: 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
preferenceKey?.$selection.wrappedValue = result
}
}
.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: $preferenceKey,
detents: $detents,
limits: $limits
) {
mcontent
.frame(width: geometry.size.width)
}

}
.background(background)
.frame(height:
(limits.max - geometry.safeAreaInsets.top) > 0
? limits.max - geometry.safeAreaInsets.top
: limits.max
)
.offset(y: UIScreen.main.bounds.height - translation)
.onChange(of: translation) { newValue in
if limits.max == 0 { return }
translation = min(limits.max, max(newValue, limits.min))
}
.onAnimationChange(of: translation) { value in
translationKey?.$translation.wrappedValue = value
}
.animation(
.interpolatingSpring(
mass: animationCurve.mass,
stiffness: animationCurve.stiffness,
damping: animationCurve.damping
}
)
)
.onDisappear {
translation = 0
detents = []
offset = 0
newValue = 0
limits = (min: 0, max: 0)

onDismiss()

UIScrollViewWrapper(
translation: $translation,
preferenceKey: $sheetConfig,
limits: limits,
detents: detents
) {
mainContent
.frame(width: geometry.size.width)
}
}
.edgesIgnoringSafeArea([.bottom])
.transition(.move(edge: .bottom))
.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))
}
.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))
}
.animation(
.interpolatingSpring(
mass: animationCurve.mass,
stiffness: animationCurve.stiffness,
damping: animationCurve.damping
)
)
}
.onPreferenceChange(SheetPlusTranslation.self) { value in
self.translationKey = value
}
.onPreferenceChange(SheetPlusConfiguration.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 }

.onPreferenceChange(SheetPlusKey.self) { value in
sheetConfig = value
translation = value.translation

detents = value.detents
limits = detentLimits(detents: value.detents)

if value.selection == .height(.zero) {
translation = limits.min
} else {
translation = value.$selection.wrappedValue.size
}

limits = detentLimits(detents: detents)
}
}
}
24 changes: 8 additions & 16 deletions Sources/BottomSheet/Preference Keys/ConfigKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,21 @@

import SwiftUI

struct SheetPlusConfigKey: Equatable {
let id = UUID().uuidString

struct SheetPlusConfig: Equatable {
let detents: Set<PresentationDetent>
@Binding var selection: PresentationDetent
@Binding var selectedDetent: PresentationDetent
let translation: CGFloat

init(
detents: Set<PresentationDetent>,
selection: Binding<PresentationDetent> = .constant(.height(.zero))
) {
self.detents = detents
self._selection = selection
}

static func == (lhs: SheetPlusConfigKey, rhs: SheetPlusConfigKey) -> Bool {
return lhs.id == rhs.id
static func == (lhs: SheetPlusConfig, rhs: SheetPlusConfig) -> Bool {
return lhs.selectedDetent == rhs.selectedDetent && lhs.translation == rhs.translation && lhs.detents == rhs.detents
}
}

struct SheetPlusConfiguration: PreferenceKey {
static var defaultValue: SheetPlusConfigKey = SheetPlusConfigKey(detents: [])
struct SheetPlusKey: PreferenceKey {
static var defaultValue: SheetPlusConfig = SheetPlusConfig(detents: [], selectedDetent: .constant(.height(.zero)), translation: 0)

static func reduce(value: inout SheetPlusConfigKey, nextValue: () -> SheetPlusConfigKey) {
static func reduce(value: inout SheetPlusConfig, nextValue: () -> SheetPlusConfig) {
value = nextValue()
}
}
31 changes: 0 additions & 31 deletions Sources/BottomSheet/Preference Keys/TranslationKey.swift

This file was deleted.

Loading

0 comments on commit 715f6c1

Please sign in to comment.