Contents

william-weng/wwcircularrangepickerui

English | 繁體中文

✨ [功能特色](https://peterpanswift.github.io/iphone-bezels/)

  • 支援雙把手的圓形區間選擇。
  • 使用純 SwiftUI 實作。
  • 支援自訂起點與終點把手視圖。
  • 提供預設把手圖示,方便快速整合。
  • 透過 closure 回傳選取的 tick index。
  • 支援負角度、360 度循環與正規化顯示。
  • 選取弧線支援 Shape + AnimatablePair 動畫。

📦 安裝方式

Swift Package Manager

在 Xcode 中加入套件:

  1. 打開你的專案。
  2. 選擇 File > Add Package Dependencies...
  3. 貼上 repository URL。
  4. WWCircularRangePickerUI 加入你的 target。

🚀 基本用法

import SwiftUI
import WWCircularRangePickerUI

struct ContentView: View {

    @State private var startAngle: Angle = .degrees(0)
    @State private var endAngle: Angle = .degrees(90)
    @State private var timeRangeText: String = ""

    var body: some View {

        ZStack {

            Text(timeRangeText)
                .font(.title)

            WWCircularRangePickerUI(startAngle: $startAngle, endAngle: $endAngle) {
                updateText(from: $0, to: $1)
            }
            .padding()
        }
        .padding()
    }
}

private extension ContentView {

    func calculateTime(at index: Int) -> String {
        let totalMinutes = 10 * index
        let hour = totalMinutes / 60
        let minute = totalMinutes % 60
        return String(format: "%02d:%02d", hour, minute)
    }

    func updateText(from startIndex: Int, to endIndex: Int) {
        let startTime = calculateTime(at: startIndex)
        let endTime = calculateTime(at: endIndex)
        timeRangeText = "\(startTime) ~ \(endTime)"
    }
}

🔁 回傳值

選擇器會以 tuple 的方式回傳選取範圍:

public typealias SelectIndex = (start: Int, end: Int)

使用方式如下:

WWCircularRangePickerUI(startAngle: $startAngle, endAngle: $endAngle) { startIndex, endIndex in
    print(startIndex, endIndex)
}

🪄 預設初始化

如果你想快速開始,可以直接使用內建的把手視圖:

WWCircularRangePickerUI(
    startAngle: $startAngle,
    endAngle: $endAngle
) { startIndex, endIndex in
    print(startIndex, endIndex)
}

這個初始化器使用的預設配置大致如下:

Configure(
    lineWidth: 44,
    innerColor: .gray.opacity(0.2),
    outerColor: .yellow.opacity(0.5),
    tickCount: 72,
    tickStride: 6
)

🎛️ 客製化把手

你可以自訂起點與終點把手視圖:

WWCircularRangePickerUI(
    configure: configure,
    startAngle: $startAngle,
    endAngle: $endAngle,
    startView: Image(systemName: "moon.fill"),
    endView: Image(systemName: "alarm.fill")
) { startIndex, endIndex in
    print(startIndex, endIndex)
}

⚙️ 配置說明

選擇器透過 Configure 進行設定。

let configure = WWCircularRangePickerUI<EmptyView, EmptyView>.Configure(
    lineWidth: 44,
    innerColor: .gray.opacity(0.2),
    outerColor: .yellow.opacity(0.5),
    tickCount: 72,
    tickStride: 6
)

常用參數

  • lineWidth:圓弧線寬度。
  • innerColor:底層圓環顏色。
  • outerColor:選取區間顏色。
  • tickCount:圓盤總刻度數。
  • tickStride:主要刻度間距。
  • stepAngle:每個刻度對應的角度,通常由 tickCount 推導。

🧭 角度行為

這個元件是以圓形互動為核心,所以角度處理很重要。

  • 角度會被正規化到 0..<360
  • 支援負角度。
  • 超過 360 的值會自動循環回圓盤內。
  • 選取 index 可以透過四捨五入對應到最近的刻度。
  • 初始 callback 建議在 .onAppear 中觸發,而不是在 init 中執行。

⚠️ 注意事項

  • onAppearinit 更適合處理第一次 callback,因為 SwiftUI 的 View initializer 應盡量避免 side effect。
  • onChange 可以用來監聽 startAngleendAngle 的後續變動。
  • 如果你要把每個 tick 對應成時間,建議把轉換邏輯放在元件外部,讓 picker 保持可重用性。

⏱️ 時間範例

如果每個 tick 代表 10 分鐘,你可以把 index 轉成可讀時間字串:

func calculateTime(at index: Int) -> String {
    let totalMinutes = 10 * index
    let hour = totalMinutes / 60
    let minute = totalMinutes % 60
    return String(format: "%02d:%02d", hour, minute)
}

再搭配 callback 組合成區間文字:

func updateText(from startIndex: Int, to endIndex: Int) {
    let startTime = calculateTime(at: startIndex)
    let endTime = calculateTime(at: endIndex)
    timeRangeText = "\(startTime) ~ \(endTime)"
}

👀 預覽

#Preview {
    ContentView()
}

Package Metadata

Repository: william-weng/wwcircularrangepickerui

Default branch: main

README: README.md