Estou fazendo uma visualização simples de bússola com apenas coisas básicas para visualização e gostaria de animá-la. Estou usando a bússola do meu dispositivo físico (iPhone 13 PRO). Então, tudo parece estar bem - o rumo está correto, a visualização está girando, mas... a animação não funciona, na verdade, qualquer um dos tipos de animação. No entanto, se eu usá-lo para girar todo o ZStack, tudo bem. Não funciona quando estou tentando girar os marcadores do medidor. Por favor, veja o código abaixo:
//
// 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)
}
}
Experimente esta abordagem, onde você coloca
attach
a rotação e a animação noVStack
, conforme mostrado no código de exemplo, ou se preferir, você pode colocarattach
o rotationEffect e a animação noCompassView
próprioContentView
.Também renomeie
GaugeMarkerCompass
paraGaugeMarker
Ok, depois de brincar um pouco com a resposta de @workingdog (que foi muito útil novamente, obrigado), fiz as seguintes alterações e gostaria de publicá-las caso alguém precise de ajuda. Decidi dividir as partes fixa e móvel da minha bússola. aqui está o código e uma imagem da parte fixa: fixa_compass_part
aqui está o código
Depois fiz a rotação e a animação sugerida na resposta que aceitei e apliquei a transação que evita que os rótulos desapareçam (muito útil por sinal) e adicionei .rotationEffect e .animation ao VStack.
veja a imagem: compass_moving_part
aqui está o código:
At the end in the ContentView, I just overlay them and everything seems to work as expected: see picture compass_complete_view see code:
I am using the internal compass of the iPhone for the moment and the heading is taken from there. It is located in a separate file called CompassLogic
code:
Now, there is only one problem remaining - when the compass reaches 360 or 0 degrees it rotates all the way back until it reaches 1 degree.
I know there is a way to do that, so it will behave like a real compass with transition from 360 to 1 degree properly but I haven't managed to find it so far. I have tried different solutions here and from different articles but to no success.
Does anybody have any good suggestions?