AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 76955877
Accepted
bacata.borisov
bacata.borisov
Asked: 2023-08-23 02:09:40 +0800 CST2023-08-23 02:09:40 +0800 CST 2023-08-23 02:09:40 +0800 CST

Por que minha animação não funciona para uma bússola feita em SwiftUI

  • 772

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)
    }
}
animation
  • 2 2 respostas
  • 42 Views

2 respostas

  • Voted
  1. Best Answer
    workingdog support Ukraine
    2023-08-23T06:55:40+08:002023-08-23T06:55:40+08:00

    Experimente esta abordagem, onde você coloca attacha rotação e a animação no VStack, conforme mostrado no código de exemplo, ou se preferir, você pode colocar attacho rotationEffect e a animação no CompassViewpróprio ContentView.

    Também renomeie GaugeMarkerCompassparaGaugeMarker

    struct ContentView: View {
        @State var heading: CGFloat = 0.0  // <-- here
        
        var body: some View {
            VStack (alignment: .leading, spacing: 44) {
                // for testing
                Button("change heading") {
                    heading += 10.0
                }.padding(10).buttonStyle(.bordered)
                
                CompassView(heading: heading)  // <-- here
    //                .rotationEffect(.init(degrees: heading))  // <--- here
    //                .animation(.linear(duration: 2), value: heading)  // <--- here
            }
        }
    }
    
    struct CompassView: View {
        let heading: CGFloat  // <-- here
        
        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(GaugeMarker.labelSet()) { marker in   // <-- here
                            CompassLabelView(marker: marker,  geometry: geometry)
                                .position(CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2))
                        }
                        // <--- NOT here
                    }.aspectRatio(1, contentMode: .fit)
                }
                // --- here to stop the fading of labels
                .transaction { transaction in
                    transaction.animation = nil
                }
            }
            .rotationEffect(.init(degrees: heading))  // <--- here
            .animation(Animation.linear(duration: 2), value: heading)  // <--- here
        }
        
        //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"
            }
            
        }
        
    }
    
    • 0
  2. bacata.borisov
    2023-08-23T10:24:42+08:002023-08-23T10:24:42+08:00

    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

    //
    //  CompassStaticView.swift
    //  ExtasyCompleteNavigation
    //
    //  Created by Vasil Borisov on 22.08.23.
    //
    
    import SwiftUI
    
    struct CompassStaticView: View {
        
        var heading: CGFloat
        
        var body: some View {
            
            ZStack{
                
                GeometryReader{ geometry in
                    
                    let width = geometry.size.width
                    //let width: CGFloat = min(geometry.size.width, geometry.size.height)
                    let height = geometry.size.height
    
    
                    let fontSize = width/15
                    
                    Circle()
                        .position(.init(x: width/2, y: height/2))
                    Circle()
                        .position(.init(x: width/2, y: height/2))
                        .foregroundColor(.gray)
                        .scaleEffect(x: 0.02, y:0.02, anchor: .center)
                    Text("▼")
                        .font(Font.custom("AppleSDGothicNeo-Bold", size: fontSize))
                        .position(x:width/2, y: height/2 )
                        .offset(y: -width/2.05)
                        .foregroundColor(.red)
                    Text("\(heading, specifier: "%.0f")°\(displayDirection())")
                        .font(Font.custom("AppleSDGothicNeo-Bold", size: fontSize ))
                        .foregroundColor(.white)
                        .position(x: width/2, y:height/1.39)
                    PseudoBoat()
                        .stroke(lineWidth: 4)
                        .foregroundColor(.white)
                        .scaleEffect(x: 0.28, y: 0.58, anchor: .top)
                        .offset(y: geometry.size.height/5.5)
                    
                }//.overlay(CompassView(heading: 0))
                .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 22.5..<67.5:
                return "NE"
            case 67.5..<112.5:
                return "E"
            case 112.5..<157.5:
                return "SE"
            case 157.5..<202.5:
                return "S"
            case 202.5..<247.5:
                return "SW"
            case 247.5..<292.5:
                return "W"
            case 292.5..<337.5:
                return "NW"
            default:
                return "N"
            }
            
        }
    }
    
    struct CompassStaticView_Previews: PreviewProvider {
        static var previews: some View {
            CompassStaticView(heading: 0)
        }
    }
    
    

    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:

    //
    //  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
                        
                        //compass degree ring
                        
                        Group{
                            MyCompassShape(sections: 12, lineLengthPercentage: 0.15)
                                .stroke(Color.white, style: StrokeStyle(lineWidth: 5))
                            
                            MyCompassShape(sections: 360, lineLengthPercentage: 0.15)
                                .stroke(Color.white, style: StrokeStyle(lineWidth: 2))
                        }
                        .scaleEffect(x: 0.75, y:0.75)
                        
                        //heading values
                        ForEach(GaugeMarkerCompass.labelSet()) { marker in
                            CompassLabelView(marker: marker,  geometry: geometry)
                                .position(CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2))
                            
                        }
                    }
                }.aspectRatio(1, contentMode: .fit)
            }.transaction { transaction in
                transaction.animation = nil
            }
            //rotate only the second Vstack
            .rotationEffect(.init(degrees: heading))  // <--- here
            .animation(.easeInOut(duration: 2), value: heading)// <--- here
            
            
        }
        
        struct MyCompassShape : Shape {
            var sections : Int
            var lineLengthPercentage: CGFloat
            
            func path(in rect: CGRect) -> Path {
                let radius = rect.width / 1.6
                let degreeSeparation : Double = 360.0 / Double(sections)
                var path = Path()
                for index in 0..<Int(360.0/degreeSeparation) {
                    let degrees = Double(index) * degreeSeparation
                    let center = CGPoint(x: rect.midX, y: rect.midY)
                    let innerX = center.x + (radius - rect.size.width * lineLengthPercentage / 2) * CGFloat(cos(degrees / 360 * Double.pi * 2))
                    let innerY = center.y + (radius - rect.size.width * lineLengthPercentage / 2) * CGFloat(sin(degrees / 360 * Double.pi * 2))
                    let outerX = center.x + radius * CGFloat(cos(degrees / 360 * Double.pi * 2))
                    let outerY = center.y + radius * CGFloat(sin(degrees / 360 * Double.pi * 2))
                    path.move(to: CGPoint(x: innerX, y: innerY))
                    path.addLine(to: CGPoint(x: outerX, y: outerY))
                }
                return path
            }
        }
        //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.7)
                }.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(.black)
        }
    }
    
    

    At the end in the ContentView, I just overlay them and everything seems to work as expected: see picture compass_complete_view see code:

    //
    //  ContentView.swift
    //  ExtasyCompleteNavigation
    //
    //  Created by Vasil Borisov on 13.06.23.
    //
    
    import SwiftUI
    import CocoaMQTT
    
    struct ContentView: View {
        
        //instance of the ConnectionManager which is observableobject
        @StateObject var mqttManager = MQTTManager()
        @StateObject var compassHeading = CompassHeading()
        
        var body: some View {
            
            VStack {
                CompassStaticView(heading: compassHeading.degrees)
                    .overlay(CompassView(heading: compassHeading.degrees))
    
                
                AnemometerView(windAngle: mqttManager.windAngle, windSpeed: mqttManager.windSpeed)
                
                
            }.onAppear{
                
                //that is commented so it doesn't connect every time to the RPi server
                //if you want to test, you have uncomment that
                _ = mqttManager.mqttClient.connect()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.white)
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    

    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:

    //
    //  CompassLogic.swift
    //  ExtasyCompleteNavigation
    //
    //  Created by Vasil Borisov on 21.08.23.
    //
    
    import Foundation
    import Combine
    import CoreLocation
    
    class CompassHeading: NSObject, ObservableObject, CLLocationManagerDelegate {
        var objectWillChange = PassthroughSubject<Void, Never>()
        @Published var degrees: Double = .zero {
            didSet {
                objectWillChange.send()
            }
        }
        
        private let locationManager: CLLocationManager
        
        override init() {
            self.locationManager = CLLocationManager()
            super.init()
            
            self.locationManager.delegate = self
            self.setup()
        }
        
        private func setup() {
            self.locationManager.requestWhenInUseAuthorization()
            
            if CLLocationManager.headingAvailable() {
                self.locationManager.startUpdatingLocation()
                self.locationManager.startUpdatingHeading()
            }
        }
        
        
        //fix the logic here when passing through 0
        func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
            self.degrees = newHeading.magneticHeading
        }
    }
    
    

    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?

    • 0

relate perguntas

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    destaque o código em HTML usando <font color="#xxx">

    • 2 respostas
  • Marko Smith

    Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}?

    • 1 respostas
  • Marko Smith

    Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)?

    • 2 respostas
  • Marko Smith

    Por que as compreensões de lista criam uma função internamente?

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 respostas
  • Marko Smith

    Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)?

    • 4 respostas
  • Marko Smith

    Por que o construtor de uma variável global não é chamado em uma biblioteca?

    • 1 respostas
  • Marko Smith

    Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto?

    • 1 respostas
  • Marko Smith

    Somente operações bit a bit para std::byte em C++ 17?

    • 1 respostas
  • Martin Hope
    fbrereto Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi Por que as compreensões de lista criam uma função internamente? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A formato fmt %H:%M:%S sem decimais 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python std::views::filter do C++20 não filtrando a visualização corretamente 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa Por que o construtor de uma variável global não é chamado em uma biblioteca? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev Por que os compiladores perdem a vetorização aqui? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan Somente operações bit a bit para std::byte em C++ 17? 2023-08-17 17:13:58 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve