Last active
December 29, 2022 22:26
-
-
Save mattyoung/c57245926e2f79eab24c3ee93f5c310d to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// ContentView.swift | |
// CoordinateShapeVsViewFrameOffset | |
// | |
// Created by Matthew Young on 12/26/22. | |
// | |
import SwiftUI | |
// VVVVVv | |
// the same math is used everywhere | |
enum Elliptical { | |
/// Calculate the cartitian coordinate (x, y) | |
/// - Parameters: | |
/// - angle: at this angle | |
/// - bounds: ellipse bounds | |
/// - Returns: the coordinate in a tuple | |
static func coordinate(angle: Angle, bounds: CGSize) -> (x: Double, y: Double) { | |
(x: bounds.width / 2 + bounds.width / 2 * cos(angle.radians), y: bounds.height / 2 + bounds.height / 2 * sin(angle.radians)) | |
} | |
} | |
struct BoxesAroundEllipseShape: Shape { | |
func path(in rect: CGRect) -> Path { | |
var path = Path() | |
// Start at angle zero coordinate | |
path.move(to: CGPoint(x: rect.width, y: rect.height / 2)) | |
for degree in stride(from: 0.0, through: 360.0, by: 30.0) { | |
// VVVVVv | |
// use the same calculation and this shape look right on screen | |
let (x, y) = Elliptical.coordinate(angle: Angle(degrees: degree), bounds: rect.size) | |
let boxSize = 18.0 | |
path.addRect(.init(origin: .init(x: x - boxSize / 2, y: y - boxSize / 2), size: .init(width: boxSize, height: boxSize))) //(to: CGPoint(x: x, y: y)) | |
} | |
return path | |
} | |
} | |
struct BoxesAroundEllipseView: View { | |
@ViewBuilder | |
func hourlyMarker(hour: Int, proxy: GeometryProxy) -> some View { | |
let dotSize = 50.0 | |
let degreesPerHour = 30.0 | |
let rotation = Angle.degrees(-90 + degreesPerHour * Double(hour) + rotation.degrees) | |
let (x, y) = Elliptical.coordinate(angle: rotation, bounds: proxy.size) | |
Circle() | |
.foregroundColor(.clear) | |
.frame(width: dotSize, height: dotSize) | |
.overlay { | |
Text(String("ππππππππππππππππππππππππππππ".randomElement()!)) | |
.font(.system(size: 50)) | |
.foregroundColor(.teal) | |
.fixedSize() | |
.rotationEffect(rotation) | |
} | |
.offset(x: x - dotSize / 2, y: y - dotSize / 2) | |
} | |
@State private var rotation = Angle.zero | |
func realBody(proxy: GeometryProxy) -> some View { | |
ForEach(0...11, id: \.self) { hour in | |
hourlyMarker(hour: hour, proxy: proxy) | |
} | |
} | |
var body: some View { | |
GeometryReader { proxy in | |
realBody(proxy: proxy) | |
} | |
.onAppear { | |
withAnimation(.linear(duration: 3).repeatForever(autoreverses: false)) { | |
rotation = Angle.degrees(360) | |
} | |
} | |
} | |
} | |
struct EllipticalOrbitDot: View { | |
let angle: Angle | |
@ViewBuilder | |
func dot(proxy: GeometryProxy) -> some View { | |
let dotSize = 50.0 | |
let (x, y) = Elliptical.coordinate(angle: angle, bounds: proxy.size) | |
Circle() | |
.switchingColor(period: 1, onChange: Animation.linear, colors: Color.someColors) | |
.frame(width: dotSize, height: dotSize) | |
.offset(x: x - dotSize / 2, y: y - dotSize / 2) | |
.animation(.linear, value: angle) | |
} | |
var body: some View { | |
GeometryReader { proxy in | |
dot(proxy: proxy) | |
} | |
} | |
} | |
extension Angle { | |
mutating func advance(by: Angle) -> Self { | |
self = self + by | |
return self | |
} | |
} | |
struct EllipticalOrbit: View { | |
@State private var viewSize = CGSize.zero | |
@State private var rotation = Angle.zero | |
let duration = 5.0 | |
var body: some View { | |
ZStack { | |
Ellipse() | |
.foregroundColor(.accentColor) | |
// this cyan color dot move around a circle works | |
// Circle() | |
// .foregroundColor(.pink) | |
// .frame(width: 50, height: 50) | |
// .overlay { | |
// Text("Circular Orbit") | |
// .fixedSize() | |
// .rotationEffect(-rotation) | |
// } | |
// .offset(x: 150, y: 150) | |
// .rotationEffect(rotation) | |
// .animation(Animation.linear(duration: duration).repeatForever(autoreverses: false), value: rotation) | |
let (x, y) = Elliptical.coordinate(angle: rotation, bounds: viewSize) | |
let dotSize = 250.0 | |
Circle() | |
.foregroundColor(.red) | |
.frame(width: dotSize, height: dotSize) | |
.overlay { | |
Text("\(rotation.degrees, format: .number.precision(.fractionLength(1)))") | |
.font(.largeTitle) | |
.fixedSize() | |
.rotationEffect(-rotation) | |
} | |
.offset(x: x - dotSize / 2, y: y - dotSize / 2) | |
.rotationEffect(rotation) | |
.animation(Animation.linear(duration: duration).repeatForever(autoreverses: true), value: rotation) | |
} | |
.rotationEffect(rotation) | |
.animation(.linear(duration: duration).repeatForever(autoreverses: true), value: rotation) | |
.onAppear { | |
withAnimation(.linear.repeatForever(autoreverses: false)){ | |
rotation = Angle.degrees(360) | |
} | |
} | |
.readSize($into: $viewSize) | |
} | |
} | |
extension Date { | |
var angle: Angle { | |
let secondCount = Int(self.timeIntervalSinceReferenceDate * 10) % 60 | |
return .degrees(Double(secondCount) * 360.0 / 60) | |
} | |
} | |
struct ContentView: View { | |
@State private var rotation = Angle.zero | |
var overlay: some View { | |
VStack { | |
// BoxesAroundEllipseShape() | |
// BoxesAroundEllipseView() | |
// EllipticalOrbit() | |
ZStack { | |
let colors = Color.rainbowColors.shuffled() | |
Ellipse() | |
.strokeBorder(.angularGradient(colors: colors + [colors.first!], center: .center, startAngle: .degrees(0.0), endAngle: .degrees(360)), lineWidth: 15) | |
BoxesAroundEllipseView() | |
.overlay { | |
TimelineView(.animation) { context in | |
EllipticalOrbitDot(angle: context.date.angle) | |
} | |
} | |
} | |
} | |
} | |
var body: some View { | |
VStack { | |
VStack(spacing: -20){ | |
ForEach(0..<10, id: \.self) { i in | |
Ring(id: i) | |
.overlay { | |
BoxesAroundEllipseView() | |
} | |
.overlay { overlay } | |
.scaleEffect(x: Double(i) / 10, y: Double(i) / 5) | |
} | |
} | |
.padding(.bottom, 80) | |
Text("Merry Christmas!") | |
.font(.largeTitle) | |
.foregroundColor(.purple) | |
} | |
// .offset(y: -110) | |
.onAppear { | |
withAnimation(.linear(duration: 3).repeatForever(autoreverses: false)) { | |
rotation = Angle.degrees(360) | |
} | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment