Create a Mesmerizing 3D Cube Animation with SwiftUI

pouyasadri
4 min readMar 13, 2024

--

Create a Mesmerizing 3D Cube Animation with SwiftUI

Welcome to the exciting world of SwiftUI and 3D animations! Whether you’re just starting your iOS development journey or looking to spice up your app with eye-catching visuals, this tutorial is for you. We’ll dive into creating a stunning 3D cube animation using SwiftUI, all the while keeping our code clean and maintainable with the MVVM architectural pattern.

Before We Begin

Ensure you have Xcode installed on your Mac and a basic understanding of SwiftUI. This guide is crafted to be beginner-friendly, so don’t worry if you’re just dipping your toes into the world of iOS development.

SwiftUI Magic: Create an Eye-Catching 3D Cube Animation![MVVM architecture] [Beginner] [2024]

Setting Up Your SwiftUI Project

Open Xcode and create a new SwiftUI project. Name it “Cube3DAnimation” or anything that sparks joy. We’ll start with a blank canvas to build our masterpiece.

Crafting the Data Model

Our animation consists of a 3D cube that can rotate, so let’s define our cubes. The AllCubes enum represents each face of the cube with distinct cases (.one, .two, .three, etc.), where each case corresponds to a different color of the cube face. This structure is pivotal for managing the cube's appearance during the animation, allowing us to switch views based on the cube's state:

import SwiftUI

//MARK: - AllCubes Enum
enum AllCubes: CaseIterable{
static var indexOffset : Int = 0

case one, two, three, four, five, clear

var view: AnyView{
switch self{
case .one, .two, .three, .four, .five:
return AnyView(Image(name()).resizable().frame(width: 80,height: 80))
default:
return AnyView(EmptyView())
}
}

private func name() -> String{
switch self {
case .one: return "green"
case .two: return "yellow"
case .three: return "red"
case .four: return "blue"
case .five: return "orange"
default: return ""
}
}
}

This approach ensures that our cube’s faces are easily identifiable and modifiable, allowing for a dynamic and interactive animation experience.

Implementing the ViewModel

The CubeViewModel acts as the intermediary between our model and views, utilizing the @Published properties to trigger UI updates when our cube's state changes. The rotation logic is encapsulated within the startRotation and rotate functions. These functions utilize SwiftUI's animation capabilities to smoothly transition between cube states:

import SwiftUI

class CubeViewModel: ObservableObject{
@Published var allCubes = AllCubes.allCases
@Published var allIndicies: [(CGFloat,CGFloat,Double,Bool)] = [
(-80, 40, 5, true),
(-40, 20, 3, false),
(0, 0, 1, false),
(40, 20, 2, true),
(0, 40, 4, false),
(-40, 60, 6, false)
]
@Published var currentIndex : Int = 4

func startRotation(){
withAnimation{
rotate()
}
}

private func rotate(){
let clearPosition = allIndicies[5]

allIndicies[5] = allIndicies[currentIndex]
allIndicies[currentIndex] = clearPosition

currentIndex = currentIndex - 1

if currentIndex == -1 {
currentIndex = 4
}

if allIndicies[currentIndex].3 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1){
withAnimation{
self.rotate()
}
}
}else{
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3){
withAnimation{
self.rotate()
}
}
}
}
}

This class uses SwiftUI’s ObservableObject to update our views whenever our cube's state changes. The rotation logic cleverly manipulates the cube’s state to create the illusion of a rotating 3D object, showcasing the power of SwiftUI’s animation system.

Designing the View

In the view layer, ContentView acts as the entry point, which then leads to FinalView where the cube and its animations are displayed. This separation ensures that our SwiftUI app is structured and manageable:

import SwiftUI
//MARK: - ContentView
struct ContentView: View {
var body: some View {
FinalView()
}
}

#Preview {
ContentView()
}

struct FinalView: View {
var body: some View {
ZStack{
Color.black
.ignoresSafeArea()
CubesView()
.offset(x:0, y: -95)
bottomPanel()
}
}
// Bottom panel with welcome text and arrow
@ViewBuilder
private func bottomPanel() -> some View{
ZStack{
RoundedRectangle(cornerRadius: 75)
.frame(width:450,height: 400)
.foregroundStyle(.gray).opacity(0.2)
.offset(x:0,y:350)

VStack{
Text("WELCOME")
.font(.title)
.fontWeight(.regular)
.foregroundStyle(Color.white)
arrowCircle()
}
.offset(x:0,y:300)
}
}

//Arrow inside a circle
@ViewBuilder
private func arrowCircle() -> some View{
ZStack{
Circle()
.frame(width: 75,height: 75)
.foregroundStyle(.clear)
.overlay(Circle().stroke(Color.white,lineWidth: 2))

Image(systemName: "arrow.right")
.resizable()
.frame(width: 35,height: 30)
.foregroundStyle(.white)
}
}
}

//MARK: - CubesView
struct CubesView:View {
var body: some View {
ZStack{
ForEach(0 ..< 10){index in
CubeSetView()
.offset(x:100)
.rotationEffect(.degrees(Double(index) * 60 ))

}
}
}
}

//MARK: - CubeSet View

struct CubeSetView : View {
@ObservedObject var viewModel = CubeViewModel()

var body: some View {
ZStack{
ForEach(0..<viewModel.allCubes.count, id : \.self){index in
cubeView(index:index)

}
}
.onAppear(perform: viewModel.startRotation)
}

private func cubeView(index: Int) -> some View{
let offset = viewModel.allIndicies[index]
return viewModel.allCubes[index].view
.offset(x: offset.0,y: offset.1)
.zIndex(offset.2)
}
}

The FinalView composes the cube using CubesView, a component that orchestrates the individual cube faces and controls their animation through the ViewModel.

Animation Logic

The real magic happens in how we define our animations within SwiftUI. By leveraging SwiftUI’s withAnimation and animation modifiers, we create smooth, continuous rotations that bring our cube to life. The key is in how we update the allIndicies within CubeViewModel to change each cube face's position and rotation angle, then use these updates to animate our views.

Wrapping Up

Congratulations! You’ve just created a captivating 3D cube animation with SwiftUI. Experiment with different animations, colors, and sizes. SwiftUI’s power is in your hands now, and the possibilities are endless.

Don’t forget to share your creations and any questions you might have. Happy coding!

Github Repository

--

--