Welcome to the Buttonify - Lightweight SwiftUI Button Library for SwiftUI!
This library provides a customizable button component that supports different interaction states (tap, holding, release) with optional haptic feedback and a flexible styling system.
- Multiple Interaction States: Supports tap, holding, and release states.
- Customizable Styles: Easily customize fonts, colors, padding, borders, shadows, and more.
- Haptic Feedback: Optional haptic feedback for different interaction types.
- Loading State: Built-in support for displaying a loading indicator.
- Adaptive Layout: Supports large and small button sizes.
- Accessibility: Designed with accessibility in mind.
- iOS 15.0+
- macOS 11.0+
- Xcode 13.0+ for building on iOS 15+ and macOS 11+.
To use the Buttonify UI Library in your SwiftUI project, you have two options:
-
Copying the Source Files: You can copy the provided source files into your project directory or include them as part of your UI components module.
-
Swift Package Manager (SPM): Add Buttonify to your project using SPM.
- Open your project in Xcode.
- Navigate to
File > Add Packages...
. - Enter the repository URL of Buttonify in the search bar (use the copy button below):
https://github.com/filsv/buttonify.git
- Choose the package options that fit your needs and click
Add Package
.
Once added, you can start using Buttonify in your SwiftUI project.
An example project is included in the Buttonify repository to demonstrate how to integrate and use the Buttonify components in your SwiftUI app.
- Clone the Repository:
- Clone the repository to your local machine using Git:
git clone https://github.com/filsv/buttonify.git
- Open the Example Project:
- Navigate to the ButtonifyExample/ folder.
- Open the ButtonifyExample.xcodeproj file using Xcode.
- Build and Run:
- In Xcode, select the ButtonifyExample scheme from the scheme selector in the top left.
- Choose your preferred simulator or device (e.g., iPhone or macOS).
- Press Cmd + R to build and run the example app.
- Explore the Example:
- Once the app is running on the simulator or device, you’ll see various examples of how Buttonify components, such as HoverButton, are used in SwiftUI views.
Here's how you can use the HoverButton
in your SwiftUI views:
import SwiftUI
import Buttonify
struct ContentView: View {
@State private var interactionType: InteractionType = .none
@State private var isLoading: Bool = false
var body: some View {
HoverButton(
interactionCallback: { interaction in
interactionType = interaction
}
) {
Text("Press Me")
}
.hoverButtonStyle(.primary(isLarge: false, isLoading: $isLoading, shrinkable: true))
}
}
You can customize the appearance of the HoverButton by using predefined styles or creating your own:
HoverButton(
style: .secondary(isLarge: true),
interactionCallback: { interaction in
// Handle interactions
}
) {
Text("Secondary Button")
}
.hoverButtonStyle(.primary(isLarge: true))
To create a custom style:
let customStyle = Styles(
tint: .white,
selectedTint: .gray,
font: .headline,
radius: 12,
background: .green,
hoveredBackground: .green.opacity(0.8),
padding: 10,
horizontalPadding: 20,
border: Styles.Border(color: .white, width: 2),
shadow: Styles.Shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2),
isLarge: true
)
HoverButton(
interactionCallback: { interaction in
// Handle interactions
}
) {
Text("Custom Button")
}
.hoverButtonStyle(.custom(style: customStyle))
The interactionCallback closure provides updates on the button’s interaction state:
HoverButton(
interactionCallback: { interaction in
switch interaction {
case .tap:
print("Button was tapped")
case .holding:
print("Button is being held")
case .released:
print("Button was released")
case .none:
print("No interaction")
}
}
) {
Text("Interactable Button")
}
.hoverButtonStyle(.secondary(isLarge: false))
You can enable haptic feedback by providing a HapticConfiguration to the HoverButton. This configuration allows you to specify haptic feedback for different interaction types.
let hapticConfig = HapticConfiguration(
tap: .impact(.light),
longPress: .impact(.heavy),
release: .selection
)
HoverButton(
hapticConfig: hapticConfig
interactionCallback: { interaction in
// Handle interactions
}
) {
Text("Haptic Button")
}
.hoverButtonStyle(.secondary(isLarge: false))
The HoverButton supports a loading state, displaying a ProgressView when isLoading is true:
@State private var isLoading = false
HoverButton(
isLoading: $isLoading,
interactionCallback: { interaction in
if interaction == .tap {
isLoading = true
// Simulate a network request
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isLoading = false
}
}
}
) {
Text("Submit")
}
.hoverButtonStyle(.secondary(isLarge: false))
The Style enum and Styles struct allow you to define the appearance of the HoverButton:
.primary(isLarge: Bool)
.secondary(isLarge: Bool)
.shadowed(isLarge: Bool)
.bordered(isLarge: Bool)
Use .custom(style: Styles) to apply it. // Create a Styles instance with your desired properties.
tint: Text color.
selectedTint: Text color when pressed.
font: Font style.
radius: Corner radius.
background: Background color.
hoveredBackground: Background color when pressed.
padding: Vertical padding.
horizontalPadding: Horizontal padding.
border: Styles.Border (color and width).
shadow: Styles.Shadow (color, radius, x, y).
isLarge: Adjusts the button’s size and layout.
The InteractionType enum represents the button’s interaction states: .tap: The button was tapped. .holding: The button is being held down. .released: The button was released after a hold. .none: No interaction (default state).
Use the interactionCallback closure to handle these interactions in your view logic.
Below are comprehensive examples demonstrating the use of HoverButton with different styles, interaction handling, and customizations.
import SwiftUI
struct PrimaryButtonsExample: View {
@State private var interactionType: InteractionType = .none
var body: some View {
VStack(spacing: 20) {
Text("Primary Buttons")
.font(.headline)
.padding()
HoverButton(
hapticConfig: HapticConfiguration(
tap: .impact(.light),
longPress: .impact(.heavy),
release: .selection
)
) {
Text("Large Primary")
} interactionCallback: { interaction in
interactionType = interaction
}
.hoverButtonStyle(.primary(isLarge: true))
HoverButton(
hapticConfig: HapticConfiguration(
tap: .impact(.light),
longPress: .impact(.heavy),
release: .selection
)
) {
Text("Small Primary")
} interactionCallback: { interaction in
interactionType = interaction
}
.hoverButtonStyle(.primary(isLarge: false))
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
}
import SwiftUI
struct LoadingButtonsExample: View {
@State private var interactionType: InteractionType = .none
@State private var isLoading: Bool = false
var body: some View {
VStack(spacing: 20) {
Text("Loading Buttons")
.font(.headline)
.padding()
HoverButton(
isLoading: $isLoading,
shrinkable: true,
hapticConfig: HapticConfiguration(tap: .impact(.light))
) {
Text(isLoading ? "Loading..." : "Large Loading")
} interactionCallback: { interaction in
interactionType = interaction
if interaction == .tap {
initiateLoading()
}
}
.hoverButtonStyle(.primary(isLarge: true))
HoverButton(
isLoading: $isLoading,
shrinkable: true,
hapticConfig: HapticConfiguration(tap: .impact(.light))
) {
Text(isLoading ? "Loading..." : "Small Loading")
} interactionCallback: { interaction in
interactionType = interaction
if interaction == .tap {
initiateLoading()
}
}
.hoverButtonStyle(.primary(isLarge: false))
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
private func initiateLoading() {
isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
isLoading = false
}
}
}
import SwiftUI
struct SecondaryButtonsExample: View {
@State private var interactionType: InteractionType = .none
var body: some View {
VStack(spacing: 20) {
Text("Secondary Buttons")
.font(.headline)
.padding()
HoverButton(
hapticConfig: HapticConfiguration(tap: .impact(.light))
) {
Text("Large Secondary")
} interactionCallback: { interaction in
interactionType = interaction
}
.hoverButtonStyle(.secondary(isLarge: true))
HoverButton(
hapticConfig: HapticConfiguration(tap: .impact(.light))
) {
Text("Small Secondary")
} interactionCallback: { interaction in
interactionType = interaction
}
.hoverButtonStyle(.secondary(isLarge: false))
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
}
import SwiftUI
struct ShadowedButtonsExample: View {
@State private var interactionType: InteractionType = .none
var body: some View {
VStack(spacing: 20) {
Text("Shadowed Buttons")
.font(.headline)
.padding()
HoverButton(
style: .shadowed(isLarge: true),
tapHaptic: .impact(.medium)
) {
Text("Large Shadowed")
} interactionCallback: { interaction in
interactionType = interaction
}
HoverButton(
style: .shadowed(isLarge: false),
tapHaptic: .impact(.medium)
) {
Text("Small Shadowed")
} interactionCallback: { interaction in
interactionType = interaction
}
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
}
import SwiftUI
struct BorderedButtonsExample: View {
@State private var interactionType: InteractionType = .none
var body: some View {
VStack(spacing: 20) {
Text("Bordered Buttons")
.font(.headline)
.padding()
HoverButton(
style: .bordered(isLarge: true),
tapHaptic: .selection,
holdingThreshold: 1.0 // Holding state triggered after 1 second
) {
Text("Large Bordered")
} interactionCallback: { interaction in
interactionType = interaction
if interaction == .holding {
print("Button held for 1 second")
}
}
HoverButton(
style: .bordered(isLarge: false),
tapHaptic: .selection,
holdingThreshold: 1.0
) {
Text("Small Bordered")
} interactionCallback: { interaction in
interactionType = interaction
}
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
}
import SwiftUI
struct CustomButtonsExample: View {
@State private var interactionType: InteractionType = .none
var body: some View {
VStack(spacing: 20) {
Text("Custom Buttons")
.font(.headline)
.padding()
// Custom Large Button
let customStyleLarge = Styles(
tint: .orange,
selectedTint: .black,
font: .largeTitle,
radius: 5.0,
background: .red,
isLarge: true
)
HoverButton(
style: .custom(style: customStyleLarge),
tapHaptic: .impact(.heavy)
) {
Text("Custom Large")
} interactionCallback: { interaction in
interactionType = interaction
}
// Custom Small Button
let customStyleSmall = Styles(
tint: .yellow,
selectedTint: .black,
font: .headline,
radius: 5.0,
background: .orange,
padding: 5,
horizontalPadding: 10,
shadow: Styles.Shadow(color: .black, radius: 5, x: 5, y: 5),
isLarge: false
)
HoverButton(
style: .custom(style: customStyleSmall),
tapHaptic: .impact(.light)
) {
Text("Custom Small")
} interactionCallback: { interaction in
interactionType = interaction
}
Text("Current Interaction: \(interactionType.description)")
.padding()
}
}
}
This Buttonify Library is available under the MIT License. Feel free to use and modify it in your projects.