SwiftUI | How to make 3D carousel view
While I looking for some design references, I came across the image of Pantone’s plastic color chip sets which can be commonly seen in any design studios. After seeing the image, I came up with the idea to make it a kind of experiential content, recalling the first time I saw it and spinned it with my hands.
For those who are hasty, here’s the final view.
Then, let’s dive in!
How to rotate?
This can be implemented by using
To make it simple, let’s begin with the empty square. As you can see from the above GIF,
rotation3DEffect make a view rotate around x, y, z axis. You can check details in the official document.
So what we have to do first is making our view to rotate around y-axis like real Pantone color sets. In addition, to make our view act more naturally, we should think about other options like
anchor should be placed in the position where the central axis is desired and
perspectiveis used to adjust perspective literally. I set
UnitPoint(x: -2, y: -1.5) because people are used to the top-down view. Okay. Now it seems the only thing we should do is just replacing
Rectangle to a card shape.
However, before we jump into it, there is an unsolved problem we should deal with. That is, because of the feature of
zIndex of cards at the back is higher than card at the front, and so the card at the front is blocked by them. We will look into this issue in more detail at next part. In conclusion, this requires adjusting the
zIndex of each card.
Deal with zIndex
Simply put, this problem can be solved when the front card has a larger
zIndex than the back card. For this, I came up with some concepts:
relativeIndex is the index of cards newly calculated based on 0 degrees, that is, the rightmost part of carousel. In other words, It can be said I created kind of new angular coordinate system. Anyway, the code is below.
zIndexPreset is an array which has decreasing numbers. For example, a
zIndexPreset of length 5 will be an array like
[5, 4, 3, 2, 1]. Of course, actual element in there will be little bit different because
zIndex has to have its input as
Double between 0 and 1. I will use
zIndexPreset to define the
zIndex of each card in the way like
relativeIndex is used without any correction, the
relativeIndex of the card in front of 0 degrees becomes larger than the cards behind, which means they will be blocked by the back card again. To prevent this, I created a variable called
Actually, before doing this, we have to do some math figuring out which card is located in the 0 degree based on the current angle.
Yeah, it’s quite complicated. I tried my best, but I believe there would be easier and better solution. If you have any better ideas, It would be appreciated if you let me know...
Add some details
If you add
offset you can make your card pop out.
.offset(y: currentCard == index ? 100 : 0)
Basic things are all done. You can now add some design elements like ‘card case’, ‘shadow’ to your liking. I added spanning RGB color and using
VStack to stack them up.
Actually, the color codes written on the chips are ‘RGB’ code, not the ‘Pantone’ code. Except for that, it works really fine anyway.🤣
You can get full codes from my Github repo