iosxcodeswiftuiios-animationssf-symbols

Trying to get SF Symbols to animate (bounce once when pressed on specified tab) in a TabView using SwiftUI


I am trying to get my SF Symbols to animate when I click the tabs at the bottom of the screen. I have it set up where if the user is signed in, it will show the TabView of "Home" and "Profile" [tabItems]. For some reason, the animation is not rendering on my simulator in Xcode. I'm not sure if it is because I don't have it in a V or HStack or if it is some other reason. I saw other tutorials about different ways to do this but none showed how to create a TabView with animated SF Symbols. I am currently trying to follow the SwiftUI Masterclass: Build To Do List App | Youtube to build the actual to-do list app, but just want to add the animation for the tab icons to bounce when you click them.

Screenshot of Xcode simulator iOS

I apologize, the image doesn't really contain much content as I am still working on the project, but I thought I would include it to help you visualize where the icons are on the screen.

I initially did not have the Buttons inside the .tabItem for ToDoListView() and ProfileView() (these are both views in separate files and have their own View Model files as well). I'm not sure if having the Button in there is messing it up, but I couldn't find resources on how to do it another way. If anyone has suggestions on how I can keep it in a TabView with .tabItem (without the Buttons) with the animations, feel free to chime in. But this is the code I have, the icons show up, but no animation is present.

I used .symbolEffect() to call for the effect to animate and copied the configuration from SF Symbols to Xcode, so I don't think that is the issue, but I am also a beginner, so advice is welcome. I specifically want to use the bounce animation, as shown in my code, but I tried to toggle wiggle and breathe and they didn't work either.

import SwiftUI
struct MainView: View {
    @StateObject var viewModel = VM_MainView()
    @State private var bounce = false
    
    
    var body: some View {
        if viewModel.isSignedIn, !viewModel.currentUserId.isEmpty {
            TabView {
                ToDoListView()
                    .tabItem {
                        Button{
                            bounce.toggle()
                        }label: {
                            Text("Home")
                            
                        }
                        Image(systemName: "house.fill")
                            .scaleEffect(2)
                            .symbolRenderingMode(.hierarchical)
                            .symbolEffect(.bounce.up.wholeSymbol, value: bounce)
                    }
                ProfileView()
                    .tabItem {
                        Button{
                            bounce.toggle()
                        }label: {
                            Text("Profile")
                            Image(systemName: "person.crop.circle.fill")
                                .scaleEffect(2)
                                .symbolRenderingMode(.hierarchical)
                                .symbolEffect(.bounce.up.wholeSymbol, value: bounce)
                        }
                    }
                }
            
        } else {
            LoginView()
        }
    }
}

#Preview {
    MainView()
}

Solution

  • It seems currently that doing animations or symboleffects within TabViews is not properly supported in SwiftUI. When I played around with it, a lot of the times, both buttons would trigger the symboleffect animation at the same time or trigger twice for no reason, so I decided to go with a work around. Instead of a tab view, I would just overlay views and use buttons. Here is a working example of what I think you were trying to do:

    import SwiftUI
    
    struct BounceTestView: View {
        @State private var bounce1 = false
        @State private var bounce2 = false
        @State private var showProfileView = false
    
    var body: some View {
        ZStack {
    
            ToDoListView()
                .opacity(showProfileView ? 0 : 1)
    
            if showProfileView {
                ProfileView()
                    .transition(.opacity)
            }
    
            VStack {
                Spacer()
    
                HStack(spacing: 150) {
                    
                    Button(action: {
                        showProfileView = false
                        bounce1.toggle()
                    }, label: {
                        VStack(spacing: 5) {
                            Image(systemName: "house.fill")
                                .font(.system(size: 25))
                                .symbolRenderingMode(.hierarchical)
                                .foregroundColor(showProfileView ? .gray : .teal)
                                .symbolEffect(.bounce, value: bounce1)
                            Text("Home")
                                .font(.system(size: 10, weight: .bold))
                                .foregroundColor(showProfileView ? .gray : .teal)
                        }
                    })
                    
                    Button(action: {
                        showProfileView = true
                        bounce2.toggle()
                    }, label: {
                        VStack(spacing: 5) {
                            Image(systemName: "person.circle.fill")
                                .font(.system(size: 25))
                                .symbolRenderingMode(.hierarchical)
                                .foregroundColor(showProfileView ? .teal : .gray)
                                .symbolEffect(.bounce, value: bounce2)
                            Text("Profile")
                                .font(.system(size: 10, weight: .bold))
                                .foregroundColor(showProfileView ? .teal : .gray)
                        }
                    })
                }
                Spacer().frame(height: 25)
            }
            .edgesIgnoringSafeArea(.bottom)
        }
      }
    }
    
    #Preview {
        BounceTestView()
    }
    
    struct ProfileView: View {
        var body: some View {
            Text("Profile View")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.blue.opacity(0.3))
        }
    }
    
    struct ToDoListView: View {
        var body: some View {
            Text("To Do List View")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.green.opacity(0.3))
        }
    }