Implemented the RecodingButtonStyle button style in the Recording package target.

This commit is contained in:
2026-07-03 19:08:42 +02:00
parent 1487ecb3f4
commit 2ae4306c3f
@@ -0,0 +1,143 @@
import SwiftUI
/// The button style for the controls of a ``RecordingView``.
///
/// The style renders the button's label over a padded, circular red background, shrinks it slightly while pressed, and dims it while disabled.
struct RecordingButtonStyle: ButtonStyle {
// MARK: Properties
/// Whether the button allows user interaction.
@Environment(\.isEnabled) private var isEnabled
// MARK: Methods
/// Creates the styled body of the button.
///
/// - Parameter configuration: The properties of the button being styled.
func makeBody(
configuration: Configuration
) -> some View {
configuration.label
.frame(
width: Constant.Size.button,
height: Constant.Size.button
)
.padding(Constant.Padding.button)
.background(
Circle()
.fill(.red)
)
.opacity(opacity)
.scaleEffect(scaleEffect(configuration.isPressed))
.animation(
.easeOut(duration: Constant.Animation.duration),
value: configuration.isPressed
)
}
}
// MARK: - Helpers
private extension RecordingButtonStyle {
// MARK: Computed
/// The opacity of the button: dimmed while disabled, fully opaque otherwise.
var opacity: Double {
isEnabled
? Constant.Opacity.enabled
: Constant.Opacity.disabled
}
// MARK: Methods
/// Provides the scale of the button for a press state.
///
/// - Parameter isPressed: Whether the button is currently pressed.
/// - Returns: A slightly shrunken scale while pressed, the resting scale otherwise.
func scaleEffect(
_ isPressed: Bool
) -> CGFloat {
isPressed
? Constant.Scale.pressed
: Constant.Scale.idle
}
}
// MARK: - Styles
extension ButtonStyle where Self == RecordingButtonStyle {
/// The button style for the controls of a ``RecordingView``.
static var recording: RecordingButtonStyle {
.init()
}
}
// MARK: - Constants
/// The constant values used across the button style.
private enum Constant {
/// The animation constants.
enum Animation {
/// The duration of the press animation.
static let duration: TimeInterval = 0.15
}
/// The opacity constants.
enum Opacity {
/// The opacity of a disabled button.
static let disabled: Double = 0.4
/// The opacity of an enabled button.
static let enabled: Double = 1
}
/// The padding constants.
enum Padding {
/// The padding between a button's label and its background.
static let button: CGFloat = 8
}
/// The scale constants.
enum Scale {
/// The scale of a button at rest.
static let idle: CGFloat = 1
/// The scale of a button while pressed.
static let pressed: CGFloat = 0.85
}
/// The size constants.
enum Size {
/// The width and height of a button's label.
static let button: CGFloat = 24
}
}
// MARK: - Previews
#Preview(
"Recording button style"
) {
@Previewable @State var isDisabled: Bool = false
Button {} label: {
Image(.Icon.record)
.resizable()
}
.buttonStyle(.recording)
.disabled(isDisabled)
.safeAreaInset(edge: .bottom) {
Toggle(
"Button disabled",
isOn: $isDisabled
)
.fontWeight(.semibold)
.dynamicTypeSize(.large)
.padding()
.tint(.red)
}
}