Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Created May 19, 2024 13:48
Show Gist options
  • Save Koshimizu-Takehito/5c13a01de3f30b6073ad847672f72dfb to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/5c13a01de3f30b6073ad847672f72dfb to your computer and use it in GitHub Desktop.
スクロール位置に応じてY軸回転
import SwiftUI
struct ContentView: View {
@State private var containerFrame: CGRect = .zero
private var colors = [[Color]](repeating: .rainbow(count: 50), count: 300)
.flatMap { $0 }
.enumerated()
.map { $0 }
var body: some View {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(colors, id: \.offset) { color in
Item { color.element }
}
}
}
.background {
Color.clear.modifier(FrameReader($containerFrame))
}
.background {
Color.black.ignoresSafeArea()
}
.environment(\.containerFrame, containerFrame)
}
}
struct Item<Content: View>: View {
@Environment(\.containerFrame) var containerFrame
@State private var frame: CGRect = .zero
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
content()
.frame(height: 60)
.padding(8)
.padding(.horizontal, 30)
.modifier(FrameReader($frame))
.overlay {
Text(String(describing: Int(ratio * 100)) + "%")
.fontWeight(.medium)
.font(.title2.monospacedDigit())
.foregroundStyle(.black)
}
.rotation3DEffect(
.radians(radians_ratio * .pi/3),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center
)
.opacity(10 * (ratio + 0.1))
}
var radians_ratio: Double {
(2.0 * (ratio + 0.12) - 1)
}
var ratio: Double {
guard containerFrame.size.height > 0 else {
return 0
}
let x = containerFrame.size.height
let y = frame.origin.y - containerFrame.origin.y
return y / x
}
}
struct FrameReader: ViewModifier {
var nextValue: (CGRect) -> ()
init(_ nextValue: @escaping (CGRect) -> Void) {
self.nextValue = nextValue
}
init(_ value: Binding<CGRect>) {
self.nextValue = { value.wrappedValue = $0 }
}
func body(content: Content) -> some View {
content.background {
GeometryReader {
Color.clear.preference(key: Key.self, value: $0.frame(in: .global))
}
}
.onPreferenceChange(Key.self) { value in
DispatchQueue.main.async {
nextValue(value)
}
}
}
struct Key: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
}
extension EnvironmentValues {
private struct ContainerFrame: EnvironmentKey {
static let defaultValue: CGRect = .zero
}
var containerFrame: CGRect {
get { self[ContainerFrame.self] }
set { self[ContainerFrame.self] = newValue }
}
}
extension [Color] {
static func rainbow(hue: Double = 0, count: Int) -> Self {
(0..<count).map { i in
var value = hue + Double(i) / Double(count)
value -= floor(value)
return Color(hue: value, saturation: 0.60, brightness: 1)
}
}
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment