Search code examples
swiftswiftuisf-symbols

Swift .symbolEffect disable under condition


I have a SwiftUI Image view structure inside view

Image(systemName: TabBarItem.chat.iconName)
                .resizable()
                .symbolVariant(localSelection == .chat ? .fill : .none)
                .symbolEffect(.bounce, options: .repeating, value: tabBarStateManager.shouldAnimateChatTabIcon)
                .symbolEffect(.variableColor.iterative, options: .repeating, value: tabBarStateManager.shouldAnimateChatTabIcon)

along with two symbol effects. I want these effects to repeat as long as tabBarStateManager's @Published var shouldAnimateChatTabIcon: Bool is equal to true. I guess that value argument in .symbolEffect modifier is only to trigger the animation, but how to disable it on some condition?


Solution

  • If I understand correctly, you want the symbol effect to begin when the flag is set to true and the effect should repeat continuously until the flag is set to false. Then it should stop.

    You can get quite close to this just by making the SymbolEffectOptions conditional on the flag. What then happens is that when the flag changes to false, it performs one more cycle and then stops. If a small lag before stopping is acceptable then this gives an easy solution:

    .symbolEffect(
        .bounce,
        options: tabBarStateManager.shouldAnimateChatTabIcon ? .repeating : .nonRepeating,
        value: tabBarStateManager.shouldAnimateChatTabIcon
    )
    .symbolEffect(
        .variableColor.iterative,
        options: tabBarStateManager.shouldAnimateChatTabIcon ? .repeating : .nonRepeating,
        value: tabBarStateManager.shouldAnimateChatTabIcon
    )
    

    Alternatively, the lag can be avoided altogether by introducing a second flag:

    @State private var runAnimation = false
    

    The flag in the model can then be used as the parameter to .symbolEffectsRemoved, which allows the symbol effects to be removed conditionally. When the effects are present, the second flag is used to set them running.

    It is interesting that .symbolEffectsRemoved needs to be applied before the .symbolEffect modifiers. The documentation gives a clue as to why:

    Returns a new view with its inherited symbol image effects either removed or left unchanged.

    So it seems that a symbol can have some inherited effects and the .symbolEffect modifiers set them into action. To prevent the .symbolEffect modifiers from working, the inherited effects must be removed first, not afterwards.

    Image(systemName: TabBarItem.chat.iconName)
        .resizable()
        .symbolVariant(localSelection == .chat ? .fill : .none)
        .symbolEffectsRemoved(!tabBarStateManager.shouldAnimateChatTabIcon)
        .symbolEffect(.bounce, options: .repeating, value: runAnimation)
        .symbolEffect(.variableColor.iterative, options: .repeating, value: runAnimation)
        .onChange(of: tabBarStateManager.shouldAnimateChatTabIcon) { oldVal, newVal in
            if newVal {
                runAnimation.toggle()
            }
        }
    

    Animation