|
// |
|
// LongPressButton.swift |
|
// Pachino |
|
// |
|
// Created by Frad LEE on 6/28/21. |
|
// |
|
|
|
import SwiftUI |
|
|
|
// MARK: - LongPressButton |
|
|
|
/// A iOS flashlight like button. |
|
/// |
|
/// # References |
|
/// |
|
/// - [Building Fluid Interfaces. How to create natural gestures and…](https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5) |
|
/// - [Composing SwiftUI Gestures](https://developer.apple.com/documentation/swiftui/composing-swiftui-gestures) |
|
struct LongPressButton<Label: View>: View { |
|
// MARK: Lifecycle |
|
|
|
public init(@ViewBuilder label: () -> Label, action: @escaping () -> Void) { |
|
self.action = action |
|
self.label = label() |
|
} |
|
|
|
// MARK: Internal |
|
|
|
@State var viewState: Bool = false |
|
|
|
var action: () -> Void |
|
var label: Label |
|
|
|
// var alertMessage: String |
|
// var alertDestructiveButton: String |
|
var body: some View { |
|
let minimumLongPressDuration = 0.3 |
|
let longPress = |
|
LongPressGesture( |
|
minimumDuration: minimumLongPressDuration |
|
) |
|
.sequenced( |
|
before: DragGesture(minimumDistance: 0) |
|
) |
|
.updating($pressState) { value, state, _ in |
|
switch value { |
|
case .first: |
|
state = .activated |
|
case .second: |
|
state = .confirmed |
|
} |
|
} |
|
.onEnded { value in |
|
guard case .second = value else { return } |
|
self.viewState.toggle() |
|
self.showingAlert = true |
|
action() |
|
} |
|
|
|
Button(action: {}, label: { |
|
label |
|
.symbolRenderingMode(.hierarchical) |
|
.scaleEffect(pressState.isPressing ? 1.4 : 1) |
|
.animation(.easeOut(duration: minimumLongPressDuration), |
|
value: pressState.isPressing) |
|
.gesture(longPress) |
|
}) |
|
// .alert(alertMessage, isPresented: $showingAlert) { |
|
// Button("Cancel", role: .cancel) {} |
|
// Button(alertDestructiveButton, role: .destructive) {} |
|
// } |
|
} |
|
|
|
// MARK: Private |
|
|
|
@GestureState private var pressState = PressState.idle |
|
@State private var showingAlert = false |
|
} |
|
|
|
// MARK: - PressState |
|
|
|
private enum PressState { |
|
/// **Default state**. |
|
/// The button is ready to be activiated. |
|
case idle |
|
/// The button with enough duration long pressing. |
|
case activated |
|
/// The button has recently switched on/off. |
|
case confirmed |
|
|
|
// MARK: Internal |
|
|
|
var isPressing: Bool { |
|
switch self { |
|
case .activated: |
|
return true |
|
case .confirmed, .idle: |
|
return false |
|
} |
|
} |
|
} |
|
|
|
// MARK: - LongPressButton_Previews |
|
|
|
struct LongPressButton_Previews: PreviewProvider { |
|
static var previews: some View { |
|
LongPressButton(label: { |
|
Image(systemName: "checkmark.circle.fill") |
|
.font(.system(size: 64)) |
|
}, action: {}) |
|
.frame(width: 64, height: 64, alignment: .center) |
|
.foregroundColor(.red) |
|
} |
|
} |