Skip to content

Commit 7cfa698

Browse files
committed
Add Liquefied Effect and Restructure Files
1 parent 98d5188 commit 7cfa698

File tree

5 files changed

+466
-108
lines changed

5 files changed

+466
-108
lines changed

Demo/Demo.xcodeproj/project.pbxproj

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
Samples/GlassFlower.swift,
8282
Samples/GlassFlowerRotate.swift,
8383
Samples/Input.swift,
84+
Samples/Liquefied.swift,
8485
);
8586
target = 3A80CECB2DB4C144006BDD0A /* Watch Watch App */;
8687
};
@@ -649,7 +650,7 @@
649650
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
650651
CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
651652
CODE_SIGN_STYLE = Automatic;
652-
CURRENT_PROJECT_VERSION = 1;
653+
CURRENT_PROJECT_VERSION = 9;
653654
ENABLE_PREVIEWS = YES;
654655
GENERATE_INFOPLIST_FILE = YES;
655656
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
@@ -666,7 +667,7 @@
666667
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
667668
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
668669
MACOSX_DEPLOYMENT_TARGET = 15.4;
669-
MARKETING_VERSION = 1.0;
670+
MARKETING_VERSION = 1.7.0;
670671
PRODUCT_BUNDLE_IDENTIFIER = media.1998.Demo;
671672
PRODUCT_NAME = "$(TARGET_NAME)";
672673
REGISTER_APP_GROUPS = YES;
@@ -687,7 +688,7 @@
687688
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
688689
CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
689690
CODE_SIGN_STYLE = Automatic;
690-
CURRENT_PROJECT_VERSION = 1;
691+
CURRENT_PROJECT_VERSION = 9;
691692
ENABLE_PREVIEWS = YES;
692693
GENERATE_INFOPLIST_FILE = YES;
693694
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
@@ -704,7 +705,7 @@
704705
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
705706
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
706707
MACOSX_DEPLOYMENT_TARGET = 15.4;
707-
MARKETING_VERSION = 1.0;
708+
MARKETING_VERSION = 1.7.0;
708709
PRODUCT_BUNDLE_IDENTIFIER = media.1998.Demo;
709710
PRODUCT_NAME = "$(TARGET_NAME)";
710711
REGISTER_APP_GROUPS = YES;
@@ -810,7 +811,7 @@
810811
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
811812
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
812813
CODE_SIGN_STYLE = Automatic;
813-
CURRENT_PROJECT_VERSION = 1;
814+
CURRENT_PROJECT_VERSION = 9;
814815
DEVELOPMENT_TEAM = "";
815816
ENABLE_PREVIEWS = YES;
816817
GENERATE_INFOPLIST_FILE = YES;
@@ -821,7 +822,7 @@
821822
"$(inherited)",
822823
"@executable_path/Frameworks",
823824
);
824-
MARKETING_VERSION = 1.0;
825+
MARKETING_VERSION = 1.7.0;
825826
PRODUCT_BUNDLE_IDENTIFIER = media.1998.Demo.watchkitapp;
826827
PRODUCT_NAME = "$(TARGET_NAME)";
827828
SDKROOT = watchos;
@@ -839,7 +840,7 @@
839840
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
840841
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
841842
CODE_SIGN_STYLE = Automatic;
842-
CURRENT_PROJECT_VERSION = 1;
843+
CURRENT_PROJECT_VERSION = 9;
843844
DEVELOPMENT_TEAM = "";
844845
ENABLE_PREVIEWS = YES;
845846
GENERATE_INFOPLIST_FILE = YES;
@@ -850,7 +851,7 @@
850851
"$(inherited)",
851852
"@executable_path/Frameworks",
852853
);
853-
MARKETING_VERSION = 1.0;
854+
MARKETING_VERSION = 1.7.0;
854855
PRODUCT_BUNDLE_IDENTIFIER = media.1998.Demo.watchkitapp;
855856
PRODUCT_NAME = "$(TARGET_NAME)";
856857
SDKROOT = watchos;

Demo/Demo/Samples/Liquefied.swift

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//
2+
// Liquefied.swift
3+
// Demo
4+
//
5+
// Created by Ming on 21/4/2025.
6+
//
7+
8+
import SwiftUI
9+
import SwiftGlass
10+
11+
struct Liquefied: View {
12+
@State private var colorIndex: Int = 0
13+
@State private var timer: Timer? = nil
14+
15+
// Use computed properties to get current color and name
16+
private var color: Color {
17+
// Get the current color from colorOptions
18+
return colorOptions[colorIndex].color
19+
}
20+
21+
private var colorName: String {
22+
// Get the current color name from colorOptions
23+
return colorOptions[colorIndex].name
24+
}
25+
26+
var body: some View {
27+
ZStack {
28+
// Background color with opacity
29+
color
30+
.opacity(0.2)
31+
.ignoresSafeArea()
32+
VStack {
33+
Spacer()
34+
35+
// Animated color name text
36+
Text(colorIndex == 0 ? "" : colorName)
37+
.font(.title2)
38+
.fontWeight(.heavy)
39+
.foregroundStyle(color)
40+
.padding(colorIndex == 0 ? 0 : 100)
41+
.liquefiedGlass(color: color, blobIntensity: 0.2)
42+
.contentTransition(.numericText(countsDown: true))
43+
44+
// Uncomment below for animated circles
45+
/*
46+
if colorIndex == 0 {
47+
Spacer()
48+
}
49+
50+
ForEach(Array(zip([100, 50, 30, 10], [
51+
CGPoint(x: 40, y: 15),
52+
CGPoint(x: 70, y: 30),
53+
CGPoint(x: 90, y: 50),
54+
CGPoint(x: 110, y: 65),
55+
])), id: \.0) { size, offset in
56+
Circle()
57+
.fill(.clear)
58+
.liquefiedGlass(color: color, blobIntensity: 0.2)
59+
.frame(width: colorIndex == 0 ? 0 : CGFloat(size), height: colorIndex == 0 ? 0 : CGFloat(size))
60+
.offset(x: offset.x, y: offset.y)
61+
}
62+
*/
63+
64+
Spacer()
65+
66+
// Uncomment below for footer label
67+
/*
68+
HStack {
69+
Spacer()
70+
Text("SwiftGlass 1.7 - Liquefied")
71+
.bold()
72+
.font(.caption)
73+
.foregroundStyle(color)
74+
.padding(25)
75+
.glass(color: color, shadowColor: color)
76+
}
77+
*/
78+
}
79+
.multilineTextAlignment(.center)
80+
.padding(.horizontal)
81+
}
82+
.onAppear {
83+
// Start timer to cycle colors
84+
timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
85+
withAnimation(.easeInOut(duration: 1.5)) {
86+
colorIndex = (colorIndex + 1) % colorOptions.count
87+
}
88+
}
89+
}
90+
.onDisappear {
91+
// Invalidate timer when view disappears
92+
timer?.invalidate()
93+
timer = nil
94+
}
95+
}
96+
}
97+
98+
#Preview("Dark") {
99+
Liquefied()
100+
.preferredColorScheme(.dark)
101+
}
102+
103+
// Create a ColorOption struct to keep color and name together
104+
private struct ColorOption {
105+
let color: Color
106+
let name: String
107+
}
108+
109+
// Combined array of color options
110+
private let colorOptions: [ColorOption] = [
111+
// SwiftUI Colors
112+
ColorOption(color: .gray, name: "gray"),
113+
ColorOption(color: .blue, name: "blue"),
114+
ColorOption(color: .purple, name: "purple"),
115+
ColorOption(color: .green, name: "green"),
116+
ColorOption(color: .orange, name: "orange"),
117+
ColorOption(color: .pink, name: "pink"),
118+
ColorOption(color: .red, name: "red"),
119+
ColorOption(color: .yellow, name: "yellow"),
120+
ColorOption(color: .cyan, name: "cyan"),
121+
ColorOption(color: .indigo, name: "indigo"),
122+
ColorOption(color: .mint, name: "mint"),
123+
ColorOption(color: .teal, name: "teal"),
124+
ColorOption(color: .brown, name: "brown"),
125+
126+
// Luxury Collection
127+
// ColorOption(color: Color(red: 0.98, green: 0.48, blue: 0.21), name: "Hermes Orange"),
128+
// ColorOption(color: Color(red: 0.51, green: 0.85, blue: 0.82), name: "Tiffany Blue"),
129+
// ColorOption(color: Color(red: 0.10, green: 0.50, blue: 0.26), name: "Gucci Green"),
130+
// ColorOption(color: Color(red: 0.85, green: 0.79, blue: 0.72), name: "Burberry Beige"),
131+
// ColorOption(color: Color(red: 0.76, green: 0.70, blue: 0.38), name: "Versace Gold"),
132+
// ColorOption(color: Color(red: 0.98, green: 0.83, blue: 0.26), name: "Fendi Yellow"),
133+
// ColorOption(color: Color(red: 0.71, green: 0.12, blue: 0.16), name: "Cartier Red"),
134+
// ColorOption(color: Color(red: 0.30, green: 0.18, blue: 0.15), name: "Louis Vuitton Brown"),
135+
]
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
//
2+
// BlobShape.swift
3+
// SwiftGlass
4+
//
5+
// Created by Ming on 21/4/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
@available(iOS 15.0, macOS 14.0, watchOS 10.0, tvOS 15.0, visionOS 1.0, *)
11+
public struct BlobGlassModifier: ViewModifier {
12+
// Parameters
13+
let color: Color
14+
let blobIntensity: CGFloat
15+
let animationSpeed: Double
16+
let complexity: Int
17+
let material: Material
18+
let gradientOpacity: Double
19+
let shadowOpacity: Double
20+
21+
// Animation state
22+
@State private var animationProgress: CGFloat = 0.0
23+
24+
// Initializer
25+
public init(
26+
color: Color,
27+
blobIntensity: CGFloat,
28+
animationSpeed: Double,
29+
complexity: Int,
30+
material: Material,
31+
gradientOpacity: Double,
32+
shadowOpacity: Double
33+
) {
34+
self.color = color
35+
self.blobIntensity = min(max(blobIntensity, 0.1), 1.0) // Constrain between 0.1 and 1.0
36+
self.animationSpeed = animationSpeed
37+
self.complexity = min(max(complexity, 4), 12) // Constrain between 4 and 12
38+
self.material = material
39+
self.gradientOpacity = gradientOpacity
40+
self.shadowOpacity = shadowOpacity
41+
}
42+
43+
// ViewModifier Body
44+
public func body(content: Content) -> some View {
45+
content
46+
// Clip content to animated blob shape
47+
.clipShape(
48+
BlobShape(
49+
complexity: complexity,
50+
animationProgress: animationProgress,
51+
intensity: blobIntensity
52+
)
53+
)
54+
// Blob glass background and effects
55+
.background(
56+
BlobShape(
57+
complexity: complexity,
58+
animationProgress: animationProgress,
59+
intensity: blobIntensity
60+
)
61+
.fill(material)
62+
.overlay(
63+
BlobShape(
64+
complexity: complexity,
65+
animationProgress: animationProgress,
66+
intensity: blobIntensity
67+
)
68+
.stroke(
69+
LinearGradient(
70+
colors: [
71+
color.opacity(gradientOpacity),
72+
color.opacity(0),
73+
color.opacity(gradientOpacity),
74+
color.opacity(0)
75+
],
76+
startPoint: .topLeading,
77+
endPoint: .bottomTrailing
78+
),
79+
lineWidth: 1.5
80+
)
81+
)
82+
.shadow(
83+
color: color.opacity(shadowOpacity),
84+
radius: 10,
85+
x: 0,
86+
y: 5
87+
)
88+
)
89+
.onAppear {
90+
// Start the continuous animation
91+
withAnimation(
92+
Animation
93+
.linear(duration: 10 / animationSpeed)
94+
.repeatForever(autoreverses: true)
95+
) {
96+
animationProgress = 1.0
97+
}
98+
}
99+
}
100+
}
101+
102+
// BlobShape
103+
@available(iOS 15.0, macOS 14.0, watchOS 10.0, tvOS 15.0, visionOS 1.0, *)
104+
public struct BlobShape: Shape {
105+
// Control points for the blob's perimeter
106+
var controlPoints: [UnitPoint]
107+
var animationProgress: CGFloat
108+
var intensity: CGFloat
109+
110+
// Initializer
111+
public init(complexity: Int = 8, animationProgress: CGFloat = 0.0, intensity: CGFloat = 0.5) {
112+
self.controlPoints = BlobShape.generateControlPoints(count: complexity)
113+
self.animationProgress = animationProgress
114+
self.intensity = intensity
115+
}
116+
117+
// Animatable
118+
public var animatableData: CGFloat {
119+
get { animationProgress }
120+
set { animationProgress = newValue }
121+
}
122+
123+
// Path Generation
124+
public func path(in rect: CGRect) -> Path {
125+
let center = CGPoint(x: rect.midX, y: rect.midY)
126+
let minDimension = min(rect.width, rect.height)
127+
let radius = minDimension / 2
128+
129+
// Calculate points on the blob's perimeter
130+
let points = controlPoints.map { unitPoint -> CGPoint in
131+
let angle = 2 * .pi * unitPoint.x + (animationProgress * 2 * .pi)
132+
let distortionAmount = sin(angle * 3 + animationProgress * 4) * intensity * 0.2
133+
let blobRadius = radius * (1 + distortionAmount)
134+
let pointX = center.x + cos(angle) * blobRadius
135+
let pointY = center.y + sin(angle) * blobRadius
136+
return CGPoint(x: pointX, y: pointY)
137+
}
138+
139+
// Use cubic Bézier curves for smoothness
140+
var path = Path()
141+
guard points.count > 2 else { return path }
142+
143+
path.move(to: points[0])
144+
145+
// Calculate control points for smooth cubic Bézier curves
146+
let count = points.count
147+
for i in 0..<count {
148+
let prev = points[(i - 1 + count) % count]
149+
let curr = points[i]
150+
let next = points[(i + 1) % count]
151+
let next2 = points[(i + 2) % count]
152+
153+
// Calculate control points for smoothness
154+
let control1 = CGPoint(
155+
x: curr.x + (next.x - prev.x) / 6,
156+
y: curr.y + (next.y - prev.y) / 6
157+
)
158+
let control2 = CGPoint(
159+
x: next.x - (next2.x - curr.x) / 6,
160+
y: next.y - (next2.y - curr.y) / 6
161+
)
162+
163+
path.addCurve(to: next, control1: control1, control2: control2)
164+
}
165+
166+
path.closeSubpath()
167+
return path
168+
}
169+
170+
// Helper: Generate evenly distributed control points around a circle
171+
private static func generateControlPoints(count: Int) -> [UnitPoint] {
172+
var points = [UnitPoint]()
173+
let angleStep = 1.0 / CGFloat(count)
174+
175+
for i in 0..<count {
176+
let angle = CGFloat(i) * angleStep
177+
points.append(UnitPoint(x: angle, y: angle))
178+
}
179+
180+
return points
181+
}
182+
}

0 commit comments

Comments
 (0)