Created
August 13, 2025 21:38
-
-
Save valvoline/7f50bdaeca070abd5e50133c7d764da3 to your computer and use it in GitHub Desktop.
A simple card with loading bar on border.
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 BorderLineLoadingView: View { | |
@State private var rotation: Double = 0 | |
var animate = false | |
var cornerRadius: Double = 20 | |
var speed: Double = 2 | |
var strokeWidth: Double = 3 | |
var startColor: Color = .red | |
var endColor: Color = .blue | |
var backgroundColor: Color = Color(.systemGray4) | |
var body: some View { | |
ConcentricRectangle() | |
.fill(backgroundColor) | |
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) | |
.overlay(alignment: .top) { | |
if animate { | |
GeometryReader { geo in | |
let pWidth = geo.size.width | |
let pHeight = geo.size.height | |
let maxDimension = max(pWidth, pHeight) | |
let minDimension = min(pWidth, pHeight) | |
ConcentricRectangle() | |
.fill(LinearGradient(colors: [startColor, endColor], startPoint: .top, endPoint: .bottom)) | |
.frame(width: minDimension/2, height: maxDimension + (maxDimension*45/100)) | |
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) | |
.position(x: pWidth / 2, y: pHeight / 2) | |
.rotationEffect(Angle(degrees: rotation)) | |
.mask { | |
ConcentricRectangle().stroke(.black, style: .init(lineWidth: strokeWidth), antialiased: true) | |
.frame(width: pWidth-3, height: pHeight-3) | |
} | |
} | |
} | |
} | |
.containerShape(.rect(cornerRadius: cornerRadius)) | |
.onAppear { | |
startAnimating() | |
} | |
.onChange(of: speed) { | |
startAnimating() | |
} | |
.onChange(of: animate) { | |
startAnimating() | |
} | |
.id(speed) | |
} | |
private func startAnimating() { | |
if animate { | |
rotation = 0 | |
let k: Double = 5.0 // costante per calibrare il tempo | |
let duration = k / max(speed, 0.1) // evita divisione per zero | |
DispatchQueue.main.async { | |
withAnimation(.linear(duration: duration).repeatForever(autoreverses: false)) { | |
rotation += 360 | |
} | |
} | |
} | |
} | |
} | |
struct ContentView: View { | |
@State private var cornerRadius: Double = 40 | |
@State private var speed: Double = 2 | |
@State private var width: Double = 200 | |
@State private var height: Double = 200 | |
@State private var animate: Bool = false | |
@State private var startColor: Color = .red | |
@State private var endColor: Color = .blue | |
@State private var strokeWidth: Double = 3 | |
@State private var backgroundColor: Color = Color(.systemGray4) | |
var body: some View { | |
VStack { | |
BorderLineLoadingView(animate: animate, | |
cornerRadius: cornerRadius, | |
speed: speed, | |
strokeWidth: strokeWidth, | |
startColor: startColor, | |
endColor: endColor, | |
backgroundColor: backgroundColor) | |
.frame(maxWidth: width, maxHeight: height) | |
Spacer() | |
VStack(alignment: .leading) { | |
Text("animating") | |
Picker("", selection: $animate) { | |
Text("On").tag(true) | |
Text("Off").tag(false) | |
} | |
.pickerStyle(SegmentedPickerStyle()) | |
} | |
VStack(alignment: .leading) { | |
ColorPicker("startColor", selection: $startColor) | |
ColorPicker("endColor", selection: $endColor) | |
} | |
VStack(alignment: .leading) { | |
Text("width") | |
Slider(value: $width, in: 0...400, step: 1) | |
} | |
VStack(alignment: .leading) { | |
Text("height") | |
Slider(value: $height, in: 0...400, step: 1) | |
} | |
VStack(alignment: .leading) { | |
Text("cornerRadius") | |
Slider(value: $cornerRadius, in: 0...100, step: 1) | |
} | |
VStack(alignment: .leading) { | |
Text("stroke") | |
Slider(value: $strokeWidth, in: 0...10, step: 1) | |
} | |
VStack(alignment: .leading) { | |
Text("speed") | |
Slider(value: $speed, in: 0...10, step: 1) | |
} | |
} | |
.padding() | |
.background { | |
Color(.secondarySystemBackground).ignoresSafeArea() | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment