Skip to content

Instantly share code, notes, and snippets.

@mattyoung
Last active December 29, 2022 22:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattyoung/c57245926e2f79eab24c3ee93f5c310d to your computer and use it in GitHub Desktop.
Save mattyoung/c57245926e2f79eab24c3ee93f5c310d to your computer and use it in GitHub Desktop.
//
// 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