Skip to content

Instantly share code, notes, and snippets.

@valvoline
Created August 13, 2025 21:38
Show Gist options
  • Save valvoline/7f50bdaeca070abd5e50133c7d764da3 to your computer and use it in GitHub Desktop.
Save valvoline/7f50bdaeca070abd5e50133c7d764da3 to your computer and use it in GitHub Desktop.
A simple card with loading bar on border.
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