This SDK provides a comprehensive suite of UI components and utilities that streamline and enhance the development of SwiftUI applications.
From customizable alerts, buttons, and progress indicators to specialized text fields, sliders, and advanced animations like shimmer effects, this SDK offers a wide range of tools to create polished, interactive, and visually appealing user interfaces.
Each component is designed with flexibility and ease of use in mind, ensuring seamless integration and consistent performance across various app environments. Whether you need simple alerts or complex loading indicators, this SDK equips you with the resources to build high-quality, feature-rich SwiftUI apps.
Platform | Minimum Version |
---|---|
iOS | 15.0 |
This project can be installed using Swift Package Manager
and CocoaPod
.
- Open your project in Xcode.
- Navigate to
File
>Swift Packages
>Add Package Dependency
. - Paste the repository URL:
https://github.com/Enryun/Common_SwiftUI
- Follow the prompts to add the package to your project.
For more details on using Swift Package Manager, visit Apple's Swift Package Manager documentation.
CocoaPods is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the Get Started section for more details.
Add the following entry to your Podfile:
pod 'CommonSwiftUI'
Then run pod install
.
To use any of the components provided by CommonSwiftUI
, such as RangeSlider
or QRScannerView
, you need to import the library at the beginning of your SwiftUI view file. This ensures that all the features and components from the library are accessible in that file. Simply add the following line at the top of your .swift
file where you plan to use these components:
import CommonSwiftUI
- Alert
- Button
- Dropdown
- FloatingButton
- GlassMorphism
- HoldDownButton
- LoadingIndicator
- ProgressView
- QRScanner
- SegmentControl
- Slider
- Text
- TextField
- Toast
- ViewModifier
Presents an alert with customizable text fields and actions.
AlertWithTextFields(
title: "Login",
message: "Please enter your password",
textFields:
[
AlertTextField(placeholder: "username"),
AlertTextField(placeholder: "password", isSecureTextEntry: true)
],
actions:
[
AlertAction(title: "Cancel", style: .cancel) { result in
print(result)
},
AlertAction(title: "Login", style: .default) { result in
print(result)
}
]
)
Parameters:
title
: The title of the alert.message
: The message displayed in the alert.textFields
: An array of AlertTextField, configuring each text field within the alert.actions
: An array of AlertAction, representing the actions that can be taken from the alert.
This function creates and displays an UIAlertController
with a specified title and message, incorporating any number of customizable text fields and actions into SwiftUI
. Each text field can be tailored with specific attributes like placeholders and keyboard types, while actions can define their title, style, and a completion handler that processes the entered text.
Represents a configurable text field for use within an alert dialog.
AlertTextField(placeholder: "password", isSecureTextEntry: true)
Parameters:
placeholder
: AString
that appears in the text field when it's empty, hinting at the expected input.keyboardType
: The type of keyboard to display. Defaults to.default
.isSecureTextEntry
: ABool
indicating whether the text field is for secure text entry (e.g., passwords). Defaults tofalse
.autocapitalizationType
: The autocapitalization strategy for the text field. Defaults to.none
.
This structure allows for the creation of a text field with customizable properties such as placeholder text, keyboard type, secure text entry for passwords, and text capitalization behavior. It can be used to gather input from users in a variety of contexts, ensuring that the text field is tailored to the specific type of data being requested.
Defines an action for an alert dialog.
AlertAction(title: "Login", style: .default) { result in
print(result)
}
Parameters:
title
: The text to display on the action button.style
: The visual style of the action, defined byUIAlertAction.Style
.completion
: A closure that is called when the action is selected, passing an array ofString
as its parameter.
This structure encapsulates the information needed to present an action within an alert, including the action's title, its visual style, and a completion handler that executes when the action is selected. The completion handler passes back an array of strings, allowing for flexible use cases such as returning input from text fields within the alert.
Example:
var body: some View {
Button(action: {
AlertWithTextFields(
title: "Login",
message: "Please enter your password",
textFields:
[
AlertTextField(placeholder: "username"),
AlertTextField(placeholder: "password", isSecureTextEntry: true)
],
actions:
[
AlertAction(title: "Cancel", style: .cancel) { result in
print(result)
},
AlertAction(title: "Login", style: .default) { result in
print(result)
}
]
)
}, label: {
Text("Present Alert")
})
}
CommonSwiftUI_AlertTexField.mp4
This setup presents an alert for login, with text fields for username and password and options to cancel or log in.
This solution presents a customizable alert whenever the Binding
error data has value. The error must conform to the provided CommonAlert protocol.
Adopting the CommonAlert
component helps projects by standardizing alert presentations across an application, ensuring a consistent user interface. This consistency can reduce development time and errors by providing a unified method for creating and managing alerts.
public protocol CommonAlert {
var title: String { get }
var subTitle: String? { get }
var buttons: AnyView { get }
}
Properties:
title
: The primary text displayed in the alert, typically used for summarizing the alert's purpose.subTitle
: An optional secondary text providing additional details or context.buttons
: A view component representing the actionable items or responses available for the alert.
This protocol
standardizes the way alerts are created by specifying essential elements that each alert should contain. Conforming to this protocol
ensures consistency in alert presentation and functionality.
Pass a binding of your custom alert conforming to CommonAlert
to this function to display it:
public func showCustomAlert<T>(alert: Binding<T?>) -> some View where T : CommonSwiftUI.CommonAlert
Parameter:
alert
: A binding to an optionalCommonAlert
conforming object that provides the data for the alert.
This function displays an alert based on the properties defined in an instance of T
, where T
conforms to the CommonAlert
protocol. It allows for dynamic alert content including title, subtitle, and custom button actions.
Example:
Define Custom Error conforming to CommonAlert
protocol. This standardizes and concentrates the way alerts are created and managed by specifying essential elements that each alert should contain.
enum MyCustomAlert: Error, LocalizedError, CommonAlert {
case noInternetConnection(onPress: () -> Void)
case dataNotFound
case urlError(error: Error)
var errorDescription: String? {
switch self {
case .noInternetConnection:
return "Please check your Internet connection and try again"
case .dataNotFound:
return "There is an error loading data. Please try again!"
case .urlError(error: let error):
return "Error calling \(error.localizedDescription)"
}
}
var title: String {
switch self {
case .noInternetConnection:
return "No Internet Connection"
case .dataNotFound:
return "Data not found"
case .urlError(let error):
return "Error \(error.localizedDescription)"
}
}
var subTitle: String? {
switch self {
case .noInternetConnection:
return "Please check your Internet connection and try again"
case .dataNotFound:
return "There is an error loading data. Please try again!"
case .urlError(error: let error):
return "Error calling \(error.localizedDescription)"
}
}
var buttons: AnyView {
AnyView(getButtonsForAlert())
}
@ViewBuilder
func getButtonsForAlert() -> some View {
switch self {
case .noInternetConnection(onPress: let onPress):
Button("OK") {
onPress()
}
case .dataNotFound:
Button("RETRY") {
}
case .urlError(let error):
Button("DELETE", role: .destructive) {
print(error.localizedDescription)
}
}
}
}
Now Alert will show whenever the error is set:
@State private var error: MyCustomAlert? = nil
var body: some View {
VStack(spacing: 35) {
Button("No Internet Connection") {
error = MyCustomAlert.noInternetConnection(onPress: {
print("On Pressed")
})
}
.buttonStyle(.borderedProminent)
Button("Data Not Found") {
error = MyCustomAlert.dataNotFound
}
.buttonStyle(.borderedProminent)
Button("URL Error") {
error = MyCustomAlert.urlError(error: URLError(.badURL))
}
.buttonStyle(.borderedProminent)
}
.showCustomAlert(alert: $error)
}
.showCustomAlert(alert: $error)
CommonSwiftUI_AlertErrorBinding.mp4
By conforming to the CommonAlert
protocol, developers can customize alert components while maintaining a coherent appearance and functionality. This modularity and consistency in design make the component particularly useful in large projects or those requiring frequent alert updates.
This solution enables the presentation of a customizable alert over the existing view content, using specified configurations and a custom view builder for the alert's content.
public func alert<Content>(alertConfig: Binding<CommonSwiftUI.UniversalAlertConfig>, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View
Parameters:
alertConfig
: A binding to the UniversalAlertConfig instance which controls the appearance and behavior of the alert.content
: A view builder that generates the content to be displayed in the alert. This allows for full customization of the alert's appearance and the interactive elements within it.
UniversalAlertConfig
configures the presentation and behavior of a customizable alert view in a SwiftUI application.
UniversalAlertConfig(
enableBackgroundBlur: true,
disableOutsideTap: false,
transitionType: .slide,
slideEdge: .bottom
)
Parameters:
enableBackgroundBlur
: A Boolean value that determines whether the background should be blurred when the alert is presented.disableOutsideTap
: A Boolean value that if set to true, disables dismissing the alert by tapping outside its bounds.transitionType
: The type of transition animation used when the alert is presented. Can be either.slide
or.opacity
.slideEdge
: The edge from which the alert should slide in if the transition type is.slide
.show
: A Boolean value indicating whether the alert is currently presented.
Methods:
present()
: Sets theshow
property to true to present the alert.dismiss()
: Sets theshow
property to false to dismiss the alert.
This configuration struct allows you to customize alert presentations with various properties such as background blur, disable interactions outside the alert, and choose from different transition animations.
A view container that serves as the root of a view hierarchy and can display an overlay window.
RootView
is designed to embed any SwiftUI view and has the capability to present additional content in an overlay window on top of the existing UI. This is particularly useful for displaying elements like toasts or alerts that should float above all other content.
RootView {
ContentView()
}
Parameters:
content
: A closure returning the content of the view.
On appear, RootView
automatically checks for an existing overlay window and, if none is found, creates and displays a new one, allowing for content like Toast
or Alert
to be shown on top of the primary view hierarchy.
Let's go through some examples.
The first required step is to wrapped your Application inside RootView:
@main
struct TestCommonUIApp: App {
var body: some Scene {
WindowGroup {
RootView {
ContentView()
}
}
}
}
Then you need to define the alertConfig
:
@State private var alert: UniversalAlertConfig = .init(enableBackgroundBlur: false, disableOutsideTap: false)
Next is to create the custom alert view:
struct CustomAlertView: View {
var title: String
var message: String
var confirmAction: () -> Void
var cancelAction: () -> Void
var body: some View {
VStack(spacing: 20) {
Text(title)
.font(.headline)
.padding(.top, 20)
Text(message)
.font(.subheadline)
.padding(.horizontal, 20)
Divider()
HStack {
Button(action: cancelAction) {
Text("Cancel")
.foregroundColor(.red)
.frame(maxWidth: .infinity)
}
.padding(.vertical, 10)
Divider()
Button(action: confirmAction) {
Text("OK")
.foregroundColor(.blue)
.frame(maxWidth: .infinity)
}
.padding(.vertical, 10)
}
.frame(height: 50)
}
.background(.white)
.cornerRadius(15)
.shadow(radius: 10)
.padding(.horizontal, 40)
}
}
Now using the alert
to enable the presentation of a customizable alert over the existing view content:
var body: some View {
VStack {
Button("Show Alert") {
alert.present()
}
.buttonStyle(.borderedProminent)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.yellow)
.alert(alertConfig: $alert) {
CustomAlertView(
title: "Custom Alert",
message: "This is a custom alert message.",
confirmAction: {
print("OK pressed")
},
cancelAction: {
print("Cancel pressed")
alert.dismiss()
}
)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}
CommonSwiftUI_UniversalAlert1.mp4
We can enable blur effect with enableBackgroundBlur
:
@State private var alert: UniversalAlertConfig = .init(enableBackgroundBlur: true, disableOutsideTap: false)
Let's make an custom bottom sheet view for this example:
struct BottomSheetView: View {
var icon: String
var title: String
var message: String
var confirmText: String
var confirmAction: () -> Void
var cancelText: String
var cancelAction: () -> Void
var body: some View {
VStack(spacing: 20) {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.foregroundColor(.white)
.background(Circle().fill(.blue))
Text(title)
.font(.headline)
.padding(.top, 10)
Text(message)
.font(.subheadline)
.padding(.horizontal, 20)
.multilineTextAlignment(.center)
VStack(spacing: 12) {
Button(action: confirmAction) {
Text(confirmText)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Button(action: cancelAction) {
Text(cancelText)
.frame(maxWidth: .infinity)
.padding()
.background(Color.gray.opacity(0.2))
.foregroundColor(.black)
.cornerRadius(10)
}
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
.background(Color.white)
.cornerRadius(15)
.shadow(radius: 10)
.padding(.horizontal, 30)
.padding(.bottom, 40)
}
}
Use the public method dismiss()
to dismiss the view when tap outside:
var body: some View {
VStack {
Button("Show Alert") {
alert.present()
}
.buttonStyle(.borderedProminent)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.yellow)
.onTapGesture {
alert.dismiss()
}
.alert(alertConfig: $alert) {
BottomSheetView(
icon: "questionmark.circle.fill",
title: "Replace Existing Folder?",
message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
confirmText: "Replace",
confirmAction: {
print("Confirm action")
},
cancelText: "Cancel",
cancelAction: {
print("Cancel action")
alert.dismiss()
}
)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.ignoresSafeArea()
}
}
CommonSwiftUI_UniversalAlert2.mp4
Use this solution to seamlessly integrate custom alerts into any SwiftUI view, enhancing user interaction and providing a dynamic and adaptable alerting solution.
A ButtonStyle
for SwiftUI that applies a Capsule Shape with customizable color styles.
CapsuleButtonStyle(textColor: Color.white, backgroundColor: gradient)
Parameters:
textColor
: The color or style applied to the text inside the button. Defaults to.white
.backgroundColor
: The background color or style of the button, conforming toShapeStyle
. Defaults to.blue
.verticalPadding
: The vertical padding inside the button. Defaults to10
.horizontalPadding
: The horizontal padding inside the button. Defaults to20
.
Example:
let gradient = LinearGradient(gradient: Gradient(colors: [Color.red, Color.orange]), startPoint: .leading, endPoint: .trailing)
var body: some View {
VStack(spacing: 25) {
Button("Capsule 1") { }
.buttonStyle(CapsuleButtonStyle())
Button("Capsule 2") { }
.buttonStyle(CapsuleButtonStyle(textColor: .black, backgroundColor: .green))
Button("Capsule 3") { }
.buttonStyle(CapsuleButtonStyle(textColor: .white, backgroundColor: gradient))
Button(action: {}, label: {
HStack {
Image(systemName: "cloud.sun")
Text("Capsule 4")
}
})
.buttonStyle(CapsuleButtonStyle(textColor: Color.white, backgroundColor: gradient))
}
}
![](https://private-user-images.githubusercontent.com/45484154/357135698-89fcae09-da1b-4470-96b1-b01bd75a692f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTcxMzU2OTgtODlmY2FlMDktZGExYi00NDcwLTk2YjEtYjAxYmQ3NWE2OTJmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWNmMGI0MDQ4NzQ4NzQwODQyOGI5ZDJmMjAzZWJjNDAxZDVkY2MwYmE2NWIwNTY4M2QzMjUzMTFlZDJhNjMzNTUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.7Du174AFIaRTdMVKDBV88YY80-BvpPlIswPuaMjC7ms)
This style gives buttons a modern, rounded look suitable for various UI contexts.
A ButtonStyle
for SwiftUI that allows customization of the button's shape and color.
ShapeButtonStyle(textColor: .white, backgroundColor: gradient, shape: .circle)
Parameters:
textColor
: The color or style applied to the text inside the button. Default is.primary
.backgroundColor
: The background color or style of the button, conforming toShapeStyle
. Default is.secondary
.shape
: The custom shape for the button, conforming toShape
. The default shape isCapsule()
.verticalPadding
: The vertical padding inside the button. Defaults to10
.horizontalPadding
: The horizontal padding inside the button. Defaults to20
.
Example:
let gradient = LinearGradient(gradient: Gradient(colors: [Color.red, Color.orange]), startPoint: .leading, endPoint: .trailing)
var body: some View {
VStack(spacing: 25) {
Button("ShapeButton 1") { }
.buttonStyle(ShapeButtonStyle(textColor: .white, backgroundColor: .blue, shape: .rect))
Button("ShapeButton 2") { }
.buttonStyle(ShapeButtonStyle(textColor: .primary, backgroundColor: .green, shape: .rect(cornerRadius: 8)))
Button("ShapeButton 3") { }
.buttonStyle(ShapeButtonStyle(textColor: .white, backgroundColor: .red, shape: .capsule))
Button("ShapeButton 4") { }
.buttonStyle(ShapeButtonStyle(textColor: .white, backgroundColor: .orange, shape: .ellipse))
Button(action: {}, label: {
Image(systemName: "heart.fill")
.font(.title)
.padding(5)
})
.buttonStyle(ShapeButtonStyle(textColor: .white, backgroundColor: gradient, shape: .circle))
}
}
![](https://private-user-images.githubusercontent.com/45484154/357139967-bd90bcd0-3510-4796-903e-4f78ac4b12c3.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTcxMzk5NjctYmQ5MGJjZDAtMzUxMC00Nzk2LTkwM2UtNGY3OGFjNGIxMmMzLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZkYmQyNmFiYjI0NDFjMTM2OTlhMzY1ZTExYzczMTk5MTA3ZTRmYWE3M2QyOTg4ZDZiNzcwZmRiZTc3MzE1ODImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.2_oY3CcxLz2PuIO29o-nHjJrZpWaHxxZRKQNk-dLfro)
This style modifies the appearance of buttons to fit within a specified shape, with customizable foreground, background colors and padding. It is highly flexible, accommodating various shapes and color styles.
A ButtonStyle
for SwiftUI that scales the button on press, with customizable shape and color styles.
GrowingButtonStyle(textColor: .primary, backgroundColor: .green, shape: .rect(cornerRadius: 4))
Parameters:
textColor
: The color or style for the text inside the button, defaulting to.white
.backgroundColor
: The background color or style of the button, conforming toShapeStyle
, with a default of.blue
.shape
: The custom shape for the button, conforming toShape
. The default shape isCapsule()
.verticalPadding
: The vertical padding inside the button. Defaults to10
.horizontalPadding
: The horizontal padding inside the button. Defaults to20
.
Example:
let gradient = LinearGradient(gradient: Gradient(colors: [Color.red, Color.orange]), startPoint: .leading, endPoint: .trailing)
var body: some View {
VStack(spacing: 25) {
Button("Growing 1") { }
.buttonStyle(GrowingButtonStyle())
Button("Growing 2") { }
.buttonStyle(GrowingButtonStyle(textColor: .primary, backgroundColor: .green, shape: .rect(cornerRadius: 4)))
Button("Growing 3") { }
.buttonStyle(GrowingButtonStyle(textColor: .primary, backgroundColor: gradient, shape: .rect(cornerRadius: 4)))
}
}
CommonSwiftUI_GrowingButtonStyle.mp4
This button style provides an interactive feedback effect by increasing the button's scale when pressed. It allows for customization of the button's foreground and background colors, shape, and padding.
A ButtonStyle
for SwiftUI that provides a customizable button with a loading indicator.
LoadingButtonStyle(isLoading: $isLoading, loadingState: .resize, backgroundColor: .indigo)
Parameters:
isLoading
: A binding to a boolean indicating whether the button is in a loading state.loadingState
: An enum that determines the button's behavior when loading. Defaults to.center
.textColor
: The color or style for the text inside the button, defaulting to.white
.backgroundColor
: The background color or style of the button, conforming toShapeStyle
, with a default of.blue
.disabledLoadingColor
: The background color or style of the button when it is loading, conforming toShapeStyle
, with a default of.gray
.shape
: The custom shape for the button, conforming toShape
. The default shape isCapsule()
.verticalPadding
: The vertical padding inside the button. Defaults to10
.horizontalPadding
: The horizontal padding inside the button. Defaults to20
.
Example:
@State private var isLoading: Bool = false
var body: some View {
VStack(spacing: 25) {
Button("Loading Button 1") {
isLoading = true
// Simulate a network request or some action
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isLoading = false
}
}.buttonStyle(LoadingButtonStyle(isLoading: $isLoading))
Button("Loading Button 2") {}
.buttonStyle(LoadingButtonStyle(isLoading: $isLoading, loadingState: .leading, backgroundColor: .cyan, horizontalPadding: 40))
Button(action: {}, label: {
Text("Loading Button 3")
.frame(width: 250, height: 40)
})
.buttonStyle(LoadingButtonStyle(isLoading: $isLoading, loadingState: .resize, backgroundColor: .indigo))
Button(action: {}, label: {
HStack(spacing: 12) {
Image(systemName: "person.crop.circle")
.font(.title2)
Text("Loading Button 4")
.font(.title)
}
.frame(width: 250, height: 40)
})
.buttonStyle(LoadingButtonStyle(isLoading: $isLoading, loadingState: .leading, backgroundColor: .red, disabledLoadingColor: .red.opacity(0.5)))
Button(action: {}, label: {
Image(systemName: "heart.fill")
.font(.title)
.padding(5)
})
.buttonStyle(LoadingButtonStyle(isLoading: $isLoading, loadingState: .top, backgroundColor: .green, shape: .circle))
}
}
CommonSwiftUI_LoadingButtonStyle.mp4.mp4
This button style offers interactive feedback by displaying a ProgressView
when in a loading state. It allows for extensive customization of the button's appearance, including text color, background color, shape, padding, and the position of the loading indicator. When loading, the button can optionally gray out the background and disable user interactions.
A flexible and customizable dropdown component for SwiftUI. This view allows for displaying a list of selectable items with customizable appearance and interactivity.
DropDown(options: DropDownOptions.allCases, selection: $selectedOption) { item, isSelected, isPlaceHolderShow, isExpand in
} placeHolder: { isExpand in
}
Parameters:
options
: An array ofItem
, representing the content of the dropdown. Conform toHashable
.selection
: A binding to the currently selected item of typeItem
.rowHeight
: Height of each dropdown row.displayItem
: A closure that provides a view for each item. It receives four parameters:item
: The current item to display,isSelected
: A Boolean that indicates if the item is currently selected,isPlaceHolderShow
: A Boolean that indicates if the placeholder is currently shown, andisExpand
: A Boolean that indicates if the dropdown is expanded.placeHolder
: An optional closure that returns a view used as the dropdown's placeholder. It receives a Boolean parameter indicating if the dropdown is expanded.
This component offers flexibility in appearance and behavior, supporting dynamic content adjustments based on user selections. It provides customization options for row height, and allows for an optional placeholder view.
Example 1:
private var options: [String] = ["Option 1", "Option 2", "Option 3", "Option 4"]
@State private var selectedOption: String = "Option 1"
var body: some View {
VStack {
DropDown(options: options, selection: $selectedOption) { item, isSelected, isPlaceHolderShow, isExpand in
Text(item)
.foregroundStyle(isSelected ? .black : .white)
.font(.title3)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal)
.background {
Rectangle()
.fill(isSelected ? .green : .gray)
}
}
Spacer()
}
}
CommonSwiftUI_DropDown3.mp4
Example 2:
private var options: [String] = ["Option 1", "Option 2", "Option 3", "Option 4"]
@State private var selectedOption: String = "Option 1"
var body: some View {
VStack {
DropDown(options: options, selection: $selectedOption4, rowHeight: 68) { item, isSelected, isPlaceHolderShow, isExpand in
Text(item)
.foregroundStyle(.black)
.font(.title3)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal)
.background {
RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? .green : .gray, lineWidth: 2)
.padding(2)
}
.padding(.top, 8)
}
Spacer()
}
}
CommonSwiftUI_DropDown4.mp4
Example 3:
enum DropDownOptions: String, CaseIterable {
case north = "North"
case south = "South"
case east = "East"
case west = "West"
}
@State private var selectedOption: DropDownOptions = .east
var body: some View {
VStack {
DropDown(options: DropDownOptions.allCases, selection: $selectedOption, rowHeight: 60) { item, isSelected, isPlaceHolderShow, isExpand in
Text(item.rawValue)
.foregroundStyle(isSelected && !isPlaceHolderShow ? .blue : .gray)
.font(.title3)
} placeHolder: {_ in
Text("Select an option")
.foregroundStyle(.gray)
.font(.title3)
}
Spacer()
}
}
CommonSwiftUI_DropDown1.mp4
Example 4:
enum DropDownOptions: String, CaseIterable {
case north = "North"
case south = "South"
case east = "East"
case west = "West"
}
@State private var selectedOption: DropDownOptions = .east
var body: some View {
VStack {
DropDown(options: DropDownOptions.allCases, selection: $selectedOption) { item, isSelected, isPlaceHolderShow, isExpand in
Text(item.rawValue)
.foregroundStyle(.black)
.font(.title3)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal)
.background {
Rectangle()
.fill(isSelected && !isPlaceHolderShow ? .clear : .gray.opacity(0.5))
}
} placeHolder: { isExpand in
HStack {
Text("Select the Direction")
.foregroundStyle(.primary)
.font(.title3)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
Spacer()
Image(systemName: "chevron.down")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.rotationEffect(isExpand ? .degrees(90) : .zero)
}
}
Spacer()
}
}
CommonSwiftUI_DropDown2.mp4
Use multiple DropDowns for complicated form:
CommonSwiftUI_DropDown5.mp4
This example demonstrates a DropDown
menu utilizing an enumeration for options, showcasing custom text styling and a placeholder. Be creative with these options for your style.
A customizable floating action button component that arcs around a main button, revealing multiple action buttons.
ArcFloatingButton(alignment: .halfmoonTop) {
} label: { isExpanded in
}
Parameters:
buttonSize
: The diameter of each action button.alignment
: The alignment dictates the starting point and direction in which the action buttons will arc (e.g., topLeading, fullmoon).spacing
: The spacing between the expanded action buttons.shape
: The shape of each action button, conforming to theShape
protocol.actions
: An array of FloatingAction objects defining the actions for the expanded buttons.label
: A view builder that generates the content displayed on the expandable floating button.
ArcFloatingButton
allows for a radial or semi-circular placement of action buttons that emerge from behind the main button. It supports various alignments and can adapt to custom shapes for each action button.
Represents a customizable floating action button with identifiable properties. Used for ArcFloatingButton
and ExpandFloatingButton
.
FloatingAction(image: Image(systemName: "house.fill"), tint: .blue) {
print("Heart")
}
Parameters:
id
: A unique identifier for the button, useful for distinguishing multiple instances.image
: The SwiftUIImage
to display on the button.font
: The font style for any textual content inside the button.tint
: The color of the button's content, typically the icon or text.background
: The background color of the button.action
: The closure that executes when the button is tapped.
FloatingAction
configures a button that can be prominently displayed over content, commonly used for actions such as creating new items or triggering specific functions. This struct allows customization of the button's icon, font, colors, and action.
Define actions data using FloatingAction:
private let actions = [
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .red, background: .white) {
print("Tray")
},
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .red, background: .white) {
print("Spark")
},
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .red, background: .white) {
print("Share")
}
]
Top Leading:
ScrollView(.vertical) { ... }
.overlay(alignment: .topLeading) {
ArcFloatingButton(alignment: .topLeading, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
.padding()
}
CommonSwiftUI_ArcFloatButton1.mp4
Top Trailing:
ScrollView(.vertical) { ... }
.overlay(alignment: .topTrailing) {
ArcFloatingButton(alignment: .topTrailing, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
.padding()
}
CommonSwiftUI_ArcFloatButton2.mp4
Bottom Leading:
ScrollView(.vertical) { ... }
.overlay(alignment: .bottomLeading) {
ArcFloatingButton(alignment: .bottomLeading, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
.padding()
}
CommonSwiftUI_ArcFloatButton3.mp4
Bottom Trailing:
ScrollView(.vertical) { ... }
.overlay(alignment: .bottomTrailing) {
ArcFloatingButton(alignment: .bottomTrailing, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
.padding()
}
CommonSwiftUI_ArcFloatButton4.mp4
Alternatively, FloatingAction actions can be listed orderly inside the action closure
.
Halfmoon Top:
.overlay(alignment: .center) {
VStack {
ArcFloatingButton(alignment: .halfmoonTop) {
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .teal) {
print("Tray")
}
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .teal) {
print("Spark")
}
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .teal) {
print("Share")
}
FloatingAction(image: Image(systemName: "heart.fill"), tint: .teal) {
print("Heart")
}
FloatingAction(image: Image(systemName: "house.fill"), tint: .teal) {
print("Heart")
}
} label: { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
}
}
CommonSwiftUI_ArcFloatButton5.mp4
Halfmoon Bottom:
.overlay(alignment: .center) {
VStack {
ArcFloatingButton(alignment: .halfmoonBottom) {
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .orange) {
print("Tray")
}
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .orange) {
print("Spark")
}
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .orange) {
print("Share")
}
FloatingAction(image: Image(systemName: "heart.fill"), tint: .orange) {
print("Heart")
}
FloatingAction(image: Image(systemName: "house.fill"), tint: .orange) {
print("Heart")
}
} label: { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
}
}
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-08-17.at.01.24.26.mp4
Halfmoon Leading:
.overlay(alignment: .center) {
ArcFloatingButton(alignment: .halfmoonLeading) {
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .yellow) {
print("Tray")
}
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .yellow) {
print("Spark")
}
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .yellow) {
print("Share")
}
FloatingAction(image: Image(systemName: "heart.fill"), tint: .yellow) {
print("Heart")
}
FloatingAction(image: Image(systemName: "house.fill"), tint: .yellow) {
print("Heart")
}
} label: { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
}
CommonSwiftUI_ArcFloatButton7.mp4
Halfmoon Trailing:
.overlay(alignment: .center) {
ArcFloatingButton(alignment: .halfmoonTrailing) {
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .green) {
print("Tray")
}
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .green) {
print("Spark")
}
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .green) {
print("Share")
}
FloatingAction(image: Image(systemName: "heart.fill"), tint: .green) {
print("Heart")
}
FloatingAction(image: Image(systemName: "house.fill"), tint: .green) {
print("Heart")
}
} label: { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
}
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-08-17.at.01.29.43.mp4
FullMoon:
.overlay(alignment: .center) {
ArcFloatingButton(alignment: .fullmoon) {
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .red) {
print("Tray")
}
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .orange) {
print("Spark")
}
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .yellow) {
print("Share")
}
FloatingAction(image: Image(systemName: "heart.fill"), tint: .green) {
print("Heart")
}
FloatingAction(image: Image(systemName: "house.fill"), tint: .blue) {
print("Heart")
}
FloatingAction(image: Image(systemName: "paperplane"), tint: .cyan) {
print("Heart")
}
} label: { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.scaleEffect(1.02)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
// Scaling Effect when expanded
.scaleEffect(isExpanded ? 0.9 : 1)
}
}
CommonSwiftUI_ArcFloatButton9.mp4
This component enhances the user interface by seamlessly integrating multiple actions into a single floating action button, providing both aesthetic appeal and functional space-saving benefits.
This component is ideal for interfaces that require quick access to multiple actions without cluttering the UI.
A SwiftUI view component that displays a floating action button with expandable action buttons.
ExpandFloatButton(alignment: .leading) {
} label: { isExpanded in
}
Parameters:
buttonSize
: The size of the floating button and each action button.alignment
: The direction in which the action buttons will expand from the main button. (e.g., leading, trailing, top, bottom).spacing
: The space between the expanded action buttons.shape
: The shape of each action button, conforming to theShape
protocol.actions
: An array of FloatingAction objects defining the actions for the expanded buttons.label
: A view builder that generates the content displayed on the expandable floating button.
ExpandFloatButton
offers a dynamic way to present multiple action buttons from a main floating button. It supports expansion in specified directions and can adapt the shape of the action buttons.
Define actions data using FloatingAction:
private let actions: [FloatingAction] = [
FloatingAction(image: Image(systemName: "tray.full.fill"), tint: .red) {
print("Tray")
},
FloatingAction(image: Image(systemName: "lasso.badge.sparkles"), tint: .orange) {
print("Spark")
},
FloatingAction(image: Image(systemName: "square.and.arrow.up.fill"), tint: .yellow) {
print("Share")
},
FloatingAction(image: Image(systemName: "heart.fill"), tint: .green) {
print("Heart")
},
FloatingAction(image: Image(systemName: "paperplane"), tint: .cyan) {
print("Plane")
}
]
Leading:
ScrollView(.vertical) { ... }
.overlay(alignment: .bottomLeading) {
ExpandFloatButton(alignment: .leading, actions: actions, shape: .rect(cornerRadius: 8)) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .rect(cornerRadius: 8))
}
.padding()
}
CommonSwiftUI_ExpandFloatButton1.mp4
Trailing:
ScrollView(.vertical) { ... }
.overlay(alignment: .bottomTrailing) {
ExpandFloatButton(alignment: .trailing, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
}
.padding()
}
CommonSwiftUI_ExpandFloatButton2.mp4
Top:
ScrollView(.vertical) { ... }
.overlay(alignment: .topTrailing) {
ExpandFloatButton(alignment: .top, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
}
.padding()
}
CommonSwiftUI_ExpandFloatButton3.mp4
Bottom:
ScrollView(.vertical) { ... }
.overlay(alignment: .bottomTrailing) {
ExpandFloatButton(alignment: .bottom, actions: actions) { isExpanded in
Image(systemName: "plus")
.font(.title3.bold())
.foregroundStyle(.white)
.rotationEffect(.init(degrees: isExpanded ? 45 : 0))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black, in: .circle)
}
.padding()
}
CommonSwiftUI_ExpandFloatButton4.mp4
The ExpandFloatButton
efficiently enhances the user interface by integrating multiple action options into a single floating button. This design not only saves valuable screen space but also adds a sophisticated aesthetic element to the user interface.
It is particularly beneficial in applications where quick access to multiple functions is necessary without cluttering the screen, offering an intuitive and streamlined user experience.
A view creating a glassmorphism effect with customizable properties.
GlassMorphismView(cornerRadius: 25, blurRadius: 8, saturationAmount: 1.8)
Parameters:
cornerRadius
: The radius of the corners for the card. Default is 0.blurRadius
: The intensity of the blur effect. A value of 0 uses the system default.saturationAmount
: The saturation effect's intensity. A value of 0 uses the system default.border
: The thickness of the card's border. Default is 0 (no border).
This view uses blur and saturation effects to achieve a frosted glass look, further enhanced with a customizable border. The effect's intensity and appearance can be tailored through parameters for corner radius, blur, saturation, and border thickness.
Example:
ZStack {
Circle()
.frame(width: 100, height: 100, alignment: .center)
.foregroundColor(.red)
GlassMorphismView(cornerRadius: 25, blurRadius: 8, saturationAmount: 1.8)
Text("CommonSwiftUI made by James Thang")
.font(.title3)
.fontWeight(.medium)
.foregroundStyle(.white)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
.padding()
}
.frame(height: 250)
.padding()
![](https://private-user-images.githubusercontent.com/45484154/358862586-abbb1edd-f559-451f-9a47-0847d506e942.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTg4NjI1ODYtYWJiYjFlZGQtZjU1OS00NTFmLTlhNDctMDg0N2Q1MDZlOTQyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTIyM2ZmNzY0NDIzNGEyNzM2Njk3ODNiMWEyZDc3ZWE3NTYyOGNmMzcyZTU4ZTM2OWY2NzU4MWUwNDJiZDNjMjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.l0EnwkpY01xZStAREHIi32PlGAsXxd5IllPZQJBx0PQ)
This example illustrates the GlassMorphismView
with a circular red background, a specified border, and overlaid text to demonstrate the glassmorphism effect.
A SwiftUI view that implements a hold-down button with progress feedback.
HoldDownButton(text: "Hold Down Button", color: .white, background: .black, loadingTint: .yellow, clipShape: .capsule) {
print("Finish")
}
Parameters:
text
: The label displayed on the button.paddingHorizontal
: Horizontal padding around the text.paddingVertical
: Vertical padding around the text.duration
: The time in seconds the button needs to be held to activate.scale
: The scale effect applied to the button when pressed.color
: The text color.background
: The button's background color.loadingTint
: The color of the progress indicator.shape
: The shape of the button, defined using a genericShape
.action
: The closure to execute when the hold duration is completed.
HoldDownButton
allows users to interact with a button that requires being held down for a specific duration to activate. It visually indicates the progress of the hold duration using a loading bar and supports customizable text, colors, and shape.
Example:
VStack(spacing: 24) {
HoldDownButton(text: "Hold Down Button", color: .white, background: .black, loadingTint: .yellow, clipShape: .capsule) {
print("Finish")
}
HoldDownButton(text: "Hold Down Button", loadingTint: .white, clipShape: .rect(cornerRadius: 8)) {
print("Finish")
}
HoldDownButton(text: "Hold Down Button") {
print("Finish")
}
HoldDownButton(
text: "Press and Hold",
paddingHorizontal: 24,
paddingVertical: 12,
duration: 2,
scale: 0.95,
color: .white,
background: .blue,
loadingTint: .green.opacity(0.5),
clipShape: RoundedRectangle(cornerRadius: 10),
action: {
print("Action triggered!")
}
)
}
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-08-17.at.00.54.33.mp4
This component is useful for actions that require confirmation or extended interaction, preventing accidental triggers.
A simple, customizable loading indicator view. This view displays a circular loading indicator that rotates according to the specified loading speed. The appearance of the indicator, including its color, background color, line width, and speed, can be customized.
SimpleLoadingIndicator(color: .green, backgroundColor: .clear, lineWidth: 12, loadingSpeed: .custom(2))
.frame(width: 200, height: 200)
Parameters:
color
: The color of the loading indicator. Default is.blue
.backgroundColor
: The background color of the loading indicator. Default is.gray
.lineWidth
: The thickness of the loading indicator's line. Default is 5.loadingSpeed
: The speed at which the loading indicator rotates. This uses the shared type Speed. Default is.medium
.
An enumeration representing the speed of an operation.
This enum provides both predefined and customizable time intervals to represent different speeds at which an operation can occur. Each case of the enum corresponds to a specific speed, with four predefined speeds and one customizable option, allowing for flexible loading behavior tailored to specific needs.
Cases:
flash
: A very fast operation speed, with a time interval of 0.1 second.fast
: A fast operation speed, with a time interval of 1 second.medium
: A medium operation speed, with a time interval of 2 seconds.slow
: A slow operation speed, with a time interval of 3 seconds.custom(TimeInterval)
: A customizable operation speed, where the time interval can be specified dynamically.
CommonSwiftUI_SimpleLoadingIndicator.mp4
The loading indicator will rotate continuously to signify an ongoing loading process.
Visibility and Size:
- Frame Size: Adjust the indicator's frame size with
.frame(width:height:)
modifier to fit various UI spaces. - Dynamic Visibility: Manage the visibility using
.opacity()
modifier orif-else
conditions based on your application's state. This helps integrate the indicator seamlessly into your UI or hide it when not needed.
A SwiftUI view that displays a series of animated concentric circles, creating a dynamic loading indicator.
FancyLoading()
Parameters:
color
: The color of the circle strokes. The default is.primary
.
CommonSwiftUI_FancyLoading.mp4
Each circle in the animation is individually timed to create a smooth, rhythmic effect that visually indicates an ongoing process.
Visibility:
- Manage the visibility using
.opacity()
modifier orif-else
conditions based on your application's state. This helps integrate the indicator seamlessly into your UI or hide it when not needed.
A view displaying a shimmering loading placeholder.
ShimmerView()
This view simulates a 'shimmer' effect commonly used as a placeholder during content loading. It consists of multiple shimmering elements: a pair of small circular views at the top and bottom, and larger rectangular views in between, all showcasing the shimmer effect.
CommonSwiftUI_ShimmerView.mp4
No additional configuration is needed. The shimmer effect starts automatically, simulating content loading in your UI.
Visibility:
- Manage the visibility using
.opacity()
modifier orif-else
conditions based on your application's state. This helps integrate the indicator seamlessly into your UI or hide it when not needed.
For more customization, look at Shimmer view modifier to apply a shimmer effect to any SwiftUI view.
A rectangular progress bar view for SwiftUI.
ProgressBar(progress: $progress, color: .blue)
Parameters:
progress
: A binding to aCGFloat
that represents the current progress (from 0.0 to 1.0).color
: The color of the bar's tint.colors
: An array ofColor
to create a gradient for the progress bar (used when more than one color is desired).backgroundColor
: The color of the bar's background.
You can use either a single color or a gradient of colors for the progress bar. The background of the bar can also be customized.
Example:
@State private var progress: CGFloat = 0.75
var body: some View {
VStack(spacing: 30) {
ProgressBar(progress: $progress, color: .green, backgroundColor: .clear)
.frame(width: 300, height: 20)
ProgressBar(progress: $progress, color: .orange, backgroundColor: .orange.opacity(0.2))
.frame(width: 300, height: 20)
ProgressBar(progress: $progress, colors: [.red, .blue])
.frame(width: 300, height: 16)
ProgressBar(progress: $progress, color: .blue)
.frame(width: 300, height: 8)
ProgressBar(progress: $progress, colors: [.indigo, .teal])
.frame(width: 300, height: 8)
}
}
![](https://private-user-images.githubusercontent.com/45484154/357300195-60cfef78-3aaa-4567-ae3e-b01a099d6aeb.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTczMDAxOTUtNjBjZmVmNzgtM2FhYS00NTY3LWFlM2UtYjAxYTA5OWQ2YWViLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTVhZjNjNDdlZDhkNjE0ZGZlNjU2NGQxNzcyYjI4MjNmMjQyNTI0YTI5MDNkYWMzNTg1OTM5YTEyYTVmYWU1NDEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.nBjnIRcsNtzOs3XjmQxaOgok_2VjTSqOlyc2RNPVa9c)
This view displays a rectangular progress indicator that fills up based on the current progress
. The rectangle can be customized with different colors
and a backgroundColor
. If a gradient is desired, provide multiple colors.
Size:
- Frame Size: Adjust the view's frame size with
.frame(width:height:)
modifier to fit various UI spaces.
A circular progress bar view for SwiftUI.
RingProgress(progress: $progress, lineWidth: 16, color: .green)
Parameters:
progress
: A binding to aCGFloat
that represents the current progress (from 0.0 to 1.0).lineWidth
: The thickness of the progress bar's line.startAngle
: The angle at which the progress starts, with.zero
being the default.color
: The color of the bar's tint.colors
: An array ofColor
to create a gradient for the progress bar (used when more than one color is desired).backgroundColor
: The color of the bar's background.
You can use either a single color or a gradient of colors for the progress bar.
Example:
@State private var progress: CGFloat = 0.75
var body: some View {
VStack(spacing: 50) {
HStack(spacing: 50) {
RingProgress(progress: $progress, lineWidth: 16, startAngle: .degrees(90), color: .blue)
.frame(width: 150, height: 150)
RingProgress(progress: $progress, lineWidth: 12, startAngle: .degrees(90), color: .red)
.frame(width: 80, height: 80)
}
HStack(spacing: 50) {
RingProgress(progress: $progress, lineWidth: 16, startAngle: .degrees(90), colors: [.blue, .green])
.frame(width: 150, height: 150)
RingProgress(progress: $progress, lineWidth: 8, startAngle: .degrees(90), colors: [.red, .green])
.frame(width: 50, height: 50)
}
}
}
![](https://private-user-images.githubusercontent.com/45484154/357311114-34af9314-f8e4-4695-ab76-a433d116b9f7.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTczMTExMTQtMzRhZjkzMTQtZjhlNC00Njk1LWFiNzYtYTQzM2QxMTZiOWY3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTNkMDhmY2Y1ZTY3NGUxNTE2M2ViMTIwOGUzZjMxNGVjOWVlNGM4YWI4YzM4MzYzNmMzYjM5OGRiZGExZmFmZjMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.RDnadrMhdANB7tyrwrf5CIYyxBeVypCJIqxxkjZWoLI)
This view displays a circular progress indicator that fills up based on the current progress
. The progress circle can be customized with different lineWidth
, startAngle
, colors
, and a backgroundColor
.
Size:
- Frame Size: Adjust the view's frame size with
.frame(width:height:)
modifier to fit various UI spaces.
A customizable circular progress bar for SwiftUI, unique for its adjustable trim and rotation.
ArcProgress(progress: $progress, lineWidth: 16, color: .orange)
Parameters:
progress
: A binding to aCGFloat
that represents the current progress (from 0.0 to 1.0).lineWidth
: The thickness of the progress bar's line.color
: The color of the bar's tint.colors
: An array ofColor
to create a gradient for the progress bar (used when more than one color is desired).backgroundColor
: The color of the bar's background.
You can use either a single color or a gradient of colors for the progress bar.
Example:
@State private var progress: CGFloat = 0.75
var body: some View {
VStack(spacing: 50) {
HStack(spacing: 50) {
ArcProgress(progress: $progress, lineWidth: 16, color: .red)
.frame(width: 150, height: 150)
ArcProgress(progress: $progress, lineWidth: 10, color: .pink)
.frame(width: 50, height: 50)
}
HStack(spacing: 50) {
ArcProgress(progress: $progress, lineWidth: 16, colors: [.orange, .yellow, .purple])
.frame(width: 150, height: 150)
ArcProgress(progress: $progress, lineWidth: 10, colors: [.yellow, .blue])
.frame(width: 100, height: 100)
}
}
}
![](https://private-user-images.githubusercontent.com/45484154/357313367-aa098c14-1738-4c18-b706-50d8754b920c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTczMTMzNjctYWEwOThjMTQtMTczOC00YzE4LWI3MDYtNTBkODc1NGI5MjBjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMxZGIxMDk5MzQzZWYxOTFlZTA5Nzg4MTdmM2ZlMTlmNzhlZjg0YWRiM2U4YTEyYzRlMzY0NzQ4ZTkwYWE3YjQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.bETEnP7AYdf1R7dpdPVc7RIRmWturTdVRuEC04T0dYk)
This view displays a circular progress indicator that fills up based on the current progress
, but unlike traditional full-circle progress bars, this one fills up to 75% of the circle. The progress circle can be customized with different lineWidth
, colors
, and a backgroundColor
.
The progress bar uniquely fills up to 75% of the circle and starts at a 135-degree angle.
Size:
- Frame Size: Adjust the view's frame size with
.frame(width:height:)
modifier to fit various UI spaces.
A robust QR code scanner view for SwiftUI, providing interactive scanning capabilities.
QRScannerView(isScanning: $isScanning) { result in
switch result {
case .success(let code):
print("Scanned code: \(code)")
case .failure(let error):
print("Scanning failed: (error.localizedDescription)")
}
}
Parameters:
isScanning
: A binding to control the scanning process.showScanningAnimation
: A Boolean value that determines whether to show a scanning animation.showErrorAlert
: A Boolean value that determines whether to show an alert on scanning errors.completion
: A closure executed with the scanning result, returning aString
on success or anError
on failure.
QRScannerView
integrates camera functionality to scan QR codes and handle the results dynamically through a completion handler. It supports customization of scanning animation and error handling.
@State var isScanning: Bool = false
@State var successResult: String = ""
var body: some View {
VStack(spacing: 20) {
Text(successResult)
.font(.title)
.fontWeight(.semibold)
QRScannerView(isScanning: $isScanning, showScanningAnimation: true, showErrorAlert: false) { result in
switch result {
case .success(let result):
successResult = result
case .failure(let error):
print("This is Error Cases")
print(error)
print(error.localizedDescription)
}
}
Button("Start Scanning") {
successResult = ""
isScanning = true
}
Button("Stop Scanning") {
successResult = ""
isScanning = false
}
}
}
CommonSwiftUI_QRScan.mp4
This component is designed to provide a seamless integration of QR scanning functionality within your SwiftUI applications, enhancing user interaction and data capture capabilities.
A customizable segment control view in SwiftUI.
SegmentControl(tabs: SegmentedTab.allCases, activeTab: $activeTab, height: 40, activeTint: .primary, inActiveTint: .gray.opacity(0.5), indicatorConfiguration: .init(tint: .blue, cornerRadius: 0, style: .bottom)) { item in
HStack {
Image(systemName: item.imageName)
Text(item.rawValue)
}
.font(.title3)
}
Parameters:
tabs
: An array ofItem
, representing each segment option. Required to conform toHashable
.activeTab
: A binding to the currently active segment.height
: The height of the segment control.activeTint
: Color for the active segment.inActiveTint
: Color for inactive segments.indicatorConfiguration
: Configuration for the segment indicator, including color and corner radius.displayItem
: A closure that provides a view for displaying each segment option.
SegmentControl
provides a customizable segmented control interface, allowing for the selection among multiple options. It features customizable active/inactive tint colors, an adjustable height, and a dynamic or static indicator for the active tab. Additionally, it offers a configuration for the indicator's appearance and position based on the selected segment.
Example:
enum SegmentedTab: String, CaseIterable {
case home = "Home"
case favorite = "Love"
case profile = "Profile"
var imageName: String {
switch self {
case .home:
return "house.fill"
case .favorite:
return "heart.fill"
case .profile:
return "person.crop.circle"
}
}
}
@State private var activeTab: SegmentedTab = .home
@State private var activeTab2: SegmentedTab = .home
@State private var activeTab3: SegmentedTab = .home
var body: some View {
VStack(spacing: 25) {
SegmentControl(tabs: SegmentedTab.allCases, activeTab: $activeTab, height: 40, activeTint: .primary, inActiveTint: .gray.opacity(0.5), indicatorConfiguration: .init(tint: .blue, cornerRadius: 0, style: .bottom)) { item in
HStack {
Image(systemName: item.imageName)
Text(item.rawValue)
}
.font(.title3)
}
SegmentControl(tabs: SegmentedTab.allCases, activeTab: $activeTab2, height: 40, activeTint: .primary, inActiveTint: .gray.opacity(0.5), indicatorConfiguration: .init(tint: .orange, cornerRadius: 4, style: .background)) { item in
HStack {
Image(systemName: item.imageName)
Text(item.rawValue)
}
.font(.title3)
}
SegmentControl(tabs: SegmentedTab.allCases, activeTab: $activeTab3, height: 40, activeTint: .primary, inActiveTint: .gray.opacity(0.5), indicatorConfiguration: .init(tint: .yellow, cornerRadius: 20, style: .background)) { item in
HStack {
Image(systemName: item.imageName)
Text(item.rawValue)
}
.font(.title3)
}
}
.padding()
}
CommonSwiftUI_SegmentControl.mp4
This example demonstrates a SegmentControl
with custom tab items, including icons and text, showcasing how to integrate it into a SwiftUI view.
A customizable range slider view in SwiftUI.
RangeSlider(
selection: $selection,
range: 10...100,
minimumDistance: 10,
lineWidth: 15,
tint: .red,
controlConfig: .init(width: 20, enableShadow: true)
)
.frame(height: 100)
Parameters:
selection
: A binding to the selected range of values.range
: The total range from which values can be selected.minimumDistance
: The minimum allowable distance between the two thumbs.lineWidth
: The thickness of the slider's active range.tint
: The color of the slider's active range and thumbs.backgroundColor
: The color of the slider's track.controlConfig
: ControlConfig is the configuration for the control's appearance including thumb tint, width, and shadow ofRangeSlider
.
Allows users to select a closed range of values using two draggable thumbs. This component is highly customizable with options for defining the range limits, thumb spacing, and appearance.
ControlConfig
provides customizable settings for UI controls of RangeSlider
.
Parameters:
tint
: The color used for the control. Defaults to.white
.width
: The width of the control in points. This could affect the size of the control or its border depending on how it's used.enableShadow
: Determines whether a shadow is applied to the control. Defaults totrue
.
This structure configures the appearance and behavior of the slider controls in RangeSlider
, including the color, width, and shadow of the slider handles.
Example:
@State private var selection: ClosedRange<CGFloat> = 60...90
@State private var selection2: ClosedRange<CGFloat> = 10...50
var body: some View {
VStack(spacing: 50) {
RangeSlider(
selection: $selection,
range: 10...100,
minimumDistance: 10,
lineWidth: 15,
tint: .red,
controlConfig: .init(width: 20, enableShadow: true)
)
.frame(height: 100)
Text("\(Int(selection.lowerBound)):\(Int(selection.upperBound))")
.font(.largeTitle.bold())
.padding(.top, 10)
RangeSlider(
selection: $selection2,
range: 0...100,
minimumDistance: 1,
lineWidth: 8,
tint: .green,
controlConfig: .init(width: 12, enableShadow: true)
)
.frame(height: 100)
Text("\(Int(selection2.lowerBound)):\(Int(selection2.upperBound))")
.font(.largeTitle.bold())
.padding(.top, 10)
}
}
CommonSwiftUI_RangeSlider.mp4
This setup demonstrates configuring a RangeSlider
, displaying the selected value range with customized control appearance.
A customizable ring-shaped slider view for selecting angular ranges.
RingSlider(
startAngle: $startAngle,
toAngle: $endAngle,
lineWidth: 40,
tint: .purple,
controlConfig: .init(
width: 40,
startSliderImage: Image(systemName: "moon.fill"),
endSliderImage: Image(systemName: "alarm")
)
)
.frame(width: 300, height: 300)
Parameters:
startAngle
: The starting angle of the slider, modifiable via a binding.toAngle
: The ending angle of the slider, modifiable via a binding.lineWidth
: The thickness of the ring's line.tint
: The primary color of the slider's line and handle if not using images.backgroundColor
: The color behind the slider's line for contrast.controlConfig
: ControlConfig is the configuration for the slider's handles, including color, width, images, and shadow ofRingSlider
.
RingSlider
provides a visual and interactive way to select a range of angles using draggable handles that can be customized with images or styled directly via a ControlConfig
. The appearance of the slider, including line width, colors, and handle customization, is adjustable.
ControlConfig
provides customizable settings for UI controls of RingSlider
.
Parameters:
tint
: The color used for the slider control's tint, defaulting to.white
.width
: The thickness or size of the slider handles in points.startSliderImage
: An optional image for the slider's starting handle.endSliderImage
: An optional image for the slider's ending handle.enableShadow
: A Boolean value that determines whether a shadow is applied to the slider handles, defaulting totrue
.
This structure configures the appearance and behavior of the slider controls in RingSlider
, including optional images for slider handles and shadow effects.
Example:
@State var startAngle: Angle = .degrees(50)
@State var endAngle: Angle = .degrees(90)
var body: some View {
VStack(spacing: 50) {
RingSlider(
startAngle: $startAngle,
toAngle: $endAngle,
lineWidth: 40,
tint: .purple,
controlConfig: .init(width: 40, startSliderImage: Image(systemName: "moon.fill"),
endSliderImage: Image(systemName: "alarm"))
)
.frame(width: 300, height: 300)
Text("Start Angle: \(Int(startAngle.degrees)) - End Angle: \(Int(endAngle.degrees))")
}
}
CommonSwiftUI_RingSlider.mp4
This configuration leverages the ControlConfig
to apply custom images for the handles and additional styling options, enhancing the user interaction experience.
A SwiftUI view that arranges text in a circular path with enhanced customization.
CircularText(text: "#OPENTOWORK", radius: 150, spacing: 8, alignment: .outside, reverseStyle: false)
.font(.largeTitle.bold())
.lineSpacing(5)
Parameters:
text
: The string of text to be displayed circularly.radius
: The radius of the circle along which the text is arranged.spacing
: The spacing between characters, defaulting to 4.alignment
: The position of the text relative to the circle's radius (inside
,center
,outside
).reverseStyle
: Iftrue
, reverses the direction and orientation of the text.textModifier
: A closure that allows for custom styling of the text, applied per character.
CircularText
displays text along a specified radius, offering settings for alignment, character spacing, and style reversal. It utilizes a generic view modifier to apply custom styling to each character, making it versatile for various design needs.
Example:
VStack(spacing: 40) {
ZStack {
Circle()
.fill(.gray.opacity(0.3))
CircularText(text: "#OPENTOWORK", radius: 150, spacing: 8, alignment: .outside, reverseStyle: false)
.font(.largeTitle.bold())
.lineSpacing(5)
}
.frame(width: 300, height: 300)
ZStack {
Circle()
.fill(.gray.opacity(0.3))
CircularText(text: "#OPENTOWORK", radius: 150, spacing: 8, alignment: .inside, reverseStyle: true, textModifier: { text in
text.font(.largeTitle.bold())
.lineSpacing(5)
})
.rotationEffect(.degrees(-140))
}
.frame(width: 300, height: 300)
}
![](https://private-user-images.githubusercontent.com/45484154/357336408-556ebe60-acb5-440a-a69a-7d6b5ebe987e.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNTczMzY0MDgtNTU2ZWJlNjAtYWNiNS00NDBhLWE2OWEtN2Q2YjVlYmU5ODdlLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFjNDNiYWRlMWFlY2IzY2IyZTM3ZThmNzI0ZTI0OGM3MTdlYjg3YTliOTU4ZDg0MmEwNDQyNDk2OGM4ZTUyYmEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.V6H5P8vngbdIeKKjeaDs5XWeWlCsoUqPjm5xWLbpUJU)
This view is perfect for creating visually compelling text effects such as circular labels or decorative text in a SwiftUI application.
Provides an animated text effect that mimics hacking by changing characters randomly before revealing the final text.
HackerText(
text: text,
trigger: trigger,
transition: .hyper,
speed: .custom(0.06)
)
.font(.largeTitle.bold())
.lineLimit(2)
Parameters:
text
: The final text to display after animation.trigger
: A Boolean that starts the animation when toggled.transition
: The style of animation — eitherhyper
for all hacker style ornumeric
for wheeling style only.duration
: Total animation duration.speed
: Time interval for character changes. This uses the shared type Speed.
Example:
@State private var trigger: Bool = false
@State private var text = "Common SwiftUI"
var body: some View {
VStack(alignment: .leading, spacing: 30) {
HackerText(
text: text,
trigger: trigger,
transition: .hyper,
speed: .custom(0.06)
)
.font(.largeTitle.bold())
.lineLimit(2)
HackerText(
text: text,
trigger: trigger,
transition: .numeric,
speed: .custom(0.06)
)
.font(.largeTitle.bold())
.lineLimit(2)
Button(action: {
if text == "Common SwiftUI" {
text = "Made with SwiftUI\nBy James Thang"
} else {
text = "Common SwiftUI"
}
trigger.toggle()
}, label: {
Text("Trigger")
.fontWeight(.semibold)
})
.buttonStyle(.borderedProminent)
.frame(maxWidth: .infinity)
}
.padding(15)
.frame(maxWidth: .infinity, alignment: .leading)
}
CommonSwiftUI_HackerText.mp4
This view is particularly effective for creating engaging and eye-catching textual displays in apps that require a dramatic presentation.
A SwiftUI view that simulates a typewriter effect for displaying text.
TypeWriterText(text: "James Thang", font: .title, fontWeight: .regular)
Parameters:
text
: The text to display using the typewriter effect.font
: The font style of the text. Default is.caption
.fontWeight
: The weight of the font. Default is.medium
.color
: The color of the text. Default is.primary
.alignment
: The alignment of the text within its container. Default is.center
.speed
: The speed at which characters are displayed. This uses the shared type Speed. Default is.flash
.
This view gradually displays characters of a string, mimicking the typing effect seen in a typewriter. Customization options include font, weight, color, alignment, and the speed of typing. The speed of typing can be one of the predefined speeds or a custom duration specified in seconds.
Example:
VStack(spacing: 16){
TypeWriterText(text: "James Thang", font: .title, fontWeight: .regular)
TypeWriterText(text: "iOS Developer | Author | Builder | Writer | Dreamer", font: .title2)
TypeWriterText(text: "My journey through the tech world is a testament to the idea that anyone can follow their passion and acquire new skills. While my educational background lies in Finance and Economics, I felt a compelling drive to explore the dynamic realm of Apps development. The potentials of it that anyone in this modern world now have a smartphone with them and spend most of their daily time on it. With dedication and self-education, I transitioned into a seasoned iOS developer and then a professional one, accumulating over 3 years of valuable industry experience.", speed: .flash)
}
CommonSwiftUI_TypeWriterText.mp4
This view is ideal for scenarios where text needs to be presented in a dramatic, engaging manner.
A SwiftUI view that provides a text field with a character limit and visual feedback on input progress.
LimitedTextField(
config: .init(
limit: 40,
tint: .secondary,
autoResizes: true,
allowExcessTyping: false
),
hint: "Type here",
value: $text
)
.frame(maxHeight: 150)
Parameters:
config
: Config is the configuration settings including character limit, tint, resizing behavior, and typing overflow control. Details below.hint
: Placeholder text displayed when the text field is empty.value
: A binding to the text inputted by the user.
LimitedTextField
offers a customizable text input field that restricts the number of characters based on a specified limit. It features visual indicators such as a progress ring or text counter and can be styled with custom colors and borders.
Manages the main settings for the text field.
.init(limit: 40, tint: .secondary, autoResizes: true, allowExcessTyping: false)
Parameters:
limit
: The maximum number of characters.tint
: The color of the text and progress indicators.autoResizes
: Whether the text field should automatically resize to fit content.allowExcessTyping
: Allows input beyond the limit without saving excess characters.progressConfig
: ProgressConfig is settings for the progress indicators.borderConfig
: BorderConfig is the styling options for the border.
Configures visual feedback on typing progress.
Parameters:
showsRing
: Displays a circular progress ring.showsText
: Shows current and maximum character counts.alignment
: Aligns the progress text indicator.
Customizes the border appearance.
Parameters:
show
: Enables or disables the border.radius
: Sets the border radius.width
: Defines the border thickness.
Example:
@State private var text: String = ""
var body: some View {
LimitedTextField(
config: .init(
limit: 40,
tint: .secondary,
autoResizes: true,
allowExcessTyping: false
),
hint: "Type here",
value: $text
)
.frame(height: 150)
}
CommonSwiftUI_LimitedTextField.mp4
This component is ideal for forms, comments, or any user input that requires length constraints.
A SwiftUI view that provides a text field with extensive validation capabilities, including secure text entry.
ValidationTextField(title: "First Name", text: $firstName, isValid: $isFormFirstNameValid)
.autocorrectionDisabled()
.focused($focus, equals: .firstName)
.isMandatory(true)
Parameters:
title
: The label text for the text field.text
: A binding to the user input text.isValidBinding
: A binding reflecting the current validation state.isSecured
: Indicates if the text field should obscure text input.config
: Configuration for visual properties like border and validation message styles.
Modifiers:
clearButtonHidden
: Controls visibility of the clear button.secureButtonHidden
: Controls visibility of the secure text toggle button.isMandatory
: Marks the field as required and provides a custom message if validation fails.onValidate
: Adds custom validation logic for the text field.onFormValidate
: Handles form-level validation by providing an array of validation results.
ValidationTextField
allows visual feedback and validation for user inputs, suitable for both standard and secure text fields. It supports environmental properties to customize behavior and appearance based on validation results.
Example 1:
enum FocusableField {
case firstName, lastName, address, password
}
@State private var firstName = ""
@State private var lastName = ""
@FocusState private var focus: FocusableField?
@State private var isFormFirstNameValid = false
@State private var isFormLastNameValid = false
var body: some View {
VStack(spacing: 15) {
ValidationTextField(title: "First Name", text: $firstName, isValid: $isFormFirstNameValid)
.autocorrectionDisabled()
.focused($focus, equals: .firstName)
.isMandatory(true)
ValidationTextField(title: "Last Name", text: $lastName, isValid: $isFormLastNameValid)
.focused($focus, equals: .lastName)
.isMandatory(true)
Spacer()
Button("Submit") {
// Handle submit logic
}
.buttonStyle(.borderedProminent)
.disabled(!(isFormFirstNameValid && isFormLastNameValid))
}
}
CommonSwiftUI_ValidationTextField1.mp4
Example 2:
@State private var address = ""
var body: some View {
ValidationTextField(title: "Address", text: $address)
.clearButtonHidden(false)
.autocorrectionDisabled()
.padding()
}
CommonSwiftUI_ValidationTextField2.mp4
Example 3:
enum FocusableField {
case firstName, lastName, address, password
}
enum TextFieldError: LocalizedError {
case weakPassword
var errorDescription: String? {
switch self {
case .weakPassword:
return "Password has to be at least 6 characters"
}
}
}
@State private var password = ""
@FocusState private var focus: FocusableField?
@State private var isPasswordValid = false
var body: some View {
VStack(spacing: 15) {
ValidationTextField(title: "Password", text: $password, isValid: $isPasswordValid, isSecured: true)
.focused($focus, equals: .password)
.isMandatory(true)
.onValidate { value in
value.count >= 6 ? .success("Good Password") : .failure(TextFieldError.weakPassword)
}
.secureTextButtonHidden(false)
.autocorrectionDisabled()
Spacer()
Button("Submit") {
// Handle submit logic
}
.disabled(!isPasswordValid)
}
}
CommonSwiftUI_ValidationTextField3.mp4
Example 4:
enum FocusableField {
case firstName, lastName, address, password
}
@State private var password = ""
@FocusState private var focus: FocusableField?
@State private var isPasswordValid = false
var body: some View {
VStack(spacing: 15) {
ValidationTextField(title: "Strong Password", text: $password, isValid: $isPasswordValid, isSecured: true)
.focused($focus, equals: .password)
.isMandatory(true)
.secureTextButtonHidden(false)
.autocorrectionDisabled()
.onFormValidate { text in
// Check if text contains any letters (both uppercase and lowercase)
let atLeast6Char = text.count >= 6
// Check if text contains any numbers (decimal digit)
let containNumbers = text.rangeOfCharacter(from: .decimalDigits) != nil
// Check if text contains special characters "!@#%^&"
let containPunctuation = text.rangeOfCharacter(from: CharacterSet(charactersIn: "!@#%^&")) != nil
return [
.init(message: atLeast6Char ? "Password is at least 6 characters" : "Password need to be at least 6 characters", isValid: atLeast6Char),
.init(message: containNumbers ? "Password contains number" : "Password need to contain number", isValid: containNumbers),
.init(message: containNumbers ? "Password contains special character !@#%^&" : "Password need to contain special character !@#%^&", isValid: containPunctuation)
]
}
Spacer()
Button("Submit") {
// Handle submit logic
}
.buttonStyle(.borderedProminent)
.disabled(!isPasswordValid)
}
}
CommonSwiftUI_ValidationTextField4.mp4
Now using this powerful component to make a Sign Up Screen:
enum FocusableField {
case firstName, lastName, address, password, confirmPassword
}
enum TextFieldError: LocalizedError {
case weakPassword
case invalidConfirmPassword
var errorDescription: String? {
switch self {
case .weakPassword:
return "Password has to be at least 6 characters"
case .invalidConfirmPassword:
return "Confirm Password do not match"
}
}
}
@State private var firstName = ""
@State private var lastName = ""
@State private var address = ""
@State private var password = ""
@State private var confirmPassword = ""
@FocusState private var focus: FocusableField?
@State private var isFormFirstNameValid = false
@State private var isFormLastNameValid = false
@State private var isPasswordValid = false
@State private var isConfirmPasswordValid = false
var body: some View {
VStack(spacing: 15) {
ValidationTextField(title: "First Name", text: $firstName, isValid: $isFormFirstNameValid)
.autocorrectionDisabled()
.focused($focus, equals: .firstName)
.isMandatory(true)
ValidationTextField(title: "Last Name", text: $lastName, isValid: $isFormLastNameValid)
.focused($focus, equals: .lastName)
.isMandatory(true)
ValidationTextField(title: "Address", text: $address)
.clearButtonHidden(false)
.focused($focus, equals: .address)
ValidationTextField(title: "Password", text: $password, isValid: $isPasswordValid, isSecured: true)
.focused($focus, equals: .password)
.isMandatory(true)
.secureTextButtonHidden(false)
.autocorrectionDisabled()
.onFormValidate { text in
// Check if text contains any letters (both uppercase and lowercase)
let atLeast6Char = text.count >= 6
// Check if text contains any numbers (decimal digit)
let containNumbers = text.rangeOfCharacter(from: .decimalDigits) != nil
// Check if text contains special characters "!@#%^&"
let containPunctuation = text.rangeOfCharacter(from: CharacterSet(charactersIn: "!@#%^&")) != nil
return [
.init(message: atLeast6Char ? "Password is at least 6 characters" : "Password need to be at least 6 characters", isValid: atLeast6Char),
.init(message: containNumbers ? "Password contains number" : "Password need to contain number", isValid: containNumbers),
.init(message: containNumbers ? "Password contains special character !@#%^&" : "Password need to contain special character !@#%^&", isValid: containPunctuation)
]
}
ValidationTextField(title: "Confirm Password", text: $confirmPassword, isValid: $isConfirmPasswordValid, isSecured: true)
.focused($focus, equals: .confirmPassword)
.isMandatory(true)
.onValidate { value in
value == password ? .success("Password Match") : .failure(TextFieldError.invalidConfirmPassword)
}
.onChange(of: password) { newValue in
if password != confirmPassword {
isConfirmPasswordValid = false
confirmPassword = ""
}
}
.secureTextButtonHidden(false)
.autocorrectionDisabled()
Spacer()
Button("Creat New Account") {
}
.buttonStyle(.borderedProminent)
.disabled(!(isFormFirstNameValid && isFormLastNameValid && isPasswordValid && isPasswordValid && isConfirmPasswordValid))
}
}
CommonSwiftUI_ValidationTextField5.mp4
This example effectively demonstrates how to configure and use ValidationTextField
for a form handling multiple fields, ensuring that all entries meet specified validation criteria before enabling form submission.
Toast
provides functionality to present and remove toast messages.
Access the shared singleton instance with Toast.shared
. It uses an observable object pattern to update UI components when toasts are added or removed.
Use this function present
to display a toast message with customizable options.
public func present(title: String, symbol: String?, tint: Color = .primary, isUserInteractionEnabled: Bool = false, timing: Speed = .medium)
Parameters:
title
: The text to display in the toast.symbol
: An optional symbol to display alongside the text. Defaults to nil.tint
: The color of the text and symbol. Defaults to.primary
.isUserInteractionEnabled
: A Boolean value that determines whether the toast allows user interaction. Defaults to false.timing
: The duration for which the toast should remain on screen. This uses the shared type Speed. Defaults to.medium
.
The first required step is to wrapped your Application inside RootView:
@main
struct TestCommonUIApp: App {
var body: some Scene {
WindowGroup {
RootView {
ContentView()
}
}
}
}
Now for every scene, use the present
method from the singleton Toast.shared
to present your toast message:
VStack(spacing: 25) {
Button("Toast 1") {
Toast.shared.present(
title: "Hello World",
symbol: "hand.wave",
tint: .blue,
isUserInteractionEnabled: true,
timing: .slow
)
}
Button("Toast 2") {
Toast.shared.present(
title: "I am James Thang",
symbol: "book.fill",
tint: .black,
isUserInteractionEnabled: true,
timing: .slow
)
}
Button("Toast 3") {
Toast.shared.present(
title: "This is CommonSwiftUI",
symbol: "lightbulb.circle.fill",
tint: .purple,
isUserInteractionEnabled: true,
timing: .slow
)
}
}
CommonSwiftUI_Toast.mp4
All of the toast messages will be at the top level of your application.
The Toast
component provides a streamlined and non-intrusive way to display brief notifications or messages within an application's interface.
Starting with iOS 17
, the onChange modifier with one parameter is deprecated. To maintain backward compatibility, this view modifier was created.
![CommonSwiftUI_CustomChangeViewModifier](https://private-user-images.githubusercontent.com/45484154/361014171-a1ac66a5-45e7-4270-a47a-d693e4a1df95.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjYwOTQzMjcsIm5iZiI6MTcyNjA5NDAyNywicGF0aCI6Ii80NTQ4NDE1NC8zNjEwMTQxNzEtYTFhYzY2YTUtNDVlNy00MjcwLWE0N2EtZDY5M2U0YTFkZjk1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA5MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwOTExVDIyMzM0N1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTczOWFhZGMyMGQ3NGViYjZlMDI1NzM0Y2IwOTZhNjUzYzMzNDRiOGI2MTkzZDcxYzliNDcwMDk1NWIzODQyZGImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.RevlfZExsAq_-HaRnKNL4ivjzE-oSUdYVM-591_gSw0)
.customChange(value: someObservableValue) { newValue in
print("Value changed to \(newValue)")
}
Parameters:
value
: The value to observe for changes. Must conform toEquatable
.result
: A closure that is executed when the observed value changes.
Use this function to seamlessly handle value changes across different iOS versions with custom logic in the closure.
Applies a shimmer effect to any SwiftUI view.
public func shimmer(tint: Color, highlight: Color, blur: CGFloat = 0, highlightOpacity: CGFloat = 1, speed: CommonSwiftUI.Speed = .medium) -> some View
Parameters:
tint
: The background color of the shimmer.highlight
: The color of the shimmering highlight.blur
: The amount of blur applied to the shimmer effect. Default is 0.highlightOpacity
: The opacity of the shimmer highlight. Default is 1.speed
: The speed of the shimmer effect. Default is case .medium for 2 second.
This function overlays a shimmer animation on the calling view, commonly used as a placeholder during content loading. The shimmer can be customized with different colors, opacity levels, and speed.
Example:
VStack {
HStack {
Circle()
.frame(width: 55, height: 55)
VStack(alignment: .leading, spacing: 6) {
RoundedRectangle(cornerRadius: 4)
.frame(height: 10)
RoundedRectangle(cornerRadius: 4)
.frame(height: 10)
.padding(.trailing, 50)
RoundedRectangle(cornerRadius: 4)
.frame(height: 10)
.padding(.trailing, 100)
}
}
.padding(15)
.padding(.horizontal, 30)
.shimmer(tint: .gray.opacity(0.3), highlight: .white, blur: 5)
Text("Loading...")
.shimmer(tint: .gray.opacity(0.3), highlight: .white, blur: 5)
}
CommonSwiftUI_ShimmerModifier.mp4
Customize the parameters to fit the style of your app's loading indicators.
Attaches a callback that performs an action when the view loads.
This function allows for executing code at the moment the view is first rendered. It's useful for initiating data fetching, analytics, or other one-time setup tasks when a view appears.
public func onLoad(perform action: (() -> Void)? = nil) -> some View
Parameter:
action
: An optional closure to perform when the view loads. If nil, no action is taken.
Usage:
Text("Welcome")
.onLoad {
print("View has loaded")
}
This example prints a message to the console when the Text
view loads.
Applies a custom transformation to a view and returns the resulting view.
This function allows for applying transformations conditionally based on the iOS
version or custom conditions. It's particularly useful for modifying views with version-specific features or styles.
public func apply<Content>(@ViewBuilder _ transform: (Self) -> Content) -> Content
Parameter:
transform
: A closure that takes the original view as an argument and returns the modified view.
Simplified Usage with iOS Version Check:
Text("Conditional Styling")
.apply {
if #available(iOS 16.0, *) {
$0.padding()
} else {
$0.padding().background(Color.gray)
}
}
In this usage example, padding is applied universally, but a gray background is only applied if the iOS version is below 16.0.
James Thang, find me on LinkedIn
Learn more about SwiftUI, check out my book 📚 on Amazon