Files
sodalive-ios/SodaLive/Sources/Live/Room/V2/Component/WaterHeartView.swift
2025-11-05 22:00:54 +09:00

136 lines
4.2 KiB
Swift

//
// WaterHeartView.swift
// SodaLive
//
// Created by klaus on 11/5/25.
//
import SwiftUI
struct WaterHeartView: View {
var progress: CGFloat = 0 // 0...1
var show: Bool = false //
var phase: CGFloat = 0 // ()
var body: some View {
GeometryReader { geo in
let size = min(geo.size.width, geo.size.height)
ZStack {
//
HeartShape()
.stroke(lineWidth: size * 0.01)
.foregroundStyle(Color(hex: "ff959a"))
.opacity(show ? 1 : 0)
// ( )
ZStack {
// ( )
Rectangle()
.fill(Color(hex: "ff959a"))
.frame(height: geo.size.height * progress)
.position(x: geo.size.width/2,
y: geo.size.height - (geo.size.height * progress)/2)
// ( )
WaveShape(progress: progress,
phase: phase,
amplitude: max(2, size * 0.015))
.fill(Color(hex: "ff959a"))
}
.mask(HeartShape())
.opacity(show ? 1 : 0)
.animation(.easeInOut(duration: 0.2), value: show)
}
}
.aspectRatio(1, contentMode: .fit)
}
}
struct HeartShape: Shape {
func path(in rect: CGRect) -> Path {
let w = rect.width
let h = rect.height
let scale: CGFloat = 0.9 //
let cx = rect.midX
let cy = rect.midY
let r = min(w, h) * 0.5 * scale
func pt(_ nx: CGFloat, _ ny: CGFloat) -> CGPoint {
CGPoint(x: cx + nx * r, y: cy + ny * r)
}
var p = Path()
//
p.move(to: pt(0.0, -0.45))
// : -> ->
p.addCurve(
to: pt(0.0, 0.65), // ()
control1: pt(0.62, -1.02), // ( /)
control2: pt(1.22, -0.04) // ( , )
)
// :
p.addCurve(
to: pt(0.0, -0.45), // ( )
control1: pt(-1.22, -0.04), //
control2: pt(-0.62, -1.02) //
)
p.closeSubpath()
return p
}
}
/// ( )
struct WaveShape: Shape {
/// 0...1 (1 )
var progress: CGFloat
/// ()
var phase: CGFloat
///
var amplitude: CGFloat
func path(in rect: CGRect) -> Path {
let width = rect.width
let height = rect.height
// progress 0 , 1
let waterLevel = height * (1 - progress)
var path = Path()
path.move(to: CGPoint(x: 0, y: waterLevel))
//
let wavelength = width / 1.2
let step: CGFloat = 4
for x in stride(from: 0, through: width, by: step) {
let relative = x / wavelength
let y = waterLevel + sin(relative * 2 * .pi + phase) * amplitude
path.addLine(to: CGPoint(x: x, y: y))
}
// ( )
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.closeSubpath()
return path
}
var animatableData: AnimatablePair<CGFloat, CGFloat> {
get { AnimatablePair(progress, phase) }
set {
progress = newValue.first
phase = newValue.second
}
}
}
#Preview {
WaterHeartView()
}