touchable list row; prevent tap gesture passtrough

This commit is contained in:
Levitating Pineapple
2024-09-26 14:58:15 +03:00
parent 240aa6a2b1
commit 07165039a8
2 changed files with 75 additions and 15 deletions
@@ -77,6 +77,7 @@ struct UserPicker: View {
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: 20, maxHeight: 20)
.padding(.horizontal, rowPadding)
.contentShape(Rectangle())
.onTapGesture {
if (colorScheme == .light) {
ThemeManager.applyTheme(systemDarkThemeDefault.get())
@@ -164,19 +165,15 @@ struct UserPicker: View {
}
}
}
private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet) -> some View {
Button {
activeSheet = sheet
} label: {
settingsRow(icon, color: theme.colors.secondary) {
Text(title).foregroundColor(.primary)
}
settingsRow(icon, color: theme.colors.secondary) {
Text(title).foregroundColor(.primary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, rowPadding)
.padding(.vertical, rowVerticalPadding)
.contentShape(Rectangle())
.modifier(ListRow { activeSheet = sheet })
}
private func unreadBadge(_ u: UserInfo) -> some View {
@@ -191,6 +188,64 @@ struct UserPicker: View {
}
}
struct ListRow: ViewModifier {
@State private var touchDown = false
let action: () -> Void
func body(content: Content) -> some View {
ZStack {
Color(touchDown ? .systemGray4 : .clear)
content
TouchOverlay(touchDown: $touchDown, action: action)
}
}
struct TouchOverlay: UIViewRepresentable {
@Binding var touchDown: Bool
let action: () -> Void
func makeUIView(context: Context) -> TouchView {
let touchView = TouchView()
let gesture = UILongPressGestureRecognizer(
target: touchView,
action: #selector(touchView.longPress(gesture:))
)
gesture.delegate = touchView
gesture.minimumPressDuration = 0.05
touchView.addGestureRecognizer(gesture)
return touchView
}
func updateUIView(_ touchView: TouchView, context: Context) {
touchView.representer = self
}
class TouchView: UIView, UIGestureRecognizerDelegate {
var representer: TouchOverlay?
@objc
func longPress(gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
representer?.touchDown = true
case .ended:
representer?.touchDown = false
if hitTest(gesture.location(in: self), with: nil) == self {
representer?.action()
}
default: break
}
}
func gestureRecognizer(
_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer
) -> Bool { true }
}
}
}
struct UserPicker_Previews: PreviewProvider {
static var previews: some View {
@State var activeSheet: UserPickerSheet?
@@ -42,6 +42,7 @@ struct SheetRepresentable<Content: View>: UIViewControllerRepresentable {
private let animator = UIViewPropertyAnimator(duration: sheetAnimationDuration, curve: .easeInOut)
private let representer: SheetRepresentable<C>
private var retainedFraction: CGFloat = 0
private var sheetHeight: Double { hostingController.view.frame.height }
init(content: C, representer: SheetRepresentable<C>) {
self.representer = representer
@@ -83,9 +84,11 @@ struct SheetRepresentable<Content: View>: UIViewControllerRepresentable {
animator.pausesOnCompletion = true
animator.scrubsLinearly = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.animator.addAnimations {
sheet.transform = CGAffineTransform(translationX: 0, y: -sheet.frame.height)
self?.view.backgroundColor = .black.withAlphaComponent(0.3)
if let self {
self.animator.addAnimations {
sheet.transform = CGAffineTransform(translationX: 0, y: -self.sheetHeight)
self.view.backgroundColor = .black.withAlphaComponent(0.3)
}
}
}
}
@@ -99,11 +102,11 @@ struct SheetRepresentable<Content: View>: UIViewControllerRepresentable {
animator.pauseAnimation()
retainedFraction = animator.fractionComplete
case .changed:
animator.fractionComplete = retainedFraction - gesture.translation(in: view).y / hostingController.view.frame.height
animator.fractionComplete = retainedFraction - gesture.translation(in: view).y / sheetHeight
case .ended, .cancelled:
let velocity = gesture.velocity(in: view).y
animator.isReversed = velocity.sign == .plus
let defaultVelocity = hostingController.view.frame.height / sheetAnimationDuration
animator.isReversed = (velocity - (animator.fractionComplete - 0.5) * 100).sign == .plus
let defaultVelocity = sheetHeight / sheetAnimationDuration
let fractionRemaining = 1 - animator.fractionComplete
let durationFactor = min(fractionRemaining / (abs(velocity) / defaultVelocity), 1)
animator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor)
@@ -118,7 +121,9 @@ struct SheetRepresentable<Content: View>: UIViewControllerRepresentable {
func tap(gesture: UITapGestureRecognizer) {
switch gesture.state {
case .ended:
representer.isPresented = false
if gesture.location(in: view).y < view.frame.height - sheetHeight {
representer.isPresented = false
}
default: break
}
}