Search code examples
swiftuiswiftui-animation

How to alter a button's label animations from multiple view in my project?


I have a SwiftUI button in one of my views. For the label I use two different SF Symbols with different colors and animations based on the value of a var "gpsIsActive"

Here is a video:

enter image description here

The button works fine. But I need to trigger these same color and animation changes in multiple views across multiple files. How do I do this⁉️

I have tried making a public function that returned "some View" and all it did was return the Image. Everywhere I called this function I received warning that I was not using the returned result (because I simply wanted to trigger the changes).

Now I need to call this from another file and the fact that it is a function that returns a View is causing further problems.

The answer may be something related to @State & @Binding but I can't figure it out. Please help! LOL

The Original Button...

Button {
    daFellowBeenTapped()
 } label: {
    Image(systemName: gpsIsActive ? "figure.run.circle" :"figure.wave.circle")
       .contentTransition(.symbolEffect(.replace.downUp))
       .symbolRenderingMode(.palette)
       .foregroundStyle(personColor, circleColor)
       .font(.system(size: 50, weight: .thin))
       .frame(width: Constants.roundedViewL, height: Constants.roundRectViewH)
       .foregroundColor(.clear)
       .background(Color(.clear))
       .clipShape(Circle())
       .symbolEffect(gpsIsActive ? .bounce.up.byLayer : .bounce.down.byLayer, 
            options: gpsIsActive ? .speed(0.0).repeating : .nonRepeating, value: gpsIsActive)

 }
 .frame(height: 56)

The Button with Function (causing problems elsewhere)...

 Button {
    daFellowBeenTapped()
 } label: {
    daButtonPretty()
 }
public func daButtonPretty() -> some View {
   return Image(systemName: gpsMonitoringIsActive ? "figure.run.circle" :  "figure.wave.circle")
      .contentTransition(.symbolEffect(.replace.downUp))
      .symbolRenderingMode(.palette)
      .foregroundStyle(personColor, circleColor)
      .font(.system(size: 50, weight: .thin))
      .frame(width: Constants.General.roundedViewLength, height: Constants.General.roundRectViewHeight)
      .foregroundColor(.clear)
      .background(Color(.clear))
      .clipShape(Circle())
      .symbolEffect(gpsMonitoringIsActive ? .bounce.up.byLayer : .bounce.down.byLayer, options: gpsMonitoringIsActive ? .speed(0.0).repeating : .nonRepeating, value: gpsMonitoringIsActive)
}

Solution

  • It sounds like you need to encapsulate the state information in a model and then pass the model to the views that need to use it.

    • You could either do this by using a struct which is held as a State variable in the parent view and passed as a Binding to the child views

    • or, you could use an ObservableObject, which is held as a StateObject in the parent view and observed as an ObservedObject in the child views.

    Here is an example of the latter approach:

    class ActiveInfo: ObservableObject {
        @Published var isActive: Bool
    
        init(isActive: Bool) {
            self.isActive = isActive
        }
    
        var color: Color {
            isActive ? Color.green : Color.red
        }
    
        func toggle() {
            isActive.toggle()
        }
    }
    
    struct ContentView: View {
        @StateObject private var activeInfo = ActiveInfo(isActive: false)
    
        var body: some View {
            VStack(spacing: 50) {
                Text(activeInfo.isActive ? "ACTIVE" : "INACTIVE")
                DaFellowButton(activeInfo: activeInfo)
                Button("Toggle") {
                    activeInfo.toggle()
                }
                .buttonStyle(.borderedProminent)
                .tint(activeInfo.color)
            }
        }
    }
    
    struct DaFellowButton: View {
    
        @ObservedObject var activeInfo: ActiveInfo
        let circleColor = Color.blue
    
        private func daFellowBeenTapped() {
            activeInfo.toggle()
        }
    
        private var gpsIsActive: Bool {
            activeInfo.isActive
        }
    
        private var personColor: Color {
            activeInfo.color
        }
    
        var body: some View {
            Button {
                daFellowBeenTapped()
             } label: {
                Image(systemName: gpsIsActive ? "figure.run.circle" :"figure.wave.circle")
                   .contentTransition(.symbolEffect(.replace.downUp))
                   .symbolRenderingMode(.palette)
                   .foregroundStyle(personColor, circleColor)
                   .font(.system(size: 50, weight: .thin))
                   .frame(width: Constants.roundedViewL, height: Constants.roundRectViewH)
                   .foregroundColor(.clear)
                   .background(Color(.clear))
                   .clipShape(Circle())
                   .symbolEffect(gpsIsActive ? .bounce.up.byLayer : .bounce.down.byLayer,
                        options: gpsIsActive ? .speed(0.0).repeating : .nonRepeating, value: gpsIsActive)
    
             }
             .frame(height: 56)
        }
    }
    

    Animation