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:
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
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)
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)
}
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)
}
}