Created
July 20, 2023 06:10
-
-
Save renyello/862a0626e5c2ade40ed3364e2cd7acaa to your computer and use it in GitHub Desktop.
四つの小さな円が正方形の角をサイズを変えながら移動します。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct ProgressRectangle: View { | |
@State private var index = [0, 1, 2, 3] | |
@State private var scale: [CGFloat] = [1.0, 0.4, 1.0, 0.4] // Scale for each circle | |
let colors = [Color("Red"), Color("Green"), Color("Blue"), Color("Yellow")] // Colors for each circle | |
let minScale: CGFloat = 1.0 // Minimum scale value | |
let maxScale: CGFloat = 2.0 // Maximum scale value | |
@State private var opacity = 1.0 | |
@State private var percentage: Int = 0 // Progress percentage | |
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() // Timer to update the percentage | |
let timer2 = Timer.publish(every: 0.3, on: .main, in: .common).autoconnect() | |
@State private var shouldAnimate = true // Control animation | |
var body: some View { | |
ZStack{ | |
Color("Beige") | |
.ignoresSafeArea() | |
GeometryReader { geometry in | |
let dimension = min(geometry.size.width, geometry.size.height) * 0.4 // 30% of the minimum dimension | |
let smallSquareSize = dimension / 5 | |
let offsetX = (geometry.size.width - dimension) / 2 | |
let offsetY = (geometry.size.height - dimension) / 2 | |
let positions = [ | |
CGPoint(x: offsetX + smallSquareSize / 2, y: offsetY + smallSquareSize / 2), | |
CGPoint(x: offsetX + dimension - smallSquareSize / 2, y: offsetY + smallSquareSize / 2), | |
CGPoint(x: offsetX + dimension - smallSquareSize / 2, y: offsetY + dimension - smallSquareSize / 2), | |
CGPoint(x: offsetX + smallSquareSize / 2, y: offsetY + dimension - smallSquareSize / 2) | |
] | |
ZStack { // Set the origin at top leading | |
ForEach(0..<4) { i in | |
// Small Circles | |
Circle() | |
.fill(colors[i]) // Assigning color from the colors array | |
.opacity(opacity) | |
.frame(width: smallSquareSize, height: smallSquareSize) | |
.scaleEffect(scale[i]) // Apply scale effect | |
.position(positions[index[i]]) | |
.animation(shouldAnimate ? .spring(response: 1, dampingFraction: 1, blendDuration: 1) : .default, value: index[i]) // Control animation with shouldAnimate | |
} | |
Text("\(percentage)%") | |
.font(.system(size: 25)) // smaller font size | |
.opacity(opacity) | |
.foregroundColor(Color.gray) | |
.onReceive(timer2) { _ in | |
if percentage < 100 { | |
percentage += Int.random(in: 1...5) | |
if percentage >= 100 { | |
percentage = 100 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | |
withAnimation(.linear(duration: 0.4)) { | |
self.opacity = 0 | |
} | |
} | |
} | |
} | |
} | |
} | |
.onAppear { | |
// Trigger the timer manually for the first time | |
index = index.map { ($0 + 1) % positions.count } | |
scale = scale.indices.map { i in | |
if (i == 0 || i == 2) { | |
return scale[i] == minScale && percentage < 100 ? maxScale : minScale | |
} else { | |
return scale[i] == minScale && percentage < 100 ? maxScale : minScale | |
} | |
} | |
// Then schedule the timer to fire periodicall | |
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in | |
if shouldAnimate { // Only animate when shouldAnimate is true | |
index = index.map { ($0 + 1) % positions.count } | |
// Update scale individually for each circle | |
scale = scale.indices.map { i in | |
if (i == 0 || i == 2) { | |
return scale[i] == minScale && percentage < 100 ? maxScale : minScale | |
} else { | |
return scale[i] == minScale && percentage < 100 ? maxScale : minScale | |
} | |
} | |
} | |
} | |
} | |
} | |
.padding() // Padding applied here | |
} | |
} | |
} | |
struct ProgressRectangle_Previews: PreviewProvider { | |
static var previews: some View { | |
ProgressRectangle() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment