Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save UnderscoreDavidSmith/fd77499b3ca791093abeb58407683f8b to your computer and use it in GitHub Desktop.
Save UnderscoreDavidSmith/fd77499b3ca791093abeb58407683f8b to your computer and use it in GitHub Desktop.
SwiftUI UnitPoint for Angle
//
// ContentView.swift
// GradientComponenet
//
// Created by David Smith on 2/21/23.
//
import SwiftUI
struct ContentView: View {
@State var rotationAngle:CGFloat = 0.0
func unitSquareIntersectionPoint(_ angle:Angle) -> UnitPoint {
var normalizedDegree = angle.degrees
while normalizedDegree > 360.0 {
normalizedDegree -= 360.0
}
while normalizedDegree < 0.0 {
normalizedDegree += 360.0
}
if normalizedDegree < 45.0 || normalizedDegree >= 315 {
//Right Edge, x = 1.0
var degreeToConsider = normalizedDegree
if degreeToConsider < 45.0 {
degreeToConsider = normalizedDegree + 360.0
//angle now between 315 & 405
}
let degreeProportion = (degreeToConsider - 315.0) / 90.0
return UnitPoint(x: 1.0, y: 1.0 - degreeProportion)
} else if normalizedDegree < 135.0 {
//Top Edge, y = 0.0
let degreeProportion = (normalizedDegree - 45.0) / 90.0
return UnitPoint(x: 1.0 - degreeProportion, y: 0.0)
} else if normalizedDegree < 225.0 {
//left Edge, x = 0.0
let degreeProportion = (normalizedDegree - 135) / 90.0
return UnitPoint(x: 0.0, y: degreeProportion)
} else if normalizedDegree < 315.0 {
//Bottom Edge, y = 1.0
let degreeProportion = (normalizedDegree - 225) / 90.0
return UnitPoint(x: degreeProportion, y: 1.0)
}
return .zero
}
var startPoint:UnitPoint {
return unitSquareIntersectionPoint(Angle(degrees: 360.0 * rotationAngle))
}
var endPoint:UnitPoint {
return unitSquareIntersectionPoint(Angle(degrees: 360.0 * rotationAngle + 180.0))
}
var body: some View {
VStack {
let start = startPoint
let end = endPoint
ZStack {
LinearGradient(colors: [Color.red, Color.blue], startPoint: startPoint, endPoint: endPoint)
.border(.black)
Circle()
.fill(.red)
.frame(width:15, height:15)
.overlay(Circle().stroke(.black))
.position(x:start.x * 200, y:start.y * 200)
Rectangle()
.fill(.blue)
.frame(width:15, height:15)
.border(.black)
.position(x:end.x * 200, y:end.y * 200)
}
.frame(width:200, height:200)
Text("\(Int(rotationAngle * 360.0))°")
HStack {
Circle().fill(.red).frame(width:10, height:10)
Text("Start: \(String(format:"%0.1f", start.x)),\(String(format:"%0.1f", start.y))")
}
HStack {
Rectangle().fill(.blue).frame(width:10, height:10)
Text("End: \(String(format:"%0.1f", end.x)),\(String(format:"%0.1f", end.y))")
}
GeometryReader { proxy in
ZStack {
Color(white:0.9)
RoundedRectangle(cornerRadius: 8)
.fill(Color.green)
.frame(width:32, height:32)
.overlay(
HStack(spacing:3) {
RoundedRectangle(cornerRadius: 4)
.fill(.white)
.frame(width: 2, height:24)
RoundedRectangle(cornerRadius: 4)
.fill(.white)
.frame(width: 2, height:24)
RoundedRectangle(cornerRadius: 4)
.fill(.white)
.frame(width: 2, height:24)
}
)
.position(x:22 + ((proxy.size.width - 44) * rotationAngle), y:proxy.size.height * 0.5)
.highPriorityGesture(DragGesture().onChanged { dragValue in
var dragLocation = dragValue.location.x
dragLocation = max(22, dragLocation)
dragLocation = min(proxy.size.width - 22, dragLocation)
rotationAngle = (dragLocation - 22) / (proxy.size.width - 44)
})
}
}
.frame(height:44)
Text("Rotation")
.frame(minWidth:0, maxWidth:.infinity)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@UnderscoreDavidSmith
Copy link
Author

Screenshot 2023-02-22 at 11 59 57 AM

Quick SwiftUI file for calculating arbitrary LinearGradient angles.

@DrewFitz
Copy link

Thanks for the great article! I updated the geometry calculation to be an angle-based interpolation vs the linear interpolation you used! The linear solution is very close so I included a reference line to show the input angle.

https://gist.github.com/DrewFitz/9447d8ccbc2b89cac7b5d41ebfbb2a79

@SSteve
Copy link

SSteve commented Feb 24, 2023

This:

var normalizedDegree = angle.degrees
while normalizedDegree > 360.0 {
    normalizedDegree -= 360.0
}
while normalizedDegree < 0.0 {
    normalizedDegree += 360.0
}

can be simplified to this:

var normalizedDegree = angle.degrees % 360;
normalizedDegree = normalizedDegree < 0 ? normalizedDegree + 360 : normalizedDegree;

That first while loop is the definition of the % operator. Using % with a negative value on the left results in a negative number greater than -360 so adding 360 brings in into the range 0-359.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment