12
votes

How can I animate Tabbar Items (of a TabView) on selection in SwiftUI?

for example give the selected item a .scaleEffect() with .spring() animation or sth like below:

Example

This is what I've tried so far:

struct MyTabbedView: View {
    @State var enlargeIt1 = false
    @State var enlargeIt2 = true

    var body: some View {
        TabView {
            Text("Item 1")
                .onAppear {
                    self.enlargeIt1.toggle()
                    self.enlargeIt2.toggle()
                }
                .tabItem{
                    VStack{
                        Image(systemName: "music.note")
                            .font(self.enlargeIt1 ? .system(size: 30) : .system(size: 15) )
                            .animation(.interpolatingSpring(mass: 0.7, stiffness: 200, damping: 10, initialVelocity: 4))
                        Text("Music")
                    }
                }.tag(1)

            Text("Item 2")
                .onAppear {
                    self.enlargeIt1.toggle()
                    self.enlargeIt2.toggle()
                }
                .tabItem{
                    VStack{
                        Image(systemName: "music.mic")
                            .font(self.enlargeIt2 ? .system(size: 30) : .system(size: 15) )
                            .animation(.interpolatingSpring(mass: 0.7, stiffness: 200, damping: 10, initialVelocity: 4))
                        Text("Mic")
                    }
                }.tag(2)

        }
    }
}

and the result is this:

enter image description here

I tried approximately the same code in a separate View called TestView :

struct TestView: View {
    @State var enlargeIt1 : Bool = false

    var body: some View {
        VStack{
            Image(systemName: "music.note")
                .font(self.enlargeIt1 ? .system(size: 30) : .system(size: 15) )
                .animation(.interpolatingSpring(mass: 0.7, stiffness: 200, damping: 10, initialVelocity: 4))
            Text("Music")
        }
        .onTapGesture {
            self.enlargeIt1.toggle()
        }

    }
}

and this is the result:

TestView

What's wrong with the TabView I've created that it's not giving the same result?

2
"Tab views only support tab items of type Text, Image, or an image followed by text. Passing any other type of view results in a visible but empty tab item." It sounds like you can't really modify the style of tab items. sourceBenjamin Kindle
please be a bit more specific, what exactly do you want / expect to happen?thisIsTheFoxe
@thisIsTheFox sth like this: dribbble.com/shots/5693620-Mobile-tabbar-QontoMehdi
@kontiki My friend kontiki the brain💡, any opinions?Mehdi

2 Answers

1
votes

Here is possible approach for standard TabView (for provided code snapshot).

The idea is to use animatable modifier for font size over used SF images.

Tested with Xcode 11.4 / iOS 13.4

demo

// Animating font size
struct AnimatableSFImage: AnimatableModifier {
    var size: CGFloat

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

    func body(content: Self.Content) -> some View {
        content
            .font(.system(size: size))
    }
}

// helper extension
extension Image {
    func animatingSF(size: CGFloat) -> some View {
        self.modifier(AnimatableSFImage(size: size))
    }
}

// Modified test code snapshot
struct TestAnimatedTabBar: View {
    @State var enlargeIt1 = false
    @State var enlargeIt2 = true

    var body: some View {
        TabView {
            Text("Item 1")
                .onAppear {
                    self.enlargeIt1.toggle()
                    self.enlargeIt2.toggle()
                }
                .tabItem{
                    VStack{
                        Image(systemName: "music.note")
                            .animatingSF(size: self.enlargeIt1 ? 30 : 15 )
                        Text("Music")
                    }.animation(.interpolatingSpring(mass: 0.7, 
             stiffness: 200, damping: 10, initialVelocity: 4))
                }.tag(1)

            Text("Item 2")
                .onAppear {
                    self.enlargeIt1.toggle()
                    self.enlargeIt2.toggle()
                }
                .tabItem{
                    VStack{
                        Image(systemName: "music.mic")
                            .animatingSF(size: self.enlargeIt2 ? 30 : 15 )
                        Text("Mic")
                    }.animation(.interpolatingSpring(mass: 0.7, 
             stiffness: 200, damping: 10, initialVelocity: 4))
                }.tag(2)

        }
    }
}
0
votes

Someone created a custom TabView that might be of help to you. I'm sure you could modify it to suit your needs.

BottomBar component for SwiftUI inspired by this concept

https://github.com/smartvipere75/bottombar-swiftui

import SwiftUI
import BottomBar_SwiftUI

let items: [BottomBarItem] = [
    BottomBarItem(icon: "house.fill", title: "Home", color: .purple),
    BottomBarItem(icon: "heart", title: "Likes", color: .pink),
    BottomBarItem(icon: "magnifyingglass", title: "Search", color: .orange),
    BottomBarItem(icon: "person.fill", title: "Profile", color: .blue)
]

struct BasicView: View {
    let item: BottomBarItem

    var detailText: String {
    "\(item.title) Detail"
}

var followButton: some View {
    Button(action: openTwitter) {
        VStack {
            Text("Developed by Bezhan Odinaev")
                .font(.headline)
                .color(item.color)

            Text("@smartvipere75")
                .font(.subheadline)
                .foregroundColor(.gray)
        }
    }
}

var destination: some View {
    Text(detailText)
        .navigationBarTitle(Text(detailText))
}

var navigateButton: some View {
    NavigationLink(destination: destination) {
        ZStack {
            Rectangle()
                .fill(item.color)
                .cornerRadius(8)
                .frame(height: 52)
                .padding(.horizontal)

            Text("Navigate")
                .font(.headline)
                .foregroundColor(.white)
        }
    }
}

func openTwitter() {
    guard let url = URL(string: "https://twitter.com/smartvipere75") else {
        return
    }
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

var body: some View {
    VStack {
        Spacer()

        followButton

        Spacer()

        navigateButton
        }
    }
}

struct ContentView : View {
    @State private var selectedIndex: Int = 0

    var selectedItem: BottomBarItem {
        items[selectedIndex]
    }

    var body: some View {
        NavigationView {
            VStack {
                BasicView(item: selectedItem)
                    .navigationBarTitle(Text(selectedItem.title))
                BottomBar(selectedIndex: $selectedIndex, items: items)
            }
        }
    }
}