RotatingButtonStyle.swift

Home   »   RotatingButtonStyle.swift

//
//  RotatingButtonStyle.swift
//
//  Created by Thomas on 02/05/2021.
//  based on pausable animation sollution:
//  https://stackoverflow.com/a/64318967/147163
//

import SwiftUI

struct RotatingButtonStyleView: View {
    @State private var isPlaying: Bool = true

    var body: some View {
        VStack {
            Text("isPlaying: \(isPlaying.description)")
            Button {
                isPlaying.toggle()
            } label: {
                Text("💿")
                    .font(.system(size: 200))
            }
            .buttonStyle(RotatingButtonStyle(isRotating: isPlaying))
        }
    }
}

struct RotatingButtonStyle: ButtonStyle {
    var isRotating: Bool

    @State private var desiredAngle: CGFloat = 0.0
    @State private var currentAngle: CGFloat = 0.0

    var startAngle: CGFloat {
        currentAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2)
    }
    var angleDelta: CGFloat {
        isRotating ? CGFloat.pi * 2 : 0.0
    }

    var foreverAnimation: Animation {
        Animation.linear(duration: 2)
            .repeatForever(autoreverses: false)
    }

    var stoppingAnimation = Animation.linear(duration: 0)

    func toggleRotation(when isNowRotating: Bool) {

        withAnimation(isNowRotating ? foreverAnimation : stoppingAnimation) {
            desiredAngle = startAngle + angleDelta
        }
    }

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .modifier(
                PausableRotationEffect(
                    desiredAngle: desiredAngle,
                    currentAngle: $currentAngle
                )
            )
            .onAppear {
                toggleRotation(when: isRotating)
            }
            .onChange(of: isRotating, perform: { isRotatingChanged in
                toggleRotation(when: isRotatingChanged)
            })

        Text("desiredAngle \(desiredAngle)")
        Text("currentAngle \(currentAngle)")
        Text("startAngle \(startAngle)")
        Text("angleDelta \(angleDelta)")

    }

}

struct PausableRotationEffect: GeometryEffect {
    @Binding var currentAngle: CGFloat
    private var currentAngleValue: CGFloat = 0.0

    var animatableData: CGFloat {
        get { currentAngleValue }
        set { currentAngleValue = newValue }
    }

    init(desiredAngle: CGFloat, currentAngle: Binding) {
        currentAngleValue = desiredAngle
        _currentAngle = currentAngle
    }

    func effectValue(size: CGSize) -> ProjectionTransform {
        DispatchQueue.main.async {
            currentAngle = currentAngleValue
        }
        let xOffset = size.width / 2
        let yOffset = size.height / 2
        let transform =
            CGAffineTransform(translationX: xOffset, y: yOffset)
            .rotated(by: currentAngleValue)
            .translatedBy(x: -xOffset, y: -yOffset)

        return ProjectionTransform(transform)
    }
}

struct RotatingButtonStyleTestView_Previews: PreviewProvider {
    static var previews: some View {
        RotatingButtonStyleView()
    }
}

Leave a Reply

Your email address will not be published.