{"id":11,"date":"2021-12-11T20:40:08","date_gmt":"2021-12-11T19:40:08","guid":{"rendered":"https:\/\/techjourney.me\/?p=11"},"modified":"2021-12-23T22:01:50","modified_gmt":"2021-12-23T21:01:50","slug":"transitions-in-swiftui","status":"publish","type":"post","link":"https:\/\/techjourney.me\/index.php\/2021\/12\/11\/transitions-in-swiftui\/","title":{"rendered":"Transitions in SwiftUI"},"content":{"rendered":"\n

In this post we will take a closer look at transitions in SwiftUI. SwiftUI provides powerful APIs which do a lot of heavy lifting and makes it really easy for developers to add powerful transitions to their app.<\/p>\n\n\n\n

First thing to notice with SwiftUI is that it provides animations and transitions and the question that follows: what is the difference between them? In simple terms, transitions are animations that are triggered when a view is added or removed. Animations apply to changes to any aspect of the view i.e. state, frame, translation, rotation etc. Transitions also require you to set an animation which makes things a little more murky. This is because transitions define the effect that will be used when adding\/removing the view and animation provides the timing curve over which that affect will be played out. This will become clearer as we look at examples below<\/p>\n\n\n\n

Sliding Menu Example<\/h2>\n\n\n\n

Lets take the example of a sliding menu from left that we want to animate in and out of the view when the user presses a button. Lets start with drawing out the menu and hooking it up to a button on screen that will open and close it.<\/p>\n\n\n\n

Lets first create the menu and conditionally add it to the UI when user taps a button:<\/p>\n\n\n\n

struct SimpleTransitionSample: View {\n    @State var showMenu: Bool = true\n\n    var menuButtonIcon: Image {\n        if showMenu {\n            return Image(systemName: "xmark.square")\n        }\n        else {\n            return Image(systemName: "menucard")\n        }\n    }\n\n    var body: some View {\n        HStack(alignment: .top) {\n            if showMenu {\n                Menu()\n            }\n\n            Button {\n                self.showMenu.toggle()\n            } label: {\n                menuButtonIcon\n            }\n            .padding()\n            .foregroundColor(.black)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)\n    }\n}\n\nstruct Menu: View {\n    var body: some View {\n        VStack(alignment: .leading, spacing: 40) {\n            Button(action: { }) {\n                Image(systemName: "person.crop.circle.fill")\n            }\n            .padding()\n\n            Button(action: { }) {\n                Image(systemName: "gearshape.fill")\n            }\n            .padding()\n\n            Button(action: { }) {\n                Image(systemName: "square.and.arrow.up.fill")\n            }\n            .padding()\n\n            Spacer()\n\n            Button(action: { }) {\n                Image(systemName: "info.circle.fill")\n            }\n            .padding()\n        }\n        .padding(.horizontal)\n        .foregroundColor(.black)\n        .background(Color.brown)\n    }\n}\n\nstruct SimpleTransitionSample_Previews: PreviewProvider {\n    static var previews: some View {\n        SimpleTransitionSample()\n    }\n}<\/code><\/pre><\/div>\n\n\n\n

If you try this in the simulator or preview, you can now see the menu being added\/removed when you press the button. For testing, I have set the initial state to be visible. The layout of the menu is very simplistic as that is not the focus of this post.<\/p>\n\n\n\n

Adding Animation<\/h3>\n\n\n\n

Now we can start discussing ways to animate the menu to slide in and out on button click. Lets first talk about a way of doing it with animation but without using transition by updating the code to:<\/p>\n\n\n\n

var body: some View {\n        HStack(alignment: .top) {\n            Menu()\n                .opacity(showMenu ? 1.0 : 0.0)\n                .offset(x: showMenu ? 0.0 : -100.0)\n                .animation(.default, value: showMenu)\n\n            Button {\n                self.showMenu.toggle()\n            } label: {\n                menuButtonIcon\n            }\n            .padding()\n            .foregroundColor(.black)\n            .offset(x: showMenu ? 0 : -100)\n            .animation(.default, value: showMenu)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)\n}<\/code><\/pre><\/div>\n\n\n\n

You can see now that the menu slides in and out as well as fades in and out. The button just slides along the menu. While this works and achieves what we wanted to do, there is 1 big issue with this i.e. the magic number -100<\/code>. This is really easy to break e.g. if the user has accessibility font sizes enabled. Ideally we want to offset it by the menu’s width and calculate that width dynamically so it is always correct. This is entirely possible using GeometryReader<\/code> but there is a much easier way to manage this using transitions so that is what we will look at instead.<\/p>\n\n\n\n

Using Transitions on Menu<\/h3>\n\n\n\n

We can replace the conditional offset and opacity above with transition as below:<\/p>\n\n\n\n

var body: some View {\n        HStack(alignment: .top) {\n            if showMenu {\n                Menu()\n                    .transition(\n                        .move(edge: .leading).combined(with: .opacity)\n                    )\n            }\n\n            Button {\n                self.showMenu.toggle()\n            } label: {\n                menuButtonIcon\n            }\n            .padding()\n            .foregroundColor(.black)\n        }\n        .animation(.default, value: showMenu)\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)\n}<\/code><\/pre><\/div>\n\n\n\n

And we get the exact same effect as before but now there are no magic numbers required! One important thing to note here is how the .animation()<\/code> is applied to the HStack<\/code> instead of Menu()<\/code>. This is to let SwiftUI animate all changes together and to evaluate when a view is being added or removed. As an exercise, if you add the .animation()<\/code> after the .transition<\/code> instead, you will notice that removal animates but adding the menu does not.<\/p>\n\n\n\n

I have used the .default<\/code> animation (which is .easeInOut<\/code>) here but you can try varying intervals and curves to change it as needed. For example, try the follow spring animation to get a bounce effect:<\/p>\n\n\n\n

.animation(.spring(response: 0.35, dampingFraction: 0.5), value: showMenu)<\/code><\/pre><\/div>\n\n\n\n

Transition API<\/h3>\n\n\n\n

The transition API is provides a lot of convenience when adding transitions. The APIs are designed to be very flexible. Some transitions that are available in the API include:<\/p>\n\n\n\n