I am making a simple compass view with just basic things for a view and I would like to have it animated. I am using the compass of my physical device (iPhone 13 PRO). So, everything seems to be fine - heading is correct, view is rotating but... the animation does not work, actually any of the animation types. However if I use it to rotate the whole ZStack is fine. It doesn't work once I am trying to rotate the gauge markers. Please see code below:
//
// CompassView.swift
// ExtasyCompleteNavigation
//
// Created by Vasil Borisov on 20.08.23.
//
import SwiftUI
struct CompassView: View {
var heading: CGFloat
var body: some View {
VStack{
ZStack{
GeometryReader{ geometry in
let width = geometry.size.width
let height = geometry.size.height
let fontSize = width/13
//compass background
Circle()
//compass heading display and direction
Text("\(-heading,specifier: "%.0f")°\n\(displayDirection())")
.font(Font.custom("AppleSDGothicNeo-Bold", size: width/13))
.foregroundColor(.white)
.position(x: width/2, y:height/2)
//compass degree ring
Group{
MyShape(sections: 12, lineLengthPercentage: 0.15)
.stroke(Color.white, style: StrokeStyle(lineWidth: 5))
MyShape(sections: 360, lineLengthPercentage: 0.15)
.stroke(Color.white, style: StrokeStyle(lineWidth: 2))
//compass arrow
Text("▼")
.font(Font.custom("AppleSDGothicNeo-Bold", size: fontSize))
.position(x:width/2, y: height/200 )
.foregroundColor(.red)
PseudoBoat()
.stroke(lineWidth: 4)
.foregroundColor(.white)
.scaleEffect(x: 0.30, y: 0.55, anchor: .top)
.offset(y: geometry.size.height/5)
}
//heading values
ForEach(GaugeMarkerCompass.labelSet()) { marker in
CompassLabelView(marker: marker, geometry: geometry)
.position(CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2))
}
.rotationEffect(.init(degrees: heading))
.animation(Animation.easeInOut(duration: 3), value: heading)
}.aspectRatio(1, contentMode: .fit)
}
}
}
//function can be moved in a structure with the rest of the tools in swift file
func displayDirection() -> String {
switch heading {
case 0:
return "N"
case 90:
return "E"
case 180:
return "S"
case 270:
return "W"
case 90..<180:
return "SE"
case 180..<270:
return "SW"
case 270..<360:
return "NW"
default:
return "NE"
}
}
//to be moved to a swift file and keep it separate
public struct CompassLabelView: View {
let marker: GaugeMarker
let geometry: GeometryProxy
@State var fontSize: CGFloat = 12
@State var paddingValue: CGFloat = 100
public var body: some View {
VStack {
Text(marker.label)
.foregroundColor(Color.gray)
.font(Font.custom("AppleSDGothicNeo-Bold", size: geometry.size.width * 0.05))
.rotationEffect(Angle(degrees: 0))
.padding(.bottom, geometry.size.width * 0.72)
}.rotationEffect(Angle(degrees: marker.degrees))
.onAppear {
paddingValue = geometry.size.width * 0.72
fontSize = geometry.size.width * 0.07
}
}
}
struct GaugeMarkerCompass: Identifiable, Hashable {
let id = UUID()
let degrees: Double
let label: String
init(degrees: Double, label: String) {
self.degrees = degrees
self.label = label
}
// adjust according to your needs
static func labelSet() -> [GaugeMarker] {
return [
GaugeMarker(degrees: 0, label: "N"),
GaugeMarker(degrees: 30, label: "30"),
GaugeMarker(degrees: 60, label: "60"),
GaugeMarker(degrees: 90, label: "E"),
GaugeMarker(degrees: 120, label: "120"),
GaugeMarker(degrees: 150, label: "150"),
GaugeMarker(degrees: 180, label: "S"),
GaugeMarker(degrees: 210, label: "210"),
GaugeMarker(degrees: 240, label: "240"),
GaugeMarker(degrees: 270, label: "W"),
GaugeMarker(degrees: 300, label: "300"),
GaugeMarker(degrees: 330, label: "330")
]
}
}
}
struct CompassView_Previews: PreviewProvider {
static var previews: some View {
CompassView(heading: 0)
.background(.white)
}
}
Try this approach, where you
attachthe rotation and the animation to theVStack, as shown in the example code, or if you prefer, you canattachthe rotationEffect and animation to theCompassViewitself inContentView.Also rename
GaugeMarkerCompasstoGaugeMarker