Last active
March 24, 2025 13:17
-
-
Save dkun7944/e1e7e6bfed0362bc510c1766d5dc1428 to your computer and use it in GitHub Desktop.
SwiftUI + Swift.Shader CD
This file contains 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
// | |
// CDView.swift | |
// CD | |
// | |
// Created by Daniel Kuntz on 7/3/23. | |
// | |
import SwiftUI | |
struct ShapeWithHole: Shape { | |
let cutout: CGSize | |
func path(in rect: CGRect) -> Path { | |
var path = Rectangle().path(in: rect) | |
let hole = Circle().path(in: CGRect(origin: CGPoint(x: rect.midX - cutout.width / 2, y: rect.midY - cutout.height / 2), size: cutout)).reversed | |
path.addPath(hole) | |
return path | |
} | |
} | |
extension Path { | |
var reversed: Path { | |
let reversedCGPath = UIBezierPath(cgPath: cgPath) | |
.reversing() | |
.cgPath | |
return Path(reversedCGPath) | |
} | |
} | |
struct CircularTextView: View { | |
var title: String | |
var radius: Double | |
@State private var letterWidths: [Int:Double] = [:] | |
var lettersOffset: [(offset: Int, element: Character)] { | |
return Array(title.enumerated()) | |
} | |
var body: some View { | |
ZStack { | |
ForEach(lettersOffset, id: \.offset) { index, letter in // Mark 1 | |
VStack { | |
Text(String(letter)) | |
.font(.system(size: 5, weight: .bold, design: .monospaced)) | |
.foregroundColor(.yellow) | |
.kerning(5) | |
.background(LetterWidthSize()) // Mark 2 | |
.onPreferenceChange(WidthLetterPreferenceKey.self, perform: { width in // Mark 2 | |
letterWidths[index] = width | |
}) | |
Spacer() // Mark 1 | |
} | |
.rotationEffect(fetchAngle(at: index)) // Mark 3 | |
} | |
} | |
.frame(width: 107, height: 107) | |
.rotationEffect(.degrees(280)) | |
} | |
func fetchAngle(at letterPosition: Int) -> Angle { | |
let times2pi: (Double) -> Double = { $0 * 2 * .pi } | |
let circumference = times2pi(radius) | |
let finalAngle = times2pi(letterWidths.filter{$0.key <= letterPosition}.map(\.value).reduce(0, +) / circumference) | |
return .radians(finalAngle) | |
} | |
} | |
struct WidthLetterPreferenceKey: PreferenceKey { | |
static var defaultValue: Double = 0 | |
static func reduce(value: inout Double, nextValue: () -> Double) { | |
value = nextValue() | |
} | |
} | |
struct LetterWidthSize: View { | |
var body: some View { | |
GeometryReader { geometry in | |
Color | |
.clear | |
.preference(key: WidthLetterPreferenceKey.self, | |
value: geometry.size.width) | |
} | |
} | |
} | |
struct CDView: View { | |
let size: CGFloat = 350.0 | |
@StateObject private var manager = MotionManager() | |
@State private var p: CGFloat = 0.42 | |
@State private var timer: Timer? | |
private let shaderFunction = ShaderFunction(library: .default, name: "cd") | |
var body: some View { | |
ZStack { | |
Color(white: 0.0) | |
Circle() | |
.frame(width: size - 50.0, height: size - 50.0) | |
.foregroundColor(.white) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 200.0, height: 200.0))) | |
.blur(radius: 50.0) | |
.opacity(0.5) | |
.offset(x: manager.roll * -80.0, y: manager.pitch * 80.0) | |
ZStack { | |
Group { | |
Circle() | |
.foregroundColor(.white.opacity(0.3)) | |
.frame(width: size+12, height: size+12) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 150.0, height: 150.0))) | |
.shadow(color: .black, radius: 5) | |
Circle() | |
.foregroundColor(Color.black.opacity(0.9)) | |
.frame(width: size+4, height: size+4) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 110.0, height: 110.0))) | |
Circle() | |
.foregroundColor(.white.opacity(0.15)) | |
.frame(width: size+12, height: size+12) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 40.0, height: 40.0))) | |
Circle() | |
.stroke(Color.black.opacity(0.9), lineWidth: 1.0) | |
.frame(width: size+12, height: size+12) | |
Circle() | |
.stroke(Color.black, lineWidth: 1.0) | |
.frame(width: 40.0, height: 40.0) | |
Circle() | |
.stroke(Color.black.opacity(0.2), lineWidth: 2.0) | |
.frame(width: 80.0, height: 80.0) | |
Circle() | |
.stroke(Color.black, lineWidth: 16.0) | |
.frame(width: 110.0, height: 110.0) | |
} | |
Circle() | |
.foregroundColor(.white) | |
.frame(width: size+12, height: size+12) | |
.colorEffect( | |
Shader(function: shaderFunction, | |
arguments: [ | |
.float2(Float(size), | |
Float(size)), | |
.float((manager.pitch * 0.02) + 0.02) | |
]) | |
) | |
.blur(radius: 10.0) | |
.contrast(3.4) | |
.opacity(0.4) | |
.mask(Circle()) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 110.0, height: 110.0))) | |
Circle() | |
.foregroundColor(.white) | |
.frame(width: size+2, height: size+2) | |
.colorEffect( | |
Shader(function: shaderFunction, | |
arguments: [ | |
.float2(Float(size), | |
Float(size)), | |
.float((manager.pitch * 0.02) + 0.01) | |
]) | |
) | |
.blur(radius: 10.0) | |
.contrast(3.4) | |
.brightness(0.2) | |
.mask(Circle()) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 110.0, height: 110.0))) | |
Circle() | |
.foregroundColor(.white) | |
.frame(width: size, height: size) | |
.colorEffect( | |
Shader(function: shaderFunction, | |
arguments: [ | |
.float2(Float(size), | |
Float(size)), | |
.float(((manager.pitch + manager.roll + 0.4) * 0.06)) | |
]) | |
) | |
.blur(radius: 28.0) | |
.contrast(3.7) | |
.mask(Circle()) | |
.clipShape(ShapeWithHole(cutout: CGSize(width: 110.0, height: 110.0))) | |
.rotationEffect(.degrees((manager.pitch + manager.roll) * 20.0)) | |
Circle() | |
.stroke(Color.black.opacity(0.5), lineWidth: 1.0) | |
.frame(width: 117.0, height: 117.0) | |
CircularTextView(title: "SWIFT.SHADER", radius: 80.0) | |
.rotationEffect(.degrees((manager.pitch + manager.roll)*20.0)) | |
} | |
.drawingGroup() | |
.rotation3DEffect(.degrees(manager.pitch * 20.0), axis: (1, 0, 0)) | |
.rotation3DEffect(.degrees(manager.roll * 20.0), axis: (0, 1, 0)) | |
VStack { | |
Spacer() | |
Text(String(manager.pitch)) | |
Text(String(manager.roll)) | |
Text(String(((manager.pitch + manager.roll) * 0.02) - 0.01)) | |
} | |
} | |
.ignoresSafeArea() | |
.statusBarHidden() | |
} | |
} | |
#Preview { | |
CDView() | |
} |
This file contains 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
// | |
// MotionManager.swift | |
// CD | |
// | |
// Created by Daniel Kuntz on 7/3/23. | |
// | |
import SwiftUI | |
import CoreMotion | |
final class MotionManager: ObservableObject { | |
@Published var pitch: Double = 0.0 | |
@Published var roll: Double = 0.0 | |
private var manager: CMMotionManager | |
init() { | |
self.manager = CMMotionManager() | |
self.manager.deviceMotionUpdateInterval = 1/60 | |
self.manager.startDeviceMotionUpdates(to: .main) { [weak self] (motionData, error) in | |
guard error == nil else { | |
print(error!) | |
return | |
} | |
if let motionData = motionData { | |
self?.pitch = motionData.attitude.pitch | |
self?.roll = motionData.attitude.roll | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment