実装
import SwiftUI
struct HomeView: View {
@State private var currentUsagemount: Double = 25_000
@State private var maxUsageAmount: Double = 50_000
@State private var isProgressAnimating = false
private var progressPercentage: Double {
return min(currentUsagemount / maxUsageAmount, 1.0) // 100%より上の限界突破ブロックのため
}
var body: some View {
VStack(spacing: 40) {
topSection()
// 円形プログレスバー
ZStack {
circleView()
middleSection()
}
// 統計情報
VStack(spacing: 16) {
staticTopSection()
Divider()
staticControlSection()
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
.padding(.horizontal)
Spacer()
}
.padding()
.onAppear {
isProgressAnimating = true
}
}
@ViewBuilder
private func topSection() -> some View {
Text("利用状況")
.font(.title2)
.fontWeight(.medium)
.foregroundColor(.gray)
}
@ViewBuilder
private func middleSection() -> some View {
VStack(spacing: 8) {
Text("今月の利用額")
.font(.caption)
.foregroundColor(.gray)
Text("¥\(Int(currentUsagemount).formatted())")
.font(.system(size: 32, weight: .bold, design: .rounded))
.foregroundColor(.primary)
Text("/ ¥\(Int(maxUsageAmount).formatted())")
.font(.caption)
.foregroundColor(.gray)
}
}
@ViewBuilder
private func staticTopSection() -> some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("利用率")
.font(.caption)
.foregroundColor(.gray)
Text("\(Int(progressPercentage * 100))%")
.font(.title3)
.fontWeight(.semibold)
}
Spacer()
VStack(alignment: .trailing, spacing: 4) {
Text("残り利用可能額")
.font(.caption)
.foregroundColor(.gray)
Text("¥\(Int(maxUsageAmount - currentUsagemount).formatted())")
.font(.title3)
.fontWeight(.semibold)
.foregroundColor(.green)
}
}
.padding(.horizontal)
}
@ViewBuilder
private func staticControlSection() -> some View {
VStack(spacing: 16) {
Text("金額を変更")
.font(.headline)
HStack(spacing: 20) {
Button("¥10,000") {
withAnimation(.easeInOut(duration: 0.8)) {
currentUsagemount = 10_000
}
}
.buttonStyle(.bordered)
Button("¥25,000") {
withAnimation(.easeInOut(duration: 0.8)) {
currentUsagemount = 25_000
}
}
.buttonStyle(.bordered)
Button("¥40,000") {
withAnimation(.easeInOut(duration: 0.8)) {
currentUsagemount = 40_000
}
}
.buttonStyle(.bordered)
}
Slider(value: $currentUsagemount, in: 0...maxUsageAmount, step: 1_000)
.padding(.horizontal)
}
}
@ViewBuilder
private func circleView() -> some View {
// 背景の円
Circle()
.stroke(Color.gray.opacity(0.2), lineWidth: 12)
.frame(width: 250, height: 250)
// プログレス円
Circle()
.trim(from: 0, to: isProgressAnimating ? progressPercentage : 0)
.stroke(
LinearGradient(
colors: [Color.blue.opacity(0.7), Color.cyan],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
style: StrokeStyle(lineWidth: 12, lineCap: .round)
)
.frame(width: 250, height: 250)
.rotationEffect(.degrees(-90))
.animation(.easeInOut(duration: 1.5), value: isProgressAnimating)
}
}