Floating action button with an animated menu in SwiftUI

Share on facebook
Share on twitter
Share on pinterest

Welcome to a new SwiftUI tutorial! Today we will learn how to create a floating action button with an animated floating Menu.

This is what our app will look like at the end:

If you want to skip the tutorial, you can simply view the relevant source code here.

First, we create a new SwiftUI Single View App in Xcode. Then we add a new File-New-File to our project and create a new SwiftUI View. We call this FloatingMenu. In this view, we will implement the floating action button with its corresponding menu. We will then use the finished menu inside our actual ContentView.

Creating the Floating Button ☁️

For the floating button, we insert a corresponding system icon into our FloatingMenu‘s body.

struct FloatingMenu: View {
    var body: some View {
        Image(systemName: "plus.circle.fill")
    }
}

We enlarge the image and give the icon a purple color.

Image(systemName: "plus.circle.fill")
            .resizable()
            .frame(width: 80, height: 80)
            .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))

To give the icon a certain plasticity, we would like to add a slight drop shadow to it. To do this we use the .shadow modifier and create a slightly shifted, gray shadow with a small radius.

Image(systemName: "plus.circle.fill")
            .resizable()
            .frame(width: 80, height: 80)
            .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
            .shadow(color: .gray, radius: 0.2, x: 1, y: 1)

With a slight drop shadow, we create some plasticity to our button

Finally, we wrap the image into a button. For now, we use a dummy print statement as the button’s action.

Button(action: {
            print("Show Menu")
        }) {
            Image(systemName: "plus.circle.fill")
                //...
        }

Adding the Floating Menu Items ✍️

When the user taps the button, we want to display the menu items above the floating action button. To do this, we wrap the button into a VStack.

VStack {
            Button(action: {
                print("Show Menu")
            }) {
                //...
            }
        }

Each menu item consists of a circle containing a certain icon. To stack these two elements on top of each other we use a ZStack inside the VStack.

ZStack {
                Circle()
                    .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
                    .frame(width: 55, height: 55)
                Image(systemName: "camera.fill")
                    .imageScale(.large)
                    .foregroundColor(.white)
            }

The menu item itself should have a drop shadow as well, so we reuse the .shadow modifier for the ZStack itself.

ZStack {
                //...
            }
                .shadow(color: .gray, radius: 0.2, x: 1, y: 1)

We can now CMD-Click on the ZStack and select “Extract as Subview”. We call this view MenuItem. Since each MenuItem should have a different icon, we add a property to the MenuItem struct that we use for the icon image.

struct MenuItem: View {
    
    var icon: String
    
    var body: some View {
        ZStack {
            Circle()
                .foregroundColor(Color(red: 153/255, green: 102/255, blue: 255/255))
                .frame(width: 55, height: 55)
            Image(systemName: icon)
                .imageScale(.large)
                .foregroundColor(.white)
        }
        .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
    }
}

We then initialize the icon from our FloatingMenu view.

VStack {
            MenuItem(icon: "camera.fill")
            //...
        }

Then, we add two more MenuItems with different icons. We push the whole menu down by using a Spacer.

VStack {
            Spacer()
            MenuItem(icon: "camera.fill")
            MenuItem(icon: "photo.on.rectangle")
            MenuItem(icon: "square.and.arrow.up.fill")
            //...
        }

The MenuItems should only be displayed if the user has tapped on the button. Thus, we create three different States in our FloatingMenu view for the three according MenuItems

struct FloatingMenu: View {
    
    @State var showMenuItem1 = false
    @State var showMenuItem2 = false
    @State var showMenuItem3 = false
    
    var body: some View {
        //...
    }
}

Only if these States are true, we want to display the MenuItems.

VStack {
            Spacer()
            if showMenuItem1 {
                MenuItem(icon: "camera.fill")
            }
            if showMenuItem2 {
                MenuItem(icon: "photo.on.rectangle")
            }
            if showMenuItem3 {
                MenuItem(icon: "square.and.arrow.up.fill")
            }
            //...
        }

We can now toggle the States from our action button. For this purpose, we create a function called showMenu.

struct FloatingMenu: View {
    
    //...
    
    var body: some View {
        //...
    }
    
    func showMenu() {
        showMenuItem3.toggle()
        showMenuItem2.toggle()
        showMenuItem1.toggle()
    }
}

We can now call this function from our button:

Button(action: {
                self.showMenu()
            }) {
                Image(systemName: "plus.circle.fill")
                    //...
            }

Execute the app in the preview simulator and click on the floating button. Great, our menu items show up! When we click on the button again, our menu disappears again.


However, we want the different MenuItems to appear one after the other rather than at the same time. They should also move in from the right side of the screen.

Animating the Items 🚀

To display the different MenuItems delayed, we have to toggle the corresponding states in our showMenu function with a delay as well.

To execute code with a delay, we use the DispatchQueue.main.asyncAfter method.

func showMenu() {
        showMenuItem3.toggle()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            self.showMenuItem2.toggle()
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            self.showMenuItem1.toggle()
        })
    }

If we now click on the action button, the showMenu1 state will be toggled immediately. After one-tenth of a second the showMenuItem2 state and after another tenth of a second the showMenuItem3 state gets toggled.


Now we want the MenuItems to move in from the right side. To animate the toggling of the MenuItems we have to use the withAnimation statement within our showMenu function.

func showMenu() {
        withAnimation {
            showMenuItem3.toggle()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            withAnimation {
                self.showMenuItem2.toggle()
            }
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            withAnimation {
                self.showMenuItem1.toggle()
            }
        })
    }

Now we can add a transition to our MenuItem view.

ZStack {
            //...
        }
        .shadow(color: .gray, radius: 0.2, x: 1, y: 1)
        .transition(.move(edge: .trailing))

Hint: The transition may not be displayed correctly in the preview simulator. We will change this in a moment by adding the FloatingMenu to our ContentView. Then we will be able to run our app in the normal simulator.

Inserting the Floating Menu into our ContentView 🖼

We would like to add the FloatingMenu to the bottom right of our ContentView. To make this possible, we first have to create a transparent view that covers the entire screen. For this purpose, we use a Rectangle object.

struct ContentView: View {
    var body: some View {
        Rectangle()
            .foregroundColor(.clear)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

To stack the FloatingMenu on top of this transparent rectangle, we wrap the rectangle into a ZStack and use the .bottomTrailing alignment option.

ZStack(alignment: .bottomTrailing) {
            Rectangle()
                .foregroundColor(.clear)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }

Now we can insert our FloatingMenu. We also apply some padding to it

ZStack(alignment: .bottomTrailing) {
            Rectangle()
                .foregroundColor(.clear)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            FloatingMenu()
                .padding()
        }

If we now run the app in the regular simulator, we see our floating action button in the lower right corner. If we tap on it, the individual menu items move in from the right side of the screen!

Conclusion 🎊

Awesome! We learned how to create our own floating action button with an animated menu in SwiftUI. You can now use this knowledge to add your own floating menus to your SwiftUI apps.

The complete source code for the app can be found here.

I hope you enjoyed this tutorial! If you want to learn more about SwiftUI, check out our other tutorials! Also make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

2 replies on “Floating action button with an animated menu in SwiftUI”

Overall great tutorial, I was just wondering how to hook up the menu items with buttons that execute functions from my ContentView?

Hello Tamas and thanks for your feedback! You can simply wrap the Text views into buttons as described in this tutorial. For instance you can use them to navigate to a new view. I hope that helps you!

Leave a Reply

Your email address will not be published. Required fields are marked *

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount

small_c_popup.png

Are you ready for a new era of iOS development?

Start learning swiftUI today - download our free e-book