Hello. Was wondering if anybody has come across code or tutorial on creating a dynamic wind compass. Already tied into weather api, struggling with UI
Something like this?
This is what I wrote and use in my own app, so if you use it, please make it look a little different... or I'll sue 😉
import SwiftUI
struct ContentView: View {
var body: some View {
let windDegrees = 255.0
let frameSize = 240.0
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.init(red: 91.0/255.0, green: 100.0/255.0, blue: 157.0/255.0), Color.init(red: 125.0/255.0, green: 108.0/255.0, blue: 142.0/255.0)]), startPoint: .top, endPoint: .bottom)
ZStack {
Circle()
.opacity(0.1)
Image("compassMarker")
.resizable()
.scaledToFit()
.frame(width: 10, height: 100)
.rotationEffect(Angle(degrees: windDegrees - 180))
.shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1)
Circle()
.fill(.black.opacity(0.15))
.frame(width: frameSize/2, height: frameSize/2)
VStack {
Text(convertWindDirectionToCompassPoint(windDegrees))
.font(.system(size: 18, weight: .bold))
.foregroundStyle(.white)
.shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1)
Text("\(String(format: "%.0f", windDegrees))º")
.font(.system(size: 12))
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(0.6)
.shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1)
}
ForEach(CompassMarker.markers(), id: \.self) { marker in
CompassMarkerView(marker: marker, compassDegress: 0)
}
}
.frame(width: frameSize, height: frameSize)
}
.ignoresSafeArea()
}
struct CompassMarker: Hashable {
let degrees: Double
let label: String
init(degrees: Double, label: String = "") {
self.degrees = degrees
self.label = label
}
static func markers() -> [CompassMarker] {
return [
CompassMarker(degrees: 0, label: "N"),
CompassMarker(degrees: 30),
CompassMarker(degrees: 60),
CompassMarker(degrees: 90, label: "E"),
CompassMarker(degrees: 120),
CompassMarker(degrees: 150),
CompassMarker(degrees: 180, label: "S"),
CompassMarker(degrees: 210),
CompassMarker(degrees: 240),
CompassMarker(degrees: 270, label: "W"),
CompassMarker(degrees: 300),
CompassMarker(degrees: 330)
]
}
func degreeText() -> String {
return String(format: "%.0f", self.degrees)
}
}
struct CompassMarkerView: View {
let marker: CompassMarker
let compassDegress: Double
var body: some View {
VStack {
Capsule()
.frame(width: 2, height: self.capsuleHeight())
.foregroundStyle(Color.white)
.opacity(0.6)
.padding(.bottom, self.capsulePadding())
Text(marker.label)
.font(.system(size: 16, weight: .bold))
.foregroundStyle(Color.white)
.opacity(0.6)
.rotationEffect(self.textAngle())
Spacer(minLength: 97)
}
.rotationEffect(Angle(degrees: marker.degrees))
}
private func capsuleHeight() -> CGFloat {
return (marker.label != "" ? 8 : 12)
}
private func capsulePadding() -> CGFloat {
return (marker.label != "" ? -12 : -6)
}
private func textAngle() -> Angle {
return Angle(degrees: -self.compassDegress - self.marker.degrees)
}
}
func convertWindDirectionToCompassPoint(_ degrees: Double) -> String {
var degrees_: Double = fmod(degrees, 360.0)
var point: String = ""
if(degrees_ > 360) {
degrees_ = degrees_.truncatingRemainder(dividingBy: 360)
}
if((degrees_ >= 0.0 && degrees_ <= 11.25) || (degrees_ > 348.75 && degrees_ <= 360.0)) {
point = "N"
}
if(degrees_ > 11.25 && degrees_ <= 33.75) {
point = "NNE"
}
if(degrees_ > 33.75 && degrees_ <= 56.25) {
point = "NE"
}
if(degrees_ > 56.25 && degrees_ <= 78.75) {
point = "ENE"
}
if(degrees_ > 78.75 && degrees_ <= 101.25) {
point = "E"
}
if(degrees_ > 101.25 && degrees_ <= 123.75) {
point = "ESE"
}
if(degrees_ > 123.75 && degrees_ <= 146.25) {
point = "SE"
}
if(degrees_ > 146.25 && degrees_ <= 168.75) {
point = "SSE"
}
if(degrees_ > 168.75 && degrees_ <= 191.25) {
point = "S"
}
if(degrees_ > 191.25 && degrees_ <= 213.75) {
point = "SSW"
}
if(degrees_ > 213.75 && degrees_ <= 236.25) {
point = "SW"
}
if(degrees_ > 236.25 && degrees_ <= 258.75) {
point = "WSW"
}
if(degrees_ > 258.75 && degrees_ <= 281.25) {
point = "W"
}
if(degrees_ > 281.25 && degrees_ <= 303.75) {
point = "WNW"
}
if(degrees_ > 303.75 && degrees_ <= 326.25) {
point = "NW"
}
if(degrees_ > 326.25 && degrees_ <= 348.75) {
point = "NNW"
}
return point
}
}
#Preview {
ContentView()
}
Pass in your windDegrees
(line 5) as a Double.
Oh, and here's an image for the compassMarker
arrow:
The size of the compass is constrained by the frameSize
so if you change it you'll need to alter the arrow image.
You also don't need to show the degrees and 'WSW' text (for example), and can easily put your own text in the VStack
.