Skip to content

Commit

Permalink
Add README and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
philptr committed Dec 24, 2024
1 parent ef2e87c commit 8a3423f
Show file tree
Hide file tree
Showing 16 changed files with 331 additions and 50 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/Packages
/*.xcodeproj
xcuserdata/
.swiftpm
7 changes: 0 additions & 7 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

This file was deleted.

12 changes: 8 additions & 4 deletions Example/CircularControlExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = CircularControlExample/CircularControlExample.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"CircularControlExample/Preview Content\"";
DEVELOPMENT_TEAM = CN5H83SSGJ;
DEVELOPMENT_TEAM = D7DS2U3H59;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -276,8 +277,9 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = work.philz.CircularControlExample;
PRODUCT_BUNDLE_IDENTIFIER = work.philz.CircularControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -293,10 +295,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = CircularControlExample/CircularControlExample.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"CircularControlExample/Preview Content\"";
DEVELOPMENT_TEAM = CN5H83SSGJ;
DEVELOPMENT_TEAM = D7DS2U3H59;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -315,8 +318,9 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = work.philz.CircularControlExample;
PRODUCT_BUNDLE_IDENTIFIER = work.philz.CircularControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
2 changes: 2 additions & 0 deletions Example/CircularControlExample/SimpleDemoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct SimpleDemoView: View {
),
shadow: .init(color: .teal.opacity(0.6), radius: 12)
), format: .fraction)
.circularControlProgressAnimation(.bouncy)
.fontDesign(.serif)
.padding()

Expand All @@ -44,6 +45,7 @@ struct SimpleDemoView: View {
.font(.largeTitle)
.foregroundStyle(.orange)
}
.circularControlProgressAnimation(.bouncy(duration: 2.4))
.padding()
}
}
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "PZCircularControl",
platforms: [
.iOS(.v17), .macOS(.v14), .watchOS(.v10), .tvOS(.v17)
.iOS(.v17), .macOS(.v14), .watchOS(.v10), .tvOS(.v17), .visionOS(.v1)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
Expand Down
174 changes: 166 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,170 @@
![Circular Control](https://github.com/philzet/PZCircularControl/blob/master/Resources/CircularControl.png)
![Circular Control](./Resources/CircularControl.png)

# SwiftUI Circular Control

A custom circular progress bar made in SwiftUI.
A cross-platform, highly customizable circular progress control for SwiftUI.

[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://swift.org/package-manager)
[![Platforms](https://img.shields.io/badge/Platforms-iOS_17.0+_macOS_14.0+_visionOS_1+-blue?style=flat-square)](https://developer.apple.com/swift)
[![Swift](https://img.shields.io/badge/Swift-6.0+-orange?style=flat-square)](https://swift.org)

## Features

* Highly customizable
* Animatable
* Updated to the latest syntax
- Use as an editable control or a progress indicator
- Rich customization options with built-in and custom styles
- Built-in animation and transition support

## Installation

### Swift Package Manager

Add PZCircularControl to your project through Xcode's package manager:

1. File > Add Package Dependencies
2. Add https://github.com/philptr/PZCircularControl.git

Or add it to your `Package.swift`:

```swift
dependencies: [
.package(url: "https://github.com/philptr/PZCircularControl.git", from: "1.0.0")
]
```

## Quick Start

### Static Progress

![Basic Usage](./Resources/static-progress.gif)

You can add a simple yet customizable progress indicator to your view in a single line of code.

```swift
// Basic usage:
CircularControl(progress: 0.4)

// Customized style:
CircularControl(
progress: 0.75,
strokeWidth: 15,
style: .init(
track: Color.secondary.opacity(0.25),
progress: LinearGradient(
colors: [.teal, .mint],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
)
```

### Interactive Control

![Interactive Control](./Resources/interactive-control.gif)

```swift
import CircularControl

struct ContentView: View {
@State private var progress = 0.7

var body: some View {
// A basic interactive control usage with the default style:
CircularControl(progress: $progress)

// A customized interactive control with a custom style:
CircularControl(
progress: $progress,
strokeWidth: 15,
style: .init(
track: Color.secondary.opacity(0.2),
progress: LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
)
}
}
```

Check out the example project for more usage examples.

## Customization

### Styles

PZCircularControl supports custom styles for the track, the progress bar, and the interactive knob:

```swift
let style = CircularControlStyle(
track: Color.gray.opacity(0.2),
progress: LinearGradient(colors: [.blue, .purple]),
knob: Color.white
)

CircularControl(
progress: progress,
style: style
) {
Text("\(Int(progress * 100))%")
}
```

### Custom Labels

You can provide any SwiftUI view as a label:

```swift
CircularControl(progress: progress) {
VStack {
Image(systemName: "star.fill")
Text("\(Int(progress * 100))%")
}
}
```

To simplify your custom label implementation, you can read `circularControlProgress` from environment.

### Environment Configuration

Configure behavior through environment values:

```swift
CircularControl(progress: $progress)
// Enable continuous wrapping (off by default).
.circularControlAllowsWrapping(true)
// Adjust the scale of the interactive knob.
.circularControlKnobScale(2.0)
```

### Progress Updates

You don't have to use the Binding-based initializer to support user interaction. Instead, you may provide an initial value, specify the `isEditable` parameter, and track changes via a closure.

```swift
CircularControl(
progress: progress,
isEditable: true
) { newValue in
print("Progress updated to \(newValue).")
}
```

## Migration

The API surface of PZCircularControl has been completely reworked in 1.0.0 to better match the latest Swift and SwiftUI conventions, support editing, and provide more opportunities for client customization. For details about the legacy API and support for older OS versions, view the section below.

Migration guide:
1. Replace `PZCircularControl` with `CircularControl`.
2. Convert `PZCircularControlParams` to the new style system.
3. Move any overlay views to the new label view builder.
4. Update progress handling to use the new binding-based API.

<details>

<summary>Versions 0.x</summary>

## Usage

Expand All @@ -30,7 +186,7 @@ PZCircularControl(

This produces the following output:

![Output Image](https://github.com/philzet/PZCircularControl/blob/master/Resources/Example1.png)
![Output Image](./Resources/old-example-1.png)

The params object's instance data can be modified and animated. For example, the following code animates the control to the 35% state when the button is tapped:

Expand Down Expand Up @@ -76,7 +232,7 @@ PZCircularControl(
)
```

![Output Image](https://github.com/philzet/PZCircularControl/blob/master/Resources/Example2.png)
![Output Image](./Resources/old-example-2.png)

## Customization

Expand All @@ -93,11 +249,13 @@ Some customization options include:
* `tintColor` – the tint color of the active area of your control. Has to conform to `ShapeStyle` (ie. anything from `Color` to `Gradient`).
* `textFormatter` – an optional closure that takes in a CGFloat value of the current progress between 0.0 and 1.0 and returns formatted text that will be displayed in the center of the progress bar.

</details>

## License

MIT License

Copyright (c) 2019-2021 Phil Zet (a.k.a. Phil Zakharchenko)
Copyright (c) 2019-2024 Phil Zakharchenko

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
Binary file added Resources/interactive-control.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
Binary file added Resources/static-progress.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion Sources/PZCircularControl/CircularControl+Knob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ extension CircularControl {
.fill(style.opacity(isEnabled ? isDragging ? 0.8 : 1 : 0.3))
.background(.thinMaterial)
.clipShape(.circle)
.contentShape(.circle)
.shadow(radius: 0.5)
.shadow(radius: isDragging ? 3 : 2)
#if !os(macOS)
#if !os(macOS) && !os(watchOS)
.hoverEffect()
#endif
}
Expand Down
7 changes: 5 additions & 2 deletions Sources/PZCircularControl/CircularControl+Track.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extension CircularControl {
@State private var isDragging = false

@Environment(\.isEnabled) private var isEnabled
@Environment(\.circularControlProgressAnimation) private var progressAnimation

var body: some View {
GeometryReader { geometry in
Expand Down Expand Up @@ -63,11 +64,13 @@ extension CircularControl {
.padding(strokeWidth / 2)
.onChange(of: progress) { oldValue, newValue in
if !isDragging {
currentProgress = newValue
withAnimation(progressAnimation) {
currentProgress = newValue
}
}
}
.onAppear {
withAnimation(.snappy) {
withAnimation(progressAnimation) {
currentProgress = progress
}
}
Expand Down
Loading

0 comments on commit 8a3423f

Please sign in to comment.