PullToClear.swift (1799B)
1 import SwiftUI 2 3 /// Pull distance at which the indicator signals readiness and completed item clearing triggers. 4 let pullClearThreshold: CGFloat = 50 5 6 struct PullToClearIndicator: View { 7 let pullOffset: CGFloat 8 9 private var progress: CGFloat { min(1, pullOffset / pullClearThreshold) } 10 private var isReady: Bool { pullOffset >= pullClearThreshold } 11 private let textSlideDistance: CGFloat = 22 12 13 var body: some View { 14 HStack(spacing: 6) { 15 ZStack { 16 Image(systemName: "checkmark") 17 .offset(y: isReady ? 0 : textSlideDistance) 18 Image(systemName: "tray") 19 .offset(y: isReady ? -textSlideDistance : 0) 20 } 21 .frame(width: 26, height: textSlideDistance, alignment: .leading) 22 .clipped() 23 .foregroundStyle(.secondary) 24 .font(.system(size: 17)) 25 .fontWeight(.semibold) 26 .animation(.easeInOut(duration: 0.15), value: isReady) 27 ZStack(alignment: .leading) { 28 Text("Release to clear") 29 .offset(y: isReady ? 0 : textSlideDistance) 30 Text("Clear completed") 31 .offset(y: isReady ? -textSlideDistance : 0) 32 } 33 .foregroundStyle(.secondary) 34 .font(ItemRowMetrics.hintSUI) 35 .frame(height: textSlideDistance, alignment: .topLeading) 36 .clipped() 37 .animation(.easeInOut(duration: 0.15), value: isReady) 38 } 39 .frame(maxWidth: .infinity) 40 .frame(height: 56) 41 // Reveal from the bottom upward as the user pulls 42 .frame(height: min(pullOffset, 56), alignment: .bottom) 43 .clipped() 44 .opacity(Double(progress)) 45 .allowsHitTesting(false) 46 } 47 }