[ad_1]
One cause why I actually take pleasure in programming utilizing the SwiftUI framework is that it makes really easy to animate view modifications. Particularly, the introduction of the matchedGeometryEffect
modifier, launched in iOS 14, additional simplifies the implementation of view animations. With matchedGeometryEffect
, all you want is describe the looks of two views. The modifier will then compute the distinction between these two views and robotically animates the scale/place modifications.
We have now written a detailed tutorial on matchedGeometryEffect
. I extremely suggest you to test it out if that is the very first time you come throughout this modifier. On this tutorial, we’ll make use of matchedGeometryEffect
to develop an animated navigation menu just like the one proven under.

Editor’s Be aware: To dive deeper into SwiftUI animation and study extra concerning the SwiftUI framework, you’ll be able to try the guide here.
Creating the Navigation Menu
Earlier than we create the animated menu, let’s begin by creating the static model. For instance, the navigation menu solely shows three menu gadgets.

To format three textual content views horizontally with equal spacing, we use the HStack
view and Spacer
to rearrange the views. Right here is the code pattern:
let menuItems = [ “Travel”, “Nature”, “Architecture” ]
var physique: some View {
HStack {
Spacer()
Textual content(menuItems[0])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade.purple))
.foregroundColor(.white)
Spacer()
Textual content(menuItems[1])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade(uiColor: .systemGray5)))
Spacer()
Textual content(menuItems[2])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade(uiColor: .systemGray5)))
Spacer()
}
.body(minWidth: 0, maxWidth: .infinity)
.padding()
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
struct NavigationMenu: View {
let menuItems = [ “Travel”, “Nature”, “Architecture” ]
var physique: some View { HStack { Spacer()
Textual content(menuItems[0]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade.purple)) .foregroundColor(.white)
Spacer()
Textual content(menuItems[1]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade(uiColor: .systemGray5)))
Spacer()
Textual content(menuItems[2]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade(uiColor: .systemGray5)))
Spacer() } .body(minWidth: 0, maxWidth: .infinity) .padding() } } |
As you’ll be able to see from the code above, it comprises numerous duplications. It may be additional simplified with ForEach
:
var physique: some View {
HStack {
Spacer()
ForEach(menuItems.indices) { index in
if index == selectedIndex {
Textual content(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade.purple))
.foregroundColor(.white)
} else {
Textual content(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade(uiColor: .systemGray5)))
.onTapGesture {
selectedIndex = index
}
}
Spacer()
}
}
.body(minWidth: 0, maxWidth: .infinity)
.padding()
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
struct NavigationMenu: View { @State var selectedIndex = 0 var menuItems = [ “Travel”, “Nature”, “Architecture” ]
var physique: some View { HStack { Spacer()
ForEach(menuItems.indices) { index in
if index == selectedIndex { Textual content(menuItems[index]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade.purple)) .foregroundColor(.white) } else { Textual content(menuItems[index]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade(uiColor: .systemGray5))) .onTapGesture { selectedIndex = index } }
Spacer() }
} .body(minWidth: 0, maxWidth: .infinity) .padding() } } |
We added a state variable named selectedIndex
to maintain observe of the chosen menu merchandise. When the menu merchandise is chosen, we spotlight it in purple. In any other case, its background coloration is about to mild grey.
To detect customers’ contact, we hooked up the .onTapGesture
modifier to the textual content view. When it’s tapped, we replace the worth of selectedIndex
to spotlight the chosen textual content view.
Animating the Navigation Menu
Now that we’ve carried out the navigation menu, nevertheless, it misses the required animation. To animating the view change each time a menu merchandise is chosen, all we have to do is create a namespace variable and fix the matchedGeometryEffect
modifier to the textual content view in purple:
.
.
.
var physique: some View {
HStack {
Spacer()
ForEach(menuItems.indices) { index in
if index == selectedIndex {
Textual content(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Shade.purple))
.foregroundColor(.white)
.matchedGeometryEffect(id: “menuItem”, in: menuItemTransition)
} else {
.
.
.
}
Spacer()
}
}
.body(minWidth: 0, maxWidth: .infinity)
.padding()
.animation(.easeInOut, worth: selectedIndex)
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
struct NavigationMenu: View { @Namespace non-public var menuItemTransition
. . .
var physique: some View { HStack { Spacer()
ForEach(menuItems.indices) { index in
if index == selectedIndex { Textual content(menuItems[index]) .padding(.horizontal) .padding(.vertical, 4) .background(Capsule().foregroundColor(Shade.purple)) .foregroundColor(.white) .matchedGeometryEffect(id: “menuItem”, in: menuItemTransition) } else { . . . }
Spacer() }
} .body(minWidth: 0, maxWidth: .infinity) .padding() .animation(.easeInOut, worth: selectedIndex) } } |
The ID and namespace are used for figuring out which views are a part of the identical transition. We additionally want to connect the .animation
modifier to the HStack
view to allow the view animation. Be aware that this mission is constructed utilizing Xcode 13. The animation
modifier is up to date within the new model of iOS. You need to present the worth to watch for modifications. Right here, it’s the selectedIndex
.
When you made the modifications, you’ll be able to take a look at the NavigationMenu
view in a simulator. Faucet a menu merchandise and you will note a pleasant animation when the merchandise is transited from one state to a different.

Utilizing the Animated Navigation Menu View
To use this animated navigation menu to your mission, you’ll be able to modify the NavigationMenu
view to simply accept a binding to the chosen index:
@Binding var selectedIndex: Int |
For instance, you’ve gotten created a page-based tab view like this:
var physique: some View {
TabView(choice: $selectedTabIndex) {
ForEach(menuItems.indices) { index in
Textual content(menuItems[index])
.body(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Shade.inexperienced)
.foregroundColor(.white)
.font(.system(measurement: 50, weight: .heavy, design: .rounded))
.tag(index)
}
}
.tabViewStyle(.web page(indexDisplayMode: .by no means))
.ignoresSafeArea()
.overlay(alignment: .backside) {
NavigationMenu(selectedIndex: $selectedTabIndex, menuItems: menuItems)
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
struct ContentView: View { @State var selectedTabIndex = 0 let menuItems = [ “Travel”, “Film”, “Food & Drink” ]
var physique: some View { TabView(choice: $selectedTabIndex) {
ForEach(menuItems.indices) { index in Textual content(menuItems[index]) .body(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .background(Shade.inexperienced) .foregroundColor(.white) .font(.system(measurement: 50, weight: .heavy, design: .rounded)) .tag(index) } } .tabViewStyle(.web page(indexDisplayMode: .by no means)) .ignoresSafeArea() .overlay(alignment: .backside) { NavigationMenu(selectedIndex: $selectedTabIndex, menuItems: menuItems) } } } |
You’ll be able to add the NavigationMenu
view as an overlay and use your individual menu gadgets.

[ad_2]