Skip to content

Instantly share code, notes, and snippets.

@ObuchiYuki
Created March 15, 2025 06:29
Show Gist options
  • Save ObuchiYuki/d15e799268730f7b49e0a423ae75502b to your computer and use it in GitHub Desktop.
Save ObuchiYuki/d15e799268730f7b49e0a423ae75502b to your computer and use it in GitHub Desktop.
Glowing Glass View
//
// GlassView.swift
// MFileViewer
//
// Created by yuki on 2025/03/14.
//
import SwiftUI
struct GlassViewMain: View {
@State var value = 0.4
@State var isGrowing = false
var body: some View {
VStack(spacing: 16) {
GlassView(thickness: .thin) {
VStack(alignment: .leading) {
Text("Volume")
.font(.system(size: 14))
.fontWeight(.bold)
.foregroundColor(.white.opacity(0.8))
HStack(spacing: 16) {
Button(action: {
withAnimation {
self.value -= 0.1
self.value = max(0, self.value)
self.isGrowing = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
withAnimation {
self.isGrowing = false
}
}
}) {
Image(systemName: "speaker.fill")
.font(.system(size: 20))
.frame(width: 20, height: 20)
}
.buttonStyle(GlassButtonStyle())
GeometryReader { proxy in
Capsule()
.fill(Color.black.opacity(0.1))
.overlay(alignment: .leading) {
GlassView(thickness: .regular, cornerRadius: .infinity, isGrowing: self.isGrowing) {
Color.clear
.frame(width: self.value * proxy.size.width)
}
}
}
.frame(maxWidth: .infinity)
.frame(height: 20)
Button(action: {
withAnimation {
self.value += 0.1
self.value = min(1, self.value)
self.isGrowing = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
withAnimation {
self.isGrowing = false
}
}
}) {
Image(systemName: "speaker.wave.3.fill")
.font(.system(size: 20))
.frame(width: 20, height: 20)
}
.buttonStyle(GlassButtonStyle())
}
}
.padding()
}
}
.padding()
.background {
Image(.background)
}
}
}
struct GlassButtonStyle: ButtonStyle {
let cornerRadius: CGFloat
init(cornerRadius: CGFloat = .infinity) {
self.cornerRadius = cornerRadius
}
func makeBody(configuration: Configuration) -> some View {
GlassView(
thickness: .regular,
cornerRadius: self.cornerRadius,
isGrowing: configuration.isPressed
) {
configuration.label
.padding()
}
.animation(.easeInOut(duration: 0.26), value: configuration.isPressed)
}
}
struct GlassView<Content: View>: View {
enum Thickness {
case thin
case regular
case thick
}
let thickness: Thickness
let content: () -> Content
let cornerRadius: CGFloat
let isGrowing: Bool
init(
thickness: Thickness = .regular,
cornerRadius: CGFloat = 10,
isGrowing: Bool = false,
@ViewBuilder content: @escaping () -> Content
) {
self.thickness = thickness
self.cornerRadius = cornerRadius
self.isGrowing = isGrowing
self.content = content
}
var body: some View {
self.content()
.background {
RoundedRectangle(cornerRadius: self.cornerRadius)
.fill(
Color.black
.shadow(.inner(color: .white, radius: 1/3, x: 1/3, y: 2/3))
.shadow(.inner(color: .white, radius: 1/3, x: -1/3, y: -2/3))
)
.blendMode(.overlay)
}
.background {
Color.white
.opacity(self.isGrowing ? 1 : self.opacity)
}
.background(self.material)
.clipShape(RoundedRectangle(cornerRadius: self.cornerRadius))
.shadow(color: self.isGrowing ? .white.opacity(0.15) : .black.opacity(0.15), radius: 1, x: -1, y: 0)
.shadow(color: self.isGrowing ? .white.opacity(0.15) : .black.opacity(0.15), radius: 1, x: 1, y: 0)
.shadow(color: self.isGrowing ? .white.opacity(0.36) : .black.opacity(0.36), radius: 30, x: 0, y: 2)
}
private var opacity: Double {
switch self.thickness {
case .thin: return 0.4
case .regular: return 0.6
case .thick: return 0.65
}
}
private var material: Material {
switch self.thickness {
case .thin: return .ultraThinMaterial
case .regular: return .ultraThinMaterial
case .thick: return .regularMaterial
}
}
}
#Preview {
GlassViewMain()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment