SwiftUI lessons (part 8)

link to part 7

Basic Animations and Movements

SwiftUI provides the ability to animate changes for individual views and transitions between views. You can use built-in animations to create various effects and, of course, create custom ones. In this part, we will explore implicit and explicit animations in SwiftUI and also create some demo projects for practice.

Explicit and Implicit Animations

SwiftUI provides two types of animations: implicit and explicit. Both approaches allow you to animate views and transitions between views. To implement implicit animations, use the animation modifier, which is attached to the animated views. You can specify your preferred animation type, and optionally specify a duration and delay. SwiftUI will automatically play animations based on changes in view state.

Explicit animations provide more precise control over animations. Instead of attaching a modifier to the view, you specify in the withAnimation() block which state changes you want to animate.

If this looks a little confusing to you, don't worry. After viewing a few examples, you will have a clearer understanding.

Implicit Animations

Let's start with implicit animation. Create a new project, call it SwiftUIAnimation (or whatever name you like). Be sure to choose SwiftUI for the interface!

Look at the screenshots.

Here we see a simple click on the view, which consists of a red circle and a heart. When the user clicks on the heart or circle, the color of the circle changes to light gray and the color of the heart changes to red. At the same time, the size of the heart increases. Various state changes occur here:

  1. The color of the circle changes from red to light gray.

  2. The color of the heart changes from white to red.

  3. The size of the heart is doubled compared to the original.

To implement circle clicking using SwiftUI, add the following code to ContentView.swift:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 200, height: 200)
				.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
		}
		.onTapGesture {
			circleColorChanged.toggle()
			heartColorChanged.toggle()
			heartSizeChanged.toggle()
		}
	}
}

To model the states of circle color, heart color, and heart size, we define three state variables with initial values ​​of false. To create the circle and heart, we use ZStack to overlay the heart image onto the circle. SwiftUI provides an onTapGesture modifier that allows you to detect a tap gesture. You can attach it to any view to make it clickable. In the onTapGesture code block we change states to change the appearance of the view.

In the preview canvas, click on the view with the heart. The color of the circle and heart should change accordingly. However, these changes are not animated.

To animate the changes, you need to apply an animation modifier to the Circle and Image views:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 200, height: 200)
				.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
				.animation(.default, value: circleColorChanged)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
				.animation(.default, value: heartSizeChanged)
		}
		.onTapGesture {
			circleColorChanged.toggle()
			heartColorChanged.toggle()
			heartSizeChanged.toggle()
		}
	}
}

SwiftUI tracks changes in the value you specify in the animation modifier. When a change occurs, it calculates and plays an animation that allows views to smoothly transition from one state to another. Click on the heart again and you will see a smooth animation.

You can apply an animation modifier not only to one view, but also to a group of views. For example, you could rewrite the code above by attaching an animation modifier to ZStack like this:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 200, height: 200)
				.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
		}
		.animation(.default, value: circleColorChanged)
		.animation(.default, value: heartSizeChanged)
		.onTapGesture {
			circleColorChanged.toggle()
			heartColorChanged.toggle()
			heartSizeChanged.toggle()
		}
	}
}

It works in a similar way. SwiftUI looks for all state changes nested in the ZStack and creates animations. In the example we are using the default animation. SwiftUI provides a number of built-in animations to choose from, including linear, smooth start, smooth end, smooth start and end, and spring. Linear animation animates changes at a linear speed, while other animations have varying speeds. For detailed information you can visit the website www.easings.netto see the difference between each of the ease.

To use an alternate animation, you simply need to set the specific animation in the animation modifier. Let's say you want to use spring animation, you can change .default to the following:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 200, height: 200)
				.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
		}
		.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3),
				   value: circleColorChanged)
		.animation(.default, value: heartSizeChanged)
		.onTapGesture {
			circleColorChanged.toggle()
			heartColorChanged.toggle()
			heartSizeChanged.toggle()
		}
	}
}

This creates a spring-based animation that gives the heart a twitching effect. You can adjust the fade and blend values ​​to achieve a different effect.

Explicit Animation

So we figured out how to animate a view using implicit animation. Now let's see how we can achieve the same result using explicit animation. As explained earlier, you need to wrap the state changes in a withAnimation block.

To create the same animated effect, you can write the code like this:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
		.frame(width: 200, height: 200) .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
		}
		.onTapGesture {
			withAnimation(.default) {
				self.circleColorChanged.toggle()
				self.heartColorChanged.toggle()
				self.heartSizeChanged.toggle()
			}
		}
	}
}

We no longer use the animation modifier, instead we wrap the code in onTapGesture using withAnimation.

The withAnimation call takes an animation parameter. Here we specify to use the default animation. When you want to change the animation in spring you can update withAnimation like this:

		.onTapGesture {
			withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
				self.circleColorChanged.toggle()
				self.heartColorChanged.toggle()
			}
			self.heartSizeChanged.toggle()
		}

In this case, SwiftUI only animates the color change of the circle and heart. You no longer see the animated heart enlargement effect. You may be wondering if it is possible to disable the zoom animation using implicit animation. You can! You can reorder the .animation modifier to prevent SwiftUI from animating a specific state change. Here's code that achieves the same effect:

struct ContentView: View {
	@State private var circleColorChanged = false
	@State private var heartColorChanged = false
	@State private var heartSizeChanged = false
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 200, height: 200)
				.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
				.animation(.spring(response: 0.3,
								   dampingFraction: 0.3,
								   blendDuration: 0.3),
						   value: circleColorChanged)
			Image(systemName: "heart.fill")
				.foregroundColor(heartColorChanged ? .red : .white)
				.font(.system(size: 100))
				.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3
								  ),
						   value: heartColorChanged)
				.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
		}
		.onTapGesture {
			self.circleColorChanged.toggle()
			self.heartColorChanged.toggle()
			self.heartSizeChanged.toggle()
		}
	}
}

For the Image view, we place the animation modifier right before the scaleEffect. This will cancel the animation. Changing the state of the scaleEffect modifier will not animate. Although you can create the same animation using implicit animation, in my opinion, in this case it is more convenient to use explicit animation.

Creating a Loading Indicator Using RotationEffect

The power of SwiftUI animation is that you don't have to worry about how to animate views. You just need to provide the start and end state. SwiftUI will automatically figure out the rest. Using this concept, you can create different types of animations.

For example, let's create a simple loading indicator that is typically found in real world applications. To create a loading indicator as shown in the screenshot, we will start with an open circle:

struct ContentView: View {
	var body: some View {
		Circle()
			.trim(from: 0, to: 0.7)
			.stroke(Color.green, lineWidth: 5)
			.frame(width: 100, height: 100)
	}
}

How will we rotate the circle? We use rotationEffect and animation modifiers. The idea is to constantly rotate the circle 360 ​​degrees. Here's the code:

struct ContentView: View {
	@State private var isLoading = false
	var body: some View {
		   Circle()
			.trim(from: 0, to: 0.7)
			.stroke(Color.green, lineWidth: 5)
			.frame(width: 100, height: 100)
			.rotationEffect(Angle(degrees: isLoading ? 360 : 0)) .animation(.default.repeatForever(autoreverses: false), value: isLoading)
			.onAppear() {
				isLoading = true
			}
   }
}

The rotationEffect modifier accepts a rotation angle (360 degrees). In the code above we have a state variable to control the loading status. When set to true, the rotation angle will be set to 360 degrees to rotate the circle. In the animation modifier we tell it to use the .default animation, and we tell SwiftUI to repeat the same animation over and over again. This technique creates a loading animation.

Note: If you don't see the animation in the preview, run the app in an emulator.

If you want to change the animation speed, you can use linear animation and specify the duration like this:

struct ContentView: View {
	@State private var isLoading = false
	var body: some View {
		   Circle()
			.trim(from: 0, to: 0.7)
			.stroke(Color.green, lineWidth: 5)
			.frame(width: 100, height: 100)
			.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
			.animation(.linear(duration: 5).repeatForever(autoreverses: false), value: isLoading)
			.onAppear() {
				isLoading = true
			}
   }
}

The larger the duration value, the slower the animation (rotation). The onAppear modifier may be new to you. If you have some knowledge of UIKit, this modifier is very similar to viewDidAppear. It is automatically called when the view is displayed on the screen.

In the code, we change the loading status to true to start the animation when the view loads. With this technique, you can customize the design and develop different versions of the loading indicator. For example, you can overlay an arc on a circle to create a nice loading indicator.

struct ContentView: View {
	@State private var isLoading = false
	var body: some View {
		ZStack {
			Circle()
				.stroke(Color(.systemGray5), lineWidth: 14)
				.frame(width: 100, height: 100)
			Circle()
				.trim(from: 0, to: 0.2)
				.stroke(Color.green, lineWidth: 7)
				.frame(width: 100, height: 100)
				.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
				.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
				.onAppear() {
					isLoading = true
				}
		}
	}
}

The loading indicator does not have to be round. You can also use Rectangle or RoundedRectangle to create an indicator. Instead of changing the rotation angle, you can change the offset value to create an animation as shown below.

To create the animation, we overlay two rounded rectangles on top of each other. The top rectangle is much shorter than the bottom one. When loading starts, we update its offset value. This code moves the green rectangle along the line. When you repeat the same animation over and over again, it becomes a loading animation from -110 to 110.

struct ContentView: View {
	@State private var isLoading = false
	var body: some View {
		ZStack {
			Text("Loading")
				.font(.system(.body, design: .rounded))
				.bold()
				.offset(x: 0, y: -25)
			RoundedRectangle(cornerRadius: 3)
				.stroke(Color(.systemGray5), lineWidth: 3)
				.frame(width: 250, height: 3)
			RoundedRectangle(cornerRadius: 3)
				.stroke(Color.green, lineWidth: 3)
				.frame(width: 30, height: 3)
				.offset(x: isLoading ? 110 : -110, y: 0)
				.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
		}
		.onAppear() {
			self.isLoading = true
		}
	}
}

This code moves the green rectangle along the line. When you repeat the same animation over and over again, it becomes a loading animation.

Creating a progress bar

A loading indicator provides feedback to the user that the application is doing some work. However, it does not show actual progress. If you want to provide users with more information about the progress of a task, you may want to create a progress bar.

Creating a progress bar is very similar to creating a loading bar. You need a state variable to track progress. Here is a code snippet for creating an indicator:

struct ContentView: View {
	@State private var progress: CGFloat = 0.0
	var body: some View {
		ZStack {
			Text("\(Int(progress * 100))%")
				.font(.system(.title, design: .rounded))
				.bold()
			Circle()
				.stroke(Color(.systemGray5), lineWidth: 10)
				.frame(width: 150, height: 150)
			Circle()
				.trim(from: 0, to: progress)
				.stroke(Color.green, lineWidth: 10)
				.frame(width: 150, height: 150)
				.rotationEffect(Angle(degrees: -90))
		}
		.onAppear() {
			Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
				progress += 0.05
				print(progress)
				if progress >= 1.0 {
					timer.invalidate()
				}
			}
		}
	}
}

Instead of a boolean status variable, we use a floating point number to store the status. To display progress, we set the trim modifier with the progress value. In a real application, you can update the progress value to show the actual progress of the operation. For this demo, we used a timer that updates progress every half second.

Animation Delay

SwiftUI allows you to control the duration of the animation, but you can also delay the animation using the delay function like this:

Animation.default.delay(1.0)

This will delay the start of the animation by 1 second. The delay function is applicable to other animations. By mixing and matching duration and delay values ​​you can achieve some interesting animations like the loading indicator below.

struct ContentView: View {
	@State private var isLoading = false
	var body: some View {
		ZStack {
			HStack {
				ForEach(0...4, id: \.self) { index in
					Circle()
						.frame(width: 10, height: 10)
						.foregroundColor(.green)
						.scaleEffect(self.isLoading ? 0 : 1) 
						.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)),
								   value: isLoading)
				}
			}
			.onAppear() {
				isLoading = true
			}
		}
	}
}

This indicator consists of five dots. Each current is animated to scale, but with different time delays.

First we use HStack to lay out the circles horizontally. Since all five circles (dots) are the same size and color, we use ForEach to create the circles. The scaleEffect modifier is used to scale the size of the circle. By default it is set to 1, which corresponds to its original size. When loading starts, the value is updated to 0. This will decrease the point. The line of code to render the animation looks a bit complicated. Let's take it apart and look at it step by step:

.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)),
								   value: isLoading)

The first part creates a linear animation lasting 0.6 seconds. This animation needs to run continuously, so we call the repeatForever function. If you run the animation without calling the delay function, all points will scale up and down at the same time. However, this is not what we want.

Instead of scaling all points at once, each point should resize independently. So we call the delay function and use a different delay value for each point (based on its order in the row).

Converting a rectangle to a circle

Sometimes you might need to seamlessly transform one shape (like a rectangle) into another (like a circle). With built-in shape and animation you can easily create this transformation

The essence of converting a rectangle to a circle is to use the RoundedRectangle shape and animate the change in the corner radius. Assuming the rectangle's width and height are the same, it turns into a circle when its corner radius is set to half its width. Here is the implementation of the button conversion:

struct ContentView: View {
	@State private var recordBegin = false
	@State private var recording = false

	var body: some View {
		ZStack {
			RoundedRectangle(cornerRadius: recordBegin ? 30 : 5)
				.frame(width: recordBegin ? 60 : 250, height: 60)
				.foregroundColor(recordBegin ? .red : .green)
				.overlay(
					Image(systemName: "mic.fill")
						.font(.system(.title))
						.foregroundColor(.white)
						.scaleEffect(recording ? 0.7 : 1)
				)
			RoundedRectangle(cornerRadius: recordBegin ? 35 : 10)
				.trim(from: 0, to: recordBegin ? 0.0001 : 1)
				.stroke(lineWidth: 5)
				.frame(width: recordBegin ? 70 : 260, height: 70)
				.foregroundColor(.green)
		}
		.onTapGesture {
			withAnimation(Animation.spring()) {
				recordBegin.toggle()
			}
			withAnimation(Animation.spring().repeatForever().delay(0.5)) { recording.toggle()
			}
		}
	}
}

We have two state variables: recordBegin and recording to control two separate animations. The first variable controls the button's transformation. As explained earlier, we use the corner radius for the conversion. The width of the rectangle is initially set to 250 points.

When the user clicks on the rectangle to initiate the transformation, the frame width changes to 60 points. With this change, the corner radius changes by 30 points, which is half the width. This way we transform the rectangle into a circle. SwiftUI automatically animates this transformation. The recording state variable controls the scaling of the microphone image. We change the scaling factor from 1 to 0.7 when it is in the recording state. (see line 23)

By running the same animation again, a pulsating animation is created. Note that the code above uses an explicit approach to animate the view. This is optional – you can also use an implicit animation approach to achieve the same result.

Understanding Transitions

What we've talked about so far is animating a view that already exists in the view hierarchy. We animate the size of the view, its angle, or other parameters. SwiftUI allows developers to do more than this.

You can define how a view will be added or removed from the view hierarchy. In SwiftUI this is called transition. By default, the framework uses a fade in and fade out transition. However, apart from this, it provides several ready-to-use transitions such as slide, move, opacity, etc. Of course, you can design your own or simply combine different types of transitions together to create the custom transition you want.

Building a simple transition

Let's look at a simple example to better understand what a transition is and how it works with animations. Create a new project named SwiftUITransition and update the ContentView as follows:

struct ContentView: View {
	var body: some View {
		VStack {
			RoundedRectangle(cornerRadius: 10)
				.frame(width: 300, height: 300)
				.foregroundColor(.green)
				.overlay(
					Text("Покажи детальную информацию")
						.font(.system(.largeTitle, design: .rounded))
						.bold()
						.foregroundColor(.white)
				)
			RoundedRectangle(cornerRadius: 10)
				.frame(width: 300, height: 300)
				.foregroundColor(.purple)
				.overlay(
					Text("Вот твоя детальная информация")
						.font(.system(.largeTitle, design: .rounded))
						.bold()
						.foregroundColor(.white))
		}
	}
}

In the code above, we are positioning two squares vertically using VStack. First the purple rectangle should be hidden. It is only shown when the user clicks on the green rectangle (i.e. Show Details). To show the purple square, we need to make the green square clickable.

To do this, we need to declare a state variable to determine whether the purple square is shown or not. Paste this line of code into ContentView:

@State private var show = false

Then, to hide the purple square, we wrap the purple square in an if clause like this:

			if show {
				RoundedRectangle(cornerRadius: 10)
					.frame(width: 300, height: 300)
					.foregroundColor(.purple)
					.overlay(
						Text("Вот твоя детальная информация")
							.font(.system(.largeTitle, design: .rounded))
							.bold()
							.foregroundColor(.white))
			}

For VStack, we will add an onTapGesture function to detect the tap and create an animation for the state change. Please note that the transition must be associated with an animation, otherwise it will not work on its own.

struct ContentView: View {

	@State private var show = false

	var body: some View {
		VStack {
			RoundedRectangle(cornerRadius: 10)
				.frame(width: 300, height: 300)
				.foregroundColor(.green)
				.overlay(
					Text("Покажи детальную информацию")
						.font(.system(.largeTitle, design: .rounded))
						.bold()
						.foregroundColor(.white)
				)
			if show {
				RoundedRectangle(cornerRadius: 10)
					.frame(width: 300, height: 300)
					.foregroundColor(.purple)
					.overlay(
						Text("Вот твоя детальная информация")
							.font(.system(.largeTitle, design: .rounded))
							.bold()
							.foregroundColor(.white))
			}
		}
		.onTapGesture {
			withAnimation(.spring()) {
				show.toggle()
			}
		}
	}
}

When the user clicks on the VStack, we toggle the show variable to display a purple square. If you run the app in the emulator or preview, you will only see a green square. Clicking on it will display a purple rectangle with a smooth fade in/out transition.

As mentioned earlier, if you don't specify the transition you want to use, SwiftUI will perform a fade in and fade out transition. To use an alternate transition, attach a transition modifier to the purple square like this:

if show {
				RoundedRectangle(cornerRadius: 10)
					.frame(width: 300, height: 300)
					.foregroundColor(.purple)
					.overlay(
						Text("Вот твоя детальная информация")
							.font(.system(.largeTitle, design: .rounded))
							.bold()
							.foregroundColor(.white))
					.transition(.scale(scale: 0, anchor: .bottom))
			}

The transition modifier takes a parameter of type AnyTransition. Here we use a transition with scaling and an anchor set at the bottom. That's all you need to do to change the transition.

Run the application in the emulator. You should see a pop-up animation when the app shows a purple square. It's better to test animations using an emulator instead of running the app in preview because the preview canvas may not display the transition correctly.

In addition to .scale, the SwiftUI framework comes with several built-in transitions, including .opaque, .offset, .move, and .slide. Replace the .scale transition with a .offset transition like this:

.transition(.offset(x: -600, y: 0))

This time the purple square slides to the left when it should appear in the VStack.

Combining Transitions

You can combine two or more transitions together by calling the combined(with:) method to create an even smoother transition. For example, to combine shift and scale animations, you could write code like this:

.transition(.offset(x: -600, y: 0).combined(with: .scale))

Here's another example that combines three transitions:

.transition(.offset(x: -600, y: 0)
						.combined(with: .scale)
						.combined(with: .opacity))

Sometimes you may need your own reusable animation. You can define an extension for AnyTransition like this:

extension AnyTransition {
	static var offsetScaleOpacity: AnyTransition {
		AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity)
	}
}

Accordingly, now you can use this setting as a separate animation

.transition(.offsetScaleOpacity)

The entire code will look like this

extension AnyTransition {
	static var offsetScaleOpacity: AnyTransition {
		AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity)
	}
}

struct ContentView: View {

	@State private var show = false

	var body: some View {
		VStack {
			RoundedRectangle(cornerRadius: 10)
				.frame(width: 300, height: 300)
				.foregroundColor(.green)
				.overlay(
					Text("Покажи детальную информацию")
						.font(.system(.largeTitle, design: .rounded))
						.bold()
						.foregroundColor(.white)
				)
			if show {
				RoundedRectangle(cornerRadius: 10)
					.frame(width: 300, height: 300)
					.foregroundColor(.purple)
					.overlay(
						Text("Вот твоя детальная информация")
							.font(.system(.largeTitle, design: .rounded))
							.bold()
							.foregroundColor(.white))
					.transition(.offsetScaleOpacity)
			}
		}
		.onTapGesture {
			withAnimation(.spring) {
				show.toggle()
			}
		}
	}
}

Asymmetric transitions

The transitions we just discussed are all symmetrical, meaning that adding and removing views use the same transition. For example, if you apply a zoom transition to a view, SwiftUI scales the view when it is inserted into the view hierarchy. When it is removed, the framework scales it back to size zero. What if you want to use a scale transition when inserting a view, and an offset transition when deleting it? This is called asymmetrical transitions in SwiftUI. It's very easy to use this type of transition. You just need to call the .assymetric method and specify the insertion and deletion transitions. Here's a code example:

.transition(.asymmetric(insertion: .scale(scale: 0, anchor: .bottom), removal: .offset(x: -600, y: 0)))

Of course, this type of transition can also be added to an extension for later reuse.

extension AnyTransition {
	static var scaleAndOffset: AnyTransition {
		AnyTransition.asymmetric(
			insertion: .scale(scale: 0, anchor: .bottom),
			removal: .offset(x: -600, y: 00)
		)
	}
}

And then use it in code like this

.transition(.scaleAndOffset)

Results

Animation plays a special role in mobile user interface design.

Thoughtful animation enhances the user experience and adds meaning to interactions with the UI. A smooth and easy transition between two views will delight and impress your users. With over 2 million apps on the App Store, it's not easy to make your app stand out. However, a well-designed UI with animation can definitely make it stand out!

Even for experienced developers, it is not an easy task to write smooth, clean and understandable animations. Luckily, the SwiftUI framework has made it easier to develop animations and transitions. You tell the framework what you want the view to look like at the beginning and end, and SwiftUI calculates everything it needs to create a smooth and pleasing animation. In this part, we've covered the basics and you've already created some great animations and transitions. Best of all, it only took a few lines of code.

As before, subscribe to my telegram channel – https://t.me/swiftexplorer

The next parts of SwiftUI tutorials will be released soon.

Thanks for reading!

Similar Posts

Leave a Reply

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