[ad_1]
One purpose why I actually take pleasure in programming utilizing the SwiftUI framework is that it makes really easy to animate view adjustments. 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 routinely animates the scale/place adjustments.
We’ve written a detailed tutorial on matchedGeometryEffect. I extremely advocate you to test it out if that is the very first time you come throughout this modifier. On this tutorial, we are going to make use of matchedGeometryEffect to develop an animated navigation menu just like the one proven beneath.

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

To structure 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(Colour.purple))
.foregroundColor(.white)
Spacer()
Textual content(menuItems[1])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Colour(uiColor: .systemGray5)))
Spacer()
Textual content(menuItems[2])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Colour(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(Colour.purple)) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .foregroundColor(.white) Â Â Â Â Â Â Â Â Â Â Â Â Â Spacer() Â Â Â Â Â Â Â Â Â Â Â Â Â Textual content(menuItems[1]) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .padding(.horizontal) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .padding(.vertical, 4) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .background(Capsule().foregroundColor(Colour(uiColor: .systemGray5))) Â Â Â Â Â Â Â Â Â Â Â Â Â Spacer() Â Â Â Â Â Â Â Â Â Â Â Â Â Textual content(menuItems[2]) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .padding(.horizontal) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .padding(.vertical, 4) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â .background(Capsule().foregroundColor(Colour(uiColor: .systemGray5))) Â Â Â Â Â Â Â Â Â Â Â Â Â Spacer() Â Â Â Â Â Â Â Â } Â Â Â Â Â Â Â Â .body(minWidth: 0, maxWidth: .infinity) Â Â Â Â Â Â Â Â .padding() Â Â Â Â } } |
As you may see from the code above, it incorporates various 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(Colour.purple))
.foregroundColor(.white)
} else {
Textual content(menuItems[index])
.padding(.horizontal)
.padding(.vertical, 4)
.background(Capsule().foregroundColor(Colour(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(Colour.purple))                         .foregroundColor(.white)                 } else {                     Textual content(menuItems[index])                         .padding(.horizontal)                         .padding(.vertical, 4)                         .background(Capsule().foregroundColor(Colour(uiColor: .systemGray5)))                         .onTapGesture {                             selectedIndex = index                         }                 }                  Spacer()             }          }         .body(minWidth: 0, maxWidth: .infinity)         .padding()     } } |
We added a state variable named selectedIndex to maintain monitor of the chosen menu merchandise. When the menu merchandise is chosen, we spotlight it in purple. In any other case, its background shade is ready to mild grey.
To detect customers’ contact, we connected 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 applied 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 connect 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(Colour.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 personal 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(Colour.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 challenge 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 observe for adjustments. Right here, it’s the selectedIndex.
When you made the adjustments, you may check 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 challenge, you may modify the NavigationMenu view to just accept a binding to the chosen index:
|
@Binding var selectedIndex: Int |
For instance, you’ve got 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(Colour.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(Colour.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 may add the NavigationMenu view as an overlay and use your personal menu gadgets.

[ad_2]
